/**
 * @class Array
 * #core
 */
Ext.apply(Array.prototype, {
	/**
	 * Creates a shallow clone of the array.
	 * @return {Array} a clone of the array.
	 */
	clone: function()
	{
		return this.slice(0);
	},

	/**
	 * Equality comparison for the Array. Only when both
	 * arrays contain the exact same elements the Arrays
	 * are equal.
	 */
	equals: function(arr)
	{
		// We are comparing the exact same references.
		if (this === arr) {
			return true;
		}

		// If the lengths are not equal, then the arrays
		// cannot be equal.
		if (this.length !== arr.length) {
			return false;
		}

		// Compare each element for equality.
		return this.every(function(element, index) {
			return element === arr[index];
		});
	}
});

/* istanbul ignore if */
if (!Array.prototype.find) {
	/**
	 * Returns the value of the first element in the array that satisfies
	 * the provided testing function. Otherwise undefined is returned.
	 * @param {Function} predicate Function to execute on each value in the array
	 * @return {Mixed} A value in the array if an element passes the test; otherwise, undefined.
	 */
	Object.defineProperty(Array.prototype, 'find', {
		value: function(predicate) {
			// 1. Let O be ? ToObject(this value).
			if (this === null) {
				throw new TypeError('"this" is null or not defined');
			}

			var o = Object(this);

			// 2. Let len be ? ToLength(? Get(O, "length")).
			var len = o.length >>> 0;

			// 3. If IsCallable(predicate) is false, throw a TypeError exception.
			if (typeof predicate !== 'function') {
				throw new TypeError('predicate must be a function');
			}

			// 4. If thisArg was supplied, let T be thisArg; else let T be undefined.
			var thisArg = arguments[1];

			// 5. Let k be 0.
			var k = 0;

			// 6. Repeat, while k < len
			while (k < len) {
				// b. Let kValue be ? Get(O, Pk).
				// a. Let Pk be ! ToString(k).
				// c. Let testResult be ToBoolean(? Call(predicate, T, « kValue, k, O »)).
				// d. If testResult is true, return kValue.
				var kValue = o[k];
				if (predicate.call(thisArg, kValue, k, o)) {
					return kValue;
				}
				// e. Increase k by 1.
				k++;
			}

			// 7. Return undefined.
			return undefined;
		}
	});
}
/**
 * @class Date
 *
 * The date parsing and formatting syntax contains a subset of
 * <a href="http://www.php.net/date">PHP's date() function</a>, and the formats that are
 * supported will provide results equivalent to their PHP versions.
 *
 * The following is a list of all currently supported formats:
 * <pre>
Format  Description                                                               Example returned values
------  -----------------------------------------------------------------------   -----------------------
  d     Day of the month, 2 digits with leading zeros                             01 to 31
  D     A short textual representation of the day of the week                     Mon to Sun
  j     Day of the month without leading zeros                                    1 to 31
  l     A full textual representation of the day of the week                      Sunday to Saturday
  N     ISO-8601 numeric representation of the day of the week                    1 (for Monday) through 7 (for Sunday)
  S     English ordinal suffix for the day of the month, 2 characters             st, nd, rd or th. Works well with j
  w     Numeric representation of the day of the week                             0 (for Sunday) to 6 (for Saturday)
  z     The day of the year (starting from 0)                                     0 to 364 (365 in leap years)
  W     ISO-8601 week number of year, weeks starting on Monday                    01 to 53
  F     A full textual representation of a month, such as January or March        January to December
  m     Numeric representation of a month, with leading zeros                     01 to 12
  M     A short textual representation of a month                                 Jan to Dec
  n     Numeric representation of a month, without leading zeros                  1 to 12
  t     Number of days in the given month                                         28 to 31
  L     Whether it's a leap year                                                  1 if it is a leap year, 0 otherwise.
  o     ISO-8601 year number (identical to (Y), but if the ISO week number (W)    Examples: 1998 or 2004
  belongs to the previous or next year, that year is used instead)
  Y     A full numeric representation of a year, 4 digits                         Examples: 1999 or 2003
  y     A two digit representation of a year                                      Examples: 99 or 03
  a     Lowercase Ante meridiem and Post meridiem                                 am or pm
  A     Uppercase Ante meridiem and Post meridiem                                 AM or PM
  g     12-hour format of an hour without leading zeros                           1 to 12
  G     24-hour format of an hour without leading zeros                           0 to 23
  h     12-hour format of an hour with leading zeros                              01 to 12
  H     24-hour format of an hour with leading zeros                              00 to 23
  i     Minutes, with leading zeros                                               00 to 59
  s     Seconds, with leading zeros                                               00 to 59
  u     Decimal fraction of a second                                              Examples:
        (minimum 1 digit, arbitrary number of digits allowed)                     001 (i.e. 0.001s) or
                                                                                  100 (i.e. 0.100s) or
                                                                                  999 (i.e. 0.999s) or
                                                                                  999876543210 (i.e. 0.999876543210s)
  O     Difference to Greenwich time (GMT) in hours and minutes                   Example: +1030
  P     Difference to Greenwich time (GMT) with colon between hours and minutes   Example: -08:00
  T     Timezone abbreviation of the machine running the code                     Examples: EST, MDT, PDT ...
  Z     Timezone offset in seconds (negative if west of UTC, positive if east)    -43200 to 50400
  c     ISO 8601 date
        Notes:                                                                    Examples:
        1) If unspecified, the month / day defaults to the current month / day,   1991 or
           the time defaults to midnight, while the timezone defaults to the      1992-10 or
           browser's timezone. If a time is specified, it must include both hours 1993-09-20 or
           and minutes. The "T" delimiter, seconds, milliseconds and timezone     1994-08-19T16:20+01:00 or
           are optional.                                                          1995-07-18T17:21:28-02:00 or
        2) The decimal fraction of a second, if specified, must contain at        1996-06-17T18:22:29.98765+03:00 or
           least 1 digit (there is no limit to the maximum number                 1997-05-16T19:23:30,12345-0400 or
           of digits allowed), and may be delimited by either a '.' or a ','      1998-04-15T20:24:31.2468Z or
        Refer to the examples on the right for the various levels of              1999-03-14T20:24:32Z or
        date-time granularity which are supported, or see                         2000-02-13T21:25:33
        http://www.w3.org/TR/NOTE-datetime for more info.                         2001-01-12 22:26:34
  U     Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT)                1193432466 or -2138434463
  M$    Microsoft AJAX serialized dates                                           \/Date(1238606590509)\/ (i.e. UTC milliseconds since epoch) or
                                                                                  \/Date(1238606590509+0800)\/
</pre>
 *
 * Example usage (note that you must escape format specifiers with '\\' to render them as character literals):
 * <pre><code>
// Sample date:
// 'Wed Jan 10 2007 15:05:01 GMT-0600 (Central Standard Time)'

var dt = new Date('1/10/2007 03:05:01 PM GMT-0600');
document.write(dt.format('Y-m-d'));                           // 2007-01-10
document.write(dt.format('F j, Y, g:i a'));                   // January 10, 2007, 3:05 pm
document.write(dt.format('l, \\t\\he jS \\of F Y h:i:s A'));  // Wednesday, the 10th of January 2007 03:05:01 PM
</code></pre>
 *
 * Here are some standard date/time patterns that you might find helpful.  They
 * are not part of the source of Date.js, but to use them you can simply copy this
 * block of code into any script that is included after Date.js and they will also become
 * globally available on the Date object.  Feel free to add or remove patterns as needed in your code.
 * <pre><code>
Date.patterns = {
    ISO8601Long:"Y-m-d H:i:s",
    ISO8601Short:"Y-m-d",
    ShortDate: "d/m/Y",
    LongDate: "l jS F Y",
    FullDateTime: "l jS F Y G:i:s",
    MonthDay: "jS F",
    ShortTime: "G:i",
    LongTime: "G:i:s",
    SortableDateTime: "Y-m-d\\TH:i:s",
    UniversalSortableDateTime: "Y-m-d H:i:sO",
    YearMonth: "F, Y"
};
</code></pre>
 *
 * Example usage:
 * <pre><code>
var dt = new Date();
document.write(dt.format(Date.patterns.ShortDate));
</code></pre>
 * <p>Developer-written, custom formats may be used by supplying both a formatting and a parsing function
 * which perform to specialized requirements. The functions are stored in {@link #parseFunctions} and {@link #formatFunctions}.</p>
 * #core
 */
Ext.apply(Date.prototype, {
	/**
	 * Provides a convenient method for performing basic date arithmetic. This method
	 * does not modify the Date instance being called - it creates and returns
	 * a new Date instance containing the resulting date value.
	 *
	 * Examples:
	 * <pre><code>
		// Basic usage:
		var dt = new Date('10/29/2006').add(Date.DAY, 5);
		document.write(dt); //returns 'Fri Nov 03 2006 00:00:00'

		// Negative values will be subtracted:
		var dt2 = new Date('10/1/2006').add(Date.DAY, -5);
		document.write(dt2); //returns 'Tue Sep 26 2006 00:00:00'

		// You can even chain several calls together in one line:
		var dt3 = new Date('10/1/2006').add(Date.DAY, 5).add(Date.HOUR, 8).add(Date.MINUTE, -30);
		document.write(dt3); //returns 'Fri Oct 06 2006 07:30:00'
	 * </code></pre>
	 *
	 * Furthermore, changes to {@link Date#HOUR hours}, {@link Date#MINUTE minutes},
	 * {@link Date#SECOND seconds} and {@link Date#MILLI milliseconds} are treated more accurately
	 * regarding DST changes then the {@link Date#DAY days}, {@link Date#MONTH months} and {@link Date#YEAR years}
	 * changes. When changing the time the standard is applied, which means that if the DST kicks in at 2AM,
	 * and the time becomes 3AM. Doing new Date('Mar 25 2012 01:00').add(Date.HOUR, 1) will be 'Mar 25 2012 03:00'.
	 * However when changing the date, we will use the JS behavior, which means that
	 * new Date('Mar 24 2012 02:00').add(Date.DAY, 1) could become 'Mar 25 2012 01:00' as JS will not correctly
	 * move the time correctly passed the DST switch.
	 *
	 * @param {String} interval A valid date interval enum value.
	 * @param {Number} value The amount to add to the current date.
	 * @return {Date} The new Date instance.
	 */
	add: function(interval, value)
	{
		var d = this.clone();
		if (!interval || value === 0) {
			return d;
		}

		switch(interval.toLowerCase()) {
			// Changing the time is done more accuretely then
			// changing the date. This is because we have to work
			// around DST issues (which we don't care for when
			// changing the day). In JS, we have the following
			// scenario at the following date: Mar 25 2012.
			// At 2:00:00 the DST kicks in and the time will be
			//     Mar 25 2012 03:00:00
			// However, when using setMilliseconds, setSeconds,
			// setMinutes or setHours, JS decides to wrap back
			// to:
			// 	Mar 25 2012 01:00:00
			// How can this go wrong, take the following date:
			//      a = new Date('Mar 25 2012 01:45:00')
			// add 30 minutes to it
			//      a.setMinutes(a.getMinutes() + 30)
			// we expect the time to be 03:15:00 however JS
			// decides to fall back to 01:15:00.
			// To fix this correctly, we have to work using timestamps
			// as JS is able to correctly step over the DST switch.
			case Date.HOUR:
				// Convert value to minutes
				value *= 60;
				/* falls through */
			case Date.MINUTE:
				// Convert value to seconds
				value *= 60;
				/* falls through */
			case Date.SECOND:
				// Convert value to milliseconds
				value *= 1000;
				/* falls through */
			case Date.MILLI:
				d = new Date(d.getTime() + value);
				break;
			// Changing the date is done with less accuracy,
			// basically we don't care if we come at exactly
			// the same time as before. If the JS decides to
			// perform weird tricks, then so be it.
			case Date.DAY:
				d.setDate(this.getDate() + value);
				break;
			case Date.MONTH:
				var day = this.getDate();
				if (day > 28) {
					day = Math.min(day, this.getFirstDateOfMonth().add(Date.MONTH, value).getLastDateOfMonth().getDate());
				}
				d.setDate(day);
				d.setMonth(this.getMonth() + value);
				break;
			case Date.YEAR:
				d.setFullYear(this.getFullYear() + value);
				break;
		}
		return d;
	},

	/**
	 * This should be called if the Date() object represents a UTC date, and we want to obtain
	 * the time it represents in UTC shown as if it was the localtime. This implies that:
	 *
	 * 00:00 UTC (01:00 GMT+0100) will be converted to 00:00 GMT+0100 (23:00 UTC)
	 *
	 * @return {Date} The UTC date
	 */
	toUTC: function()
	{
		var utc = new Date(this.getTime() + (this.getTimezoneOffset() * 60000));

		// Obtain the DST difference which might have occurred during conversion,
		// if there was a difference it must be applied to the utc date accordingly.
		utc.setMilliseconds(utc.getMilliseconds() + Date.getDSTDiff(utc, this));

		return utc;
	},

	/**
	 * This should be called if the Date() was obtained using {@link #toUTC}, and we want
	 * to convert the date back to the local representation.
	 *
	 * 00:00 GMT+0100 (23:00 UTC) with be converted to 00:00 UTC (01:00 GMT+0100)
	 *
	 * @return {Date} The local-time date
	 */
	fromUTC: function()
	{
		return new Date(this.getTime() - (this.getTimezoneOffset() * 60000));
	},

	/**
	 * Get the next given weekday starting from this date. If the current date is this weekday,
	 * then the current day will be returned.
	 * @param {Number} weekday The day in the week to skip to (0: Sunday -  6: Saturday). If
	 * not given, tomorrow will be returned.
	 * @return {Date} this or the clone
	 */
	getNextWeekDay: function(weekday)
	{
		var currentday = this.getDay();

		if (!Ext.isDefined(weekday)) {
			return this.add(Date.DAY, 1);
		} else if (weekday < currentday) {
			return this.add(Date.DAY, 7 - (currentday - weekday));
		} else {
			return this.add(Date.DAY, weekday - currentday);
		}
	},

	/**
	 * Get the previous given weekday starting from this date. If the current date is this weekday,
	 * then the current day will be returned.
	 * @param {Number} weekday The day in the week to skip to (0: Sunday -  6: Saturday). If
	 * not given, yesterday will be returned.
	 * @return {Date} this or the clone
	 */
	getPreviousWeekDay: function(weekday)
	{
		var currentday = this.getDay();

		if (!Ext.isDefined(weekday)) {
			return this.add(Date.DAY, -1);
		} else if (weekday <= currentday) {
			return this.add(Date.DAY, weekday - currentday);
		} else {
			return this.add(Date.DAY, -7 + (weekday - currentday));
		}
	},

	/**
	 * Get the next given working weekday starting from the date passed as argument or
	 * current date in case no argument was provided.
	 * @param {Date} currentDate (Optional) The date for which next working day should be returned
	 * @return {Date} Date fall on the next working day or false if no working days are defined
	 */
	getNextWorkWeekDay: function(currentDate)
	{
		currentDate = currentDate || new Date();
		var nextDate = currentDate.getNextWeekDay();
		var workingDaysList = container.getSettingsModel().get('zarafa/v1/main/working_days');
		if ( Ext.isEmpty(workingDaysList) ){
			return false;
		}

		if (workingDaysList.indexOf(nextDate.getDay()) !== -1 ) {
			return nextDate;
		} else {
			return this.getNextWorkWeekDay(nextDate);
		}
	},

	/**
	 * Attempts to clear all Second and millisecond time information from this Date by rounding the time down
	 * to the current minute.
	 * @param {Boolean} clone true to create a clone of this date, clear the time and return it (defaults to false).
	 * @return {Date} this or the clone.
	 */
	clearSeconds: function(clone)
	{
		if (clone) {
			return this.clone().clearSeconds();
		}

		// clear seconds
		this.setSeconds(0);
		this.setMilliseconds(0);

		return this;
	},

	/**
	 * Sets the time of the date to 12:00 am.
	 * @return {Date} this
	 */
	setToNoon: function()
	{
		this.clearTime().setHours(12);

		return this;
	},

	/**
	 * Checks if the given date is in the same week.
	 * @param {Date} date The date to compare
	 * @return {Boolean} true if the date is in the same week as this date, false otherwise.
	 */
	inSameWeekAs: function(date)
	{
		var clone = this.clone().setToNoon();
		clone = clone.add(Date.DAY, -1*clone.getDay());
		date.setToNoon();
		date = date.add(Date.DAY, -1*date.getDay());

		return clone.getTime() === date.getTime();
	},

	/**
	 * Checks if the given date is in the next week.
	 * @param {Date} date The date to compare
	 * @return {Boolean} true if the date is in the next week from this date, false otherwise.
	 */
	inNextWeek: function(date)
	{
		return this.add(Date.DAY, 7).inSameWeekAs(date);
	},

	/**
	 * Round the number of milliseconds/seconds/minutes or hours depending on the given value.
	 * Note that days, months and years cannot be rounded.
	 *
	 * Example of rounding:
	 *  9:12   round to 30	-> 9:00
	 *  9:17   round to 30	-> 9:30
	 *
	 * @param {String} field The field to round (e.g. {@link Date.MINUTE}, {@link Date.SECOND}, etc.)
	 * @param {Number} roundTimeValue The number of minutes to round the time to.
	 * @return {Date} this date
	 */
	round: function(field, roundTimeValue)
	{
		// For each field we have a slightly different approach.
		// In all cases, if the field-value is already rounded,
		// then we don't need to do anything.
		// The calculation for the rounded value looks a bit weird, but
		// it is a bit more optimal then calling this.floor() or this.ceiling().
		// For seconds or higher units, we set all smaller units to 0,
		// to correctly round of the entire time.
		var value;
		switch (field) {
			case Date.MILLI:
				value = this.getMilliseconds();
				if (value % roundTimeValue > 0) {
					this.setMilliseconds(value - (value % roundTimeValue) + (((value % roundTimeValue) >= (roundTimeValue / 2)) * roundTimeValue));
				}
				break;
			case Date.SECOND:
				value = this.getSeconds();
				if (value % roundTimeValue > 0) {
					this.setSeconds(value - (value % roundTimeValue) + (((value % roundTimeValue) >= (roundTimeValue / 2)) * roundTimeValue));
				}
				this.setMilliseconds(0);
				break;
			case Date.MINUTE:
				value = this.getMinutes();
				if (value % roundTimeValue > 0) {
					this.setMinutes(value - (value % roundTimeValue) + (((value % roundTimeValue) >= (roundTimeValue / 2)) * roundTimeValue));
				}
				this.setSeconds(0);
				this.setMilliseconds(0);
				break;
			case Date.HOUR:
				value = this.getHours();
				if (value % roundTimeValue > 0) {
					this.setHours(value - (value % roundTimeValue) + (((value % roundTimeValue) >= (roundTimeValue / 2)) * roundTimeValue));
				}
				this.setMinutes(0);
				this.setSeconds(0);
				this.setMilliseconds(0);
				break;
		}

		return this;
	},

	/**
	 * Function to ceil timings according to the passed ceil milliseconds, seconds, minutes or hours.
	 * Note that days, months and years cannot be ceiled.
	 * @param {String} field The field to ceil (e.g. {@link Date.MINUTE}, {@link Date.SECOND}, etc.)
	 * @param date ceilTimeValue date time which needs to be ceil (5/10/15/30/60 or so on)
	 * @return number Time number which is unixtimestamp of time.
	 *
	 * Example to understand what the code is actually suppose to do.
	 *	9:12	5min		ceil-9:15
	 *			10min		ceil-9.20
	 *			15min		ceil-9.15
	 *			30min		ceil-9.30
	 *			1hr/60min	ceil-10.00
	 *
	 */
	ceil: function(field, ceilTimeValue)
	{
		// For each field we have a slightly different approach.
		// In all cases, if the field-value is already rounded to the
		// given ceiling then we don't need to do anything.
		// For seconds or higher units, we set all smaller units to 0,
		// to correctly round of the entire time.
		var value;
		switch (field) {
			case Date.MILLI:
				value = this.getMilliseconds();
				if (value % ceilTimeValue > 0) {
					this.setMilliseconds(value - (value % ceilTimeValue) + ceilTimeValue);
				}
				break;
			case Date.SECOND:
				value = this.getSeconds();
				if (value % ceilTimeValue > 0) {
					this.setSeconds(value - (value % ceilTimeValue) + ceilTimeValue);
				}
				this.setMilliseconds(0);
				break;
			case Date.MINUTE:
				value = this.getMinutes();
				if (value % ceilTimeValue > 0) {
					this.setMinutes(value - (value % ceilTimeValue) + ceilTimeValue);
				}
				this.setSeconds(0);
				this.setMilliseconds(0);
				break;
			case Date.HOUR:
				value = this.getHours();
				if (value % ceilTimeValue > 0) {
					this.setHours(value - (value % ceilTimeValue) + ceilTimeValue);
				}
				this.setMinutes(0);
				this.setSeconds(0);
				this.setMilliseconds(0);
				break;
		}

		return this;
	},

	/**
	 * Function to floor timings according to the passed floor milliseconds, seconds, minutes or hours.
	 * Note that days, months and years cannot be floored.
	 * @param {String} field The field to floor (e.g. {@link Date.MINUTE}, {@link Date.SECOND}, etc.)
	 * @param {Number} floorTimeValue date time which needs to be floor (5/10/15/30/60 or so on)
	 * @return {Date} This Date object
	 *
	 * Example to understand what the code is actually suppose to do.
	 *	9:12	5min		floor-9.10
	 *			10min		floor-9.10
	 *			15min		floor-9.00
	 *			30min		floor-9.00
	 *			1hr/60min	floor-9.00
	 *
	 */
	floor: function(field, floorTimeValue)
	{
		// For each field we have a slightly different approach.
		// In all cases, if the field-value is already rounded to the
		// given floor then we don't need to do anything.
		// For seconds or higher units, we set all smaller units to 0,
		// to correctly round of the entire time.
		var value;
		switch (field) {
			case Date.MILLI:
				value = this.getMilliseconds();
				if (value % floorTimeValue > 0) {
					this.setMilliseconds(value - (value % floorTimeValue));
				}
				break;
			case Date.SECOND:
				value = this.getSeconds();
				if (value % floorTimeValue > 0) {
					this.setSeconds(value - (value % floorTimeValue));
				}
				this.setMilliseconds(0);
				break;
			case Date.MINUTE:
				value = this.getMinutes();
				if (value % floorTimeValue > 0) {
					this.setMinutes(value - (value % floorTimeValue));
				}
				this.setSeconds(0);
				this.setMilliseconds(0);
				break;
			case Date.HOUR:
				value = this.getHours();
				if (value % floorTimeValue > 0) {
					this.setHours(value - (value % floorTimeValue));
				}
				this.setMinutes(0);
				this.setSeconds(0);
				this.setMilliseconds(0);
				break;
		}

		return this;
	},

	/**
	 * Get the week number of the month (1 to 5)
	*/
	getWeekOfMonth: function()
	{
		// get current week number in year
		var currentWeek = this.getWeekOfYear();

		// get month's first week number in year
		var monthStartDate = this.add(Date.DAY, -(this.getDate() - 1));
		var monthStartWeek = monthStartDate.getWeekOfYear();

		return currentWeek - monthStartWeek + 1;
	},

	/**
	 * Returns the date in a 'nicely' formatted string.
	 * @param {Boolean} includeTime The time will be added to the nicely
	 * formatted string when true, or omitted otherwise. Default is true.
	 *
	 * @return {String} The nicely formatted date string.
	 */
	getNiceFormat: function(includeTime)
	{
		includeTime = includeTime !== false;

		var d = this.clearTime(true);
		var now = new Date().clearTime();

		// Check for today. We'll only use time then.
		if ( d.getTime() === now.getTime() ){
			return includeTime ? this.formatDefaultTime() : _('Today');
		}

		// Check for tomorrow.
		if ( d.add(Date.DAY, -1).getTime() === now.getTime() ){
			return includeTime ? _('Tomorrow') + ' ' + this.formatDefaultTime() :  _('Tomorrow');
		}

		// Check for future dates.
		if ( d > now ){
			return includeTime ? this.format(_('d-m-Y G:i')) : this.format(_('d-m-Y'));
		}

		// Check for past week. We'll use day (name) + time then.
		if ( d.add(Date.DAY, 6) >= now ){
			return includeTime ? this.formatDefaultTime(_('D {0}')) : this.format(_('D d-m'));
		}

		// Check for two weeks ago. We'll use the day (name) + date (optionally time) without year
		if ( d.add(Date.DAY, 14) >= now ){
			return includeTime ? this.format(_('D d-m G:i')) : this.format(_('D d-m'));
		}

		// For anything older than two weeks ago, we'll show the date or date time
		return includeTime ? this.format(_('d-m-Y G:i')) : this.format(_('d-m-Y'));
	},

	/**
	* Function returns a formatted date string with the specified time format.
	*
	* @param {String} formatString The time format string
	* @return {String} The formatted date/time string
	*/
	formatDefaultTime: function(formatString)
	{
		var timeFormat = container.settingsModel.get('zarafa/v1/main/datetime_time_format');
		var newFormat;

		if (Ext.isDefined(formatString)) {
			newFormat = String.format(formatString, timeFormat);
		} else {
			newFormat = timeFormat;
		}
		return this.format(newFormat);
	}
});

Ext.apply(Date, {
    /**
     * Get the short day name for the given day number.
     * Overridden to make the short days translatable.
     * @param {Number} day A zero-based javascript day number.
     * @return {String} The short day name.
     * @static
     */
    getShortDayName: function(day) {
		// Let's first check if the short days have been translated.
		// If not, we'll fall back to using the first 3 letters of
		// the full day.
		if (
			Date.shortDayNames[0] === 'Sun' &&
			Date.shortDayNames[1] === 'Mon' &&
			Date.shortDayNames[2] === 'Tue' &&
			Date.shortDayNames[3] === 'Wed' &&
			Date.shortDayNames[4] === 'Thu' &&
			Date.shortDayNames[5] === 'Fri' &&
			Date.shortDayNames[6] === 'Sat'
		) {
			return Date.dayNames[day].substring(0, 3);
		}

        return Date.shortDayNames[day];
    },

	/**
	 * The number milliseconds per day
	 *
	 * @property
	 * @type Number
	 */
	dayInMillis : 24 * 60 * 60 * 1000,

	/**
	 * Calculate the DST difference between the 2 given dates.
	 * The first date serves as base, so when 'date' is not DST, but
	 * the second date is DST then a negative offset is returned. A
	 * positive value is returned when it is the other way around.
	 * When both dates have the same DST offset then this returns 0.
	 * @param {Date} date The base date from where the DST is calculated.
	 * @return {Number} milliseconds The DST difference in milliseconds
	 */
	getDSTDiff: function(a, b)
	{
		return (a.getTimezoneOffset() - b.getTimezoneOffset()) * 60 * 1000;
	},

	/**
	 * Calculates the difference between the 2 given dates.
	 * This applies the {@link #getDSTDiff} if needed to ensure that
	 * it always calculates the correct difference regardless of the DST changes
	 * which might have been made.
	 *
	 * In its absolute basic this function is equal to 'a.getTime() - b.getTime()'.
	 *
	 * @param {String} field The field which indicates the accuracy of the diff (e.g. {@link Date.MINUTE}, {@link Date.SECOND}, etc.)
	 * @param {Date} a The date object
	 * @param {Date} b The date object
	 * @return {Number} The difference between the 2 given dates
	 */
	diff: function(field, a, b)
	{
		var ta = a.getTime();
		var tb = b.getTime();
		var difference = ta-tb;

		switch (field) {
			case Date.DAY:
				// For calculating days we apply the same
				// inaccuracy as Date::add() we are not 100%
				// sure a day lasts 24 hour when DST is in play.
				difference -= Date.getDSTDiff(a, b);
				difference /= 24;
				/* falls through */
			case Date.HOUR:
				difference /= 60;
				/* falls through */
			case Date.MINUTE:
				difference /= 60;
				/* falls through */
			case Date.SECOND:
				difference /= 1000;
				/* falls through */
			case Date.MILLI:
				/* falls through */
			default:
				break;
		}

		return difference;
	},

	/**
	 * Function to getTimezone and all dst props
	 * This is a hard one. To create a recurring appointment, we need to save
	 * the start and end time of the appointment in local time. So if I'm in
	 * GMT+8, and I want the appointment at 9:00, I will simply save 9*60 = 540
	 * in the startDate. To make this usable for other users in other timezones,
	 * we have to tell the server in which timezone this is. The timezone is normally
	 * defined as a startdate and enddate for DST, the offset in minutes (so GMT+2 is 120)
	 * plus the extra DST offset when DST is in effect.
	 *
	 * We can't retrieve this directly from the browser, so we assume that the DST change
	 * will occur on a Sunday at 2:00 or 3:00 AM, and simply scan all the sundays in a
	 * year, looking for changes. We then have to guess which bit is DST and which is 'normal'
	 * by assuming that the DST offset will be less than the normal offset. From this we
	 * calculate the start and end dates of DST and the actual offset in minutes.
	 *
	 * Unfortunately we can't detect the difference between 'the last week of october' and
	 * 'the fourth week of october'. This can cause subtle problems, so we assume 'last week'
	 * because this is most prevalent.
	 *
	 * Note that this doesn't work for many strange DST changes, see
	 * http://webexhibits.org/daylightsaving/g.html
	 * @static
	 */
	getTimezoneStruct: function()
	{
		var tzswitch = [],
			switchCount = 0,
			testDate = new Date(),
			tzStruct = {};

		// Clear the time
		testDate.setMonth(0);
		testDate.setDate(1);
		testDate.setMinutes(0);
		testDate.setSeconds(0);
		testDate.setMilliseconds(0);

		// Move to the next sunday
		testDate = testDate.getNextWeekDay(0);

		// Use 5:00 am because any change should have happened by then
		testDate.setHours(5);

		var lastoffset = testDate.getTimezoneOffset();

		for(var weekNr = 0; weekNr < 52; weekNr++) {
			if(testDate.getTimezoneOffset() != lastoffset) {
				// Found a switch
				tzswitch[switchCount] = {
					switchweek : testDate.getWeekOfMonth(),
					switchmonth : testDate.getMonth(),
					offset : testDate.getTimezoneOffset()
				};

				switchCount++;

				// We assume DST is only set or removed once per year
				if(switchCount == 2) {
					break;
				}

				lastoffset = testDate.getTimezoneOffset();
			}

			// advance one week
			testDate = testDate.add(Date.DAY, 7);
		}

		if(switchCount === 0) {
			// No DST in this timezone
			tzStruct = {
				timezone : testDate.getTimezoneOffset(),
				timezonedst : 0,
				dststartday : 0,
				dststartweek : 0,
				dststartmonth : 0,
				dststarthour : 0,
				dstendday : 0,
				dstendweek : 0,
				dstendmonth : 0,
				dstendhour : 0
			};

			return tzStruct;
		} else if(switchCount == 1) {
			// This should be impossible unless DST started somewhere in the year 2000
			// and ended more than a year later. This is an error.
			return tzStruct;
		} else if(switchCount == 2) {
			if(tzswitch[0].offset < tzswitch[1].offset) {
				// Northern hemisphere, eg DST is during Mar-Oct
				tzStruct = {
					timezone : tzswitch[1].offset,
					timezonedst : tzswitch[0].offset - tzswitch[1].offset,
					dststartday : 0, // assume sunday
					dststartweek : tzswitch[0].switchweek == 4 ? 5 : tzswitch[0].switchweek, // assume 'last' week if week = 4
					dststartmonth : tzswitch[0].switchmonth + 1, // javascript months are zero index based
					dststarthour : 2, // Start at 02:00 AM
					dstendday : 0,
					dstendweek : tzswitch[1].switchweek == 4 ? 5 : tzswitch[1].switchweek,
					dstendmonth : tzswitch[1].switchmonth + 1,
					dstendhour : 3
				};

				return tzStruct;

			} else {
				// Southern hemisphere, eg DST is during Oct-Mar
				tzStruct = {
					timezone : tzswitch[0].offset,
					timezonedst : tzswitch[1].offset - tzswitch[0].offset,
					dststartday : 0, // assume sunday
					dststartweek : tzswitch[1].switchweek == 4 ? 5 : tzswitch[1].switchweek, // assume 'last' week if week = 4
					dststartmonth : tzswitch[1].switchmonth + 1,
					dststarthour : 2, // Start at 02:00 AM
					dstendday : 0,
					dstendweek : tzswitch[0].switchweek == 4 ? 5 : tzswitch[0].switchweek,
					dstendmonth : tzswitch[0].switchmonth + 1,
					dstendhour : 3
				};

				return tzStruct;
			}
		} else {
			// Multi-DST timezone ? This is also an error.
			return tzStruct;
		}
	}
});
/**
 * @class Ext.EventObjectImpl
 * #core
 */
Ext.apply(Ext.EventObjectImpl.prototype, {
	/**
	 * Function will return Character code for the key event
	 * Number keys of numpad keys have keycode of 96 to 105, this function will
	 * convert it to proper ASCII character and return it
	 *
	 * @return {String} The trimmed string
	 */
	getKeyCharCode: function()
	{
		var key = this.getCharCode();
		// Handle numpad keys here
		if (key >= this.NUM_ZERO && key <= this.NUM_NINE) {
			// These are numkeys and it will have different keyCode than number's
			// ASCII value so we are mapping these keys to it's original character values.
			key = key - 48;
		}
		return String.fromCharCode(key);
	}
});
/**
 * @class String
 * #core
 */

// https://github.com/uxitten/polyfill/blob/master/string.polyfill.js
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat
if (!String.prototype.padEnd) {
  /* jshint freeze: false */
  String.prototype.padEnd = function padEnd(targetLength,padString) {
    targetLength = targetLength>>0; //floor if number or convert non-number to 0;
    padString = String(padString || ' ');
    if (this.length > targetLength) {
      return String(this);
    }
    else {
      targetLength = targetLength-this.length;
      if (targetLength > padString.length) {
        padString += padString.repeat(targetLength/padString.length); //append to original to ensure we are longer than needed
      }
      return String(this) + padString.slice(0,targetLength);
    }
  };
}

if (!String.prototype.startsWith) {
	/**
	 * Determines whether a string begins with the characters of a specified string,
	 * returning true or false as appropriate.
	 * @param {String} searchString The characters to be searched for at the start of this string.
	 * @param {Number} position The position in this string at which to begin searching for searchString; defaults to 0.
	 * @return {Boolean} true if the given characters are found at the beginning of the string; otherwise, false.
	 */
	Object.defineProperty(String.prototype, 'startsWith', {
		value: function(searchString, position) {
			return this.substr(position || 0, searchString.length) === searchString;
		}
	});
}

// https://github.com/uxitten/polyfill/blob/master/string.polyfill.js
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart
if (!String.prototype.padStart) {
  /* jshint freeze: false */
  String.prototype.padStart = function padStart(targetLength,padString) {
    targetLength = targetLength>>0; //floor if number or convert non-number to 0;
    padString = String(padString || ' ');
    if (this.length > targetLength) {
      return String(this);
    }
    else {
      targetLength = targetLength-this.length;
      if (targetLength > padString.length) {
        padString += padString.repeat(targetLength/padString.length); //append to original to ensure we are longer than needed
      }
      return padString.slice(0,targetLength) + String(this);
    }
  };
}

if (!String.prototype.repeat) {

	/**
	 * IE does not supported repeat function so, add a function into string to available repeat the string function.
	 * @param {Number} times an integer indicating the number of times to repeat the string in the newly-created string.
	 * @returns {string} A new string containing the specified number of copies of the given string.
	 */
	String.prototype.repeat = function repeat(times) {
		var repeatedString = "";

		while (times > 0) {
			repeatedString += this;
			times--;
		}

		return repeatedString;
	};
}Ext.namespace('Zarafa');

/**
 * @class Zarafa
 * Global convenience methods.
 * @singleton
 * #core
 */
Ext.apply(Zarafa, {
	/**
	 * The string we use to create the regular expressions for email address validation
	 *
	 * @property
	 * @type {String}
	 * @private
	 */
	emailAddressRegExpString: '(?:[a-zA-Z0-9.!#$%&\'*+\\-/=?^_`{|}~])+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*\\.(?:[a-zA-Z0-9]{2,15})',

	/**
	 * A regular expression to test the validity of an email address
	 *
	 * @property
	 * @type {RegExp}
	 */
	reSingleEmailAddress: undefined,

	/**
	 * A regular expression to find email addresses in a string
	 *
	 * @property
	 * @type {RegExp}
	 */
	reMultipleEmailAddresses: undefined,

	/**
	 * Ready flag which indicates that Webapp has been loaded.
	 * (See {@link #onReady}).
	 * @property
	 * @type Boolean
	 */
	isReady: false,

	/**
	 * Registration object for {@link #onReady} onto which all event
	 * handlers are being registered which want to be notified when
	 * grommunio Web has been initialized and ready for plugin interaction.
	 *
	 * @property
	 * @type Ext.util.Event
	 * @private
	 */
	readyEvent: new Ext.util.Event(),

	/**
	 * Ready flag which indicates that Webapp UI has been loaded.
	 * (See {@link #onUIReady}).
	 * @property
	 * @type Boolean
	 */
	uiReady: false,

	/**
	 * Registration object for {@link #uiReady} onto which all event
	 * handlers are being registered which want to be notified when
	 * grommunio Web has drawn the main UI and has loaded the hierarchy panel.
	 *
	 * @property
	 * @type Ext.util.Event
	 * @private
	 */
	uiReadyEvent: new Ext.util.Event(),

	/**
	 * The time that the user has not done any action
	 * (like mousemove, click, or keypress) in grommunio Web.
	 *
	 * @property
	 * @type Integer
	 * @private
	 */
	idleTime: 0,

	/**
	 * True if the user is running DeskApp to view grommunio Web, false otherwise.
	 *
	 * @property
	 * @type {Boolean}
	 */
	isDeskApp: Ext.isDefined(window.nw),

	/**
	 * Adds a listener to be notified when grommunio Web is ready. This will be somewhere during {@link Ext.onReady}, when
	 * grommunio Web has initialized the bare essentials. When the event is fired, the {@link Zarafa.core.Container} will
	 * be available, and plugins are allowed to register.
	 *
	 * @param {Function} fn The method the event invokes.
	 * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the handler function executes. Defaults to the browser window.
	 * @param {Boolean} options (optional) Options object as passed to {@link Ext.Element#addListener}. It is recommended that the options
	 * <code>{single: true}</code> be used so that the handler is removed on first invocation.
	 */
	onReady: function(fn, scope, options)
	{
		this.readyEvent.addListener(fn, scope, options);

		// If the environment is already ready, can
		// should call fireReady again to fire the
		// just registered event.
		if (this.isReady) {
			this.fireReady();
		}
	},

	/**
	 * Called when {@link Ext.onReady} has been invoked, and grommunio Web has been initialized.
	 * All handlers registered through {@link #onReady} will now be fired and {@link #isReady}
	 * will be set.
	 *
	 * @private
	 */
	fireReady: function()
	{
		this.isReady = true;
		this.readyEvent.fire();
		this.readyEvent.clearListeners();
	},

	/**
	 * Adds a listener to be notified when grommunio Web UI is drawn and the hierarchy is loaded.
	 * @param {Function} fn The method the event invokes.
	 * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the handler function executes. Defaults to the browser window.
	 * @param {Boolean} options (optional) Options object as passed to {@link Ext.Element#addListener}. It is recommended that the options
	 * <code>{single: true}</code> be used so that the handler is removed on first invocation.
	 */
	onUIReady: function(fn, scope, options)
	{
		// Force single is true for events.
		options = options || {};
		options.single = true;

		this.uiReadyEvent.addListener(fn, scope, options);

		// If the environment is already ready, call fireUIReady again
		// to fire the just registered event.
		if (this.uiReady) {
			this.fireUIReady();
		}
	},

	/**
	 * Called when grommunio Web's UI has been loaded and the hierarchy is loaded.
	 * All handlers registered through {@link #onUIReady} will now be fired and {@link #uiReady}
	 * will be set.
	 *
	 * @private
	 */
	fireUIReady: function()
	{
		this.uiReady = true;
		this.uiReadyEvent.fire();
		this.uiReadyEvent.clearListeners();
	},

	/**
	 * Initialize all Global variables as used by grommunio Web.
	 *
	 * This will utilize some global objects as received by the PHP
	 * side, and apply them into the proper classes, after which the
	 * global objects will be destroyed.
	 *
	 * This will instantiate {@link Zarafa.core.Container container}.
	 * @private
	 */
	initializeGlobals: function()
	{
		// Use native json handling of browser for performance benefit
		Ext.USE_NATIVE_JSON = true;

		//show confirm dialog before user leave the page.
		Zarafa.core.Util.enableLeaveRequester();

		// When the browser is unloading, all active requests will be aborted and
		// If more than one browser windows are open then close all browser windows.
		const handlePageHide = function () {
			if (Zarafa.core.BrowserWindowMgr.browserWindows.length > 1) {
				Zarafa.core.BrowserWindowMgr.closeAllBrowserWindow();
			}
			container.getRequest().paralyze(Zarafa.core.data.ParalyzeReason.BROWSER_RELOADING);
		};
		window.addEventListener('pagehide', handlePageHide);

		// Initialize the regular expressions that can be used to validate email addresses
		this.reSingleEmailAddress = new RegExp('^' + this.emailAddressRegExpString + '$');
		this.reMultipleEmailAddresses = new RegExp('([^,;\\n\\r]*?<{0,1}' + Zarafa.emailAddressRegExpString + ')>{0,1}(?=(?:$|[,;\\n\\r\\s]))', 'g');

		// Create global container object
		container = new Zarafa.core.Container();

		// Set the server object
		/*global user version languages serverconfig settings:true*/
		container.setServerConfig(serverconfig);
		delete serverconfig;

		// Load all settings
		container.getSettingsModel().initialize(settings);
		delete settings;

		// Initialize dark mode system (handles OnlyOffice theme, TinyMCE, mail preview)
		Zarafa.core.DarkMode.init();

		// Load all persistent settings (i.e. settings that will not be deleted when the user resets his settings)
		// Persistent settings are not added to the welcome screen, so check if they exist first.
		if ( Ext.isDefined(window.persistentsettings) ){
			container.getPersistentSettingsModel().initialize(window.persistentsettings);
			delete window.persistentsettings;
		}

		// Set the user object
		container.setUser(user);
		delete user;

		// Set the version object
		container.setVersion(version);
		delete version;

		// Set the language object
		container.setLanguages(languages);
		delete languages;

		// Set up DOMPurify
		DOMPurify.setConfig({
			FORBID_TAGS: ['iframe', 'webview', 'meta', 'html', 'head', 'link'],
			WHOLE_DOCUMENT: false,
			// Keep email <style> elements in the returned fragment. DOMPurify's
			// HTML parser may otherwise move a leading/head style element outside
			// the body fragment, which makes a safe stylesheet appear to vanish.
			FORCE_BODY: true,
			// Default regEx of DOMPurify for uri does not allow some protocols like file, smb, etc.
			// So we need to whitelist them by this new regEx.
			ALLOWED_URI_REGEXP: Object.seal(/^(?:(?:(?:f|ht)tps?|mailto|elodms|msteams|webexteams|zoommtg|zoomus|gotomeeting|tel|callto|cid|xmpp|smb|file):|[^a-z]|[a-z]:|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),
			ALLOW_DATA_ATTR: true,
			ADD_TAGS: ['svg', 'use', 'symbol'],
		});

		DOMPurify.addHook('afterSanitizeAttributes', function(node) {
			// Set all elements owning target to target=_blank.
			if ('target' in node) {
				node.setAttribute('target', '_blank');
				// prevent https://www.owasp.org/index.php/Reverse_Tabnabbing
				node.setAttribute('rel', 'noopener noreferrer external');

				if (node.nodeName === "A" && !Ext.isEmpty(node.href)){
					node.setAttribute('title', `${node.href} \n ${_("Click the link to open the URL in a new window.")}`);
				}
			} else if (!node.hasAttribute('target') && (node.hasAttribute('xlink:href') || node.hasAttribute('href'))) {
				// set non-HTML/MathML links to xlink:show=new
				node.setAttribute('xlink:show', 'new');
			}
		});

	},

	/**
	 * Initialize the ExtJs/Web environment, register generic event listeners,
	 * This will listen to the 'contextmenu' event on the {@link Ext#getBody body element}
	 * as well as the exception events on the {@link Zarafa.core.data.IPMStoreMgr} and
	 * {@link Zarafa.core.ResponseRouter}.
	 * @private
	 */
	initializeEnvironment: function()
	{
		// Register the State provider which uses the SettingsModel.
		Ext.state.Manager.setProvider(new Zarafa.core.data.SettingsStateProvider());

		// Disable contextmenu globally
		Ext.getBody().on('contextmenu', this.onBodyContextMenu, this);

		// Disable default file drop behavior
		Ext.EventManager.on(window, 'dragover', this.onWindowDragDrop, this);
		Ext.EventManager.on(window, 'drop', this.onWindowDragDrop, this);

		// Add main event handlers to listen for errors
		container.getRequest().on('connectionparalyzed', this.onConnectionParalyze, this);
		container.getRequest().on('connectioninterrupted', this.onConnectionLoss, this);
		container.getRequest().on('connectionrestored', this.onConnectionRestore, this);
		container.getResponseRouter().on('receiveexception', this.onReceiveException, this);
		// We listen on the Ext.data.DataProxy object to listen in on all exception events
		Ext.data.DataProxy.on('exception', this.onException, this);

		// Enable tooltips
		Ext.QuickTips.init();
	},

	/**
	 * Event handler which is fired when the {@link Ext#getBody &lt;body&gt;} elements fires
	 * the 'contextmenu' event. If the element which fired the event doesn't have the
	 * 'zarafa-contextmenu-enabled' class and isn't a regular text input then the
	 * Browser contextmenu will be disabled.
	 * @param {Ext.EventObject} event The event object
	 * @param {Ext.Element} el The element on which the contextmenu was requested
	 * @private
	 */
	onBodyContextMenu: function(event, el)
	{
		el = Ext.get(el);

		// Don't disable the browser contextmenu when the
		// 'zarafa-contextmenu-enabled' CSS class is applied
		// on the element.
		if ( el.hasClass('zarafa-contextmenu-enabled') || el.up('div.zarafa-contextmenu-enabled')){
			return;
		}

		// Don't disable the browser contextmenu for regular
		// text inputs.
		if (el.dom.tagName.toUpperCase() === 'INPUT')
			return;

		// Disable contextmenu.
		event.preventDefault();
	},

	/**
	 * Event handler which is fired when the 'window' element fires the 'dragover' or
	 * the 'drop' event. This happens when the user drops a file over the webpage. On
	 * some UI fields this will provide a special action, but the browsers default is
	 * to open the file in the current page, which is not what we want.
	 * @param {Ext.EventObject} event The event object
	 * @private
	 */
	onWindowDragDrop: function(event)
	{
		event.stopPropagation();
		event.preventDefault();
		return false;
	},

	/**
	 * Event handler called when the {@link Ext.data.DataProxy} fired the
	 * {@link Ext.data.DataProxy#storeexception storeexception} event.
	 * This will check what type of exception it was ('response' or 'remote') and
	 * handle the exception accordingly.
	 *
	 * @param {Misc} misc See {@link Ext.data.DataProxy}#{@link Ext.data.DataProxy#exception exception}
	 * for description.
	 * @private
	 */
	onException: function(proxy, type, action, options, response, args)
	{
		var message;
		var detailsMessage = '';
		var notificationType = 'error.proxy';

		var title = _('Error');
		if (type === 'response') {
			// The error message can be in args when it is an Error object. This happens when the
			// processing of the response throws an Javascript Exception.
			if (Ext.isDefined(args) && args.error instanceof Error) {
				message = args.error.toString();
			} else {
				// When the exception has to do with the response itself we delegate this behavior to
				// onReceiveException
				this.onReceiveException(options, response);
				return;
			}
		} else if (response && response.error) {
			var errorObj = response.error;
			switch (errorObj.type) {
				case Zarafa.core.ErrorType['MAPI']:
				case Zarafa.core.ErrorType['ZARAFA']:
				case Zarafa.core.ErrorType['GENERAL']:
					message = errorObj.info.display_message;
					detailsMessage = errorObj.info.details_message || '';
					break;
				default:
					message = _('The server reported an unknown error on your request.');
					break;
			}
			if (!Ext.isEmpty(errorObj.info) && !Ext.isEmpty(errorObj.info.title)) {
				title = errorObj.info.title;
			}
		} else {
			message = _('The server reported an unspecified error on your request.');
		}

		if (Ext.get('loading')) {
			this.setErrorLoadingMask(title, message);
		} else {
			container.getNotifier().notify('error.proxy', title, message, {
				details_message : detailsMessage
			});
		}
		// If the meeting request hasn't been sent yet, and there was an error sending it,
		// it should be possible to save it afterwards anyway.
		// See web issue #113
		if (Ext.isDefined(args.sendRecords) && Ext.isArray(args.sendRecords)) {
			args.sendRecords.forEach(record => {
				if (typeof record.isMeetingSent === 'function' &&
				    !record?.isMeetingSent() &&
				    record?.hasMessageAction('send')) {
					record.deleteMessageAction('send');
				}
			});
		}
	},

	/**
	 * Called when the connection is being paralyzed and no further requests can be made
	 * to the server. Check if we should show a notification to the user about this, and
	 * ask if the user wishes to return to the logon page.
	 * @param {Zarafa.core.Request} request The request object
	 * @param {Zarafa.core.data.ParalyzeReason} reason The reason to paralyze grommunio Web
	 * @private
	 */
	onConnectionParalyze: function(request, reason)
	{
		var message = '';
		var logoutFn = Ext.emptyFn;

		switch (reason) {
			case Zarafa.core.data.ParalyzeReason.BROWSER_RELOADING:
			/* falls through */
			default:
				// No message for the user needed.
				return;
			case Zarafa.core.data.ParalyzeReason.SESSION_EXPIRED:
				message = _('The session has expired, reauthentication is required.');
				// When logging out, we preserve the username for convenience.
				logoutFn = container.logout.createDelegate(container, [ true ], false);
				break;
			case Zarafa.core.data.ParalyzeReason.SESSION_INVALID:
				message = _('The session in the current browser window has been closed from another browser window or tab.');
				// When logging out, we preserve the username for convenience,
				// but we won't close the session which was created in the other tab.
				logoutFn = container.logout.createDelegate(container, [ true, true ], false);
				break;
		}

		if (Ext.get('loading')) {
			this.setErrorLoadingMask(_('Error'), message);
		} else {
			Ext.MessageBox.show({
				title: _('Session expired'),
				msg: message + '<br>' + _('Do you wish to be redirected to the logon page?'),
				cls: Ext.MessageBox.ERROR_CLS,
				buttons: Ext.MessageBox.YESNO,
				fn: this.onConnectionParalyzeConfirmation,
				scope: this,
				logoutFn: logoutFn
			});
		}
	},

	/**
	 * Event handler for the {@link Ext.MessageBox MessageBox} which was opened by {@link #onConnectionParalyze}.
	 * This determines what the user has pressed, and if the user wishes to {@link Zarafa.core.Container#logout}
	 * immediately or not. If not, then a {@link Zarafa.core.ui.notifier.Notifier notification} will be shown
	 * to remind the user about the session.
	 * @param {String} button The button which was pressed by the user
	 * @param {String} id The id of the button which was clicked
	 * @param {Object} opt The options which was used to create the MessageBox.
	 * @private
	 */
	onConnectionParalyzeConfirmation: function(button, value, opt)
	{
		if (button === 'yes') {
			opt.logoutFn.call(this);
		} else {
			container.getNotifier().notify('error.connection', _('Session expired'), _('Reauthentication required, click here to go to back to logon page.'), {
				persistent: true,
				listeners: {
					click: opt.logoutFn
				}
			});
		}
	},

	/**
	 * Periodic function which is used to update the {@link Zarafa.core.ui.notification.Notifier notification}
	 * on the screen with the message that there is a connection problem, and the requests will be retried
	 * after the given timeout. This timeout counter will be updated every second.
	 * @param Zarafa.core.PingService} service The ping service handling the connection loss
	 * @param {Object} object Containing the information related to the connection loss
	 * @param {Number} timeout The number of seconds until the next retry to connect to the server
	 * @private
	 */
	onConnectionTimeupdate: function(service, object, timeout)
	{
		var request = container.getRequest();

		// Since we use defers, it is possible that the
		// connection was already restored. In that case,
		// we don't need to update the Notification message.
		if (!request.isInterrupted() || request.isParalyzed()) {
			return;
		}

		// Create the notification, we store the reference in connEl,
		// if it already exists, this will be an update action, otherwise
		// a new notification will be created.
		this.connEl = container.getNotifier().notify('error.connection', _('Connection problem'),
								 String.format(_('Could not connect to server, retrying in {0} second(s)'), timeout / 1000) + '\n' + _('Click to retry now'), {
			persistent: true,
			update: !!this.connEl,
			reference: this.connEl,
			listeners: {
				// If the user clicks on the notification,
				// immediately retry to connect to to the server.
				click: service.retry,
				scope: service
			}
		});

		// Defer the function by 1 second, so we can update the retry counter.
		if (timeout > 1000) {
			this.connElTask.delay(1000, this.onConnectionTimeupdate, this, [ service, object, timeout - 1000 ]);
		}
	},

	/**
	 * Event handler for the {@link Zarafa.core.PingService#retry} event. This will
	 * cancel the currently scheduled call to {@link #onConnectionTimeupdate} and
	 * invoke it manually with the updated information.
	 * @param Zarafa.core.PingService} service The ping service handling the connection loss
	 * @param {Object} object Containing the information related to the connection loss
	 * @param {Number} timeout The number of seconds until the next retry to connect to the server
	 * @private
	 */
	onConnectionRetry: function(service, object, timeout)
	{
		// In case there was still a pending
		// update task, we interrupt that one.
		this.connElTask.cancel();

		// No we can update the notification
		this.onConnectionTimeupdate(service, object, timeout);
	},

	/**
	 * Event handler for the {@link Zarafa.core.Request#connectionloss} event. This will register the
	 * event handlers to the provided {@link Zarafa.core.PingService} which will give us the information
	 * about the next reconnect attempt.
	 * @param {Zarafa.core.Request} request The request object
	 * @param {Zarafa.core.PingService} service The ping service which is going to handle the connection loss
	 * @private
	 */
	onConnectionLoss: function(request, service)
	{
		this.connElTask = new Ext.util.DelayedTask(this.onConnectionTimeupdate, this);
		service.on('retry', this.onConnectionRetry, this);
	},

	/**
	 * Event handler for the {@link Zarafa.core.Request#connectionrestore} event. This will remove
	 * the error.connection {@link Zarafa.core.ui.notification.Notifier notification}
	 * and will show a info.connection.restore {@link Zarafa.core.ui.notification.Notifier notification}.
	 * @param {Zarafa.core.Request} request The request object
	 * @private
	 */
	onConnectionRestore: function(request)
	{
		if (this.connElTask) {
			this.connElTask.cancel();
			delete this.connElTask;
		}

		if (this.connEl) {
			container.getNotifier().notify('error.connection', null, null, {
				destroy: true,
				reference: this.connEl
			});
			container.getNotifier().notify('info.connection.restore', _('Connection restored'), _('Connection with server has been restored'));
			delete this.connEl;
		}
	},

	/**
	 * Event handler called when the PHP server returned an error
	 * in the root of the response. This indicates that the communication
	 * with the PHP server failed and the user should login again.
	 *
	 * @param {Object} requestdata The request data which was send to the server.
	 * @param {Object} xmlHttpRequest The raw browser response object
	 * @private
	 */
	onReceiveException: function(requestdata, xmlHttpRequest)
	{
		var loading = Ext.get('loading');
		var errorTitle;
		var errorMsg;

		if (xmlHttpRequest.status !== 200) {
			// # TRANSLATORS: Example: HTTP 404
			errorTitle = String.format(_('HTTP {0}'), xmlHttpRequest.status);
			errorMsg = xmlHttpRequest.statusText;
		} else {
			errorTitle = _('Error');
			errorMsg = _('Invalid data received from the server');
		}

		if (loading) {
			this.setErrorLoadingMask(errorTitle, errorMsg);
		} else {
			container.getNotifier().notify('error.json', errorTitle, errorMsg);
		}
	},

	/**
	 * Hide the loading mask which is shown before the {@link Ext.Viewport} is being rendered.
	 * The loadmask is marked with the element classname 'loading' and the background 'loading-mask'.
	 * @param {Function} callback An optional callback function that will be called when the the
	 * loading mask is completely hidden.
	 * @private
	 */
	hideLoadingMask: function(callback)
	{
		var loadingMask = Ext.get('loading-mask');

		if ( loadingMask ) {
			// Hide loading mask
			loadingMask.remove();
			if (Ext.isFunction(callback)) {
				callback();
			}
		}
	},

	/**
	 * Set an error text in the loading screen.
	 * @param {String} newError The title for the loading screen
	 * @param {String} newMessage The message for the loading screen
	 * @private
	 */
	setErrorLoadingMask: function(newTitle, newMessage)
	{
		var template = new Ext.Template('<div><b>{title}</b><br />{msg}</div>', { compiled: true, disableFormats: true });
		var message = Ext.get('loading-message');
		if (message) {
			message.dom.className = 'loading-error';
			template.overwrite(message, { title: newTitle, msg: newMessage });
		}
	},

	/**
	 * Validate the {@link Zarafa.hierarchy.data.HierarchyStore HierarchyStore} to determine
	 * if the {@link Zarafa.hierarchy.data.HierarchyStore#getDefaultStore Default Store} is present
	 * along with all the {@link Zarafa.hierarchy.data.HierarchyStore#getDefaultFolder Default Folders}.
	 * If there is a problem, this will show a {@link Zarafa.core.ui.notifier.Notifier Notification}
	 * indicating which store or folders are missing, and warning the user that not all functionality
	 * might be working as expected.
	 * @param {Zarafa.hierarchy.data.HierarchyStore} store The Hierarchy Store to validate
	 * @private
	 */
	validateHierarchy: function(store)
	{
		if (!store.getDefaultStore()) {
			container.getNotifier().notify('error.hierarchy.defaultfolder',
				_('Missing store'),
				_('The default store is missing from the hierarchy.') +
					'\n' +
					_('Not all functionality of grommunio Web might be working properly because of this.'),
				{
					persistent: true,
					listeners: {
						'click': this.onHierarchyNotifierClick,
						'scope': this
					}
				}
			);
			return;
		}

		// The following default folders are required to be present
		// to be able to properly work with grommunio Web.
		var defaultFolders = [{
			type: 'inbox',
			name: pgettext('hierarchy.foldername', 'Inbox')
		},{
			type: 'outbox',
			name: pgettext('hierarchy.foldername', 'Outbox')
		},{
			type: 'sent',
			name: pgettext('hierarchy.foldername', 'Sent Items')
		},{
			type: 'wastebasket',
			name: pgettext('hierarchy.foldername', 'Deleted items')
		},{
			type: 'calendar',
			name: pgettext('hierarchy.foldername', 'Calendar')
		},{
			type: 'contact',
			name: pgettext('hierarchy.foldername', 'Contacts')
		},{
			type: 'drafts',
			name: pgettext('hierarchy.foldername', 'Drafts')
		},{
			type: 'journal',
			name: pgettext('hierarchy.foldername', 'Journal')
		},{
			type: 'note',
			name: pgettext('hierarchy.foldername', 'Notes')
		},{
			type: 'task',
			name: pgettext('hierarchy.foldername', 'Tasks')
		},{
			type: 'junk',
			name: pgettext('hierarchy.foldername', 'Junk Email')
		}];

		var missing = [];

		// Go over all default folders which we expect to be present,
		// if any of them is missing we update the 'missing' array
		// and will show them in a notification box.
		for (var i = 0, len = defaultFolders.length; i < len; i++) {
			var folder = defaultFolders[i];

			if (!store.getDefaultFolder(folder.type)) {
				missing.push(folder.name);
			}
		}

		// If any of the folders is missing, show a notification to the
		// user to inform him of the missing folders.
		if (!Ext.isEmpty(missing)) {
			var list = missing.join(', ');

			container.getNotifier().notify('error.hierarchy.defaultfolder',
				_('Missing folders'),
				String.format(
					ngettext('The following required folder is missing in the hierarchy: {0}',
						 'The following required folders are missing in the hierarchy: {0}', missing.length), list) +
					'\n' + _('Not all functionality of grommunio Web might be working properly because of this.'),
				{
					persistent: true,
					listeners: {
						'click': this.onHierarchyNotifierClick,
						'scope': this
					}
				}
			);
		}
	},

	/**
	 * Event handler which is fired when the user clicked on the {@link Zarafa.core.ui.notifier.Notifier notification}.
	 * This will remove the notification.
	 * @param {Ext.Element} element The notification element
	 * @param {Ext.EventObject} event The event object
	 * @private
	 */
	onHierarchyNotifierClick: function(element, event)
	{
		container.getNotifier().notify('error.hierarchy.defaultfolder', null, null, {
			reference: element,
			destroy: true
		});
	},

	/**
	 * Event handler called when load is received in hierarchy. This will {@link #hideLoadingMask hide the loadmask}.
	 * @param {Zarafa.hierarchy.data.HierarchyStore} store The store which was loaded
	 * @param {Ext.data.Record[]} records The records which were loaded by the store
	 * @param {Object} options The options which were originally passed to {@link Ext.data.Store#load}.
	 * @private
	 */
	onHierarchyLoad: function(store, records, options)
	{
		if (!Ext.isEmpty(records)) {
			// We have the hierarchy, load the entire UI
			container.getMainPanel();

			// validate the hierarchy, if the hierarchy is not valid a warning
			// notification box will be shown to the user informing him of incompatibilities.
			this.validateHierarchy(store);

			// Remove loading mask, we might still be busy loading the data for the
			// store of the user, but the context will have its own loadmask for that.
			// The user is at least allowed to see the Hierarchy and press buttons.
			this.hideLoadingMask(function(){
				container.fireEvent('webapploaded');

				// Notify that grommunio Web UI is loaded.
				Zarafa.fireUIReady();
			});

			// Remove resize event listener of loading page
			window.removeEventListener('resize', resizeLoginBox);

			// Register webapp to handle mailto urls
			this.registerMailto();

			// Process data that was passed as URL data
			Zarafa.core.URLActionMgr.execute(urlActionData);
			delete urlActionData;

			// Start the keepalive to make sure we stay logged into the zarafa-server,
			// the keepalive is also used to get notifications back to the client.
			store.startKeepAlive();

			// Defer non-critical initialization so the mail-list load
			// (triggered by the context enable above) gets through the
			// PHP state-file lock first.  This avoids serialization
			// contention that would otherwise delay the first user
			// interaction after login.
			this.startDeferredServices.defer(1000, this);
		} else {
			this.setErrorLoadingMask(_('Error'), _('Loading model from server failed'));
		}
	},

	/**
	 * Start services that are not required for the initial UI render.
	 * Called with a short delay after {@link #onHierarchyLoad} so the
	 * critical mail-list request can claim the PHP state-file lock
	 * first, keeping the first user interaction responsive.
	 * @private
	 */
	startDeferredServices: function()
	{
		// Flush any settings saves that were accumulated during boot.
		// This is done here (1 s after hierarchy load) so the critical
		// hierarchy and mail-list requests finish first.
		container.getSettingsModel().setReady();

		// Out-of-office check
		var oofStore = container.getOutOfOfficeStore();
		oofStore.on('load', this.onOofStoreLoad, this, { single: true });

		// Reminder polling
		container.getReminderStore().initializeReminderInterval();

		// Shared stores unread email poller
		this.startSharedStoresHierarchyChecker();
	},

	/**
	 * Function will register webapp as default client to handle mailto urls
	 * Ideally we should only register this handler if already not registered
	 * but browsers support is not proper for that, so we are always registering it
	 * in chrome this is not a problem, in firefox it will show bar that handler is already registered
	 * but that is a bug and already fixed in trunk version https://bugzilla.mozilla.org/show_bug.cgi?id=912347
	 * Support for isProtocolHandlerRegistered is also limited https://bugzilla.mozilla.org/show_bug.cgi?id=440620
	 */
	registerMailto: function()
	{
		var navigator = window.navigator;

		if (Ext.isFunction(navigator.registerProtocolHandler)) {
			// Register webapp handler for 'mailto' protocol in browsers.
			var url = container.getBaseURL() + '?action=mailto&to=%s';

			// Check if we have already registered for this protocol
			var register = true;
			if (Ext.isFunction(navigator.isProtocolHandlerRegistered)) {
				register = !navigator.isProtocolHandlerRegistered('mailto', url);
			}

			// Register if required
			if (register) {
				navigator.registerProtocolHandler('mailto', url, 'grommunio Web');
			}
		}
	},

	/**
	 * Load the default Webclient into the browser. This will initialize the environment,
	 * load the {@link Zarafa.hierarchy.data.HierarchyStore} and open the
	 * {@link Zarafa.core.ui.MainViewport MainViewport}.
	 */
	loadWebclient: function()
	{
		// Initialize globals & environment
		Zarafa.initializeGlobals();
		Zarafa.initializeEnvironment();

		// Recolor the icons if necessary
		this.recolorIcons();

		// Start loading all plugins
		Zarafa.fireReady();

		// Initialize context - check if there is one selected in settings
		this.initContext();

		// Setup the hierarchy, this will complete the initialization
		// and allow the Main Viewport to be opened
		var hierarchyStore = container.getHierarchyStore();

		// For the hierarchy we only need to register the event handler once,
		// as only during the first time we have to remove the load mask.
		hierarchyStore.on('load', this.onHierarchyLoad, this, { single: true });

		// Load the folder hierarchy as the first and only request during
		// boot.  Non-critical requests (OOF, reminders, shared-store
		// updates) are deferred to onHierarchyLoad so they do not
		// compete for the PHP state-file lock while the hierarchy and
		// the initial mail-list load are still in progress.
		hierarchyStore.load();

		// When a client timeout has been defined, we will start keeping track
		// of idle time.
		var server = container.getServerConfig();
		var clientTimeout = server.getClientTimeout();
		if (clientTimeout){
			this.startIdleTimeChecker(clientTimeout);
		}
	},

	/**
	 * Starts the checking of idle time.
	 * This function uses original javascript events because we cannot set
	 * the useCapture property with ExtJS events.
	 * See https://developer.mozilla.org/en/docs/Web/API/EventTarget.addEventListener
	 *
	 * @param {Number} clientTimeout The timeout time in seconds.
	 * @private
	 */
	startIdleTimeChecker: function(clientTimeout)
	{
		if ( !document.addEventListener ) {
			// User is using a browser that does not support addEventListener.
			// Probably IE<9 which we don't support.
			// However there is no reason to create errors for IE<9
			// Client timeout will still be handled by the backend though,
			// but the message will only be shown to the user when he tries to
			// connect to the backend after the session has timed out.
			return;
		}
		var me = this;

		var clickHandler = function(){
			me.idleTime = 0;
		};
		var moveHandler = function(){
			me.idleTime = 0;
		};

		try {
			document.addEventListener('click', clickHandler, { capture: true, passive: true });
			document.addEventListener('mousemove', moveHandler, { capture: true, passive: true });
		} catch (ex) {
			document.addEventListener('click', clickHandler, true);
			document.addEventListener('mousemove', moveHandler, true);
		}

		document.addEventListener('keydown', function(){
			me.idleTime = 0;
		}, true);

		var hierarchyStore = container.getHierarchyStore();

		// Start an interval for increasing the idle time
		Ext.TaskMgr.start.createDelegate(this, [{
			run: function(){
				// Add 5 seconds to the idle time counter
				this.idleTime += 5;
				if ( this.idleTime > clientTimeout ){
					hierarchyStore.sendDestroySession();
				}
			},
			scope: this,
			interval: 5000 //Run every 5 seconds
		}]).defer(5000); // Start after 5 seconds

		// Start an interval for sending keepalive requests
		// We need this keepalive to keep the connection alive if the user has made an
		// action in the grommunio Web without connecting to the server. (like mousemove, click, keydown)
		// Subtracting 5 seconds to account for latency
		var interval = (clientTimeout-5)*1000;
		if ( interval < 5000 ){
			// This one is especially for Sean who was so smart to set a client timeout of 5 seconds
			// causing keepalive requests to be sent constantly and thereby ddos'ing his own server :-)
			// Let's never send keepalive requests with an interval lower than 5 seconds.
			// Anyone who sets a timeout this low deserves to be logged out! (and punished severely)
			interval = 5000;
		}

		Ext.TaskMgr.start.createDelegate(this, [{
			run: function(){
				hierarchyStore.sendKeepAlive();
			},
			scope: this,
			interval: interval
		}]).defer(interval); //Start sending keepalives after interval milliseconds.
	},

	/**
	 * Start the Shared Stores unread mail poller, fetches the hierarchy
	 * once to fill the server side cache.
	 * @private
	 */
	startSharedStoresHierarchyChecker: function()
	{
		const interval = container.getServerConfig().getSharedStorePollingInterval();

		if (!Ext.isNumber(interval)) {
			return;
		}

		// Fetch shared stores state on login.
		container.getHierarchyStore().sendSharedStoreHierarchyUpdate();

		setInterval(function() {
			container.getHierarchyStore().sendSharedStoreHierarchyUpdate();
		}, interval);
	},

	/**
	 * init UI by lazily constructing the main panel (implicit in container.getMainPanel) and
	 * setting the default context to visible. Note that during onHierarchyLoad we will also
	 * open the default folder for that specific context to ensure the user can see all from
	 * the folder.
	 * @private
	 */
	initContext: function()
	{
		var defaultContext = container.getSettingsModel().get('zarafa/v1/main/default_context');
		var plugin = container.getContextByName(defaultContext);
		if (!plugin) {
			// If the defaultContext has an invalid name,
			// we will default to the 'mail' context
			plugin = container.getContextByName('mail');
		}
		container.switchContext(plugin);
	},

	/**
	 * When an iconset has SVG icons and it has defined the primary-color and/or
	 * secondary-color properties and the active theme has defined the
	 * icons-primary-color and/or icons-secondary-color property this function
	 * will rewrite the css rules of the iconset to update the colors of the icons.
	 */
	recolorIcons: function()
	{
		// Get the properties defined by the active iconset
		var serverConfig = container.getServerConfig();
		var iconsets = serverConfig.getIconsets();
		var activeIconsetName = serverConfig.getActiveIconset();
		var activeIconset = iconsets[activeIconsetName];

		// If the iconset did not define a primary or secondary color, we cannot
		// redefine the colors, so return without doing anything
		if ( !activeIconset['primary-color'] && !activeIconset['secondary-color'] ) {
			return;
		}

		// Get the primary and secondary icon color defined in the active theme
		// (themes can override the colors of iconsets that allow recoloring)
		var themeIconsPrimaryColor = serverConfig.getPrimaryIconColor();
		var themeIconsSecondaryColor = serverConfig.getSecondaryIconColor();

		// If the active theme did not define a new primary or secondary color
		// for the icons there is nothing to do, so return
		if ( !themeIconsPrimaryColor && !themeIconsSecondaryColor ) {
			return;
		}

		// Pre-compile the color regexes once instead of per-rule.
		var primaryRe = activeIconset['primary-color'] ?
			new RegExp(activeIconset['primary-color'], 'gi') : null;
		var secondaryRe = activeIconset['secondary-color'] ?
			new RegExp(activeIconset['secondary-color'], 'gi') : null;

		// Collect the full CSS text of the iconset stylesheet in memory,
		// recoloring SVG data URIs as we go.  This avoids per-rule
		// deleteRule/insertRule calls that each trigger a CSSOM
		// recalculation — with ~700 SVG rules that caused a multi-
		// second UI freeze.
		var sheet = document.getElementById('grommunio-iconset-stylesheet');
		var rules = sheet.sheet.cssRules;
		var svgUrlRe = /url\("data:image\/svg\+xml;base64,(.+?)"\)/;
		var parts = new Array(rules.length);

		for ( var i = 0; i < rules.length; i++ ) {
			var ruleText = rules[i].cssText;
			var matches = svgUrlRe.exec(ruleText);

			if ( matches !== null ) {
				var svg = atob(matches[1]);
				var svgRecolored = svg;
				if ( primaryRe ) {
					svgRecolored = svgRecolored.replace(
						primaryRe, themeIconsPrimaryColor);
				}
				if ( secondaryRe ) {
					svgRecolored = svgRecolored.replace(
						secondaryRe, themeIconsSecondaryColor);
				}

				if ( svg !== svgRecolored ) {
					ruleText = ruleText.replace(
						/url\("data:image\/svg\+xml;base64,(.+)"\)/,
						'url("data:image/svg+xml;base64,' +
							btoa(svgRecolored) + '")'
					);
				}
			}

			parts[i] = ruleText;
		}

		// Replace the <link> with a <style> element in one DOM
		// operation so the browser only recalculates styles once.
		var newStyle = document.createElement('style');
		newStyle.id = sheet.id;
		newStyle.textContent = parts.join('\n');
		sheet.parentNode.replaceChild(newStyle, sheet);
	},

	/**
	 * Event handler called when load is received in outofoffice store.
	 * This will check if user is out of office If so, ask them if they want to switch OOF off.
	 *
	 * @param {Zarafa.common.outofoffice.data.OofStore} store The store which was loaded
	 * @param {Zarafa.common.outofoffice.data.OofRecord} records The records which were loaded by the store
	 * @param {Object} options The options which were originally passed to {@link Ext.data.Store#load}.
	 * @private
	 */
	onOofStoreLoad: function(store, records, options)
	{
		var oof = false;

		var loginUserEntryId = container.getUser().getEntryId();
		var oofUserSettings;

		// If logged in user is out of office then only this will give the user's out of office settings information.
		for (var i=0; i< records.length; i++) {
			if (Zarafa.core.EntryId.compareEntryIds(records[i].get('entryid'), loginUserEntryId)) {
				oofUserSettings = records[i];
				break;
			}
		}

		if (oofUserSettings) {
			var oofFrom = oofUserSettings.get('from');
			var oofUntil = oofUserSettings.get('until');
			var isOofSet = oofUserSettings.get('set');
			var date = new Date().getTime()/1000;

			if (isOofSet) {
				// Check if current date fall within the time span of OOF start-date and end-date, if configured.
				if (oofFrom <= date) {
					// Check if end-date is configured, no need to check otherwise
					if(oofUntil === 0 || oofUntil > date) {
						oof = true;
					} else {
						// Current date falls out of the configured time span, so disable the OOF
						oofUserSettings.set('set', false);
						store.save();
					}
				}
			}
		}

		if (oof) {
			Ext.MessageBox.show({
				title: _('Out of Office'),
				msg: _('Out of Office is currently activated. Would you like to turn it off?'),
				buttons: Ext.MessageBox.YESNO,
				fn: this.onOofConfirm,
				scope: oofUserSettings
			});
		}
	},

	/**
	 * Handler for the out of office confirmation box
	 * If the user pressed Yes/Ok, then disable out of office
	 * @param {String} id of the button that was clicked
	 * @private
	 */
	onOofConfirm: function(button)
	{
		if (button === 'yes') {
			this.set('set', false);
			this.save();
		}
	},

	/**
	 * Load the Welcome message for new users into the browser. This will initialize
	 * the environment and open the {@link Zarafa.core.ui.WelcomeViewport WelcomeViewport}.
	 */
	loadWelcome: function()
	{
		// Setup globals & environment
		this.initializeGlobals();
		this.initializeEnvironment();

		// Start loading all plugins
		this.fireReady();

		// Load the welcome view
		container.getWelcomePanel();
		this.hideLoadingMask();
	},

	//
	// Generic Utility functions, should probably be moved elsewhere
	//

	/**
	 * This will resize a canvas {@link Ext.Element element}.
	 * Canvas resizing is more tricky then one would expect. For canvas 2 settings are
	 * important: the CSS and attribute dimensions.
	 *
	 * As one would expect, the CSS dimensions, given through a CSS file, or
	 * 'style' attribute (controlled by the functions {@link Ext.Element#setWidth setWidth}
	 * and {@link Ext.Element#setHeight setHeight}) controls how big the element itself
	 * is. It however does not mean that the drawing area is of the same size. The drawing
	 * area is controlled by the element attributes 'width' and 'height'.
	 *
	 * Now for the fun part, if you use CSS to size of the element to 1000x1000px,
	 * thus the element looks like:
	 *   <canvas style="width=1000px; height=1000px;">
	 * and set the attributes to 50x50px, making the complete element look like:
	 *   <canvas style="width=1000px; height=1000px;" width="50" height="50">
	 * you can then draw anything you like on the canvas, but only the first
	 * 50x50px of the canvas will be resized to the entire element size. Making
	 * your cute little drawing of a giraffe completely stretched.
	 *
	 * @param {Ext.Element} canvas The canvas element which must be resized.
	 * @param {Number} width The desired width of the canvas element
	 * @param {Number} height The desired height of the canvas element
	 */
	resizeCanvas : function(canvas, width, height)
	{
		canvas.setWidth(canvas.dom.width = width);
		canvas.setHeight(canvas.dom.height = height);
	},

	/**
	 * Generate a random string.
	 * @param {Number} len Length of the string
	 * @return {String} Random string
	 */
	generateId: function(len)
	{
		var text = "";
		var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

		for( var i = 0; i < len; i++ ) {
			text += possible.charAt(Math.floor(Math.random() * possible.length));
		}

		return text;
	},

	/**
	 * Determine if separate window popout is supported.
	 * Support is based on browser's ability to render any element into another browser window.
	 *
	 * @return {Boolean} True if popout is supported, false otherwise
	 */
	supportsPopOut: function()
	{
		// Currently, we do not support the popout in case of IE/Edge.
		return (!(Ext.isIE || Ext.isEdge || navigator.userAgent.includes('grommunio')));
	}
});
Ext.namespace('Zarafa.core');

/**
 * @class Zarafa.core.Actions
 * List of valid action types. Action types are used for identifying request and response types for modules.
 * For instance, to request a mail from the server the client performs an 'open' action on the 'previewreadmailitemmodule'.
 * The server then responds with an 'item' action, containing the email data.
 * @singleton
 */
Zarafa.core.Actions =
{
	/**
	 * The list action retrieves a list of items such as mails, tasks, etc. The server then responds with a list action containing
	 * a list of items.
	 * @property
	 * @type String
	 */
	list: "list",

	/**
	 * Retrieves a list of entries in the global address book
	 * @property
	 * @type String
	 */
	globaladdressbook: "globaladdressbook",

	/**
	 * Retrieves the hierarchy, a set of stores and folders within those stores. Used for updating the
	 * hierarchy tree.
	 * @property
	 * @type String
	 */
	hierarchy: "hierarchy",

	/**
	 * Open an item, usually a mail item. The server responds with an item action.
	 * @property
	 * @type String
	 */
	open: 'open',

	/**
	 * Can mean different things in different contexts. Can be used to request a single item from the server, and is returned by the
	 * server to return the contents on a single item.
	 * @property
	 * @type String
	 */
	item: 'item',

	/**
	 * Update item(s)
	 * @property
	 * @type String
	 */
	update: 'update',

	/**
	 * Save an item.
	 * @property
	 * @type String
	 */
	save: 'save',

	/**
	 * Copy an item.
	 * @property
	 * @type String
	 */
	copy: 'copy',

	/**
	 * Delete an item.
	 * @property
	 * @type String
	 */
	'delete': 'delete',

	/**
	 * Gets folder details.
	 * @property
	 * @type String
	 */
	folder: 'folder',

	/**
	 * Used for setting properties.
	 * @property
	 * @type String
	 */
	set: 'set',

	/**
	 * Used for getting properties.
	 * @property
	 * @type String
	 */
	get: 'get',

	/**
	 * Used for reset properties.
	 * @property
	 * @type String
	 */
	reset: 'reset',

	/**
	 * Used for deleting properties/items.
	 * @property
	 * @type String
	 */
	_delete: 'delete',

	/**
	 * Used for searching on a folder.
	 * @property
	 * @type String
	 */
	search: 'search',

	/**
	 * Used for incremental search on folder.
	 * @property
	 * @type String
	 */
	updatesearch: 'updatesearch',

	/**
	 * Used for live scroll.
	 * @property
	 * @type String
	 */
	updatelist: 'updatelist',

	/**
	 * Used for stopping search on folder.
	 * @property
	 * @type String
	 */
	stopsearch: 'stopsearch',

	/**
	 * Used for requesting contacts from addressbook
	 * @property
	 * @type String
	 */
	contacts: 'contacts',

	/**
	 * Used to send a keepalive to the server
	 * @property
	 * @type String
	 */
	keepalive: 'keepalive',

	/**
	 * Used to send a request to destroy the session to the server
	 * @property
	 * @type String
	 */
	destroysession: 'destroysession',

	/**
	 * Used when receiving update from server indicating there is new mail
	 * @property
	 * @type String
	 */
	newmail: 'newmail',

	/**
	 * Used for creating new folder
	 * @property
	 * @type String
	 */
	addFolder: 'add',

	/**
	 * Used for renaming folder in tree
	 * @property
	 * @type String
	 */
	modifyFolder: 'modify',

	/**
	 * Used for deleting folder from tree
	 * @property
	 * @type String
	 */
	deleteFolder: 'delete',

	/**
	 * Used on Deleted Items to empty the folder
	 * @property
	 * @type String
	 */
	emptyFolder: 'emptyfolder',

	/**
	 * Used on folders to mark all messages as 'read'
	 * @property
	 * @type String
	 */
	readAllMsgs: 'readflags',
	/**
	 * Used in {@link Zarafa.hierarchy.dialogs.FolderPropertiesContentPanel FolderPropertiesContentPanel} show/update folder props
	 * @property
	 * @type String
	 */
	folderProps: 'folderprops',
	/**
	 * Used in {@link Zarafa.core.data.IPMRecipientStoreCheckNamesProxy IPMRecipientStoreCheckNamesProxy} for resolve requests
	 * @property
	 * @type String
	 */
	checknames: 'checknames',

	/**
	 * Used in {@link Zarafa.core.data.IPMExpandDistlistProxy IPMExpandDistlistProxy} for expand requests
	 * @property
	 * @type String
	 */
	expand: 'expand',

	/**
	 * Used in {@link Zarafa.core.data.IPMAttachmentProxy IPMAttachmentProxy} for uploading attachments.
	 * @property
	 * @type String
	 */
	upload: 'upload',

	/**
	 * Used for importing attached item into folder
	 * @property
	 * @type String
	 */
	import: 'import',

	/**
	 * Used for shared stores unread mail updates
	 * @property
	 * @type String
	 */
	sharedstoreupdate: 'sharedstoreupdate',

	/**
	 * Used for ensuring license for supported products.
	 * @property
	 * @type String
	 */
	ensure: 'ensure'
};
Ext.namespace('Zarafa.core');

/**
 * @class Zarafa.core.BrowserWindowMgr
 * @extends Ext.util.Observable
 *
 * The global Browser-window manager that will keep track of all the available browser windows with
 * a unique key assigned to each of the browser window as its name.
 * @singleton
 */
Zarafa.core.BrowserWindowMgr = Ext.extend(Ext.util.Observable, {
	/**
	 * The list of registered browser window. It contains the list with the window DOM object bound to a
	 * unique key.
	 * @property
	 * @type Ext.util.MixedCollection
	 * @private
	 */
	browserWindows: undefined,

	/**
	 * The key of registered browser window which is active currently.
	 * @property
	 * @type String
	 * @private
	 */
	activeBrowserWindow: undefined,

	/**
	 * The list of registered browser window with the object which contain,
	 * component The constructor of the component which has to be created in the container layer and
	 * config which must be passed to the constructor when creating the component.
	 * @property
	 * @type Ext.util.MixedCollection
	 * @private
	 */
	browserWindowComponents: undefined,

	/**
	 * Denotes that the multiple popups are blocked by the browser or not.
	 * @property
	 * @type Boolean
	 * @private
	 */
	isPopupsBlocked: false,

	/**
	 * An array which contain, component The constructor of the component which has to be created in the container layer and
	 * config which must be passed to the constructor when creating the component,
	 * for the popup window which was blocked by the browser.
	 * @property
	 * @type Array
	 * @private
	 */
	blockedPopupsContent: [],

	/**
	 * @constructor
	 */
	constructor: function()
	{
		this.browserWindows = new Ext.util.MixedCollection();

		// It is required to manage multiple browser windows when first separate window is created by user.
		// So, we must have to register the main webapp-browser-window to the BrowserWindowMgr.
		window.name = 'mainBrowserWindow';
		this.register(window);
		window.addEventListener("focus", function(event) {
			Zarafa.core.BrowserWindowMgr.setActive('mainBrowserWindow');
		}, false);
		this.browserWindowComponents = new Ext.util.MixedCollection();

		this.addEvents('separatewindowresize');
	},

	/**
	 * Register component and config which will use to re create separate window ui on reload or refresh
	 * @param {String} uniqueWindowName unique name of browser window
	 * @param {Function} component The constructor of the component which has to be created in the container layer.
	 * @param {Object} config The configuration object which must be
	 */
	initComponent: function (uniqueWindowName, component, config)
	{
		this.browserWindowComponents.add(uniqueWindowName, {
			component: component,
			config: config
		});
	},


	/**
	 * Helper function which registers necessary events. This happens mostly when anew browser window
	 * gets loaded for the first time.
	 *
	 * @param {Object} browserWindowObject The newly created window object which must be registered.
	 * @param {Ext.Component} componentInstance component which is required to be rendered into the separate browser window.
	 * @param {Ext.Container} mainContainer The container which is the parent most container of separate window.
	 * @private
	 */
	initEvents: function(browserWindowObject, componentInstance, mainContainer)
	{
		// Initialize ext doc classes on separate window.
		this.initExtCss(browserWindowObject);

		componentInstance.on('close', this.onSeparateWindowClose.createDelegate(this, [ browserWindowObject ]));
		componentInstance.on('userupdaterecord', this.onComponentUserupdateRecord.createDelegate(this, [ componentInstance ], true));

		Ext.EventManager.on(
			browserWindowObject,
			"resize",
			this.onSeparateWindowResize.createDelegate(this, [ browserWindowObject, mainContainer ]),
			browserWindowObject
		);


		// Initialize events which are use to handle drag and drop in separate window.
		Ext.dd.DragDropMgr.initEvents(browserWindowObject);

		browserWindowObject.addEventListener("focus", this.onSeparateWindowFocus.createDelegate(this, [ browserWindowObject.name ]));
		browserWindowObject.addEventListener("unload", this.onSeparateWindowUnload.createDelegate(this, [ browserWindowObject, componentInstance, mainContainer ]));

		// Disable contextmenu globally in the separate browser window.
		Ext.getBody().on('contextmenu', this.onBodyContextMenu, this);

		browserWindowObject.onkeydown = this.preventWindowReload.createDelegate(this);

		// Check component instance has before unload event handler if yes then
		// Register event handler for separate window onbeforeunload event
		// which is use to show a confirmation dialog warning if window record has any unsaved changes
		if (Ext.isFunction(componentInstance.onBeforeUnload)) {
			browserWindowObject.onbeforeunload = componentInstance.onBeforeUnload.createDelegate(componentInstance);
		}
	},

	/**
	 * Function which used to prevent to reload popout window by F5 and Ctrl + R keys.
	 * @param {Object} event The event object
	 */
	preventWindowReload: function (event)
	{
		switch (event.keyCode) {
			case 116: //F5 button
				event.returnValue = false;
				event.keyCode = 0;
				return false;
			case 82: //R button
				if (event.ctrlKey) {
					event.returnValue = false;
					event.keyCode = 0;
					return false;
				}
		}
	},

	/**
	 * Event handler for the {@link Zarafa.core.plugins.RecordComponentPlugin#userupdaterecord userupdaterecord} event
	 * on the {@link #field}. This will relay the value of {@link Zarafa.core.plugins.RecordComponentPlugin#isChangedByUser} config option.
	 * to the newly created content panel into popout window.
	 * It is required to persist the user change state into RecordComponentPlugin for this particular field.
	 * @param {Ext.Component} field The component which fired the event
	 * @param {Zarafa.core.data.MAPIRecord} record The record which was updated
	 * @param {Boolean} isChangedByUser Indicates if the record has been changed by the user since it has been loaded.
	 * @param {Ext.Component} componentInstance component which gets rendered into the separate browser window.
	 * @private
	 */
	onComponentUserupdateRecord: function(field, record, isChangedByUser, componentInstance)
	{
		// Check the record has unsaved user changes before popout
		// If true then update the record component plugin value
		if (componentInstance.isRecordChangeByUser) {
			field.recordComponentPlugin.isChangedByUser = componentInstance.isRecordChangeByUser;
		}

		// If record get saved then add record id
		if (!record.phantom) {
			var windowName = this.getOwnerWindow(componentInstance).name;
			var windowObject = this.browserWindows.get(windowName);
			if (Ext.isEmpty(windowObject.entryid)) {
				windowObject.entryid = record.get("entryid");
			}
		}
	},

	/**
	 * Register a newly created browser-window object with the {@link Zarafa.core.BrowserWindowMgr BrowserWindowMgr}.
	 * @param {Object} browserWindowObject The newly created window object which must be registered.
	 * @param {Function} component The constructor of the component which has to be created in the container layer.
	 * @param {Object} config The configuration object which must be

	 */
	register: function(browserWindowObject, component, config)
	{
		var uniqueWindowName = browserWindowObject.name;
		if (uniqueWindowName != 'mainBrowserWindow') {
			this.initComponent(uniqueWindowName, component, config);

			// add entryid along with the window object to identify browser window of particular record.
			var entryid = this.getWindowId(config.record);
			if (!Ext.isEmpty(entryid)) {
				browserWindowObject.entryid = entryid;
			}
		}
		this.browserWindows.add(uniqueWindowName, browserWindowObject);
		this.setActive(uniqueWindowName);
	},

	/**
	 * De-register an already registered browser-window object from the {@link Zarafa.core.BrowserWindowMgr BrowserWindowMgr}.
	 * @param {String} uniqueWindowName The unique name of the browser window.
	 */
	unRegister: function(uniqueWindowName)
	{
		this.browserWindows.removeKey(uniqueWindowName);
		Ext.MessageBox.removeBrowserWindowMessageBox(uniqueWindowName);
		// As the window which was closed is also the active one, we are not able to decide which window should be considered
		// as an active one. So assign 'undefined' to the activeBrowserWindow which will later on replaced while other
		// available window receive focus.
		if(this.activeBrowserWindow === uniqueWindowName) {
			this.activeBrowserWindow = undefined;
		}
	},

	/**
	 * A helper function which creates main container into the separate window and load the required
	 * component within. Some necessary events needs to be registered as well.
	 * @param {Object} separateWindowInstance Browser window object.
	 * @protected
	 */
	createUI: function(separateWindowInstance)
	{
		// Enable tooltips
		Ext.QuickTips.init();

		var separateWindowId = separateWindowInstance.name;

		//The constructor of the component which has to be created in the container layer.
		var browserWindowComponent;

		if (this.browserWindowComponents.containsKey(separateWindowId)) {
			browserWindowComponent = this.browserWindowComponents.get(separateWindowId);
		} else if (!Ext.isEmpty(this.blockedPopupsContent)) {
			// No inner components found for this particular popup window.
			// This is the situation where some popups were blocked by browser and
			// user manually allows to load the same.
			var blockedPopup = this.blockedPopupsContent.pop();
			this.register(separateWindowInstance, blockedPopup.component, blockedPopup.config);
			browserWindowComponent = this.browserWindowComponents.get(separateWindowId);
			this.isPopupsBlocked = false;
		}

		var component = browserWindowComponent.component;

		//The configuration object
		var config = {
			plugins: [ 'zarafa.contentlayerplugin' ],
			confirmClose: false
		};
		config = Ext.applyIf(config,browserWindowComponent.config);

		// Create instance of the component which is required to be rendered into the separate browser window.
		var componentInstance = new component(config);

		var mainContainer = Ext.create({
			xtype: 'panel',
			height: separateWindowInstance.innerHeight,
			width: separateWindowInstance.innerWidth,
			renderTo: Ext.get(separateWindowInstance.document.body),
			layout: 'fit',
			items: [componentInstance]
		});

		// Check the record has unsaved user changes before popout

		// If true then update the record component plugin value
		if(config.isRecordChangeByUser) {
			componentInstance.recordComponentPlugin.isChangedByUser = config.isRecordChangeByUser;
		}
		this.initEvents(separateWindowInstance, componentInstance, mainContainer);

		// We need to use some delay due to the behavioral difference between various browsers.
		// Let say we are creating 5 popups, Chrome blocks the last popups, and FF blocks the first one.
		// When first popup gets rendered and following popups will be blocked than there is no way to
		// listen to any event of already-rendered popup and display warning message in already rendered popup.
		var task = new Ext.util.DelayedTask(Zarafa.core.BrowserWindowMgr.displayBlockedPopupWarning.createDelegate(this, [separateWindowInstance], this));
		task.delay(200);
	},

	/**

	/**
	 * Provides browser window object mapped with the unique name currently assigned in {@link #activeBrowserWindow}.
	 * @return {Object} Browser window object
	 */
	getActive: function()
	{
		// Some time while child window not available any more and main window focus not set
		// at that time activeBrowser window will not available.
		// So handle this type of situation by returning main browser window while active browser window not available

		return this.browserWindows.get(this.activeBrowserWindow) || this.browserWindows.get('mainBrowserWindow');
	},

	/**
	 * Assign the provided window name to {@link #activeBrowserWindow}.
	 * @param {String} uniqueWindowName The unique name of the browser window.
	 */
	setActive: function(uniqueWindowName)
	{
		this.activeBrowserWindow = uniqueWindowName;

		// Activate respective QuickTip which is associated with the active browser window.
		if(Ext.QuickTips && Ext.QuickTips.tip) {
			var activeQuickTip = Ext.QuickTips.browserQuickTips.get(uniqueWindowName);
			if(Ext.isDefined(activeQuickTip)) {
				Ext.QuickTips.tip = activeQuickTip;
			}
		}

		// Activate respective MessageBox which is associated with the active browser window.
		Ext.MessageBox.setActiveWindowMessageBox(uniqueWindowName);
	},

	/**
	 * Determines if the webapp-main-window is currently active or not by checking that the value currently
	 * assigned into {@link #activeBrowserWindow} is 'mainBrowserWindow'.
	 * @return {Boolean} True if the active window is webapp-main-window, false otherwise.
	 */
	isMainWindowActive: function()
	{
		return this.activeBrowserWindow === 'mainBrowserWindow';
	},

	/**
	 * Helper function which allows to know which browser window object is the owner of any particular component
	 * passed as argument.
	 * @param {Ext.Component} component of which we want to get the owning browser window.
	 *
	 * @return {Boolean} true if the owner is main webapp window, false otherwise
	 */
	isOwnedByMainWindow: function(component)
	{
		var ownerWindow = this.getOwnerWindow(component);
		return ownerWindow ? ownerWindow.name === 'mainBrowserWindow' : false;
	},

	/**
	 * Helper function which returned the owning window of the element passed as argument.
	 * @param {Ext.Component/Ext.Element} component/element of which we want to get the owning browser window.
	 *
	 * @return {Object} Browser window object
	 * @private
	 */
	getOwnerWindow: function(component)
	{
		if(!Ext.isDefined(component)) {
			return undefined;
		}

		// In case we receive Ext.Component as parameter then we have to get underlying Ext.Element
		if(Ext.isFunction(component.getEl)) {
			component = component.getEl();
		}

		var componentDom = component.dom ? component.dom : component;
		var ownerDocument = componentDom ? componentDom.ownerDocument : undefined;
		var defaultView = ownerDocument ? ownerDocument.defaultView : undefined;
		return defaultView ? defaultView : this.browserWindows.get('mainBrowserWindow');
	},

	/**
	 * Event handler which is raised when browser window receives user focus.
	 * This will make respective browser window active into {@link Zarafa.core.BrowserWindowMgr BrowserWindowMgr}.
	 *
	 * @param {Object} browserWindowId The newly created window object id which must be registered.
	 * @private
	 */
	onSeparateWindowFocus: function(browserWindowId)
	{
		this.setActive(browserWindowId);
	},

	/**
	 * Event handler which is raised when browser window gets closed.
	 * This will de-register the closed component instance from {@link Zarafa.core.data.ContentPanelMgr ContentPanelMgr}.
	 * This will remove all the child elements of the parent most container of separate window.
	 * This will de-register the closed window instance from {@link Zarafa.core.BrowserWindowMgr BrowserWindowMgr}.
	 *
	 * @param {Object} browserWindowObject The newly created window object which must be registered.
	 * @param {Ext.Component} componentInstance component which is required to be rendered into the separate browser window.
	 * @param {Ext.Container} mainContainer The container which is the parent most container of separate window.
	 * @private
	 */
	onSeparateWindowUnload: function(browserWindowObject, componentInstance, mainContainer)
	{
		Ext.defer(this.onWindowRefreshOrClose, 50, this, [browserWindowObject, componentInstance, browserWindowObject.name]);

		// Remove the panel from the ContentPanelMgr
		Zarafa.core.data.ContentPanelMgr.unregister(componentInstance);

		mainContainer.destroy();
	},

	/**
	 * Function which handles the situation where window gets refreshed or closed
	 * if it was refresh then create record instance based on original record and update the browser window component config.
	 * And if window was closed then de-register the closed window component from {@link #browserWindowComponents}.
	 * @param {Object} win The browser window which gets refresh or closed.
	 * @param {Ext.Component} componentInstance component of the separate browser window.
	 * @param {Object} browserWindowId The newly created window object id which must be registered.
	 */
	onWindowRefreshOrClose: function (win, componentInstance, browserWindowId)
	{
		var isClose = false,
			oldRecord = componentInstance.record;
		try {
			if (!win || !win.innerWidth) {
				isClose = true;
			} else {
				var config = this.browserWindowComponents.get(win.name).config,
					model = container.getCurrentContext().getModel(),
					newRecord;

				if (Ext.isDefined(config.recordComponentPluginConfig) && Ext.isDefined(config.recordComponentPluginConfig.useShadowStore)) {
					config.recordComponentPluginConfig.useShadowStore = true;
				}

				// on window refresh we have to discard all unsaved user changes
				// If record is not phantom then get new record from respective store using oldRecord id properties.
				if (!oldRecord.phantom) {
					var recordConfig = {
						store_entryid: oldRecord.get('store_entryid'),
						parent_entryid: oldRecord.get('parent_entryid'),
						entryid: oldRecord.get('entryid')
					};

					newRecord = Zarafa.core.data.RecordFactory.createRecordObjectByMessageClass('IPM.Note', recordConfig, oldRecord.get('entryid'));
				} else {
					// if record is phantom then we have to discard this record and
					// create new record using respective context model
					newRecord = model.createRecord();
				}
				if (Ext.isDefined(config.isRecordChangeByUser)) {
					config.isRecordChangeByUser = undefined;
				}
				win.onbeforeunload = undefined;

				// Update the new record in respective browser window config
				config.record = newRecord;
			}
		} catch (e) {
			// Catch an Exception. Firefox will throw one when we
			// try to access property of the window that does not
			// exist anymore.
			isClose = true;
		}
		container.getShadowStore().remove(oldRecord, true);
		if (isClose) {
			this.browserWindowComponents.removeKey(browserWindowId);
			this.unRegister(browserWindowId);
		}
	},

	/**

	/**
	 * Event handler which is raised after the {@link Zarafa.core.ui.ContentPanel contentPanel} has been
	 * closed. This will de-register the closed window instance from {@link Zarafa.core.BrowserWindowMgr BrowserWindowMgr}.
	 *
	 * @private
	 */
	onSeparateWindowClose: function(browserWindowObject)
	{
		browserWindowObject.close();
	},

	/**
	 * Event handler which is raised when the browser window gets resized.
	 * This will register a {@link Ext.util.DelayedTask DelayedTask} and set the delay of 100ms.
	 * Delay is required to avoid executing the handler frequently, if event fires multiple time within the specified time-frame
	 * then the handler will gets executed only once for the last event.
	 *
	 * @param {Object} browserWindowObject The browser window which gets resized.
	 * @param {Ext.Container} mainContainer The container which needs to be resized according to the size of browser window.
	 * @private
	 */
	onSeparateWindowResize: function(browserWindowObject, mainContainer)
	{
		var resizeTask = new Ext.util.DelayedTask(
			this.doSeparateWindowResize,
			this,
			[browserWindowObject, mainContainer]
		);

		resizeTask.delay(100);
	},

	/**
	 * A {@link Ext.util.DelayedTask DelayedTask} which will set size of main container according to the browser window
	 * when it gets resized.
	 *
	 * @param {Object} browserWindowObject The browser window which gets resized.
	 * @param {Ext.Container} mainContainer The container which needs to be resized according to the size of browser window.
	 * @private
	 */
	doSeparateWindowResize: function(browserWindowObject, mainContainer)
	{
		var width = browserWindowObject.innerWidth || Ext.lib.Dom.getViewWidth();
		var height = browserWindowObject.innerHeight || Ext.lib.Dom.getViewHeight();
		mainContainer.setSize(width, height);

		this.fireEvent('separatewindowresize', browserWindowObject, width, height);
	},

	/**
	 * Event handler which is fired when the {@link Ext#getBody &lt;body&gt;} elements fires
	 * the 'contextmenu' event. If the element which fired the event doesn't have the
	 * 'zarafa-contextmenu-enabled' class then the Browser contextmenu will be disabled.
	 * @param {Ext.EventObject} event The event object
	 * @param {Ext.Element} el The element on which the contextmenu was requested
	 * @private
	 */
	onBodyContextMenu: function(event, el)
	{
		el = Ext.get(el);

		// Don't disable the browser contextmenu when the
		// 'zarafa-contextmenu-enabled' CSS class is applied
		// on the element.
		if (el.hasClass('zarafa-contextmenu-enabled') || el.up('div.zarafa-contextmenu-enabled')) {
			return;
		}

		// Don't disable the browser contextmenu for regular
		// text inputs.
		if ( el.dom.tagName.toUpperCase() === 'INPUT' ) {
			var type = el.getAttribute('type') || '';
			var readonly = !Ext.isEmpty(el.dom.attributes.readonly);
			if ( type.toUpperCase() === 'TEXT' && !readonly ) {
				return;
			}
		}

		event.preventDefault();
	},

	/**
	 * Function which is used to close all browser windows except main window
	 */
	closeAllBrowserWindow: function()
	{
		var browserWindows = Zarafa.core.BrowserWindowMgr.browserWindows.items;
		Ext.each(browserWindows,function(windowObject) {
			if(windowObject.name !== 'mainBrowserWindow') {
				windowObject.close();
			}
		});
	},

	/**
	 * Function which is used to initialize ext doc classes on separate window
	 * Ext has some separate doc classes for all browsers,and
	 * must be initialize those classes for newly created window.
	 * @param browserWindowObject The newly created window object which must be registered.
	 * @returns {boolean} true if all doc classes successfully apply on new window, false otherwise
	 */
	initExtCss: function (browserWindowObject) {
		// find the body element
		var body = browserWindowObject.document.body || browserWindowObject.document.getElementsByTagName('body')[0];
		if (!body) {
			return false;
		}

		Ext.fly(body.parentElement).addClass('x-viewport');

		var cls = [];

		if (Ext.isIE) {
			// Only treat IE9 and less like IE in the css
			if (!Ext.isIE10p) {
				cls.push('ext-ie');
			}
			if (Ext.isIE6) {
				cls.push('ext-ie6');
			} else if (Ext.isIE7) {
				cls.push('ext-ie7', 'ext-ie7m');
			} else if (Ext.isIE8) {
				cls.push('ext-ie8', 'ext-ie8m');
			} else if (Ext.isIE9) {
				cls.push('ext-ie9', 'ext-ie9m');
			} else if (Ext.isIE10) {
				cls.push('ext-ie10');
			}
		}

		if (Ext.isGecko) {
			if (Ext.isGecko2) {
				cls.push('ext-gecko2');
			} else {
				cls.push('ext-gecko3');
			}
		}

		if (Ext.isOpera) {
			cls.push('ext-opera');
		}

		if (Ext.isWebKit) {
			cls.push('ext-webkit');
		}

		if (Ext.isSafari) {
			cls.push("ext-safari " + (Ext.isSafari2 ? 'ext-safari2' : (Ext.isSafari3 ? 'ext-safari3' : 'ext-safari4')));
		} else if (Ext.isChrome) {
			cls.push("ext-chrome");
		}

		if (Ext.isMac) {
			cls.push("ext-mac");
		}
		if (Ext.isLinux) {
			cls.push("ext-linux");
		}

		// add to the parent to allow for selectors like ".ext-strict .ext-ie"
		if (Ext.isStrict || Ext.isBorderBox) {
			var p = body.parentNode;
			if (p) {
				if (!Ext.isStrict) {
					Ext.fly(p, '_internal').addClass('x-quirks');
					if (Ext.isIE9m && !Ext.isStrict) {
						Ext.isIEQuirks = true;
					}
				}
				Ext.fly(p, '_internal').addClass(((Ext.isStrict && Ext.isIE ) || (!Ext.enableForcedBoxModel && !Ext.isIE)) ? ' ext-strict' : ' ext-border-box');
			}
		}
		// Forced border box model class applied to all elements. Bypassing javascript based box model adjustments
		// in favor of css. This is for non-IE browsers.
		if (Ext.enableForcedBoxModel && !Ext.isIE) {
			Ext.isForcedBorderBox = true;
			cls.push("ext-forced-border-box");
		}

		Ext.fly(body, '_internal').addClass(cls);
		return true;
	},

	/**
	 * Function which is used to display warning message while browser blocks popups
	 */
	displayBlockedPopupWarning: function() {
		if(Zarafa.core.BrowserWindowMgr.isPopupsBlocked) {
			// We completed with the internal UI of single-allowed popup, Inform user that popup is blocked
			Ext.MessageBox.show({
				title: _("Open in new browser window"),
				msg: _("Your browser seems to have blocked one or more pop-ups. Please change your browser's settings to always allow pop-ups from grommunio Web."),
				buttons: Ext.Msg.OK,
				cls: Ext.MessageBox.WARNING_CLS
			});

			Zarafa.core.BrowserWindowMgr.isPopupsBlocked = false;
		}
	},

	/**
	 * Helper function which will bring the main webapp window to front
	 * @param {Ext.Component/Ext.Element} component/element of which will use to get the owning browser window.
	 */
	switchFocusToMainWindow: function (component)
	{
		var activeWindow = this.getActive();

		// if component belongs to one of the currently opened popout windows then get owner window of component
		if (Ext.isDefined(component) && !this.isOwnedByMainWindow(component)) {
			activeWindow = this.getOwnerWindow(component);
		}

		activeWindow.setFocusOnMainWindow();
	},

	/**
	 * Function which is use to find browser window of given record.
	 * @param {Zarafa.core.data.MAPIRecord} record The mapi record
	 * @return {Object} Browser window object
	 */
	getOpenedWindow: function (record)
	{
		var entryid = this.getWindowId(record);

		return this.browserWindows.find(function (browserWindow) {
			return Zarafa.core.EntryId.compareEntryIds(browserWindow.entryid, entryid);
		}, this);
	},

	/**
	 * Helper function which return entryid of given record.
	 * If record is attachment record then take id instead of entryid because
	 * Attachment record id contains entryid with attach_num.
	 * @param {Zarafa.core.data.MAPIRecord} record The mapi record.
	 * @returns {String} entryid Return entryid of record.
	 */
	getWindowId: function (record)
	{
		// When we have more than one record (e.g. for the reminder dialog), we
		// will concatenate the entryids to generate an id
		if (Array.isArray(record)) {
			return record.map(function(r) {
				return r.get('entryid') || r.get('id');
			}).sort().join('-');
		}

		return Array.isArray(record.get('attach_num')) ? record.id : record.get('entryid');
	},

	/**
	 * Helper function which return main browser window body.
	 * @returns {Object} body of main browser window.
	 */
	getMainBrowserWindowBody: function ()
	{
		var activeBrowserWindow = this.browserWindows.get('mainBrowserWindow');
		var documentObject = Ext.isDefined(activeBrowserWindow) ? activeBrowserWindow.document : document;
		return Ext.get(documentObject.body || documentObject.documentElement);
	}
});

Zarafa.core.BrowserWindowMgr = new Zarafa.core.BrowserWindowMgr();
Ext.namespace('Zarafa.core');

/**
 * @class Zarafa.core.ColorSchemes
 * @singleton
 *
 * An object that can be used to handle color schemes.
 * Color schemes are used by {@link Zarafa.core.Context contexts}
 * that can display different folders in one view. Currently only
 * the {@link Zarafa.calendar.CalendarContext calendar context}
 * is such a context.
 *
 * It has methods to create color schemes based on a single color
 * and to add complete color schemes.
 */
Zarafa.core.ColorSchemes = {

	/**
	 * An array with the fields that represent a color available in
	 * a color scheme. The fields have a name that can be used a key
	 * in a color scheme, and a weight (percentage) that will be used
	 * to create these color based on the base color of a color scheme.
	 * Fields can be added using the function {@link #addField}. When
	 * a field is added, all existing color schemes will also get the
	 * new field.
	 * @property
	 * @type Object[]
	 * @private
	 */
	fields: [
		{
			name: 'base',
			weight: 1
		}
	],

	/**
	 * The list of color schemes. Contexts can add color schemes
	 * using the function {@link #createColorScheme} or {@link #addColorScheme}.
	 * @property
	 * @type Object[]
	 * @private
	 */
	colorSchemes: [],

	/**
	 * Adds a field to the {@link #fields colorScheme fields}
	 * @param {Object|Array} field An object with properties name (mandatory) and
	 * weight (the weight of the color as ratio of the base color of the color
	 * scheme) or color (an RGB hex value that will be used as color for this field
	 * (e.g. '#0067AC')), or an array with field objects.
	 */
	addField: function(field)
	{
		var i;

		if ( Array.isArray(field) ){
			for ( i=0; i<field.length; i++ ){
				this.addField(field[i]);
			}
			return;
		}

		this.fields.push(field);

		// Add the color to existing color schemes
		for ( i =0; i<this.colorSchemes.length; i++ ){
			if ( !Ext.isDefined(this.colorSchemes[i][field.name]) ){
				if ( Ext.isDefined(field.color) ){
					this.colorSchemes[i][field.name] = field.color;
				} else if ( Ext.isDefined(field.weight) ){
					this.colorSchemes[i][field.name] = this.createColor(this.colorSchemes[i].base, field.weight);
				}
			}
		}
	},

	/**
	 * Converts a hexadecimal RGB color value into an object with
	 * red, green, and blue fields
	 * @param {String} hexColor A hexadecimal RGB color value
	 * (e.g. '#0067AC for grommunio Blue)
	 * @return {Object} An object with decimal red, green, and blue values
	 * @private
	 */
	hexToRgb: function(hexColor)
	{
		return {
			red: parseInt(hexColor.substring(1,3), 16),
			green: parseInt(hexColor.substring(3,5), 16),
			blue: parseInt(hexColor.substring(5,7), 16)
		};
	},

	/**
	 * Converts an RGB object into a hexadecimal RGB color value
	 * @param {Object} rgbObj An decimal RGB color object
	 * @return {String} A hexadecimal RGB color value
	 */
	rgbToHex: function(rgbObj)
	{
		// function that will convert a number to a hexadecimal string (between 0 and 255)
		const _toHex = function(n) {
			n = parseInt(n,10);
			if (isNaN(n)) {
				return "00";
			}
			n = Math.max(0,Math.min(n,255));
			return "0123456789ABCDEF".charAt((n-n%16)/16) + "0123456789ABCDEF".charAt(n%16);
		};

		return '#' + _toHex(rgbObj.red)+_toHex(rgbObj.green)+_toHex(rgbObj.blue);
	},

	/**
	 * Creates a color for a color scheme based on the baseColor of
	 * that scheme and a weight factor.
	 * @param {String} baseColor A hexadecimal RGB color value
	 * @param {Number} colorWeight A value that defines the new color
	 * as related to the baseColor. Should be between 0 and 1.
	 * @return {String} A hexadecimal RGB color value
	 * @private
	 */
	createDarkColor: function(baseColor, colorWeight)
	{
		var rgbBaseColor = this.hexToRgb(baseColor);

		var rgbColor = {
			red: rgbBaseColor.red * colorWeight,
			green: rgbBaseColor.green * colorWeight,
			blue: rgbBaseColor.blue * colorWeight
		};

		return this.rgbToHex(rgbColor);
	},

	/**
	 * Creates a color for a color scheme based on the baseColor of
	 * that scheme and a weight factor.
	 * @param {String} baseColor A hexadecimal RGB color value
	 * @param {Number} colorWeight A value that defines the new color
	 * as related to the baseColor. Should be larger than 1.
	 * @return {String} A hexadecimal RGB color value
	 * @private
	 */
	createLightColor: function(baseColor, colorWeight)
	{
		var rgbBaseColor = this.hexToRgb(baseColor);

		var rgbColor = {
			red: 255 - (255-rgbBaseColor.red) * (255-128*colorWeight) / 127,
			green: 255 - (255-rgbBaseColor.green) * (255-128*colorWeight) / 127,
			blue: 255 - (255-rgbBaseColor.blue) * (255-128*colorWeight) / 127
		};

		return this.rgbToHex(rgbColor);
	},

	/**
	 * Creates a color for a color scheme based on the baseColor of
	 * that scheme and a weight factor.
	 * @param {String} baseColor A hexadecimal RGB color value
	 * @param {Number} colorWeight A value that defines the new color
	 * as related to the baseColor. Between 0 and 1 for a color that is darker
	 * than the baseColor. Larger then 1 for a color that is lighter
	 * than the baseColor.
	 * @return {String} A hexadecimal RGB color value
	 * @private
	 */
	createColor: function(baseColor, colorWeight)
	{
		var rgbBaseColor = this.hexToRgb(baseColor);

		if ( Math.max(rgbBaseColor.red, rgbBaseColor.green, rgbBaseColor.blue) > 127 ){
			return this.createLightColor(baseColor, colorWeight);
		} else {
			return this.createDarkColor(baseColor, colorWeight);
		}
	},

	/**
	 * Creates a color scheme based on a single base color
	 *
	 * @param {String} name The unique name for this color scheme
	 * (can be used to identify the color scheme)
	 * @param {String} displayName The name of the color scheme that
	 * will be used if a name for the color scheme must be shown
	 * to the user.
	 * @param {String} baseColor an RGB hexadecimal color value
	 * (e.g. '#0067AC for grommunio Blue)
	 */
	createColorScheme: function(name, displayName, baseColor)
	{
		var i;

		if ( Array.isArray(name) ){
			for ( i=0; i<name.length; i++ ){
				this.createColorScheme(name[i]);
			}

			return;
		}

		if ( Ext.isObject(name) ){
			displayName = name.displayName;
			baseColor = name.baseColor || name.base;
			name = name.name;
		}

		var colorScheme = {
			name: name,
			displayName: displayName,
			base: baseColor
		};

		// Loop through all the fields and create a color for it in this scheme
		for ( i=0; i<this.fields.length; i++ ){
			if ( this.fields[i].name !== 'base' ){
				if ( Ext.isDefined(this.fields[i].color) ){
					colorScheme[this.fields[i].name] = this.fields[i].color;
				} else {
					colorScheme[this.fields[i].name] = this.createColor(baseColor, this.fields[i].weight);
				}
			}
		}

		if ( !Ext.isDefined(this.getColorScheme(name)) ){
			this.colorSchemes.push(colorScheme);
		}
	},

	/**
	 * Adds a complete color scheme to the color scheme list
	 * @param {Object} colorScheme
	 */
	addColorScheme: function(colorScheme)
	{
		// Simple check
		if ( !Ext.isDefined(colorScheme.name) || !Ext.isDefined(colorScheme.displayName) || !Ext.isDefined(colorScheme.base) ){
			// Missing necessary properties for a valid color scheme
			// So don't add the color scheme.
			return;
		}

		// Create colors that are not available in the passed color scheme
		for ( var i=0; i<this.fields.length; i++ ){
			if ( !Ext.isDefined(colorScheme[this.fields[i].name]) ){
				if ( Ext.isDefined(this.fields[i].color) ){
					colorScheme[this.fields[i].name] = this.fields[i].color;
				} else {
					colorScheme[this.fields[i].name] = this.createColor(colorScheme.base, this.fields[i].weight);
				}
			}
		}

		this.colorSchemes.push(colorScheme);
	},

	/**
	 * Adds the color schemes and additional color schemes that are defined
	 * in the config.php/default.php
	 */
	addColorSchemesFromConfig: function()
	{
		var i;
		var colorSchemes = container.getServerConfig().getColorSchemes();
		if ( colorSchemes && Array.isArray(colorSchemes) ){
			for ( i=0; i<colorSchemes.length; i++ ){
				this.addColorScheme(colorSchemes[i]);
			}
		}
		var additionalColorSchemes = container.getServerConfig().getAdditionalColorSchemes();
		if ( additionalColorSchemes && Array.isArray(additionalColorSchemes) ){
			for ( i=0; i<additionalColorSchemes.length; i++ ){
				this.addColorScheme(additionalColorSchemes[i]);
			}
		}
	},

	/**
	 * Returns the color scheme with the passed name if found,
	 * or undefined otherwise
	 * @param {String} colorSchemeName The name of the color scheme
	 * @return {Object|undefined} A color scheme or undefined if not found
	 */
	getColorScheme: function(colorSchemeName)
	{
		for ( var i=0; i<this.colorSchemes.length; i++ ){
			if ( this.colorSchemes[i].name === colorSchemeName ){
				return this.colorSchemes[i];
			}
		}

		return undefined;
	},

	/**
	 * Returns the array with all defined color schemes
	 * @return {Object[]} An array with all defined color schemes
	 */
	getColorSchemes: function()
	{
		return this.colorSchemes;
	},

	/**
	 * Converts an RGB color value to HSL. Conversion formula
	 * adapted from http://en.wikipedia.org/wiki/HSL_color_space.
	 * Assumes r, g, and b are contained in the set [0, 255] and
	 * returns h, s, and l in the set [0, 1].
	 *
	 * @param {Number} r    The red color value
	 * @param {Number} g    The green color value
	 * @param {Number} b    The blue color value
	 * @return {Array}      The HSL representation
	 */
	rgbToHsl: function(r, g, b)
	{
		if ( arguments.length === 1 && r.substr(0,1) === '#' ){
			var rgb = this.hexToRgb(r);
			r = rgb.red;
			g = rgb.green;
			b = rgb.blue;
		}

	  r /= 255;
	  g /= 255;
	  b /= 255;
	  var max = Math.max(r, g, b), min = Math.min(r, g, b);
	  var h, s, l = (max + min) / 2;

	  if ( max === min ) {
	    h = s = 0; // achromatic
	  } else {
	    var d = max - min;
	    s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
	    switch(max){
	      case r: h = (g - b) / d + (g < b ? 6: 0); break;
	      case g: h = (b - r) / d + 2; break;
	      case b: h = (r - g) / d + 4; break;
	    }
	    h /= 6;
	  }

	  return [h, s, l];
	},

	/**
	 * Returns the luma value (brightness perception) of a color.
	 * The color can be passed as an red, green & blue triplet or as a hex string.
	 * See http://stackoverflow.com/a/12043228
	 *
	 * @param {Number|String} r The red color value or the hex string of an rgb color (starting with #)
	 * @param {Number} g The green color value
	 * @param {Number} b The blue color value
	 * @return {Number} luma value
	 */
	getLuma: function(r, g, b)
	{
		if ( g === undefined && r.substr(0,1) === '#' ){
			var rgb = this.hexToRgb(r);
			r = rgb.red;
			g = rgb.green;
			b = rgb.blue;
		}

		var luma = 0.2126 * r + 0.7152 * g + 0.0722 * b; // per ITU-R BT.709

		return luma;
	},

	/**
	 * Checks if a color should be treated as dark (e.g. a dark background should have a light text color)
	 * The color can be passed as an red, green & blue triplet or as a hex string.
	 *
	 * @param {Number|String} r The red color value or the hex string of an rgb color (starting with #)
	 * @param {Number} g The green color value
	 * @param {Number} b The blue color value
	 * @return {Boolean} True is the given color should be considered as dark
	 */
	isDark: function(r, g, b)
	{
		// Use the (very subjective) value of 155 as the border between dark and light
		return Zarafa.core.ColorSchemes.getLuma(r, g, b) < 155;
	}
};

Zarafa.onReady(function(){
		// Load the color schemes that are defined in the config
		Zarafa.core.ColorSchemes.addColorSchemesFromConfig();
});
/**
 * @class Zarafa.core.Container
 * @extends Object
 *
 * Container class for lazy instantiation of globally used objects such as a Request instance, HierarchyStore instance, etc.
 * The Container is also used to register plugins and populate insertion points.
 * <p>
 * Don't instantiate the a new Container object yourself, just use the global <code>container</code> instance.
 */
Zarafa.core.Container = Ext.extend(Ext.util.Observable, {
	/**
	 * List of registered {@link Zarafa.core.Context context instances}.
	 * @property
	 * @private
	 * @type Array
	 */
	contexts: undefined,

	/**
	 * The Meta Data for all registered {@link #contexts}. This is an array
	 * of {@link Zarafa.core.ContextMetaData ContextMetaData instances}.
	 * @property
	 * @private
	 * @type Array
	 */
	contextsMetaData: undefined,

	/**
	 * List of registered {@link Zarafa.core.Plugin plugin instances}
	 * (also includes {@link #contexts}).
	 * @property
	 * @private
	 * @type Array
	 */
	plugins: undefined,

	/**
	 * The Meta Data for all registered {@link #plugins}. This is an array
	 * of {@link Zarafa.core.PluginMetaData PluginMetaData instances}.
	 * @property
	 * @private
	 * @type Array
	 */
	pluginsMetaData: undefined,

	/**
	 * The Meta Data for all registered {@link Zarafa.core.ui.widget.Widget widgets}. This is an array
	 * of {@link Zarafa.core.ui.widget.WidgetMetaData WidgetMetaData instances}.
	 * @property
	 * @private
	 * @type Array
	 */
	widgetsMetaData: undefined,

	/**
	 * @constructor
	 */
	constructor: function()
	{
		this.addEvents([
			/**
			 * @event contextswitch fired right before the context switch is made
			 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder folder that is loaded for the new context
			 * @param {Zarafa.core.Context} oldContext context being switched out
			 * @param {Zarafa.core.Context} newContext new context being switched in
			 * @return {Boolean} False to prevent the context from being switched
			 */
			'beforecontextswitch',
			/**
			 * @event contextswitch fired after a context switch has been performed
			 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder folder that is loaded for the new context
			 * @param {Zarafa.core.Context} oldContext context that was switched out
			 * @param {Zarafa.core.Context} newContext new context that was switched
			 */
			'contextswitch',
			/**
			 * @event contextswitch fired after a context switch has been performed and after the contextswitch has been fired.
			 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder folder that is loaded for the new context
			 * @param {Zarafa.core.Context} oldContext context that was switched out
			 * @param {Zarafa.core.Context} newContext new context that was switched
			 */
			'aftercontextswitch',
			/**
			 * Fires when the user selects a folder from the hierarchy.
			 * @event folderselect
			 * @param {Zarafa.hierarchy.data.MAPIFolderRecord[]} folder MAPI folder object.
			 */
			'folderselect',
			/**
			 * Fires when the webapp has loaded and the loading mask has been removed.
			 * @event webapploaded
			 */
			'webapploaded',
			/**
			 * Fires when the webapp will reload. Return false from an event handler to cancel the reload.
			 * @event beforewebappreload
			 */
			'beforewebappreload',
			/**
			 * Fires before the user logs out from the webapp. Return false from an event handler to stop the show.
			 * @event beforelogout
			 */
			'beforelogout',
			/**
			 * Fires when the user logs out from the webapp.
			 * @event logout
			 */
			'logout',
			/**
			 * Fires when the {@link Zarafa.core.ui.MainContentTabPanel MainContentTabPanel} rendered successfully.
			 * @event afterrendercontentpanel
			 * @param {Zarafa.core.ui.MainContentTabPanel} MainContentTabPanel The main content tab panel.
			 */
			'afterrendercontentpanel'
		]);

		Zarafa.core.Container.superclass.constructor.call(this);

		// initialize properties
		this.plugins = [];
		this.pluginsMetaData = [];
		this.contexts = [];
		this.contextsMetaData = [];
		this.widgetsMetaData = [];
	},

	/**
	 * Logout from the Webapp. this will fire the {@link #beforelogout} and
	 * {@link #logout} events before calling {@link #doLogout}.
	 * @param {Boolean} preserveUser True to preserve the username when he
	 * is forwarded to the logon page
	 * @param {Boolean} preserveSession True to preserve the existing session
	 * on the server and only redirect the user to the logon page.
	 */
	logout: function(preserveUser, preserveSession, reauthenticate)
	{
		if (this.fireEvent('beforelogout') !== false) {
			this.fireEvent('logout');
			this.doLogout(preserveUser, preserveSession, reauthenticate);
		}
	},

	/**
	 * Logout from the Webapp (this function is called by {@link #logout}.
	 * Override this to change the logout method.
	 * @param {Boolean} preserveUser True to preserve the username when he
	 * is forwarded to the logon page
	 * @param {Boolean} preserveSession True to preserve the existing session
	 * on the server and only redirect the user to the logon page.
	 * @protected
	 */
	doLogout: function(preserveUser, preserveSession, reauthenticate)
	{
		var user = ((preserveUser === true) ? ('&user=' + this.getUser().getUserName()) : '');

		Zarafa.core.Util.disableLeaveRequester();
		// OIDC is enabled, signout via oidc-client.
		if (preserveSession !== true) {
			window.location = 'index.php?logout' + user;
		} else {
			window.location = 'index.php?load=logon' + user;
		}
	},

	/**
	 * Obtain the server configuration data
	 * @return {Zarafa.core.data.ServerConfig} The server configuration data
	 */
	getServerConfig: function()
	{
		return this.serverConfigRecord;
	},

	/**
	 * Set the server configuration data
	 * @param {Object} serverData The server configuration data.
	 */
	setServerConfig: function(serverData)
	{
		this.serverConfigRecord = new Zarafa.core.data.ServerConfig(serverData);
	},

	/**
	 * Obtain the user data for the currently logged in user.
	 * @return {Zarafa.core.data.User} The user data of the currently logged in user.
	 */
	getUser: function()
	{
		return this.userRecord;
	},

	/**
	 * Set the user data for the currently logged in user.
	 * @param {Object} userData The user data of the currently logged in user.
	 */
	setUser: function(userData)
	{
		this.userRecord = new Zarafa.core.data.User(userData);
	},

	/**
	 * Obtain the versioning data for grommunio Web environment
	 * @return {Zarafa.core.data.Version} The version data of the grommunio Web environment
	 */
	getVersion: function()
	{
		return this.versionRecord;
	},

	/**
	 * Set the version data for grommunio Web environment
	 * @param {Object} versionData The version data of the grommunio Web environment
	 */
	setVersion: function(versionData)
	{
		this.versionRecord = new Zarafa.core.data.Version(versionData);
	},

	/**
	 * Obtain the array of all available languages. Each item in the array
	 * contains 2 keys. The first key is 'lang' which is the language code
	 * (e.g. en_GB), and the second key is 'name' which is the display name.
	 * @return {Array} The array of available languages
	 */
	getLanguages: function()
	{
		return this.languages;
	},

	/**
	 * Set the languages which are available to the user
	 * @param {Array} languages The available languages
	 */
	setLanguages: function(languages)
	{
		this.languages = languages;
	},

	/**
	 * Returns the currently active {@link Zarafa.core.Context context}.
	 * @return {Zarafa.core.Context} the currently active context.
	 */
	getCurrentContext: function()
	{
		return this.currentContext || this.getContextByName('default');
	},

	/**
	 * Returns the global {@link Zarafa.core.Request request} instance.
	 * All server requests should be lodged through this instance.
	 *
	 * @return {Zarafa.core.Request} the global {@link Zarafa.core.Request Request} instance.
	 */
	getRequest: function()
	{
		return this.request || (this.request = new Zarafa.core.Request({ url:"grommunio.php" }));
	},

	/**
	 * Returns the global {@link Zarafa.core.ResponseRouter ResponseRouter} instance.
	 * All server responses are lodged through this instance.
	 *
	 * @return {Zarafa.core.ResponseRouter} the global {@link Zarafa.core.ResponseRouter ResponseRouter} instance.
	 */
	getResponseRouter: function()
	{
		return this.responseRouter || (this.responseRouter = new Zarafa.core.ResponseRouter());
	},

	/**
	 * Returns the global {@link Zarafa.core.data.NotificationResolver NotificationResolver} instance.
	 * All notifications are being resolved through this instance, the constructed
	 * {@link Zarafa.core.data.AbstractResponseHandler ResponseHandler} is then returned to the
	 * {@link Zarafa.core.ResponseRouter ResponseRouter} for further processing.
	 *
	 * @return {Zarafa.core.data.NotificationResolver} the global {@link Zarafa.core.data.NotificationResolver NotificationResolver} instance.
	 */
	getNotificationResolver: function()
	{
		return this.notificationResolver || (this.notificationResolver = new Zarafa.core.data.NotificationResolver());
	},

	/**
	 * Returns the global {@link Zarafa.hierarchy.data.HierarchyStore HierarchyStore} instance.
	 * @return {Zarafa.hierarchy.data.HierarchyStore} the global {@link Zarafa.hierarchy.data.HierarchyStore HierarchyStore} instance.
	 */
	getHierarchyStore: function()
	{
		return this.hierarchyStore || (this.hierarchyStore = new Zarafa.hierarchy.data.HierarchyStore());
	},

	/**
	 * Returns the global {@link Zarafa.common.outofoffice.data.OofStore OofStore} instance.
	 * @return {Zarafa.common.outofoffice.data.OofStore} the global {@link Zarafa.common.outofoffice.data.OofStore} instance.
	 */
	getOutOfOfficeStore: function()
	{
		return this.outOfOfficeStore || (this.outOfOfficeStore = new Zarafa.common.outofoffice.data.OofStore());
	},

	/**
	 * Returns the global {@link Zarafa.settings.SettingsModel SettingsModel} instance.
	 * @return {Zarafa.settings.SettingsModel} the global {@link Zarafa.settings.SettingsModel SettingsModel} instance.
	 */
	getSettingsModel: function()
	{
		return this.settingsModel || (this.settingsModel = new Zarafa.settings.SettingsModel());
	},

	/**
	 * Returns the global {@link Zarafa.settings.PersistentSettingsModel PersistentSettingsModel} instance.
	 * @return {Zarafa.settings.PersistentSettingsModel} the global
	 * {@link Zarafa.settings.PersistentSettingsModel PersistentSettingsModel} instance.
	 */
	getPersistentSettingsModel: function()
	{
		return this.persistentSettingsModel || (this.persistentSettingsModel = new Zarafa.settings.PersistentSettingsModel());
	},

	/**
	 * Returns the {@link Zarafa.core.data.ShadowStore ShadowStore} instance.
	 * @return {Zarafa.core.data.ShadowStore} the {@link Zarafa.core.data.ShadowStore ShadowStore} instance.
	 */
	getShadowStore: function()
	{
		return this.shadowStore || (this.shadowStore = new Zarafa.core.data.ShadowStore());
	},

	/**
	 * Returns the {@link Zarafa.core.ui.notifier.Notifier Notifier} instance which can be used
	 * for sending notifications to the user.
	 * @return {Zarafa.core.ui.notifier.Notifier} The notifier for User notifications
	 */
	getNotifier: function()
	{
		return this.notifier || (this.notifier = new Zarafa.core.ui.notifier.Notifier());
	},

	/**
	 * Returns the application main panel.
	 * @return {Zarafa.core.ui.MainViewport} the application main panel.
	 */
	getMainPanel: function()
	{
		return this.mainPanel || (this.mainPanel = new Zarafa.core.ui.MainViewport());
	},

	/**
	 * Returns the applications main toolbar
	 * @return {Zarafa.core.ui.MainToolbar} then application main tool bar
	 */
	getMainToolbar: function()
	{
		return this.getMainPanel().mainToolbar;
	},

	/**
	 * Returns the application welcome panel.
	 * @return {Zarafa.core.ui.WelcomeViewport} the application welcome panel.
	 */
	getWelcomePanel: function()
	{
		return this.welcomePanel || (this.welcomePanel = new Zarafa.core.ui.WelcomeViewport());
	},

	/**
	 * Returns the application tab panel
	 * @return {Zarafa.core.ui.ContextContainer} The application tab panel
	 */
	getTabPanel: function()
	{
		return this.getMainPanel().getContentPanel();
	},

	/**
	 * Returns the application content panel
	 * @return {Zarafa.common.ui.ContextMainPanel} the application content panel.
	 */
	getContentPanel: function()
	{
		return this.getTabPanel().get(0).getActiveItem();
	},

	/**
	 * Returns the application navigation sidebar.
	 * @return {Zarafa.core.ui.NavigationPanel} the navigation sidebar
	 */
	getNavigationBar: function()
	{
		return this.getMainPanel().getNavigationPanel();
	},

	/**
	 * Returns the application widget sidebar.
	 * @return {Zarafa.core.ui.widget.WidgetPanel} the application widget sidebar.
	 */
	getWidgetSideBar: function()
	{
		return this.getMainPanel().getWidgetPanel();
	},

	/**
	 * Returns an array of all registered {@link Zarafa.core.Plugin plugins}.
	 * @return {Array} plugins
	 */
	getPlugins: function()
	{
		return this.plugins;
	},

	/**
	 * Returns the Meta Data for {@link #pluginsMetaData all registered} {@link Zarafa.core.Plugin plugins}.
	 * @return {Array} The plugins meta data
	 */
	getPluginsMetaData: function()
	{
		return this.pluginsMetaData;
	},

	/**
	 * Returns an array of all registered {@link Zarafa.core.Context contexts}.
	 * @return {Array} Contexts
	 */
	getContexts: function()
	{
		return this.contexts;
	},

	/**
	 * Returns the Meta Data for {@link #contextsMetaData all registered} {@link Zarafa.core.Context contexts}.
	 * @return {Array} The contexts meta data
	 */
	getContextsMetaData: function()
	{
		return this.contextsMetaData;
	},

	/**
	 * Returns the Meta Data for {@link #widgetsMetaData all registered} {@link Zarafa.core.ui.widget.Widget widgets}.
	 * @return {Array} The widgets meta data
	 */
	getWidgetsMetaData: function()
	{
		return this.widgetsMetaData;
	},

	/**
	 * Returns the context that matches the supplied name.
	 * @param {String} name The name of the context which is requested
	 * @return {Zarafa.core.Context} matching context or <code>undefined</code> if not found.
	 */
	getContextByName: function(name)
	{
		var contexts = this.getContexts();

		for (var index = 0, len = contexts.length; index < len; index++) {
			if (contexts[index].getName() === name) {
				return contexts[index];
			}
		}
	},

	/**
	 * Returns the Context Meta Data that matches the supplied name.
	 * @param {String} name The name of the context for which the meta data is requested
	 * @return {Zarafa.core.ContextMetaData} The Meta Data for the context or <code>undefined</code> if not found.
	 */
	getContextMetaDataByName: function(name)
	{
		var contexts = this.getContextsMetaData();

		for (var index = 0, len = contexts.length; index < len; index++) {
			if (contexts[index].getName() === name) {
				return contexts[index];
			}
		}
	},

	/**
	 * Returns the plug-in that matches the supplied name.
	 * @param {String} name The name of the plugin which is requested
	 * @return {Zarafa.core.Plugin} matching plug-in or <code>undefined</code> if not found.
	 */
	getPluginByName: function(name)
	{
		var plugins = this.getPlugins();

		for (var index = 0, len = plugins.length; index < len; index++) {
			if (plugins[index].getName() === name) {
				return plugins[index];
			}
		}
	},

	/**
	 * Returns the Plugin Meta Data that matches the supplied name.
	 * @param {String} name The name of the plugin for which the meta data is requested
	 * @return {Zarafa.core.PluginMetaData} The Meta Data for the plugin or <code>undefined</code> if not found.
	 */
	getPluginMetaDataByName: function(name)
	{
		var plugins = this.getPluginsMetaData();

		for (var index = 0, len = plugins.length; index < len; index++) {
			if (plugins[index].getName() === name) {
				return plugins[index];
			}
		}
	},

	/**
	 * Returns the Widget Meta Data that matches the supplied name.
	 * @param {String} name The name of the widget for which the meta data is requested
	 * @return {Zarafa.core.ui.widget.WidgetMetaData} The Meta Data for the widget or <code>undefined</code> if not found.
	 */
	getWidgetMetaDataByName: function(name)
	{
		var widgets = this.getWidgetsMetaData();

		for (var index = 0, len = widgets.length; index < len; index++) {
			if (widgets[index].getName() === name) {
				return widgets[index];
			}
		}
	},

	/**
	 * Queries registered plug-ins in for components and returns the gathered results.
	 * @param {String} insertionPoint name of the insertion point
	 * @param {Object} args (optional) optional arguments such as scope
	 * @return {Ext.Component[]} an array of components
	 */
	populateInsertionPoint: function(insertionPoint)
	{
		var plugins = this.getPlugins();
		var items = [];

		// convert arguments object to a real array
		var args = Ext.toArray(arguments);

		for (var i = 0, len = plugins.length; i < len; i++) {
			var plugin = plugins[i];

			var components = plugin.getComponents.apply(plugin, args);

			// FIXME: Why do we need to assign the plugin to the component?
			Ext.each(components, function(component) {
				component.plugin = plugin;
				items.push(component);
			});
		}

		// every plugin will give items in their own array so we need to merge all arrays
		// this will not interfere with objects
		return Ext.flatten(items);
	},

	/**
	 * Registers a Context Meta Data instance with the container.
	 * @param {Zarafa.core.ContextMetaData} info context to register
	 */
	registerContext: function(info)
	{
		this.getContextsMetaData().push(info);
		if (info.isEnabled()) {
			this.getContexts().push(info.getInstance());
		}

		// A Context is also a plugin, so register it
		// as such as well.
		this.registerPlugin(info);
	},

	/**
	 * Registers a Plugin Meta Data instance with the container.
	 * @param {Zarafa.core.PluginMetaData} info plugin info to register
	 */
	registerPlugin: function(info)
	{
		// Get the list of plugins that are always enabled
		var alwaysEnabledPlugins = this.getServerConfig().getAlwaysEnabledPluginsList().split(';');
		if ( alwaysEnabledPlugins.indexOf(info.name)>=0 ){
			info.allowUserDisable = false;
			info.enable = true;
		}

		this.getPluginsMetaData().push(info);
		if (info.isEnabled()) {
			this.getPlugins().push(info.getInstance());
		}
	},

	/**
	 * Registers a Widget Meta Data instance with the container.
	 * @param {Zarafa.core.ui.widget.WidgetMetaData} info widget meta data to register
	 */
	registerWidget: function(info)
	{
		this.getWidgetsMetaData().push(info);
	},

	/**
	 * Performs a context switch by switching out the current context and switching in the new one.
	 * @param {Zarafa.core.Context} context context to switch to.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder folder that should be shown by the selected context.
	 * @param {Boolean} suspended True if the {@link Zarafa.core.ContextModel model} for the
	 * {@link Zarafa.core.Context context} should be enabled {@link Zarafa.core.ContextModel#suspendLoading suspended}.
	 * @private
	 */
	switchContext: function(context, folder, suspended)
	{
		var oldContext = this.getCurrentContext();

		if (oldContext !== context && this.fireEvent('beforecontextswitch', folder, oldContext, context) !== false) {
			if (oldContext) {
				oldContext.disable();

				var oldModel = oldContext.getModel();
				if (oldModel) {
					oldModel.un('folderchange', this.onContextFolderChange, this);
				}
			}

			context.enable(folder, suspended);
			var newModel = context.getModel();
			if (newModel) {
				newModel.on('folderchange', this.onContextFolderChange, this);
			}

			this.currentContext = context;

			this.fireEvent('folderselect', folder);
			this.fireEvent('contextswitch', folder, oldContext, context);

			// Nothing needs to be done between 'contextswitch' and 'aftercontextswitch',
			// the difference between the two events is that the first one can be used
			// internally for building up the UI, while the latter event is ideal for
			// plugins which want the UI components to be setup correctly.
			this.fireEvent('aftercontextswitch', folder, oldContext, context);
		}
	},

	/**
	 * The container will start a bidding round to determine which context should be chosen to
	 * display the given folder.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder folder for which we need to find corresponding context.
	 * @return {Zarafa.core.Context} context that should be used to load data from {@link Zarafa.hierarchy.data.MAPIFolderRecord MAPIFolder}.
	 */
	getContextByFolder: function(folder)
	{
		// walk over the context list and select the one that provides the highest bid
		var selectedContext;
		var highestBid;
		var contexts = this.getContexts();

		if (contexts) {
			for(var index = 0, len = contexts.length; index < len; index++) {
				var context = contexts[index];
				var bid = context.bid(folder);

				if (highestBid === undefined || bid > highestBid) {
					highestBid = bid;
					selectedContext = context;
				}
			}
		}

		return selectedContext;
	},

	/**
	 * Select a specific folder in the UI. The container will start a bidding round to determine which context should be chosen to
	 * display the given folder. The current context is then disabled and switched out, and the newly chosen context is enabled and
	 * switched in. Fires the 'folderselect' event.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord[]} folder folder to select either as an array of
	 * {@link Zarafa.hierarchy.data.MAPIFolderRecord MAPIFolder} objects or a single object.
	 */
	selectFolder: function(folder)
	{
		var selectedContext = this.getContextByFolder(Ext.isArray(folder) ? folder[0] : folder);

		// Check if a new context has been selected, if we can
		// stay with the current context, then simply update
		// the folders.
		if (this.getCurrentContext() !== selectedContext) {
			this.switchContext(selectedContext, folder);
		} else {
			var model = selectedContext.getModel();
			if (model) {
				model.setFolders(folder);
			}
		}
	},

	/**
	 * Helps in reloading a context. It checks if given changed folder is holded by current context
	 * then re-enables context all of its folders.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder which is changed
	 */
	reloadContext: function(folder)
	{
		var currentContext = this.getCurrentContext();
		var contextModel = currentContext.getModel();

		if (!Ext.isDefined(contextModel)) {
			return;
		}

		folder = contextModel.getFolder(folder.get('entryid'));
		if (Ext.isDefined(folder)) {
			var allFolders = contextModel.getFolders();
			currentContext.disable();
			currentContext.enable(allFolders);
		}
	},

	/**
	 * Event handler which is fired when the {@link #getCurrentContext current context}
	 * {@link Zarafa.core.ContextModel model} fires the {@link Zarafa.core.ContextModel#folderchange} event.
	 * This will redirect the event and fire the {@link #folderselect} event.
	 * @param {Zarafa.core.ContextModel} model The model which fired the event
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord[]} folders The folders which are
	 * currently selected
	 * @private
	 */
	onContextFolderChange: function(model, folders)
	{
		this.fireEvent('folderselect', folders);
	},

	/**
	 * Starts a bidding round to determine what plug-in should be chosen to
	 * deliver the component that is requested. The component of the highest
	 * bidder is returned. If the supplied type is undefined the function will
	 * return undefined as well.
	 * @param {Zarafa.core.data.SharedComponentType} type Type of component a context can bid for.
	 * @param {Ext.data.Record} record (optional) Passed record.
	 * @return {Ext.Component} Component
	 *
	 */
	getSharedComponent: function(type, record)
	{
		// walk over the context list and select the one that provides the highest bid
		var selectedPlugin;
		var highestBid;
		var plugins = container.getPlugins();
		var component;

		if (type) {
			for (var i = 0, len = plugins.length; i < len; i++) {
				var plugin = plugins[i];
				var bid = plugin.bidSharedComponent(type, record);

				if (highestBid === undefined || bid > highestBid) {
					highestBid = bid;
					selectedPlugin = plugin;
				}
			}

			if (selectedPlugin && highestBid >= 0) {
				component = selectedPlugin.getSharedComponent(type, record);
			}
		}

		return component;
	},

	/**
	 * Returns the global {@link Zarafa.common.reminder.data.ReminderStore RemimderStore} instance.
	 * @return {Zarafa.common.reminder.data.ReminderStore} instance.
	 */
	getReminderStore: function()
	{
		return this.reminderStore || (this.reminderStore = new Zarafa.common.reminder.data.ReminderStore());
	},

	/**
	 * Returns base url of webapp without trailing slash, which can be used to request resources from server.
	 * @return {String} base url of webapp.
	 */
	getBaseURL: function()
	{
		var loc = window.location;
		var url = loc.protocol + '//' + loc.host + loc.pathname;

		// it's possible that webapp is loaded without index.php in url, so if it the case
		// then append index.php in url so that will make our base url complete
		if(url.indexOf('index.php') === -1) {
			url += 'index.php';
		}

		return url;
	},

	/**
	 * Returns base path of webapp with trailing slash, which can be used to request resources from server.
	 * @return {String} base path of webapp.
	 */
	getBasePath: function()
	{
		var baseURL = container.getBaseURL();

		return baseURL.substring(0, baseURL.lastIndexOf('index.php'));
	},

	/**
	 * Returns instance of factory registered with {@link Zarafa.common.data.AbstractRulesFactory} factory
	 * depending on factoryType given in the parameter.
	 *
	 * @param {Zarafa.common.data.RulesFactoryType} factoryType type of the required factory.
	 * @return {Object} factory instance based on factoryType.
	 */
	getRulesFactoryByType: function(factoryType)
	{
		return Zarafa.common.data.AbstractRulesFactory.getFactoryById(factoryType);
	},

	/**
	 * Helper function which used to check the conversation view is enabled or not.
	 * @return {Boolean} True if conversation view is enabled else false.
	 */
	isEnabledConversation: function ()
	{
		return this.getServerConfig().isConversationViewEnabled() &&
			this.getSettingsModel().get("zarafa/v1/contexts/mail/enable_conversation_view", false);
	}
});
Ext.namespace('Zarafa.core');

/**
 * @class Zarafa.core.DarkMode
 * @singleton
 *
 * Manages the dark mode state for grommunio Web.
 * Supports three modes: 'light', 'dark', 'system' (follows OS preference).
 * Uses data-theme attribute on body element for CSS targeting.
 */
Zarafa.core.DarkMode = {
	/**
	 * The current dark mode setting: 'light', 'dark', or 'system'
	 * @property
	 * @type String
	 */
	mode: 'light',

	/**
	 * The resolved theme: 'light' or 'dark' (after resolving 'system')
	 * @property
	 * @type String
	 */
	resolvedTheme: 'light',

	/**
	 * MediaQueryList for prefers-color-scheme
	 * @property
	 * @type MediaQueryList
	 * @private
	 */
	mediaQuery: null,

	/**
	 * Initialize the dark mode system.
	 * Reads the saved preference and applies it.
	 */
	init: function()
	{
		this.mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
		this.mediaQuery.addEventListener('change', this.onSystemThemeChange.bind(this));

		// Read saved mode from settings
		var savedMode = container.getSettingsModel().get('zarafa/v1/main/dark_mode');
		this.mode = savedMode || 'light';

		this.apply(true);
	},

	/**
	 * Set the dark mode and apply it.
	 * @param {String} mode One of 'light', 'dark', 'system'
	 * @param {Boolean} save Whether to save to settings (default true)
	 */
	setMode: function(mode, save)
	{
		if (mode !== 'light' && mode !== 'dark' && mode !== 'system') {
			mode = 'light';
		}
		this.mode = mode;
		this.apply();

		if (save !== false) {
			container.getSettingsModel().beginEdit();
			container.getSettingsModel().set('zarafa/v1/main/dark_mode', mode);
			container.getSettingsModel().endEdit();
		}

		this.syncSettingsCombo(mode);
		this.syncToolbarButton(mode);
	},

	/**
	 * Sync the Appearance combo in the settings panel if it is currently rendered.
	 * @param {String} mode The new mode value
	 * @private
	 */
	syncSettingsCombo: function(mode)
	{
		var combo = Ext.getCmp('darkmode-combo');
		if (combo) {
			combo.setValue(mode);
		}
	},

	/**
	 * Sync the dark mode toolbar button icon and tooltip if it is currently rendered.
	 * @param {String} mode The new mode value
	 * @private
	 */
	syncToolbarButton: function(mode)
	{
		var btn = Ext.getCmp('mainmenu-button-darkmode');
		if (btn && btn.getEl()) {
			var tabbar = btn.findParentByType('zarafa.maintabbar');
			if (tabbar) {
				btn.getEl().setStyle('backgroundImage', 'url(\'' + tabbar.getDarkModeIcon(mode) + '\')');
				btn.setTooltip(tabbar.getDarkModeTooltip(mode));
			}
		}
	},

	/**
	 * Get the current mode setting.
	 * @return {String} 'light', 'dark', or 'system'
	 */
	getMode: function()
	{
		return this.mode;
	},

	/**
	 * Check if dark mode is currently active (after resolving 'system').
	 * @return {Boolean}
	 */
	isDark: function()
	{
		return this.resolvedTheme === 'dark';
	},

	/**
	 * Apply the current mode to the DOM.
	 * @param {Boolean} initial True during first init (skip editor updates)
	 * @private
	 */
	apply: function(initial)
	{
		var previousTheme = this.resolvedTheme;

		if (this.mode === 'system') {
			this.resolvedTheme = this.mediaQuery.matches ? 'dark' : 'light';
		} else {
			this.resolvedTheme = this.mode;
		}

		// Set dark mode class on body for CSS targeting
		// Using class instead of data-attribute for maximum CSS compatibility
		document.body.classList.remove('dark-mode', 'dark-mode-system');
		if (this.resolvedTheme === 'dark') {
			document.body.classList.add('dark-mode');
		}
		if (this.mode === 'system') {
			document.body.classList.add('dark-mode-system');
		}

		// Update OnlyOffice integration
		var ooTheme = this.resolvedTheme === 'dark'
			? '{"id":"theme-dark","type":"dark"}'
			: '{"id":"theme-light","type":"light"}';
		window.localStorage.setItem('ui-theme', ooTheme);
		window.localStorage.setItem('ui-theme-id',
			this.resolvedTheme === 'dark' ? 'theme-dark' : 'theme-light');

		// Update TinyMCE editors if theme changed (skip on first init)
		if (!initial && previousTheme !== this.resolvedTheme) {
			this.updateTinyMCE();
		}

		// Always set up mail preview observer (even on init) so new iframes get dark styles
		this.updateMailPreview();
	},

	/**
	 * Handler for system color scheme changes.
	 * Only acts when mode is 'system'.
	 * @private
	 */
	onSystemThemeChange: function()
	{
		if (this.mode === 'system') {
			this.apply();
		}
	},

	/**
	 * Update all active TinyMCE 8 editor instances to match the current theme.
	 * Swaps skin CSS links in the main document and content CSS inside editor iframes.
	 * @private
	 */
	updateTinyMCE: function()
	{
		if (typeof tinymce === 'undefined' || !tinymce.get) {
			return;
		}

		var isDark = this.isDark();

		// Swap UI skin CSS links in the main document
		var links = document.querySelectorAll('link[href*="/skins/ui/"]');
		for (var i = 0; i < links.length; i++) {
			var href = links[i].href;
			if (isDark) {
				links[i].href = href.replace('/oxide/', '/oxide-dark/');
			} else {
				links[i].href = href.replace('/oxide-dark/', '/oxide/');
			}
		}

		// Update content CSS inside each editor iframe
		// tinymce.get() returns an array in TinyMCE 8
		var editors = tinymce.get();
		if (!editors || !editors.length) {
			return;
		}
		for (var ei = 0; ei < editors.length; ei++) {
			var editor = editors[ei];
			if (!editor || !editor.getDoc) {
				continue;
			}
			try {
				var doc = editor.getDoc();
				if (!doc) {
					continue;
				}
				var contentLinks = doc.querySelectorAll('link[href*="/skins/content/"]');
				for (var j = 0; j < contentLinks.length; j++) {
					var ch = contentLinks[j].href;
					if (isDark) {
						contentLinks[j].href = ch.replace('/default/', '/dark/');
					} else {
						contentLinks[j].href = ch.replace('/dark/', '/default/');
					}
				}

				// Inject dark/light mode style into editor iframe body
				var existing = doc.getElementById('grommunio-dark-editor');
				if (existing) {
					existing.parentNode.removeChild(existing);
				}
				if (doc.head) {
					var style = doc.createElement('style');
					style.id = 'grommunio-dark-editor';
					if (isDark) {
						style.textContent =
							'body { background-color: #1e1e1e !important; color: #e0e0e0 !important; }' +
							'body * { color: inherit; }';
					} else {
						style.textContent =
							'body { background-color: #ffffff !important; color: #000000 !important; }';
					}
					doc.head.appendChild(style);
				}
			} catch (e) {
				// Editor may not be fully initialized yet
			}
		}
	},

	/**
	 * Update mail preview iframes with dark mode handling.
	 * Uses smart CSS filter inversion that preserves images.
	 * @private
	 */
	updateMailPreview: function()
	{
		var isDark = this.isDark();

		// Collect all documents to scan: main window + pop-out windows
		var docs = [document];
		if (Zarafa.core.BrowserWindowMgr && Zarafa.core.BrowserWindowMgr.browserWindows) {
			Zarafa.core.BrowserWindowMgr.browserWindows.each(function(win) {
				try {
					if (win && win.document && win.document !== document) {
						docs.push(win.document);
					}
				} catch (e) {
					// cross-origin or closed window
				}
			});
		}

		var self = this;
		for (var d = 0; d < docs.length; d++) {
			var doc = docs[d];
			var iframes = doc.querySelectorAll('.preview-iframe');

			for (var i = 0; i < iframes.length; i++) {
				this.applyDarkToIframe(iframes[i], isDark);

				if (!iframes[i]._darkModeListener) {
					iframes[i]._darkModeListener = true;
					iframes[i].addEventListener('load', function() {
						self.applyDarkToIframe(this, self.isDark());
					});
				}
			}

			// Set up a MutationObserver per document
			if (!doc._grommunioDarkObserver) {
				doc._grommunioDarkObserver = new MutationObserver(function() {
					var dark = self.isDark();
					// Re-scan all documents when any DOM changes
					var allDocs = [document];
					if (Zarafa.core.BrowserWindowMgr && Zarafa.core.BrowserWindowMgr.browserWindows) {
						Zarafa.core.BrowserWindowMgr.browserWindows.each(function(win) {
							try {
								if (win && win.document && win.document !== document) {
									allDocs.push(win.document);
								}
							} catch (e) {}
						});
					}
					for (var k = 0; k < allDocs.length; k++) {
						var found = allDocs[k].querySelectorAll('.preview-iframe');
						for (var j = 0; j < found.length; j++) {
							self.applyDarkToIframe(found[j], dark);
							if (!found[j]._darkModeListener) {
								found[j]._darkModeListener = true;
								found[j].addEventListener('load', function() {
									self.applyDarkToIframe(this, self.isDark());
								});
							}
						}
					}
				});
				if (doc.body) {
					doc._grommunioDarkObserver.observe(doc.body, { childList: true, subtree: true });
				}
			}
		}
	},

	/**
	 * Apply or remove dark mode styles to/from a preview iframe.
	 * Uses CSS filter inversion for dark mode: invert(1) flips brightness
	 * while hue-rotate(180deg) preserves original hues. Images and media
	 * are re-inverted so photos and logos appear in their original colors.
	 * Emails that are already dark-themed are detected by luminance analysis
	 * and left untouched (only the background is adjusted to match the theme).
	 * @param {HTMLIFrameElement} iframe
	 * @param {Boolean} isDark
	 * @private
	 */
	applyDarkToIframe: function(iframe, isDark)
	{
		try {
			var doc = iframe.contentDocument || iframe.contentWindow.document;
			if (!doc || !doc.body) {
				return;
			}

			var existing = doc.getElementById('grommunio-dark-preview');
			if (existing) {
				existing.parentNode.removeChild(existing);
			}

			// Clean up any JS-applied background from previous invocation
			if (doc.body.style.getPropertyValue('--grommunio-dark-bg')) {
				doc.body.style.removeProperty('background-color');
				doc.body.style.removeProperty('--grommunio-dark-bg');
			}

			if (!doc.head) {
				return;
			}

			var style = doc.createElement('style');
			style.id = 'grommunio-dark-preview';

			var scrollbarCss = isDark
				? '* { scrollbar-width: thin; scrollbar-color: rgba(255,255,255,0.15) transparent; }'
				: '* { scrollbar-width: thin; scrollbar-color: rgba(0,0,0,0.15) transparent; }';

			if (isDark) {
				var luminance = this.getEmailLuminance(doc);

				if (luminance < 0.2) {
					// Already dark-themed email — just harmonize background
					style.textContent = scrollbarCss +
						'html { background-color: #1e1e1e !important; }';
				} else {
					// Light email — apply smart CSS filter inversion.
					// #e1e1e1 is the pre-inversion value that maps to exactly
					// #1e1e1e (--dm-bg-paper) after invert(1).
					style.textContent = scrollbarCss +
						'html {' +
						'  filter: invert(1) hue-rotate(180deg);' +
						'  background-color: #e1e1e1 !important;' +
						'  min-height: 100%;' +
						'}' +
						// Re-invert media so images/videos show original colors.
						// invert(1) applied twice is the identity function.
						'img, video, canvas, svg,' +
						'[style*="background-image"] {' +
						'  filter: invert(1) hue-rotate(180deg) !important;' +
						'}' +
						// Media nested inside a re-inverted background-image
						// container must not be re-inverted again (that would
						// cause a triple inversion = one net inversion).
						'[style*="background-image"] img,' +
						'[style*="background-image"] video,' +
						'[style*="background-image"] canvas,' +
						'[style*="background-image"] svg {' +
						'  filter: none !important;' +
						'}';

					this.adjustBodyBackground(doc);
				}
			} else {
				style.textContent = scrollbarCss;
			}

			doc.head.appendChild(style);
		} catch (e) {
			// Cross-origin iframe
		}
	},

	/**
	 * Calculate the perceived luminance of the email's background color.
	 * Checks the body first, then falls back to the first wrapper element
	 * (table or div), since many HTML emails set their background there.
	 * @param {Document} doc The iframe document
	 * @return {Number} Luminance between 0.0 (black) and 1.0 (white)
	 * @private
	 */
	getEmailLuminance: function(doc)
	{
		try {
			if (!doc.defaultView || !doc.body) {
				return 1.0;
			}

			var bg = doc.defaultView.getComputedStyle(doc.body).backgroundColor;

			// Transparent body — check the first wrapper element
			if (!bg || bg === 'rgba(0, 0, 0, 0)' || bg === 'transparent') {
				var wrapper = doc.body.querySelector('table, div');
				if (wrapper) {
					bg = doc.defaultView.getComputedStyle(wrapper).backgroundColor;
				}
				if (!bg || bg === 'rgba(0, 0, 0, 0)' || bg === 'transparent') {
					return 1.0;
				}
			}

			return this.parseLuminance(bg);
		} catch (e) {
			return 1.0;
		}
	},

	/**
	 * Parse a CSS rgb/rgba color string and return its relative luminance
	 * (Rec. 709 coefficients).
	 * @param {String} color CSS color like 'rgb(255, 255, 255)'
	 * @return {Number} Luminance between 0.0 and 1.0, or 1.0 on parse failure
	 * @private
	 */
	parseLuminance: function(color)
	{
		var m = color.match(/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/);
		if (!m) {
			return 1.0;
		}
		var r = parseInt(m[1], 10) / 255;
		var g = parseInt(m[2], 10) / 255;
		var b = parseInt(m[3], 10) / 255;
		return 0.2126 * r + 0.7152 * g + 0.0722 * b;
	},

	/**
	 * Set the email body background to the pre-inversion value (#e1e1e1)
	 * when it is white or transparent, so that after invert(1) it becomes
	 * exactly #1e1e1e — matching the dark theme seamlessly.
	 * @param {Document} doc The iframe document
	 * @private
	 */
	adjustBodyBackground: function(doc)
	{
		try {
			if (!doc.defaultView || !doc.body) {
				return;
			}
			var bg = doc.defaultView.getComputedStyle(doc.body).backgroundColor;
			if (!bg || bg === 'rgba(0, 0, 0, 0)' || bg === 'transparent' ||
				this.parseLuminance(bg) > 0.9) {
				doc.body.style.backgroundColor = '#e1e1e1';
				// Mark so we can clean up when switching back to light mode
				doc.body.style.setProperty('--grommunio-dark-bg', '1');
			}
		} catch (e) {
			// Ignore
		}
	}
};
Ext.namespace('Zarafa.core');

/**
 * @class Zarafa.core.DateRange
 * @extends Ext.util.Observable
 *
 * Represents a date range defined by a start and due date. The start date is inclusive, while the due date is exclusive. For example,
 * to denote an appointment that lasts all day on July 1st, 2010 one would write this as (00:00 July 1st 2010, 00:00 July 2nd 2010).
 * In sort, the range is defined as [startDate, dueDate>.
 * <p>
 * This class encapsulates such ranges because they are used often in especially the calendaring components.
 */
Zarafa.core.DateRange = Ext.extend(Ext.util.Observable, {
	/**
	 * @cfg {Date} startDate start date for this {@link Zarafa.core.DateRange DateRange}.
	 * use {@link #getStartDate} and {@link #setStartDate} to modify this value.
	 */
	startDate: null,

	/**
	 * @cfg {Date} dueDate due date for this {@link Zarafa.core.DateRange DateRange}.
	 * use {@link #getDueDate} and {@link #setDueDate} to modify this value.
	 */
	dueDate: null,

	/**
	 * @cfg {Boolean} allowBlank Specifies empty dates are accepted by this {@link Zarafa.core.DateRange DateRange},
	 * if {@link #allowBlank} is true then only we can use empty start/due dates in {@link Zarafa.core.DateRange DateRange}.
	 * otherwise {@link #startDate} and {@link #dueDate} will be initialized with current dates.
	 */
	allowBlank: false,

	/**
	 * @constructor
	 * @param {Date} startDate (optional) start date
	 * @param {Date} dueDate (optional) due date
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			allowBlank: this.allowBlank
		});

		if(config.allowBlank) {
			// if allowBlank is true then we should initialize start / due date with undefined
			// but this should only be done when start / due dates are not provided in the configs hence used Ext.applyIf
			// there is a restriction that start date can not exist without due date, so we will be initializing due date
			// same as start date if only start date is provided
			Ext.applyIf(config, {
				startDate: null,
				dueDate: config.startDate ? config.startDate.clone() : null
			});
		} else {
			// if allowBlank is false then we should initialize start / due date with current dates
			Ext.applyIf(config, {
				startDate: new Date(),
				dueDate: config.startDate ? config.startDate.clone() : new Date()
			});
		}

		Ext.apply(this, config);

		// Precondition
		if (Ext.isDate(this.startDate) && Ext.isDate(this.dueDate) && (this.getStartTime() > this.getDueTime())) {
			throw 'Invalid date range, start date is after due date';
		}

		this.addEvents(
			/**
			 * @event update
			 * Fires when the daterange is modified.
			 * @param {Zarafa.core.DateRange} newRange The changed daterange object
			 * @param {Zarafa.core.DateRange} oldRange The original daterange values (clone of the daterange object,
			 * prior of the change).
			 */
			'update'
		);

		Zarafa.core.DateRange.superclass.constructor.call(this);
	},

	/**
	 * @return {Date} the range's start date.
	 */
	getStartDate: function()
	{
		return this.startDate;
	},

	/**
	 * @return {Date} the range's due date.
	 */
	getDueDate: function()
	{
		return this.dueDate;
	},

	/**
	 * Sets the range start date.
	 * @param {Date} startDate the range's start date.
	 * @param {Boolean} silence When set to true it will not throw an start date after due date error.
	 * @param {Boolean} ignoreUpdate When set to true it will not fire the update event.
	 * @return {Zarafa.core.DateRange} this date range
	 */
	setStartDate: function(startDate, silence, ignoreUpdate)
	{
		var original = null;

		// make sure that you are saving null when no valid date is passed
		startDate = Ext.isDate(startDate) ? startDate : null;

		// Preconditions
		if (!this.allowBlank && !Ext.isDate(startDate)) {
			throw 'Cannot set DateRange start to undefined';
		} else if (Ext.isDate(this.dueDate) && Ext.isDate(startDate) && startDate.getTime() > this.getDueTime() && !silence) {
			throw 'Cannot set DateRange start date to after its due date';
		}

		// No update event when new value equals old value
		ignoreUpdate = ignoreUpdate || (Ext.isDate(this.startDate) && Ext.isDate(startDate) && this.getStartTime() == startDate.getTime());

		if (!ignoreUpdate) {
			original = this.clone();
		}

		this.startDate = startDate;

		if(!ignoreUpdate) {
			this.fireEvent("update", this, original);
		}

		return this;
	},

	/**
	 * Sets the range due date.
	 * @param {Date} dueDate the range's due date.
	 * @param {Boolean} silence When set to true it will not throw an start date after due date error.
	 * @param {Boolean} ignoreUpdate When set to true it will not fire the update event.
	 * @return {Zarafa.core.DateRange} this date range
	 */
	setDueDate: function(dueDate, silence, ignoreUpdate)
	{
		var original = null;

		// make sure that you are saving null when no valid date is passed
		dueDate = Ext.isDate(dueDate) ? dueDate : null;

		// Precondition
		if (!this.allowBlank && !Ext.isDate(dueDate)) {
			throw 'Cannot set DateRange due date to undefined';
		} else if (Ext.isDate(this.startDate) && Ext.isDate(dueDate) && dueDate.getTime() < this.getStartTime() && !silence) {
			throw 'Cannot set DateRange due date to before its start date';
		}

		// No update event when new value equals old value
		ignoreUpdate = ignoreUpdate || (Ext.isDate(this.dueDate) && Ext.isDate(dueDate) && this.getDueTime() == dueDate.getTime());

		if (!ignoreUpdate) {
			original = this.clone();
		}

		this.dueDate = dueDate;

		if(!ignoreUpdate) {
			this.fireEvent("update", this, original);
		}

		return this;
	},

	/**
	 * Sets the start and due dates.
	 * @param {Date} startDate the range's start date.
	 * @param {Date} dueDate the range's due date.
	 * @param {Boolean} silence When set to true it will not throw an start date after due date error.
	 * @param {Boolean} ignoreUpdate When set to true it will not fire the update event.
	 * @return {Zarafa.core.DateRange} this date range
	 */
	set: function(startDate, dueDate, silence, ignoreUpdate)
	{
		var original = null;

		// make sure that you are saving null when no valid date is passed
		startDate = Ext.isDate(startDate) ? startDate : null;
		dueDate = Ext.isDate(dueDate) ? dueDate : null;

		// Precondition
		if (!this.allowBlank && !Ext.isDate(startDate)) {
			throw 'Cannot set DateRange start to undefined';
		}

		if (!this.allowBlank && !Ext.isDate(dueDate)) {
			throw 'Cannot set DateRange due date to undefined';
		}

		if (Ext.isDate(startDate) && Ext.isDate(dueDate) && startDate.getTime() > dueDate.getTime() && !silence) {
			throw 'Invalid date range, start date is after due date';
		}

		ignoreUpdate = ignoreUpdate || (Ext.isDate(startDate) && Ext.isDate(dueDate) && (startDate.getTime() == this.getStartTime()) && (dueDate.getTime() == this.getDueTime()));

		if (!ignoreUpdate) {
			original = this.clone();
		}

		// don't fire update event from these functions
		this.setStartDate(startDate, true, true);
		this.setDueDate(dueDate, true, true);

		if (!ignoreUpdate) {
			this.fireEvent("update", this, original);
		}

		return this;
	},

	/**
	 * @return {Number} the range's start time in milliseconds since epoch, GMT.
	 */
	getStartTime: function()
	{
		return (Ext.isDate(this.startDate) && this.startDate.getTime()) || this.startDate;
	},

	/**
	 * @return {Number} the range's due time in milliseconds since epoch, GMT.
	 */
	getDueTime: function()
	{
		return (Ext.isDate(this.dueDate) && this.dueDate.getTime()) || this.dueDate;
	},

	/**
	 * Sets the range start time.
	 * @param {Number} startTime the range's start time.
	 * @param {Boolean} silence When set to true it will not throw an start date after due date error.
	 * @param {Boolean} ignoreUpdate When set to true it will not fire the update event.
	 * @return {Zarafa.core.DateRange} this date range
	 */
	setStartTime: function(startTime, silence, ignoreUpdate)
	{
		var original = null;

		// Preconditions
		if (!this.allowBlank && !startTime) {
			throw 'Cannot set DateRange start to undefined';
		} else if (Ext.isDate(this.dueDate) && startTime > this.dueDate.getTime() && !silence) {
			throw 'Cannot set DateRange start date to after its due date';
		}

		// No update event when new value equals old value
		ignoreUpdate = ignoreUpdate || (this.getStartTime() == startTime);

		if (!ignoreUpdate) {
			original = this.clone();
		}

		if (!Ext.isEmpty(startTime)) {
			this.startDate = new Date(startTime);
		} else {
			this.startDate = null;
		}

		if(!ignoreUpdate) {
			this.fireEvent("update", this, original);
		}

		return this;
	},

	/**
	 * Sets the range due time.
	 * @param {Number} dueTime the range's due time.
	 * @param {Boolean} silence When set to true it will not throw an start date after due date error.
	 * @param {Boolean} ignoreUpdate When set to true it will not fire the update event.
	 * @return {Zarafa.core.DateRange} this date range
	 */
	setDueTime: function(dueTime, silence, ignoreUpdate)
	{
		var original = null;

		// Precondition
		if (!this.allowBlank && !dueTime) {
			throw 'Cannot set DateRange due date to undefined';
		} else if (Ext.isDate(this.startDate) && dueTime < this.startDate.getTime() && !silence) {
			throw 'Cannot set DateRange due date to before its start date';
		}

		// No update event when new value equals old value
		ignoreUpdate = ignoreUpdate || (this.getDueTime() == dueTime);

		if (!ignoreUpdate) {
			original = this.clone();
		}

		if (!Ext.isEmpty(dueTime)) {
			this.dueDate = new Date(dueTime);
		} else {
			this.dueDate = null;
		}

		if(!ignoreUpdate) {
			this.fireEvent("update", this, original);
		}

		return this;
	},

	/**
	 * Sets the start and due times.
	 * @param {Number} startDate the range's start time.
	 * @param {Number} dueDate the range's due time.
	 * @param {Boolean} silence When set to true it will not throw an start date after due date error.
	 * @param {Boolean} ignoreUpdate When set to true it will not fire the update event.
	 * @return {Zarafa.core.DateRange} this date range
	 */
	setTime: function(startTime, dueTime, silence, ignoreUpdate)
	{
		var original = null;

		// Precondition
		if(!startTime || !dueTime) {
			if (!this.allowBlank && !startTime) {
				throw 'Cannot set DateRange start to undefined';
			}

			if (!this.allowBlank && !dueTime) {
				throw 'Cannot set DateRange due date to undefined';
			}
		} else if (startTime > dueTime && !silence) {
			throw 'Invalid date range, start date is after due date';
		}

		ignoreUpdate = ignoreUpdate || ((startTime == this.getStartTime()) && (dueTime == this.getDueTime()));

		if (!ignoreUpdate) {
			original = this.clone();
		}

		this.setStartTime(startTime, true, true);
		this.setDueTime(dueTime, true, true);

		if (!ignoreUpdate) {
			this.fireEvent("update", this, original);
		}

		return this;
	},

	/**
	 * @param {String} interval (optional) A valid date interval enum value.
	 * @return {Number} the range's duration in milliseconds
	 */
	getDuration: function(interval)
	{
		if (Ext.isDate(this.dueDate) && Ext.isDate(this.startDate)) {
			return Date.diff(interval || Date.MILLI, this.dueDate, this.startDate);
		}

		return 0;
	},

	/**
	 * @param {Number} duration the new duration of the range in milliseconds.
	 * @param {Boolean} ignoreUpdate When set to true it will not fire the update event.
	 * @return {Zarafa.core.DateRange} this date range
	 */
	setDuration: function(duration, ignoreUpdate)
	{
		var original = null;

		if(!Ext.isDate(this.startDate)) {
			// if no start date is provided then we can't set duration
			throw 'Cannot set duration when start date is not specified';
		}

		// No update event when new value equals old value
		ignoreUpdate = ignoreUpdate || (this.getDuration() == duration);

		if (!ignoreUpdate) {
			original = this.clone();
		}

		this.dueDate = new Date(this.getStartTime() + duration);

		if (!ignoreUpdate) {
			this.fireEvent("update", this, original);
		}

		return this;
	},

	/**
	 * Calculates the number of days spanned by this appointment, rounded to whole days. The function
	 * assumes that the range is an all day range. Even so, the rounding is still required to deal with
	 * date ranges that start and end in different time zones.
	 *
	 * @return {Number} the number of days spanned by this appointment, rounded to whole days.
	 */
	getNumDays: function()
	{
		var duration = this.getDuration(Date.DAY);
		if (Ext.isDefined(duration)) {
			return Math.round(duration);
		} else {
			return 0;
		}
	},

	/**
	 * Expands the range so that both the start and due times are multiples of the 'timeSlice' parameter. The start
	 * date is moved back, the due date is moved forward. Since this method does not care about time zones the value
	 * of 'timeSlice' is assumed to be <= 60 minutes.
	 *
	 * @param {Number} timeSlice the time slice to 'snap' to.
	 * @return {Zarafa.core.DateRange} this date range
	 */
	expand: function(timeSlice)
	{
		var original = this.clone();

		if(Ext.isDate(this.startDate)) {
			this.startDate = new Date(this.getStartTime() - this.getStartTime() % timeSlice);
		}

		if(Ext.isDate(this.dueDate)) {
			this.dueDate = new Date(this.getDueTime() + timeSlice - this.getDueTime() % timeSlice);
		}

		// only fire event if anything has changed
		if(Ext.isDate(this.startDate) || Ext.isDate(this.dueDate)) {
			this.fireEvent("update", this, original);
		}

		return this;
	},

	/**
	 * Deep-clones the date range.
	 * @return {Zarafa.core.DateRange} a clone of this date range.
	 */
	clone: function()
	{
		return new Zarafa.core.DateRange({
			startDate: Ext.isDate(this.startDate) ? new Date(this.getStartTime()) : undefined,
			dueDate: Ext.isDate(this.dueDate) ? new Date(this.getDueTime()) : undefined,
			allowBlank: this.allowBlank
		});
	},

	/**
	 * Test this date range for equality against another date range.
	 * @param {Zarafa.core.DateRange} otherRange a date range to compare with.
	 * @return {Boolean} true if this range equals the given other range.
	 */
	equals: function(otherRange)
	{
		if (!otherRange) {
			return false;
		}

		if(this.getStartTime() !== otherRange.getStartTime()) {
			// start dates don't match
			return false;
		}

		if(this.getDueTime() !== otherRange.getDueTime()) {
			// due dates don't match
			return false;
		}

		// both start/due dates matches
		return true;
	},

	/**
	 * Compares two date ranges for order. If range A comes before B, the function returns -1. If B comes before
	 * A, the function returns 1. If both are equal, this function returns 0. This functionality is equivalent
	 * to Java's Comparable interface.
	 *
	 * Comparison is based on the range start. If the start dates are equal, further distinction is made by
	 * due date, with earlier due dates coming first.
	 *
	 * Undefined dates will be considered as zero and compared.
	 *
	 * @param {Zarafa.core.DateRange} otherRange Date range to compare with.
	 * @return {Number} If this range 'comes before' otherRange, the function returns -1. If this range 'comes
	 * after' otherRange, return 1. Otherwise return 0.
	 *
	 */
	compare: function(otherRange)
	{
		// Compare start times.
		var aStartTime = this.getStartTime() || 0;
		var bStartTime = otherRange.getStartTime() || 0;

		if (aStartTime !== bStartTime) {
			return aStartTime > bStartTime ? 1: -1;
		}

		// If start times are equal, compare due times.
		var aDueTime = this.getDueTime() || 0;
		var bDueTime = otherRange.getDueTime() || 0;

		if (aDueTime !== bDueTime) {
			return aDueTime > bDueTime ? 1: -1;
		}

		// If ranges are equal, return 0.
		return 0;
	},

	/**
	 * Tests whether the date range is a so-called 'all day' range,
	 * meaning that start and due date duration time is more then one day(24 hours).
	 * @return {Boolean} true if the range is an all day range.
	 */
	isAllDay: function()
	{
		if(Ext.isDate(this.startDate) && Ext.isDate(this.dueDate) && !this.isZeroMinuteRange()) {
			return (this.startDate.clearTime(true).getTime() === this.getStartTime())
			&& (this.dueDate.clearTime(true).getTime() === this.getDueTime());
		}

		return false;
	},

	/**
	 * Tests whether the date range is a 0 minute.
	 * @return {Boolean} true if the range duration is 0 minute.
	 */
	isZeroMinuteRange: function()
	{
		return (this.dueDate.getTime() === this.startDate.getTime());
	},

	/**
	 * @return {Boolean} true if this date range overlaps with the other date range.
	 */
	overlaps: function(other)
	{
		var start1 = this.getStartTime();
		var due1 = this.getDueTime();
		var start2 = other.getStartTime();
		var due2 = other.getDueTime();

		return (start1 >= start2 && start1 < due2) || (start2 >= start1 && start2 < due1);
	},

	/**
	 * @param {Zarafa.core.DateRange} range date range to check against.
	 * @return {Boolean} true if this date range is inside the given date range.
	 */
	inside: function(range)
	{
		if(this.getStartTime() && range.getStartTime() && this.getDueTime() && range.getDueTime()) {
			return this.getStartTime() >= range.getStartTime() && this.getDueTime() <= range.getDueTime();
		}

		return false;
	},

	/**
	 * @param {Date} date the date to test.
	 * @return {Boolean} true if the give date is inside this date range.
	 */
	containsDate: function(date)
	{
		return this.getStartTime() <= date.getTime() && this.getDueTime() > date.getTime();
	},

	/**
	 * Formats the current visible date range as human-readable text. The formatter looks at which components the
	 * start and due dates have in common.
	 * For instance, the first and last days of a week range might lie in the same month (i.e. '13 - 19 July 2009'),
	 * or they might not (i.e. '28 September - 2 November 2009'). Finally a range may have the start and end dates
	 * in different years, i.e. '28 December 2009 - 1 January 2010'.
	 *
	 * @return {String} the current date range as text.
	 */
	format: function()
	{
		var startDate = this.startDate;
		var dueDate = this.dueDate;

		if(!Ext.isDate(startDate) || !Ext.isDate(dueDate)) {
			return '';
		}

		// If the due date is _exactly_ midnight, we must assume the last day of the period
		// is the previous day. So decrease the duedate with a day (making sure the duedate
		// does not move before the startDate).
		if (dueDate.getTime() === dueDate.clearTime(true).getTime()) {
			// Move to the previous day, use 12:00 as starting hour,
			// to prevent problems when the DST switches at 00:00 (e.g. in Brasil).
			// We don't need to restore to the original time, as the string
			// which we are going to ignore doesn't contain a time representation.
			dueDate = dueDate.clone();
			dueDate.setHours(12);
			dueDate = dueDate.add(Date.DAY, -1);

			if (dueDate.getTime() < startDate.getTime()) {
				dueDate = startDate;
			}
		}

		// The startDate and duedate are in completely different years.
		// Format the full date strings for both dates.
		if (startDate.getYear() != dueDate.getYear()) {
			// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
			return String.format('{0} - {1}', startDate.format(_('jS F Y')), dueDate.format(_('jS F Y')));
		}

		// The startDate and dueDate are in different months.
		// Format the date strings with the year in common.
		if (startDate.getMonth() != dueDate.getMonth()) {
			// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
			return String.format('{0} - {1} {2}', startDate.format(_('jS F')), dueDate.format(_('jS F')), startDate.format(_('Y')));
		}

		// The startDate and dueDate are on different days.
		// Format the date strings with the month and year in common.
		if (startDate.getDate() != dueDate.getDate()) {
			// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
			return String.format('{0} - {1} {2}', startDate.format(_('jS')), dueDate.format(_('jS')), startDate.format(_('F Y')));
		}

		// The startDate and dueDate are on the same day.
		// Format the date string with everything in common.
		// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
		return startDate.format(_('jS F Y'));
	}
});
Ext.namespace('Zarafa.core');

/**
 * @class Zarafa.core.EntryId
 *
 * Class for decoding entryids and for comparison between two entryids
 * we have basically two types of entryids object and store entryids.
 *
 * object entryids uses structure EID for entryids created
 * and for older entryids it uses structure EID_V0. Store entryids are generally wrapped
 * with some extra information (like guid for provider, dll name) which should be removed
 * before comparing two store entryids, after removing this wrapping the unwrapped entryid
 * uses the format same as object entryids (EID or EID_V0).
 *
 * version flag in EID and EID_V0 are specific flags and indicate which structure is used
 * to create that entryid, EID always contains version as '01000000' and EID_V0 always contains
 * '00000000' as version flag.
 *
 * server part of EID and EID_V0 indicates server name and it can be variable length, padding can be
 * upto 3 bytes so it can be anything between 0 to 3 bytes.
 *
 * in public store public root folder, ipm_subtree and favorites folder are custom folders
 * so they have static uniqueids.
 *
 * @singleton
 */
Zarafa.core.EntryId = (function()
{
	/* Bit definitions for abFlags[3] of ENTRYID */
	var ZARAFA_FAVORITE = '01';

	/* GUID of root public folder */
	var STATIC_GUID_PUBLICFOLDER = '00000000000000000000000000000003';
	/* GUID of root favorite folder */
	var STATIC_GUID_FAVORITE = '00000000000000000000000000000002';
	/* GUID of ipm_subtree of public store*/
	var STATIC_GUID_FAVSUBTREE = '00000000000000000000000000000001';
	/* GUID of Global Addressbook */
	var MUIDECSAB = 'AC21A95040D3EE48B319FBA753304425';
	/* GUID of Contact Provider */
	var MUIDZCSAB = '727F0430E3924FDAB86AE52A7FE46571';
	/* GUID for OneOff entryid */
	var MAPI_ONE_OFF_UID = '812B1FA4BEA310199D6E00DD010F5402';
	/* GUID for Address book recipient */
	var MUIDEMSAB = 'DCA740C8C042101AB4B908002B2FE182';

	/* Hardcoded ID used for generating entryid of addressbook container */
	var ZARAFA_UID_GLOBAL_ADDRESS_BOOK = '01000000';

	var BASE_EID = Ext.extend(Object, {

		// The entryid which this object represents
		entryId: '',

		// The length of the entryid
		length: 0,

		// Constructor
		// param: Entryid The entryid represented by this object
		constructor: function(entryId)
		{
			if(entryId) {
				// always make entryids in uppercase so comparison will be case insensitive
				this.entryId = entryId.toUpperCase();
				this.length = entryId.length;

				this.decomposeEntryId(this.entryId);
			}
		},

		// Detect padding (max 3 bytes) from the entryId
		getPadding: function(entryId)
		{
			var padding = '';
			var offset = 0;

			for (var iterations = 4; iterations > 0; iterations--) {
				if (entryId.substring(entryId.length - (offset + 2), entryId.length - offset) === '00') {
					padding += '00';
					offset += 2;
				} else {
					// if non-null character found then break the loop
					break;
				}
			}

			return padding;
		}

	});

	// Entryid from version 6
	var EID = Ext.extend(BASE_EID, {
		abFlags: '',      // BYTE[4],  4 bytes, 8 hex characters
		guid: '',       // GUID,   16 bytes, 32 hex characters
		version: '',      // ULONG,   4 bytes, 8 hex characters
		type: '',       // ULONG,   4 bytes, 8 hex characters
		uniqueId: '',     // GUID,   16 bytes, 32 hex characters
		server: '',      // CHAR,   variable length
		padding: '',      // TCHAR[3], 4 bytes, 8 hex characters (upto 4 bytes)

		MIN_LENGTH: 88,
		name: 'EID',

		// decompose the entryid and populate all flags of entryid
		decomposeEntryId: function(entryId)
		{
			var offset = 0;

			// First determine padding, and remove if from the entryId
			this.padding = this.getPadding(entryId);
			entryId = entryId.substring(0, entryId.length - this.padding.length);

			this.abFlags = entryId.substr(offset, 8);
			offset =+ 8;

			this.guid = entryId.substr(offset, 32);
			offset += 32;

			this.version = entryId.substr(offset, 8);
			offset += 8;

			this.type = entryId.substr(offset, 8);
			offset += 8;

			this.uniqueId = entryId.substr(offset, 32);
			offset += 32;

			this.server = entryId.substr(offset);
		}
	});

	// The entryid from the begin
	var EID_V0 = Ext.extend(BASE_EID, {
		abFlags: '',      // BYTE[4],  4 bytes, 8 hex characters
		guid: '',       // GUID,   16 bytes, 32 hex characters
		version: '',      // ULONG,   4 bytes, 8 hex characters
		type: '',       // ULONG,   4 bytes, 8 hex characters
		id: '',        // ULONG,   4 bytes, 8 hex characters
		server: '',      // CHAR,   variable length
		padding: '',      // TCHAR[3], 4 bytes, 8 hex characters (upto 4 bytes)

		MIN_LENGTH: 64,
		name: 'EID_V0',

		// decompose the entryid and populate all flags of entryid
		decomposeEntryId: function(entryId)
		{
			var offset = 0;

			// First determine padding, and remove if from the entryId
			this.padding = this.getPadding(entryId);
			entryId = entryId.substring(0, entryId.length - this.padding.length);

			this.abFlags = entryId.substr(offset, 8);
			offset =+ 8;

			this.guid = entryId.substr(offset, 32);
			offset += 32;

			this.version = entryId.substr(offset, 8);
			offset += 8;

			this.type = entryId.substr(offset, 8);
			offset += 8;

			this.id = entryId.substr(offset, 8);
			offset += 8;

			this.server = entryId.substr(offset);
		}
	});

	// wrapped store entryid
	var WrappedSEID = Ext.extend(BASE_EID, {
		flags: '',       // BYTE[4],   4 bytes, 8 hex characters
		providerUID: '',    // GUID,    16 bytes, 32 hex characters
		version: '',      // ULONG,    1 bytes, 2 hex characters	// zero
		type: '',       // ULONG,    1 bytes, 2 hex characters	// zero
		DLLFileName: '',    // BYTE,    variable length
		terminationChar: '',  // BYTE[1],   1 bytes, 2 hex characters	// zero
		unWrappedEntryId: '', // EID/EID_V0, variable length because it contains server name

		name: 'WrappedSEID',

		// decompose the entryid and populate all flags of entryid
		decomposeEntryId: function(storeEntryId)
		{
			var offset = 0;

			this.flags = storeEntryId.substr(offset, 8);
			offset += 8;

			this.providerUID = storeEntryId.substr(offset, 32);
			offset += 32;

			this.version = storeEntryId.substr(offset, 2);
			offset += 2;

			this.type = storeEntryId.substr(offset, 2);
			offset += 2;

			// find length of dll name, find null character which indicates end of dll name after the current offset
			var termCharIndex = storeEntryId.slice(offset).indexOf('00');
			this.DLLFileName = storeEntryId.substr(offset, termCharIndex);
			offset += termCharIndex;

			this.terminationChar = storeEntryId.substr(offset, 2);
			offset += 2;

			this.unWrappedEntryId = storeEntryId.substr(offset);

			// unwrapped entryid is actually an object entryid so decompose it
			this.unWrappedEntryId = Zarafa.core.EntryId.createEntryIdObj(this.unWrappedEntryId);
		}
	});

	// The entryid for addressbook items
	var ABEID = Ext.extend(BASE_EID, {
		abFlags: '',      // BYTE[4],  4 bytes, 8 hex characters
		guid: '',       // GUID,   16 bytes, 32 hex characters
		version: '',      // ULONG,   4 bytes, 8 hex characters
		type: '',       // ULONG,   4 bytes, 8 hex characters
		id: '',        // ULONG,   4 bytes, 8 hex characters
		extid: '',       // CHAR,   variable length
		padding: '',      // TCHAR[3], 4 bytes, 8 hex characters (upto 4 bytes)

		MIN_LENGTH: 64,
		name: 'ABEID',

		// decompose the entryid and populate all flags of entryid
		decomposeEntryId: function(entryId)
		{
			var offset = 0;

			// First determine padding, and remove if from the entryId
			this.padding = this.getPadding(entryId);
			entryId = entryId.substring(0, entryId.length - this.padding.length);

			this.abFlags = entryId.substr(offset, 8);
			offset =+ 8;

			this.guid = entryId.substr(offset, 32);
			offset += 32;

			this.version = entryId.substr(offset, 8);
			offset += 8;

			this.type = entryId.substr(offset, 8);
			offset += 8;

			this.id = entryId.substr(offset, 8);
			offset += 8;

			this.extid = entryId.substr(offset);
		}
	});

	// The entryid for local addressbook items
	var WrappedABEID = Ext.extend(BASE_EID, {
		ulVersion: '',     // ULONG,   4 bytes, 8 hex characters
		muid: '',       // MAPIUID, 16 bytes, 32 hex characters
		ulObjType: '',     // ULONG,   4 bytes, 8 hex characters
		ulOffset: '',     // ULONG,   4 bytes, 8 hex characters
		unWrappedEntryId: '', // EID/EID_V0, variable length because it contains server name

		name: 'WrappedABEID',

		// decompose the entryid and populate all flags of entryid
		decomposeEntryId: function(ABEntryId)
		{
			var offset = 0;

			this.ulVersion = ABEntryId.substr(offset, 8);
			offset += 8;

			this.muid = ABEntryId.substr(offset, 32);
			offset += 32;

			this.ulObjType = ABEntryId.substr(offset, 8);
			offset += 8;

			this.ulOffset = ABEntryId.substr(offset, 8);
			offset += 8;

			this.unWrappedEntryId = ABEntryId.substr(offset);

			// unwrapped entryid is actually an object entryid so decompose it
			this.unWrappedEntryId = Zarafa.core.EntryId.createEntryIdObj(this.unWrappedEntryId);
		}
	});

	// Wrap an entryid into a Contact Provider entryid
	// @static
	WrappedABEID.wrapABEID = function(entryId, objType)
	{
		objType = objType.toString(16);

		// add padding for the type, which is of 4 bytes (8 characters)
		objType = objType.padStart(2, '0');
		objType = objType.padEnd(8, '0');

		return '00000000' + MUIDZCSAB + objType + '00000000' + entryId;
	};

	// Unwrap an Contact Provider entryid
	// @static
	WrappedABEID.unwrapABEID = function(entryId)
	{
		// Remove ulVersion (8 char), muid (32 char), ulObjType (8 char) and ulOffset (8 char)
		return entryId.substring(56);
	};

	return {
		/**
		 * Compares two AB entryIds. It is possible to have two different entryIds that should match as they
		 * represent the same object (in multiserver environments).
		 * @param {String} entryId1 EntryID
		 * @param {String} entryId2 EntryID
		 * @return {Boolean} Result of the comparison
		 */
		compareABEntryIds: function(entryId1, entryId2)
		{
			if(!Ext.isString(entryId1) || !Ext.isString(entryId2)) {
				return false;
			}

			if(entryId1.toUpperCase() === entryId2.toUpperCase()) {
				return true;
			}
			return false;
		},

		/**
		 * Creates an object that has split up all the components of an entryID.
		 * @param {String} entryid Entryid
		 * @return {Object} EntryID object
		 */
		createEntryIdObj: function(entryid)
		{
			// check if we are dealing with old or new object entryids
			var versionString = entryid.substr(40, 8);
			var eidObj;

			if(versionString === '00000000') {
				// use EID_V0 struct
				eidObj = new EID_V0(entryid);
			} else {
				// use EID struct
				eidObj = new EID(entryid);
			}

			return eidObj;
		},

		/**
		 * Compares two entryIds. It is possible to have two different entryIds that should match as they
		 * represent the same object (in multiserver environments).
		 * @param {String} entryId1 EntryID
		 * @param {String} entryId2 EntryID
		 * @return {Boolean} Result of the comparison
		 */
		compareEntryIds: function(entryId1, entryId2)
		{
			if(!Ext.isString(entryId1) || !Ext.isString(entryId2)) {
				return false;
			}

			if(entryId1 === entryId2) {
				// if normal comparison succeeds then we can directly say that entryids are same
				return true;
			}
			return false;
		},

		/**
		 * Creates an object that has split up all the components of a store entryid.
		 * @param {String} storeEntryId unwrapped store entryid.
		 * @return {Object} store entryid object.
		 */
		createStoreEntryIdObj: function(storeEntryId)
		{
			return new WrappedSEID(storeEntryId);
		},

		/**
		 * Compares two entryIds. It is possible to have two different entryIds that should match as they
		 * represent the same object (in multiserver environments).
		 * @param {String} storeEntryId1 store entryid
		 * @param {String} storeEntryId2 store entryid
		 * @return {Boolean} Result of the comparison
		 */
		compareStoreEntryIds: function(storeEntryId1, storeEntryId2)
		{
			if(!Ext.isString(storeEntryId1) || !Ext.isString(storeEntryId2)) {
				return false;
			}

			if(storeEntryId1 === storeEntryId2) {
				// if normal comparison succeeds then we can directly say that entryids are same
				return true;
			}
			return false;
		},

		/**
		 * Unwrap an Entryid which is of the Contact Provider ({@link #hasContactProviderGUID}
		 * returned true for this entryid}.
		 * @param {String} entryId the Address Book entryid.
		 * @return {String} The unwrapped entryId
		 */
		unwrapContactProviderEntryId: function(entryId)
		{
			return WrappedABEID.unwrapABEID(entryId);
		},

		/**
		 * Wrap an EntryId which should be wrapped using the Contact Provider
		 * @param {String} entryId The entryid
		 * @return {String} The wrapped entryId
		 */
		wrapContactProviderEntryId: function(entryId, objType)
		{
			return WrappedABEID.wrapABEID(entryId, objType);
		},

		/**
		 * Create a one-off entryid from the applied parameters.
		 * @param {String} displayname displaye name as configured in record.
		 * @param {String} addrtype weather the record is of type SMTP.
		 * @param {String} emailaddress email address as configured in record.
		 * @return {String} The oneoff entryId
		 */
		createOneOffEntryId: function(displayname, addrtype, emailaddress)
		{
			return '00000000' + MAPI_ONE_OFF_UID + '00000080' + Zarafa.core.Util.encode_utf16(displayname) + '0000' + Zarafa.core.Util.encode_utf16(addrtype) + '0000' + Zarafa.core.Util.encode_utf16(emailaddress) + '0000';
		},

		/**
		 * Checks if the passed folder entryid is a folder in the favorites folder, favorites folder
		 * contains 0x01 in the abFlags[3] flag.
		 * @param {String} entryId folder entryid
		 * @return {Boolean} true of folder is a favorite folder else false
		 */
		isFavoriteFolder: function(entryId)
		{
			var entryIdObj = Zarafa.core.EntryId.createEntryIdObj(entryId);

			return (entryIdObj.abFlags.substr(6, 8) === ZARAFA_FAVORITE);
		},

		/**
		 * Checks if the given entryid is a oneoff entryid.
		 * @param {String} entryId The entryid
		 * @return {Boolean} true if the entryid is a oneoff
		 */
		isOneOffEntryId: function(entryId)
		{
			var entryIdObj = Zarafa.core.EntryId.createEntryIdObj(entryId);

			return entryIdObj.guid === MAPI_ONE_OFF_UID;
		},

		/**
		 * Checks if the GUID part of the entryid is of the Contact Provider.
		 * @param {String} entryId Address Book entryid
		 * @return {Boolean} true if guid matches the Contact Provider else false
		 */
		hasContactProviderGUID: function(entryId)
		{
			var entryIdObj = Zarafa.core.EntryId.createEntryIdObj(entryId);

			return entryIdObj.guid === MUIDZCSAB;
		},

		/**
		 * Checks if the GUID part of the entryid is of the AB Provider.
		 * @param {String} entryId Address Book entryid
		 * @return {Boolean} true if guid matches the AB Provider else false
		 */
		hasABProviderGUID: function(entryId)
		{
			var entryIdObj = Zarafa.core.EntryId.createEntryIdObj(entryId);

			return entryIdObj.guid === MUIDEMSAB;
		},

		/**
		 * Format an entryid into the Object ID text shown in the mail properties dialog.
		 * @param {String} entryId Entryid of the message record
		 * @return {String|undefined} Object ID representation or undefined when formatting fails
		 */
		formatObjectId: function(entryId)
		{
			if (!Ext.isString(entryId) || entryId.length < 136) {
				return undefined;
			}

			var provider = entryId.substr(8, 32);
			var folderGcvHex = entryId.substr(76, 12);
			var databaseGuid = entryId.substr(92, 32);
			var messageGcvHex = entryId.substr(124, 12);

			var folderGcv = parseInt(folderGcvHex, 16);
			var messageGcv = parseInt(messageGcvHex, 16);

			if (isNaN(folderGcv) || isNaN(messageGcv)) {
				return undefined;
			}

			return messageGcv + '/0x' + messageGcv.toString(16) +
				'; folder=' + folderGcv + '/0x' + folderGcv.toString(16) +
				'; dbguid=' + databaseGuid + '; store=' + provider;
		},
	};
})();
Ext.namespace('Zarafa.core');

/**
 * @class Zarafa.core.Enum
 * @extends Object
 *
 * Base class for Enumerations, which are defined as <String, Number> hash maps.
 * Provides a set of get methods for getting properties or property names by value.
 */
Zarafa.core.Enum = Ext.extend(Object, {

	/**
	 * Get a property by value.
	 * @param {Number} value
	 * @return the property value (equal to the input) iff a property with the given value exists. Returns undefined otherwise.
	 */
	get: function(value)
	{
		value = parseInt(value, 10);
		for (var key in this) {
			if (this[key]==value) {
				return this[key];
			}
		}
	},

	/**
	 * Gets a property name by value.
	 * @param {Number} value
	 * @return the property name iff a property with the given value exists. Returns undefined otherwise.
	 */
	getName: function(value)
	{
		value = parseInt(value, 10);
		for (var key in this) {
			if (this[key]==value) {
				return key;
			}
		}
	},

	/**
	 * Gets a property value by name.
	 * @param {String} key key of the <String, Number> hashmap
	 * @return {Number} the property value corresponding to string key
	 */
	getValue: function(key)
	{
		var value = this[key];

		if(Ext.isNumber(value)) {
			return value;
		}
	},

	/**
	 * Adds a new property and assigns it a unique value. If the property already exists it does not
	 * add the new property and returns the value of the existing one.
	 * @param {String} key key of the <String, Number> hashmap
	 * @return {Number} the property value corresponding to string key
	 */
	addProperty: function(propKey)
	{
		// Look for the highest value
		var highestValue = 0;
		for (var key in this) {
			if (typeof this[key] == 'number'){
				// Return the value if the property already exists
				if(key == propKey) {
					return this[key];
				}

				if(highestValue < this[key]){
					highestValue = this[key];
				}
			}
		}

		// Take the highest value, increase it by one and set it as value for the new property
		highestValue++;
		this[propKey] = highestValue;

		return highestValue;

	}
});

// Convenience method
Zarafa.core.Enum.create = function(object)
{
	var ExtendedObject = Ext.extend(Zarafa.core.Enum, object);
	return new ExtendedObject();
};
Ext.namespace('Zarafa.core');

/**
 * @class Zarafa.core.Events
 * Utility class for handling special events.
 * @singleton
 */
Zarafa.core.Events = {
	/**
	 * The list of fields and the corresponding {@link Ext.util.Observable}
	 * objects which are used to register the event handlers. Use
	 * {@link #registerListener} and {@link #unregisterListener} to add/remove
	 * {@Link Ext.util.Observable} instances. Use {@link #getListener}
	 * to obtain a {@link Ext.util.Observable} instance.
	 * @property
	 * @type Object
	 * @private
	 */
	listeners: {},

	/**
	 * Register a {@link Ext.util.Observable} instance for the given
	 * {@link Ext.form.Field}/{@link Ext.Element} pair to the {@link #listeners}
	 * object.
	 * @param {Ext.form.Field} field The register for which the observable is added
	 * @param {Ext.Element} el The element for which the the observable is added
	 * @param {Ext.util.Observable} observable The observable to register
	 * @private
	 */
	registerListener: function(field, el, observable)
	{
		var observables = Zarafa.core.Events.listeners[field.id];
		if (observables) {
			observables.push(observable);
		} else {
			Zarafa.core.Events.listeners[field.id] = [ observable ];
		}
	},

	/**
	 * Unregister a previously {@link #registerListener registered}
	 * {@link Ext.util.Observable} from the {@link #listeners}.
	 * @param {Ext.form.Field} field The register to which the observable belongs
	 * @param {Ext.Element} el The element to which the observable belongs
	 * @private
	 */
	unregisterListener: function(field, el)
	{
		var observables = Zarafa.core.Events.listeners[field.id];
		if (observables) {
			for (var i = 0, len = observables.length; i < len; i++) {
				var observable = observables[i];
				if (observable.el === el) {
					observables.splice(i, 1);
					if (observables.length === 0) {
						delete Zarafa.core.Events.listeners[field.id];
					}
				}
			}
		}
	},

	/**
	 * Obtain a previously {@link #registerListener registered}
	 * {@link Ext.util.Observable} from the {@link #listeners}.
	 * @param {Ext.form.Field} field The register to which the observable belongs
	 * @param {Ext.Element} el The element to which the observable belongs
	 * @private
	 */
	getListener: function(field, el)
	{
		var observables = Zarafa.core.Events.listeners[field.id];
		if (!observables) {
			return undefined;
		}

		for (var i = 0, len = observables.length; i < len; i++) {
			var observable = observables[i];
			if (observable.el === el) {
				return observable;
			}
		}
	},

	/**
	 * Add a special event handler to the given field to catch
	 * 'paste' events. There are multiple ways to paste text into
	 * a textfield.
	 * 1) contextmenu
	 * 2) Ctrl-V (shortcut depending on OS)
	 * 3) Drag & Drop text
	 *
	 * Support for catching these options depends severely on the browser,
	 * and thus this special event handler will serve as compatibility
	 * handler to handle the various cases correctly.
	 *
	 * @param {Ext.form.Field} field The field on which to listen for
	 * paste events.
	 * @param {Ext.Element} el The element on which the event should be registered
	 * @param {Function} fn The callback function to be called when text
	 * has been pasted. This function has no arguments (See {@link Ext.util.Observable#on}).
	 * @param {Object} scope The scope in which the function will be called (See {@link Ext.util.Observable#on}).
	 * @param {Object} obj (optional) Additional options (See {@link Ext.util.Observable#on}).
	 */
	addPasteEventHandler: function(field, el, fn, scope, obj)
	{
		// Check if this field has already been used to register
		// an event handler for pasting. If that is not the case
		// we need to construct a new Ext.util.Observable object
		// for the field and add the event handlers.
		var observable = Zarafa.core.Events.getListener(field, el);
		if (!Ext.isDefined(observable)) {
			var noOn = !Ext.isFunction(el.on);
			observable = new Ext.util.Observable();
			observable.field = field;
			observable.el = el;
			observable.hasFocus = field.hasFocus;
			observable.originalValue = field.getValue();
			observable.addEvents('paste');

			// Register the event handler for paste events. This will ensure
			// the input element will be resized when pasting.
			if (noOn) {
				el.addEventListener('paste', this.onPaste.createDelegate(observable));
			} else {
				field.mon(el, 'paste', this.onPaste, observable);
			}

			// A special kind of pasting is dragging & dropping text into
			// the boxfield. There really isn't a true event handler for that,
			// as it isn't pasting, but neither is it typing. So use the mouse
			// to detect such changes.
			//
			// For some reason the 'mouseup' event is not being triggered when
			// the user drops the text into the input field. So we must use
			// the event which is fired a bit later.
			if (noOn) {
				el.addEventListener('mouseover', this.onPasteMouseOver.createDelegate(observable));
			} else {
				field.mon(el, 'mouseover', this.onPasteMouseOver, observable);
			}
			field.on('blur', this.onPasteBlur, observable);

			Zarafa.core.Events.registerListener(field, el, observable);
		}

		observable.addListener('paste', fn, scope, obj);
	},

	/**
	 * Removes the event handler as registered by {@link #addPasteEventHandler}.
	 *
	 * @param {Ext.form.Field} field The field on which the event was registered
	 * @param {Ext.Element} el The element on which the event should be registered
	 * @param {Function} fn The function to unregister
	 * @param {Object} scope The scope for the function.
	 */
	removePasteEventHandler: function(field, el, fn, scope)
	{
		var observable = Zarafa.core.Events.getListener(field, el);
		if (Ext.isDefined(observable)) {
			observable.removeListener('paste', fn, scope);
			// If this was the last event handler, delete
			// the Observable instance.
			if (!observable.hasListener('paste')) {
				Zarafa.core.Events.unregisterListener(field, el);
			}
		}
	},

	/**
	 * As Opera doesn't have the 'paste' browser event we are mimicking the behavior
	 * here by having a global 'keyup' event handler. This event will simply check
	 * if 'Ctrl-V has been pressed and will fire the event handler.
	 *
	 * This function is called in the scope of an {@link Ext.util.Observable}.
	 *
	 * @param {Number} key The key which was pressed
	 * @param {Ext.EventObject} e The event object which fired the event
	 * @private
	 */
	onPasteKeyUp: function(key, e)
	{
		// There are references online which indicate that Opera doesn't detect
		// the Ctrl key properly and the keyCode is 0. But Opera 11 seems to
		// behave correctly...
		if (key.ctrlKey === true && key.keyCode === Ext.EventObject.V) {
			this.fireEvent('paste');
		}
	},

	/**
	 * Event handler for the 'paste' event on the {@link #el input element}.
	 * This will start the {@link #onPastePoll} function to wait for the contents
	 * to be pasted so it can fire the event handler.
	 *
	 * This function is called in the scope of an {@link Ext.util.Observable}.
	 *
	 * @private
	 */
	onPaste: function()
	{
		Zarafa.core.Events.onPastePoll.call(this, this.field, this.field.getValue(), 5);
	},

	/**
	 * The 'paste' event on an input element is fired before the text which must
	 * be pasted is added into the input field. There is no cross-browser solution
	 * to access the text before it is put into the input field, hence polling is used
	 * to wait for the input field to be updated with the new text, so it can fire
	 * the event handler.
	 *
	 * This function is called in the scope of an {@link Ext.util.Observable}.
	 *
	 * @param {Ext.form.Field} field The field to poll for the new value
	 * @param {String} oldValue The original value of the input element
	 * @param {Number} limit The number of polling tries left
	 * @private
	 */
	onPastePoll: function(field, oldValue, limit)
	{
		if (limit === 0) {
			return;
		} else if (field.getValue() === oldValue) {
			Zarafa.core.Events.onPastePoll.defer(1, this, [field, oldValue, --limit]);
			return;
		}
		this.fireEvent('paste');
		this.originalValue = this.field.getValue();
	},

	/**
	 * Event handler which is fired when the Mouse is being moved over the
	 * input field. This will check if the input field has been magically
	 * changed without the user pressing any button. If that happens, then
	 * the user has dragged text into the input element and we need to
	 * fire the event handler.
	 *
	 * This function is called in the scope of an {@link Ext.util.Observable}.
	 *
	 * @private
	 */
	onPasteMouseOver: function()
	{
		if (this.hasFocus === false) {
			if (this.originalValue !== this.field.getValue()) {
				this.field.focus();
				this.fireEvent('paste');
				this.originalValue = this.field.getValue();
			}

			this.hasFocus = true;
		}
	},

	/**
	 * Event handler which is fired when the registered field is being
	 * {@link Ext.form.Field#blur blurred}. This will store the value
	 * currently in the editor so during {@link #onPasteMouseOver} we can
	 * check if text has been dropped into the editor.
	 * @private
	 */
	onPasteBlur: function()
	{
		this.hasFocus = false;
		this.originalValue = this.field.getValue();
	}
};
Ext.namespace("Zarafa.core");

/**
 * @class Zarafa.core.HTMLParser
 *
 * Class for performing operations on HTML content.
 *
 * @singleton
 */
Zarafa.core.HTMLParser = (function() {
	// regular expression to strip all style tags with its content
	var stripStylesRe = /<style[^>]*>[\s\S]*?<\/style[^>]*>/gim;

	// regular expression to strip all style tags with its content
	var stripScriptsRe = /<script[^>]*>[\s\S]*?<\/script[^>]*>/gim;

	// regular expression to convert <br /> tags to newlines
	var br2nlRe = /<br\s*[^<>]*?\/*>/igm;

	// regular expression to convert \r\n, \n or \r tags to <br />
	var nl2brRe = /\r\n|\n|\r/gim;

	// regular expression to convert outlook style inline image urls to url which can request image using download_attachment.php
	var cidToUrlRe = /(src\s*=\s*[\"\']?)cid:([^ \"\']*)([\"\']?)/igm;

	// regular expression to convert url for inline images to outlook style url
	var urlToCidRe = /(src\s*=\s*[\"\']?)\S*attachCid=([^ &\"\']*)[^ \"\']*([\"\']?)/igm;

	var cssUrlRe = /url\(\s*(['"]?)([^'")]+)\1\s*\)/igm;
	var cssImportRe = /@import\s+(?:url\(\s*)?(['"]?)([^'")\s;]+)\1\s*\)?/igm;
	var hrefResourceTags = {
		image: true,
		use: true,
		feimage: true,
		iframe: true,
		frame: true,
		script: true,
		embed: true,
		audio: true,
		video: true,
		source: true,
		track: true
	};

	function isExternalResourceUrl(url)
	{
		if (Ext.isEmpty(url)) {
			return false;
		}

		url = String(url).trim();
		url = url.replace(/^['"]|['"]$/g, '');

		if (!url || url === '#' || url.indexOf('cid:') === 0 || url.indexOf('data:') === 0 ||
			url.indexOf('blob:') === 0 || url.indexOf('about:') === 0 || url.indexOf('mailto:') === 0 ||
			url.indexOf('tel:') === 0 || url.indexOf('javascript:') === 0) {
			return false;
		}

		// Relative urls are considered local.
		if (!/^(https?:|ftp:)?\/\//i.test(url)) {
			return false;
		}

		var baseUrl = container.getBaseURL();
		var parser = document.createElement('a');

		parser.href = baseUrl;
		var baseOrigin = parser.protocol + '//' + parser.host;

		if (url.indexOf('//') === 0) {
			url = window.location.protocol + url;
		}

		parser.href = url;
		var urlOrigin = parser.protocol + '//' + parser.host;

		return (urlOrigin !== baseOrigin);
	}

	function sanitizeCssExternalUrls(cssText)
	{
		if (Ext.isEmpty(cssText)) {
			return cssText;
		}

		cssText = String(cssText).replace(cssUrlRe, function(match, quote, url) {
			if (isExternalResourceUrl(url)) {
				return 'url("")';
			}
			return match;
		});

		cssText = cssText.replace(cssImportRe, function(match, quote, url) {
			if (isExternalResourceUrl(url)) {
				return '';
			}
			return match;
		});

		return cssText;
	}

	function hasExternalCssUrls(cssText)
	{
		if (Ext.isEmpty(cssText)) {
			return false;
		}

		var hasExternalUrl = false;
		String(cssText).replace(cssUrlRe, function(match, quote, url) {
			if (isExternalResourceUrl(url)) {
				hasExternalUrl = true;
			}
			return match;
		});

		if (hasExternalUrl) {
			return true;
		}

		String(cssText).replace(cssImportRe, function(match, quote, url) {
			if (isExternalResourceUrl(url)) {
				hasExternalUrl = true;
			}
			return match;
		});

		return hasExternalUrl;
	}

	function hasExternalSrcset(srcset)
	{
		if (Ext.isEmpty(srcset)) {
			return false;
		}

		var entries = String(srcset).split(',');
		for (var i = 0; i < entries.length; i++) {
			var entry = entries[i].trim();
			if (Ext.isEmpty(entry)) {
				continue;
			}

			var url = entry.split(/\s+/)[0];
			if (isExternalResourceUrl(url)) {
				return true;
			}
		}

		return false;
	}

	function sanitizeSrcset(srcset)
	{
		if (Ext.isEmpty(srcset)) {
			return srcset;
		}

		var keptEntries = [];
		var entries = String(srcset).split(',');
		for (var i = 0; i < entries.length; i++) {
			var entry = entries[i].trim();
			if (Ext.isEmpty(entry)) {
				continue;
			}

			var parts = entry.split(/\s+/);
			var url = parts[0];
			if (!isExternalResourceUrl(url)) {
				keptEntries.push(entry);
			}
		}

		return keptEntries.join(', ');
	}

	function parseInertHtml(data)
	{
		// Parse in an inert document so external resources are not fetched while
		// we only inspect/sanitize the markup.
		if (window.DOMParser) {
			var parsedDoc = new DOMParser().parseFromString('<!doctype html><html><body>' + data + '</body></html>', 'text/html');
			return {
				root: parsedDoc.body,
				toHtml: function() {
					return parsedDoc.body.innerHTML;
				}
			};
		}

		// Fallback for older engines.
		var inertDoc = document.implementation.createHTMLDocument('');
		inertDoc.body.innerHTML = data;
		return {
			root: inertDoc.body,
			toHtml: function() {
				return inertDoc.body.innerHTML;
			}
		};
	}

	function hasExternalHrefResource(node, tagName)
	{
		if (!hrefResourceTags[tagName]) {
			return false;
		}

		return isExternalResourceUrl(node.getAttribute('href')) ||
			isExternalResourceUrl(node.getAttribute('xlink:href'));
	}

	function sanitizeExternalHrefResource(node, tagName)
	{
		if (!hrefResourceTags[tagName]) {
			return;
		}

		if (isExternalResourceUrl(node.getAttribute('href'))) {
			node.setAttribute('href', '');
		}

		if (isExternalResourceUrl(node.getAttribute('xlink:href'))) {
			node.setAttribute('xlink:href', '');
		}
	}

	return {
		/**
		 * Strips all style tags, and also remove its contents
		 * @param {Mixed} value The text from which to strip style tags
		 * @return {String} The stripped text
		 */
		stripStyles: function(v)
		{
			return !v ? v: String(v).replace(stripStylesRe, '');
		},

		/**
		 * Strips all script tags, and also remove its contents
		 * @param {Mixed} value The text from which to strip script tags
		 * @return {String} The stripped text
		 */
		stripScripts: function(v)
		{
			return !v ? v: String(v).replace(stripScriptsRe, '');
		},

		/**
		 * Converts HTML tag <br /> to newline characters
		 * @param {String} The string value to format.
		 * @return {String} The string with embedded \n tags in place of <br />.
		 */
		br2nl: function(v)
		{
			return Ext.isEmpty(v) ? '' : v.replace(br2nlRe, '\n');
		},

		/**
		 * Converts newline characters to HTML tag <br />
		 * @param {String} The string value to format.
		 * @return {String} The string with embedded <br /> tags in place of \n.
		 */
		nl2br: function(v)
		{
			return Ext.isEmpty(v) ? '' : v.replace(nl2brRe, '<br>');
		},

		/**
		 * Converts newline(\r\n, \r) characters to \n
		 * @param {String} The string value to format.
		 * @return {String} The string with embedded \n in place of \r\n or \r.
		 */
		rlnl2nl: function(v)
		{
			return Ext.isEmpty(v) ? '' : v.replace(/\r\n|\r/gim, '\n');
		},

		/**
		 * Format string with plain-text contents into a HTML formatted string.
		 *
		 * This will convert new-line characters into <br /> elements.
		 *
		 * @param {String} content The plain-text contents to be formatted
		 * @return {String} The HTML representation of the content
		 */
		convertPlainToHTML: function(content)
		{
			if(Ext.isEmpty(content)) {
				return content;
			}

			// convert all html entities to their html equivalents
			content = Zarafa.core.HTMLParser.entityEncode(content, 'ENT_NOQUOTES');

			// We should wrap the content in <pre> tag to maintain
			// text indentation/spacing when we convert it to HTML.
			content = '<div><pre wrap style=\"white-space: pre-wrap; word-wrap: break-word;\">' + content + '</pre></div>';

			// convert all breaklines
			content = Zarafa.core.HTMLParser.nl2br(content);

			return content;
		},

		/**
		 * Format string with HTML contents into a plain-text string.
		 *
		 * This will convert <br /> characters into \n elements.
		 *
		 * @param {String} content The HTML contents to be formatted
		 * @return {String} The plain-text representation of the content
		 */
		convertHTMLToPlain: function(content)
		{
			if(Ext.isEmpty(content)) {
				return content;
			}

			//----- remove tags but preserve the content ----

			// remove all select / options tags
			content = content.replace( /<[\/]?(?:select)[^>]*>/gim, '\n');
			content = content.replace(/<[\/]?(?:option)[^>]*>/gim, '\t');

			// replace all tags with their text equivalents
			content = content.replace(/<(?:hr)[^>]*>/gim, '\n-----------\n');
			content = content.replace(/<[\/]?(?:h[123456]|div|p|pre|title)[^>]*>/gim, '\n\n');
			content = content.replace(/<[\/]?(?:ul|ol|dl|dt|textarea|img)[^>]*>/gim, '\n');
			content = content.replace(/<[\/]?(?:dd|li)[^>]*>/gim, '\t');

			// tags related to table
			content = content.replace(/<[\/]?(?:table)[^>]*>/gim, '\n\n');
			content = content.replace(/<[\/]?(?:caption|tr)[^>]*>/gim, '\n');
			content = content.replace(/<[^\/]?(?:th|td)[^>]*>/gim, '<br />');

			// remove anchor tag by preserving the links, links will be added after content of anchor tag in between <> signs
			content = content.replace(/<a[^>]* href=[\'\"]?([^\s\'\">]*)[^>]*>([\s\S]*?)<\/a[^>]*>/gim, '$2 &lt;$1&gt;');

			//------ remove tags without preserving the contents -----

			// remove style tags
			content = Zarafa.core.HTMLParser.stripStyles(content);

			// remove script tags
			content = Zarafa.core.HTMLParser.stripScripts(content);

			// remove comments
			content = content.replace(/<!--[\s\S]*?-->/gim, '');

			// we have processed tags which are useful for plain text conversion so now remove all remaining tags
			content = Zarafa.core.HTMLParser.stripUnwantedTags(content, ['br']);

			// decode html entities
			content = Zarafa.core.HTMLParser.entityDecode(content);

			// remove unnecessary space
			content = content.replace(/^\s*$/gm, '');

			// add <br> in line which hasn't <br> at end of line
			content = content.replace(/(.*[^<>\n]$)/gm, '$1<br />');

			// remove extra line breaks
			content = content.replace(/\n/gm, '');

			// convert all breaklines
			content = Zarafa.core.HTMLParser.br2nl(content);

			// remove remaining html entities
			content = content.replace(/[&][#0-9a-z]*[;]/gim, '');

			// remove breaklines from the end of the lines
			content = content.replace(/\n(?!\n)$/gm, '');

			return content;
		},

		/**
		 * Function which strips unwanted tags, This function is whitelisting based so you need
		 * to pass tags that are allowed in the text, all other tags will be removed. This function will
		 * only remove tags and will not remove content in between tags.
		 * @param {String} content html content
		 * @param {Array} allowedTags tags that should not be removed from the content.
		 * @return {String} content after removing unwanted tags.
		 */
		stripUnwantedTags: function (content, allowedTags)
		{
			// Match all HTML tags
			var matches = content.match(/(<\/?[^>]+>)/gi);

			var html = '';
			var allowedTag = '';
			var i = -1;
			var allowed = false;

			if(!allowedTags) {
				allowedTags = [];
			}

			// Go through all HTML tags
			if(matches && matches.length > 0) {
				for(var index1 = 0, len1 = matches.length; index1 < len1; index1++) {
					// Save HTML tag
					html = matches[index1].toString();

					// flag to indicate that tag should be removed or not
					allowed = false;

					// Go through all allowed tags
					for(var index2 = 0, len2 = allowedTags.length; index2 < len2; index2++) {
						allowedTag = allowedTags[index2].toLowerCase();
						i = -1;

						if (i !== 0) {
							i = html.toLowerCase().indexOf('<'+allowedTag+'>');
						}

						if (i !== 0) {
							i = html.toLowerCase().indexOf('<'+allowedTag+' ');
						}

						if (i !== 0) {
							i = html.toLowerCase().indexOf('</'+allowedTag);
						}

						// Determine
						if (i === 0) {
							allowed = true;
							break;
						}
					}

					if (allowed !== true) {
						content = content.split(html).join('');		// Custom replace. No regexing
					}
				}
			}

			return content;
		},

		/**
		 * Function will check if data contains external contents in any html tag (img, audio, video),
		 * and will also check for external stylesheets.
		 * @param {String} data data that should be checked for external content.
		 * @return {Boolean} true if data contains external content else false.
		 */
		hasExternalContent: function(data)
		{
			if(Ext.isEmpty(data)) {
				return false;
			}

			var parsed = parseInertHtml(data);
			var nodes = parsed.root.querySelectorAll('*');
			for (var i = 0; i < nodes.length; i++) {
				var node = nodes[i];
				var tagName = (node.tagName || '').toLowerCase();

				var src = node.getAttribute('src');
				if (isExternalResourceUrl(src)) {
					return true;
				}

				var background = node.getAttribute('background');
				if (isExternalResourceUrl(background)) {
					return true;
				}

				var poster = node.getAttribute('poster');
				if (isExternalResourceUrl(poster)) {
					return true;
				}

				var srcset = node.getAttribute('srcset');
				if (hasExternalSrcset(srcset)) {
					return true;
				}

				var dataAttr = node.getAttribute('data');
				if (isExternalResourceUrl(dataAttr)) {
					return true;
				}

				if (hasExternalHrefResource(node, tagName)) {
					return true;
				}

				var styleAttr = node.getAttribute('style');
				if (hasExternalCssUrls(styleAttr)) {
					return true;
				}

				// <link href> can auto-fetch fonts/stylesheets.
				if (tagName === 'link' && isExternalResourceUrl(node.getAttribute('href'))) {
					return true;
				}

				// refresh meta tags can trigger remote navigation.
				if (tagName === 'meta' && ((node.getAttribute('http-equiv') || '').toLowerCase() === 'refresh')) {
					var refreshContent = node.getAttribute('content') || '';
					var urlMatch = /url\s*=\s*([^;]+)/i.exec(refreshContent);
					if (urlMatch && isExternalResourceUrl(urlMatch[1])) {
						return true;
					}
				}
			}

			var styleTags = parsed.root.querySelectorAll('style');
			for (var j = 0; j < styleTags.length; j++) {
				if (hasExternalCssUrls(styleTags[j].textContent || styleTags[j].innerHTML)) {
					return true;
				}
			}

			return false;
		},

		/**
		 * Function will replace external content links with blank strings, so external content would not be loaded.
		 * @param {String} data raw data.
		 * @return {String} filtered data.
		 */
		blockExternalContent: function(data)
		{
			if(Ext.isEmpty(data)) {
				return data;
			}

			var parsed = parseInertHtml(data);
			var nodes = parsed.root.querySelectorAll('*');
			for (var i = 0; i < nodes.length; i++) {
				var node = nodes[i];
				var tagName = (node.tagName || '').toLowerCase();

				if (isExternalResourceUrl(node.getAttribute('src'))) {
					node.setAttribute('src', '');
				}

				if (isExternalResourceUrl(node.getAttribute('background'))) {
					node.setAttribute('background', '');
				}

				if (isExternalResourceUrl(node.getAttribute('poster'))) {
					node.setAttribute('poster', '');
				}

				if (isExternalResourceUrl(node.getAttribute('data'))) {
					node.setAttribute('data', '');
				}

				sanitizeExternalHrefResource(node, tagName);

				var srcset = node.getAttribute('srcset');
				if (!Ext.isEmpty(srcset)) {
					node.setAttribute('srcset', sanitizeSrcset(srcset));
				}

				var styleAttr = node.getAttribute('style');
				if (!Ext.isEmpty(styleAttr)) {
					node.setAttribute('style', sanitizeCssExternalUrls(styleAttr));
				}

				// <link href> auto-fetches styles/fonts.
				if (tagName === 'link' && isExternalResourceUrl(node.getAttribute('href'))) {
					node.setAttribute('href', '');
				}

				if (tagName === 'meta' && ((node.getAttribute('http-equiv') || '').toLowerCase() === 'refresh')) {
					node.setAttribute('content', '');
				}
			}

			var styleTags = parsed.root.querySelectorAll('style');
			for (var j = 0; j < styleTags.length; j++) {
				var cssText = styleTags[j].textContent || styleTags[j].innerHTML || '';
				var sanitizedCss = sanitizeCssExternalUrls(cssText);
				if (Ext.isDefined(styleTags[j].textContent)) {
					styleTags[j].textContent = sanitizedCss;
				} else {
					styleTags[j].innerHTML = sanitizedCss;
				}
			}

			return parsed.toHtml();
		},

		/**
		* Helper function which is used to figure out content has external image or not and performa
		* action based on action handler.
		*
		* @param {String} data data that should be checked for external content.
		* @param {Function} actionHandler actionHandler is callback function to perform an action.
		* @returns {Mixed} actionHandler return data.
		*/
		handleExternalImage: function (data, actionHandler) {
			var imgSrcs = [];
			// @TODO more work needs for these regular expressions or else a dom based html parser
			// check tags whose attributes are src or background

			if (data.search(/(src|background|srcset)\s*=\s*([\'\"])*?\s*(https*:[^ \'\"]*)([\'\"])*/igm) !== -1) {
				imgSrcs = data.match(/(src|background|srcset)\s*=\s*([\'\"])*?\s*(https*:[^ \'\"]*)([\'\"])*/igm);
			}
			return actionHandler(imgSrcs);
		},

		/**
		 * Function will return translation table for HTML entities which can be used to
		 * encode/decode HTML entities.
		 * @param {String} tableType type of table to get, options are HTML_SPECIALCHARS or HTML_ENTITIES.
		 * @param {String} quoteStyle options are ENT_COMPAT, ENT_NOQUOTES or ENT_QUOTES.
		 * @return {Object} table for translation of HTML entities
		 */
		getHTMLTranslationTable: function(tableType, quoteStyle)
		{
			if(!tableType) {
				tableType = 'HTML_SPECIALCHARS';
			}

			if(!quoteStyle) {
				quoteStyle = 'ENT_COMPAT';
			}

			var entities = {};
			entities['38'] = '&amp;';

			if (tableType === 'HTML_ENTITIES') {
				entities['94'] = '&circ;';
				entities['126'] = '&tilde;';

				entities['130'] = '&sbquo;';	// Single Low-9 Quotation Mark
				entities['131'] = '&fnof;';		// Latin Small Letter F With Hook
				entities['132'] = '&bdquo;';	// Double Low-9 Quotation Mark
				entities['133'] = '&hellip;';	// Horizontal Ellipsis
				entities['134'] = '&dagger;';	// Dagger
				entities['135'] = '&Dagger;';	// Double Dagger
				entities['136'] = '&circ;';		// Modifier Letter Circumflex Accent
				entities['137'] = '&permil;';	// Per Mille Sign
				entities['138'] = '&Scaron;';	// Latin Capital Letter S With Caron
				entities['139'] = '&lsaquo;';	// Single Left-Pointing Angle Quotation Mark
				entities['140'] = '&OElig;';	// Latin Capital Ligature OE
				entities['145'] = '&lsquo;';	// Left Single Quotation Mark
				entities['146'] = '&rsquo;';	// Right Single Quotation Mark
				entities['147'] = '&ldquo;';	// Left Double Quotation Mark
				entities['148'] = '&rdquo;';	// Right Double Quotation Mark
				entities['149'] = '&bull;';		// Bullet
				entities['150'] = '&ndash;';	// En Dash
				entities['151'] = '&mdash;';	// Em Dash
				entities['152'] = '&tilde;';	// Small Tilde
				entities['153'] = '&trade;';	// Trade Mark Sign
				entities['154'] = '&scaron;';	// Latin Small Letter S With Caron
				entities['155'] = '&rsaquo;';	// Single Right-Pointing Angle Quotation Mark
				entities['156'] = '&oelig;';	// Latin Small Ligature OE
				entities['159'] = '&Yuml;';		// Latin Capital Letter Y With Diaeresis

				entities['160'] = '&nbsp;';		// Non-breaking space
				entities['161'] = '&iexcl;';	// Inverted exclamation mark
				entities['162'] = '&cent;';		// Cent sign
				entities['163'] = '&pound;';	// Pound sign
				entities['164'] = '&curren;';	// Currency sign
				entities['165'] = '&yen;';		// Yen sign
				entities['166'] = '&brvbar;';	// Broken vertical bar
				entities['167'] = '&sect;';		// Section sign
				entities['168'] = '&uml;';		// Diaeresis
				entities['169'] = '&copy;';		// Copyright sign
				entities['170'] = '&ordf;';		// Feminine ordinal indicator
				entities['171'] = '&laquo;';	// Left-pointing double angle quotation mark
				entities['172'] = '&not;';		// Not sign
				entities['173'] = '&shy;';		// Soft hyphen
				entities['174'] = '&reg;';		// Registered sign
				entities['175'] = '&macr;';		// Macron
				entities['176'] = '&deg;';		// Degree sign
				entities['177'] = '&plusmn;';	// Plus-minus sign
				entities['178'] = '&sup2;';		// Superscript two
				entities['179'] = '&sup3;';		// Superscript three
				entities['180'] = '&acute;';	// Acute accent
				entities['181'] = '&micro;';	// Micro sign
				entities['182'] = '&para;';		// Pilcrow sign
				entities['183'] = '&middot;';	// Middle dot
				entities['184'] = '&cedil;';	// Cedilla
				entities['185'] = '&sup1;';		// Superscript one
				entities['186'] = '&ordm;';		// Masculine ordinal indicator
				entities['187'] = '&raquo;';	// Right-pointing double angle quotation mark
				entities['188'] = '&frac14;';	// Vulgar fraction one-quarter
				entities['189'] = '&frac12;';	// Vulgar fraction one-half
				entities['190'] = '&frac34;';	// Vulgar fraction three-quarters
				entities['191'] = '&iquest;';	// Inverted question mark
				entities['192'] = '&Agrave;';	// A with grave
				entities['193'] = '&Aacute;';	// A with acute
				entities['194'] = '&Acirc;';	// A with circumflex
				entities['195'] = '&Atilde;';	// A with tilde
				entities['196'] = '&Auml;';		// A with diaeresis
				entities['197'] = '&Aring;';	// A with ring above
				entities['198'] = '&AElig;';	// AE
				entities['199'] = '&Ccedil;';	// C with cedilla
				entities['200'] = '&Egrave;';	// E with grave
				entities['201'] = '&Eacute;';	// E with acute
				entities['202'] = '&Ecirc;';	// E with circumflex
				entities['203'] = '&Euml;';		// E with diaeresis
				entities['204'] = '&Igrave;';	// I with grave
				entities['205'] = '&Iacute;';	// I with acute
				entities['206'] = '&Icirc;';	// I with circumflex
				entities['207'] = '&Iuml;';		// I with diaeresis
				entities['208'] = '&ETH;';		// Eth
				entities['209'] = '&Ntilde;';	// N with tilde
				entities['210'] = '&Ograve;';	// O with grave
				entities['211'] = '&Oacute;';	// O with acute
				entities['212'] = '&Ocirc;';	// O with circumflex
				entities['213'] = '&Otilde;';	// O with tilde
				entities['214'] = '&Ouml;';		// O with diaeresis
				entities['215'] = '&times;';	// Multiplication sign
				entities['216'] = '&Oslash;';	// O with stroke
				entities['217'] = '&Ugrave;';	// U with grave
				entities['218'] = '&Uacute;';	// U with acute
				entities['219'] = '&Ucirc;';	// U with circumflex
				entities['220'] = '&Uuml;';		// U with diaeresis
				entities['221'] = '&Yacute;';	// Y with acute
				entities['222'] = '&THORN;';	// Thorn
				entities['223'] = '&szlig;';	// Sharp s. Also known as ess-zed
				entities['224'] = '&agrave;';	// a with grave
				entities['225'] = '&aacute;';	// a with acute
				entities['226'] = '&acirc;';	// a with circumflex
				entities['227'] = '&atilde;';	// a with tilde
				entities['228'] = '&auml;';		// a with diaeresis
				entities['229'] = '&aring;';	// a with ring above
				entities['230'] = '&aelig;';	// ae. Also known as ligature ae
				entities['231'] = '&ccedil;';	// c with cedilla
				entities['232'] = '&egrave;';	// e with grave
				entities['233'] = '&eacute;';	// e with acute
				entities['234'] = '&ecirc;';	// e with circumflex
				entities['235'] = '&euml;';		// e with diaeresis
				entities['236'] = '&igrave;';	// i with grave
				entities['237'] = '&iacute;';	// i with acute
				entities['238'] = '&icirc;';	// i with circumflex
				entities['239'] = '&iuml;';		// i with diaeresis
				entities['240'] = '&eth;';		// eth
				entities['241'] = '&ntilde;';	// n with tilde
				entities['242'] = '&ograve;';	// o with grave
				entities['243'] = '&oacute;';	// o with acute
				entities['244'] = '&ocirc;';	// o with circumflex
				entities['245'] = '&otilde;';	// o with tilde
				entities['246'] = '&ouml;';		// o with diaeresis
				entities['247'] = '&divide;';	// Division sign
				entities['248'] = '&oslash;';	// o with stroke. Also known as o with slash
				entities['249'] = '&ugrave;';	// u with grave
				entities['250'] = '&uacute;';	// u with acute
				entities['251'] = '&ucirc;';	// u with circumflex
				entities['252'] = '&uuml;';		// u with diaeresis
				entities['253'] = '&yacute;';	// y with acute
				entities['254'] = '&thorn;';	// thorn
				entities['255'] = '&yuml;';		// y with diaeresis
				entities['264'] = '&#264;';		// Latin capital letter C with circumflex
				entities['265'] = '&#265;';		// Latin small letter c with circumflex
				entities['338'] = '&OElig;';	// Latin capital ligature OE
				entities['339'] = '&oelig;';	// Latin small ligature oe
				entities['352'] = '&Scaron;';	// Latin capital letter S with caron
				entities['353'] = '&scaron;';	// Latin small letter s with caron
				entities['372'] = '&#372;';		// Latin capital letter W with circumflex
				entities['373'] = '&#373;';		// Latin small letter w with circumflex
				entities['374'] = '&#374;';		// Latin capital letter Y with circumflex
				entities['375'] = '&#375;';		// Latin small letter y with circumflex
				entities['376'] = '&Yuml;';		// Latin capital letter Y with diaeresis
				entities['402'] = '&fnof;';		// Latin small f with hook, function, florin
				entities['710'] = '&circ;';		// Modifier letter circumflex accent
				entities['732'] = '&tilde;';	// Small tilde
				entities['913'] = '&Alpha;';	// Alpha
				entities['914'] = '&Beta;';		// Beta
				entities['915'] = '&Gamma;';	// Gamma
				entities['916'] = '&Delta;';	// Delta
				entities['917'] = '&Epsilon;';	// Epsilon
				entities['918'] = '&Zeta;';		// Zeta
				entities['919'] = '&Eta;';		// Eta
				entities['920'] = '&Theta;';	// Theta
				entities['921'] = '&Iota;';		// Iota
				entities['922'] = '&Kappa;';	// Kappa
				entities['923'] = '&Lambda;';	// Lambda
				entities['924'] = '&Mu;';		// Mu
				entities['925'] = '&Nu;';		// Nu
				entities['926'] = '&Xi;';		// Xi
				entities['927'] = '&Omicron;';	// Omicron
				entities['928'] = '&Pi;';		// Pi
				entities['929'] = '&Rho;';		// Rho
				entities['931'] = '&Sigma;';	// Sigma
				entities['932'] = '&Tau;';		// Tau
				entities['933'] = '&Upsilon;';	// Upsilon
				entities['934'] = '&Phi;';		// Phi
				entities['935'] = '&Chi;';		// Chi
				entities['936'] = '&Psi;';		// Psi
				entities['937'] = '&Omega;';	// Omega
				entities['945'] = '&alpha;';	// alpha
				entities['946'] = '&beta;';		// beta
				entities['947'] = '&gamma;';	// gamma
				entities['948'] = '&delta;';	// delta
				entities['949'] = '&epsilon;';	// epsilon
				entities['950'] = '&zeta;';		// zeta
				entities['951'] = '&eta;';		// eta
				entities['952'] = '&theta;';	// theta
				entities['953'] = '&iota;';		// iota
				entities['954'] = '&kappa;';	// kappa
				entities['955'] = '&lambda;';	// lambda
				entities['956'] = '&mu;';		// mu
				entities['957'] = '&nu;';		// nu
				entities['958'] = '&xi;';		// xi
				entities['959'] = '&omicron;';	// omicron
				entities['960'] = '&pi;';		// pi
				entities['961'] = '&rho;';		// rho
				entities['962'] = '&sigmaf;';	// sigmaf
				entities['963'] = '&sigma;';	// sigma
				entities['964'] = '&tau;';		// tau
				entities['965'] = '&upsilon;';	// upsilon
				entities['966'] = '&phi;';		// phi
				entities['967'] = '&chi;';		// chi
				entities['968'] = '&psi;';		// psi
				entities['969'] = '&omega;';	// omega
				entities['977'] = '&thetasym;';	// Theta symbol
				entities['978'] = '&upsih;';	// Greek upsilon with hook symbol
				entities['982'] = '&piv;';		// Pi symbol
				entities['8194'] = '&ensp;';	// En space
				entities['8195'] = '&emsp;';	// Em space
				entities['8201'] = '&thinsp;';	// Thin space
				entities['8204'] = '&zwnj;';	// Zero width non-joiner
				entities['8205'] = '&zwj;';		// Zero width joiner
				entities['8206'] = '&lrm;';		// Left-to-right mark
				entities['8207'] = '&rlm;';		// Right-to-left mark
				entities['8211'] = '&ndash;';	// En dash
				entities['8212'] = '&mdash;';	// Em dash
				entities['8216'] = '&lsquo;';	// Left single quotation mark
				entities['8217'] = '&rsquo;';	// Right single quotation mark
				entities['8218'] = '&sbquo;';	// Single low-9 quotation mark
				entities['8220'] = '&ldquo;';	// Left double quotation mark
				entities['8221'] = '&rdquo;';	// Right double quotation mark
				entities['8222'] = '&bdquo;';	// Double low-9 quotation mark
				entities['8224'] = '&dagger;';	// Dagger
				entities['8225'] = '&Dagger;';	// Double dagger
				entities['8226'] = '&bull;';	// Bullet
				entities['8230'] = '&hellip;';	// Horizontal ellipsis
				entities['8240'] = '&permil;';	// Per mille sign
				entities['8242'] = '&prime;';	// Prime
				entities['8243'] = '&Prime;';	// Double Prime
				entities['8249'] = '&lsaquo;';	// Single left-pointing angle quotation
				entities['8250'] = '&rsaquo;';	// Single right-pointing angle quotation
				entities['8254'] = '&oline;';	// Overline
				entities['8260'] = '&frasl;';	// Fraction Slash
				entities['8364'] = '&euro;';	// Euro sign
				entities['8472'] = '&weierp;';	// Script capital
				entities['8465'] = '&image;';	// Blackletter capital I
				entities['8476'] = '&real;';	// Blackletter capital R
				entities['8482'] = '&trade;';	// Trade mark sign
				entities['8501'] = '&alefsym;';	// Alef symbol
				entities['8592'] = '&larr;';	// Leftward arrow
				entities['8593'] = '&uarr;';	// Upward arrow
				entities['8594'] = '&rarr;';	// Rightward arrow
				entities['8595'] = '&darr;';	// Downward arrow
				entities['8596'] = '&harr;';	// Left right arrow
				entities['8629'] = '&crarr;';	// Downward arrow with corner leftward. Also known as carriage return
				entities['8656'] = '&lArr;';	// Leftward double arrow. ISO 10646 does not say that lArr is the same as the 'is implied by' arrow but also does not have any other character for that function. So ? lArr can be used for 'is implied by' as ISOtech suggests
				entities['8657'] = '&uArr;';	// Upward double arrow
				entities['8658'] = '&rArr;';	// Rightward double arrow. ISO 10646 does not say this is the 'implies' character but does not have another character with this function so ? rArr can be used for 'implies' as ISOtech suggests
				entities['8659'] = '&dArr;';	// Downward double arrow
				entities['8660'] = '&hArr;';	// Left-right double arrow

				// Mathematical Operators
				entities['8704'] = '&forall;';	// For all
				entities['8706'] = '&part;';	// Partial differential
				entities['8707'] = '&exist;';	// There exists
				entities['8709'] = '&empty;';	// Empty set. Also known as null set and diameter
				entities['8711'] = '&nabla;';	// Nabla. Also known as backward difference
				entities['8712'] = '&isin;';	// Element of
				entities['8713'] = '&notin;';	// Not an element of
				entities['8715'] = '&ni;';		// Contains as member
				entities['8719'] = '&prod;';	// N-ary product. Also known as product sign. Prod is not the same character as U+03A0 'greek capital letter pi' though the same glyph might be used for both
				entities['8721'] = '&sum;';		// N-ary summation. Sum is not the same character as U+03A3 'greek capital letter sigma' though the same glyph might be used for both
				entities['8722'] = '&minus;';	// Minus sign
				entities['8727'] = '&lowast;';	// Asterisk operator
				entities['8729'] = '&#8729;';	// Bullet operator
				entities['8730'] = '&radic;';	// Square root. Also known as radical sign
				entities['8733'] = '&prop;';	// Proportional to
				entities['8734'] = '&infin;';	// Infinity
				entities['8736'] = '&ang;';		// Angle
				entities['8743'] = '&and;';		// Logical and. Also known as wedge
				entities['8744'] = '&or;';		// Logical or. Also known as vee
				entities['8745'] = '&cap;';		// Intersection. Also known as cap
				entities['8746'] = '&cup;';		// Union. Also known as cup
				entities['8747'] = '&int;';		// Integral
				entities['8756'] = '&there4;';	// Therefore
				entities['8764'] = '&sim;';		// tilde operator. Also known as varies with and similar to. The tilde operator is not the same character as the tilde, U+007E, although the same glyph might be used to represent both
				entities['8773'] = '&cong;';	// Approximately equal to
				entities['8776'] = '&asymp;';	// Almost equal to. Also known as asymptotic to
				entities['8800'] = '&ne;';		// Not equal to
				entities['8801'] = '&equiv;';	// Identical to
				entities['8804'] = '&le;';		// Less-than or equal to
				entities['8805'] = '&ge;';		// Greater-than or equal to
				entities['8834'] = '&sub;';		// Subset of
				entities['8835'] = '&sup;';		// Superset of. Note that nsup, 'not a superset of, U+2283' is not covered by the Symbol font encoding and is not included.
				entities['8836'] = '&nsub;';	// Not a subset of
				entities['8838'] = '&sube;';	// Subset of or equal to
				entities['8839'] = '&supe;';	// Superset of or equal to
				entities['8853'] = '&oplus;';	// Circled plus. Also known as direct sum
				entities['8855'] = '&otimes;';	// Circled times. Also known as vector product
				entities['8869'] = '&perp;';	// Up tack. Also known as orthogonal to and perpendicular
				entities['8901'] = '&sdot;';	// Dot operator. The dot operator is not the same character as U+00B7 middle dot

				// Miscellaneous Technical
				entities['8968'] = '&lceil;';	// Left ceiling. Also known as an APL upstile
				entities['8969'] = '&rceil;';	// Right ceiling
				entities['8970'] = '&lfloor;';	// left floor. Also known as APL downstile
				entities['8971'] = '&rfloor;';	// Right floor
				entities['9001'] = '&lang;';	// Left-pointing angle bracket. Also known as bra. Lang is not the same character as U+003C 'less than'or U+2039 'single left-pointing angle quotation mark'
				entities['9002'] = '&rang;';	// Right-pointing angle bracket. Also known as ket. Rang is not the same character as U+003E 'greater than' or U+203A 'single right-pointing angle quotation mark'

				// Geometric Shapes
				entities['9642'] = '&#9642;';	// Black small square
				entities['9643'] = '&#9643;';	// White small square
				entities['9674'] = '&loz;';		// Lozenge

				// Miscellaneous Symbols
				entities['9702'] = '&#9702;';	// White bullet
				entities['9824'] = '&spades;';	// Black (filled) spade suit
				entities['9827'] = '&clubs;';	// Black (filled) club suit. Also known as shamrock
				entities['9829'] = '&hearts;';	// Black (filled) heart suit. Also known as shamrock
				entities['9830'] = '&diams;';  // Black (filled) diamond suit
			}

			if (quoteStyle !== 'ENT_NOQUOTES') {
				entities['34'] = '&quot;';
			}

			if (quoteStyle === 'ENT_QUOTES') {
				entities['39'] = '&#39;';
			}

			entities['60'] = '&lt;';
			entities['62'] = '&gt;';

			// ascii decimals to real symbols
			var decimal;
			var hashMap = {};
			for (decimal in entities) {
				hashMap[String.fromCharCode(decimal)] = entities[decimal];
			}

			return hashMap;
		},

		/**
		 * Function will decode HTML entities in the string.
		 * @param {String} content string that should be decoded.
		 * @param {String} quoteStyle options are ENT_COMPAT, ENT_NOQUOTES or ENT_QUOTES.
		 * @return {String} decoded string.
		 */
		entityDecode: function(content, quoteStyle)
		{
			var hashMap = Zarafa.core.HTMLParser.getHTMLTranslationTable('HTML_ENTITIES', quoteStyle);
			var symbol = '';
			var entity = '';

			for (symbol in hashMap) {
				entity = hashMap[symbol];
				content = content.split(entity).join(symbol);
			}

			// Convert HTML entities like &#224;
			content = content.replace(/&#(\d+);/g, function(match, dec) {
				return String.fromCharCode(dec);
			});

			return content;
		},

		/**
		 * Function will encode HTML entities in the string.
		 * @param {String} content string that should be encoded.
		 * @param {String} quoteStyle options are ENT_COMPAT, ENT_NOQUOTES or ENT_QUOTES.
		 * @return {String} decoded string.
		 */
		entityEncode: function(content, quoteStyle)
		{
			var hashMap = Zarafa.core.HTMLParser.getHTMLTranslationTable('HTML_ENTITIES', quoteStyle);
			var symbol = '';
			var entity = '';

			for (symbol in hashMap) {
				entity = hashMap[symbol];
				content = content.split(symbol).join(entity);
			}
			content = content.split('\'').join('&#039;');

			return content;
		},

		/**
		 * Function to convert from Outlook inline attachment format (src="cid:...") to img tag
		 * that will display the image (by querying download_attachment.php)
		 * Builds up the query string for asking the image, using store and record entryid, and content id
		 * @param {String} body The message body to be modified
		 * @param {String} storeEntryId The store entryid
		 * @param {String} entryId The message entryid
		 * @param {Array} attachNum Attachment number of the attachment that should be downloaded.
		 * When accessing embedded messages this array can contain multiple elements indicating
		 * attachment numbers at each level, So value [0, 1] will indicate we want to download
		 * second attachment of first embedded message.
		 * @return {String} the modified HTML body
		 */
		inlineImgOutlookToZarafa: function(body, storeEntryId, entryId, attachNum)
		{
			var cidToUrl = function(match, srcStart, imgCid, srcEnd, offset, str) {
				if(imgCid) {
					var url = container.getBaseURL();
					url = Ext.urlAppend(url, 'load=download_attachment');
					url = Ext.urlAppend(url, 'attachCid=' + encodeURIComponent(imgCid));
					url = Ext.urlAppend(url, 'store=' + encodeURIComponent(storeEntryId));
					url = Ext.urlAppend(url, 'entryid=' + encodeURIComponent(entryId));
					url = Ext.urlAppend(url, 'contentDispositionType=inline');
					if(!Ext.isEmpty(attachNum)) {
						for (var i = 0; i< attachNum.length; i++)
						{
							url = Ext.urlAppend(url, 'attachNum[]=' + encodeURIComponent(attachNum[i]));
						}
					}

					// return our own url for getting inline attachments
					return srcStart + url + srcEnd;
				}

				// return match as it is but in a real world this is not going to happen
				return match;
			};

			// replace cid: with our own url to get inline attachment
			return body.replace(cidToUrlRe, cidToUrl);
		},

		/**
		 * Function to convert from Zarafa inline img format to Outlook format (src="cid:...")
		 * Grabs the cid from the img tag (src="...attachCid=...")
		 * @param {String} body the message body to be modified
		 * @return {String} the modified html body
		 */
		inlineImgZarafaToOutlook: function(body)
		{
			if(!Ext.isDefined(body)){
				return;
			}

			var urlToCid = function(match, srcStart, imgCid, srcEnd, offset, str) {
				if(imgCid) {
					// return img src with just cid: tag
					return srcStart + 'cid:' + imgCid + srcEnd;
				}

				// return match as it is but in a real world this is not going to happen
				return match;
			};

			// replace our own url with cid:
			return body.replace(urlToCidRe, urlToCid);
		}
	};
})();
Ext.namespace('Zarafa.core');

/**
 * @class Zarafa.core.KeyMap
 * @extends Ext.KeyMap
 *
 * This class extends {@link Ext.KeyMap KeyMap} to assign component with key events
 * so we can pass component as a parameter while calling callback function, to make
 * key event handling easier.
 */
Zarafa.core.KeyMap = Ext.extend(Ext.KeyMap, {
	/**
	 * @cfg {Ext.Component} component The component on which key event is registered.
	 */
	component: undefined,

	/**
	 * @constructor
	 * @param {Ext.Component} component The component which should be used to pass in handlers and additionally
	 * if element is not passed then get it from component.
	 * @param {Object} config The configuration object (see {@link #addBinding})
	 * @param {Ext.Element} element {optional} The element on which this keymap will be bound.
	 * @param {String} eventName (optional) The event to bind to (defaults to "keydown")
	 */
	constructor: function(component, config, element, eventName)
	{
		if(component) {
			// store reference of component in keymap that will be passed with call to handlers
			this.component = component;
		}

		// if no element is passed then get it from component
		if(!element) {
			element = component.getEl();
		}

		// if element is passed as dom node then find its corresponding Ext.Element object
		element = Ext.get(element);

		Zarafa.core.KeyMap.superclass.constructor.call(this, element, config, eventName);
	},

	/**
	 * Function overrides {@link Ext.KeyMap#addBinding addBinding} to <br>
	 * 1) add component in callback function parameter for which event is fired. <br>
	 * 2) to solve the problem of {@link Ext.KeyMap} that, When registering two different key bindings to
	 * same {@link Ext.KeyMap} object, we can't set 'stopEvent: false' in any of the bindings <br>
	 * for more info - http://www.sencha.com/forum/showthread.php?265199-Ext.KeyMap-problems-with-stopEvent-flag <br>
	 * 3) additionally this can accept some more config options which is used in {@link Zarafa.core.KeyMapMgr KeyMapMgr},
	 * <pre>
Property      Type    Description
----------     ---------  ----------------------------------------------------------------------
enableGlobally   Boolean   A flag to indicate the key binding should also be registered with the body of webapp so that key combination can globally be disabled
settingsCfg    Object   Object containing two keys:
                  1) description - description of the key combination to show in keyboard settings widget
                  2) category - name of the category in which this key combination will be added
</pre>
	 */
	addBinding: function(config)
	{
		if(Array.isArray(config)){
			Ext.each(config, function(c){
				this.addBinding(c);
			}, this);
			return;
		}
		var keyCode = config.key,
			fn = config.fn || config.handler,
			scope = config.scope,
			stopEvent = config.stopEvent;

		if(!Ext.isDefined(stopEvent)) {
			stopEvent = this.stopEvent;
		}

		if(typeof keyCode == "string"){
			var ks = [];
			var keyString = keyCode.toUpperCase();
			for(var j = 0, len = keyString.length; j < len; j++){
				ks.push(keyString.charCodeAt(j));
			}
			keyCode = ks;
		}
		var keyArray = Array.isArray(keyCode);

		var handler = function(e){
			if(this.checkModifiers(config, e)){
				var k = e.getKey();

				if(keyArray){
					for(var i = 0, len = keyCode.length; i < len; i++){
						if(keyCode[i] == k){
							if(stopEvent){
								e.stopEvent();
							}
							fn.call(scope || window, k, e, this.component);
							return;
						}
					}
				} else {
					if(k == keyCode){
						if(stopEvent){
							e.stopEvent();
						}
						fn.call(scope || window, k, e, this.component);
					}
				}
			}
		};
		this.bindings.push(handler);
	}
});Ext.namespace('Zarafa.core');

/**
 * @class Zarafa.core.KeyMapMgr
 * @extends Object
 *
 * The {@link Zarafa.core.KeyMap KeyMap} manager.
 * @singleton
 */
Zarafa.core.KeyMapMgr = Ext.extend(Object, {
	/**
	 * The list of registered {@link Zarafa.core.KeyMap keymaps}. It contains the list with the element the
	 * keymap is bound to as key.
	 * @property
	 * @type Object
	 * @private
	 */
	keyMaps: undefined,

	/**
	 * The list of key configurations (see {@link Zarafa.core.KeyMap#addBinding Zarafa.core.KeyMap.addBinding})
	 * registeredlisted to a mapId. It contains the list with the mapId as the key.
	 * @property
	 * @type Object
	 * @private
	 */
	keys: undefined,

	/**
	 * @constructor
	 */
	constructor: function()
	{
		this.keyMaps = new Ext.util.MixedCollection();
		this.keys = {};
	},

	/**
	 * Registers the key binding configuration to the specified mapId.
	 * @param {String} mapId The ID of the map keys are registered to.
	 * @param {Object|Array} keyConfig The config (see {@link Zarafa.core.KeyMap#addBinding addBinding}).
	 */
	register: function(mapId, keyConfig)
	{
		if(!this.keys[mapId]){
			this.keys[mapId] = [];
		}

		if(!Array.isArray(keyConfig)) {
			keyConfig = [keyConfig];
		}

		this.keys[mapId] = this.keys[mapId].concat(keyConfig);

		// register same bindings for global webapp as well
		this.preventBrowserDefaults(mapId, keyConfig);
	},

	/**
	 * Function will register all key bindings that are registered in {@link Zarafa.core.KeyMapMgr this}
	 * class to the 'globaldisable' mapid and assign stop event and empty handler so that we can disable
	 * default behaviour of browser when no component is focused and user tries to perform some
	 * key-combinations which isn't available globally.
	 * @param {String} mapId The ID of the map keys are registered to.
	 * @param {Object|Array} keyConfig The config (see {@link Zarafa.core.KeyMap#addBinding addBinding}).
	 */
	preventBrowserDefaults: function(mapId, keyConfig)
	{
		// Check that mapID isn't 'global' as they are already registered globally.
		// Check that mapID isn't 'globaldisable' to prevent recursive call.
		if(mapId !== 'global' && mapId !== 'globaldisable') {
			for(var i = 0, len = keyConfig.length; i < len; i++) {
				var config = keyConfig[i];

				if(config.enableGlobally !== true) {
					var disableKeyConfig = {
						handler: Ext.emptyFn,
						stopEvent: true
					};

					Ext.applyIf(disableKeyConfig, config);

					// Add all events in 'globaldisable' keymap.
					this.register('globaldisable', disableKeyConfig);
				} else if (config.ctrl === true) {
					var key = config.key;
					if(Ext.isString(key)) {
						key = key.toUpperCase().charCodeAt(0);
					}

					if (key === Ext.EventObject.A) {
						// special case for ctrl + a, as we want to allow action in text fields but not on body of webapp
						var disableKeyConfig = {
							handler: this.disableTextSelection,
							// don't blindly prevent default action instead leave that handling for the handler
							stopEvent: false
						};

						Ext.applyIf(disableKeyConfig, config);

						// Add event in 'globaldisable' keymap.
						this.register('globaldisable', disableKeyConfig);
					}
				}
			}
		}
	},

	/**
	 * Event handler for the keydown event of the {@link Ext.KeyMap KeyMap}
	 * when the user presses ctrl + a on a field which doesn't have text selection. So we will prevent
	 * default action of browser to select all text in the body.
	 * @param {Number} key Key code
	 * @param {Ext.EventObject} event The event
	 * @param {Ext.Component} component The component on which key event is fired.
	 */
	disableTextSelection: function(key, event, component)
	{
		var target = event.getTarget().nodeName.toLowerCase();

		if(target !== 'textarea' && target !== 'input') {
			// we don't want to select everything in the body
			event.stopEvent();
		}
	},

	/**
	 * Adds the keymap to the specified Element. It will take the keys registered in {@link #keys keys}
	 * under the specified mapId. If a keymap has already been registered under this element it will add new keys
	 * to the same {@link Zarafa.core.KeyMap KeyMap}.
	 *
	 * If basic shortcuts are enabled we filter the bindings obtained from the mapId to only
	 * enable the bindings which contain the basic key.
	 *
	 * @param {Ext.Component} component The component to which keymap should be bound and
	 * will listen keypress events on {@link Ext.Component#el}.
	 * @param {String} mapId The ID of the map keys are registered to.
	 * @param {Ext.Element} element {optional} if component is not present and we need to register key events
	 * on any element then we can pass it here (eg Ext.getBody()). Additionally if we want to register events on
	 * different element then {@link Ext.Component#el} then we should pass the element on which keymap
	 * will be registered (eg Zarafa.common.ui.HTMLEditor).
	 */
	activate: function(component, mapId, element)
	{
		// if element is not passed then get it from component
		if(!element) {
			element = component.getEl();
		}

		// if element is passed as dom node then find its corresponding Ext.Element object
		element = Ext.get(element);

		var elementId = element.id;
		if(Ext.isEmpty(elementId)) {
			// without element id its not possible to activate keymap
			return;
		}

		var bindings = this.getKeyBindings(mapId);
		var setting = container.getSettingsModel().get('zarafa/v1/main/keycontrols');

		// Filter basic shortcuts
		if (setting === Zarafa.settings.data.KeyboardSettings.BASIC_KEYBOARD_SHORTCUTS) {
			bindings = bindings.filter(function(binding) {
				return Ext.isDefined(binding.basic);
			});
		} else if (setting === Zarafa.settings.data.KeyboardSettings.NO_KEYBOARD_SHORTCUTS) {
			bindings = [];
		}

		if(Ext.isEmpty(bindings)) {
			// if no bindings are found then ignore
			return;
		}

		// check if we already have a registered keymap on this component
		// if we have then add binding with that keymap.
		var keymap = this.getActive(element);
		if(keymap) {
			keymap.addBinding(bindings);
		} else {
			// register event that will remove keymap from KeyMapMgr if component is destroyed
			if(component instanceof Ext.Component) {
				component.on('beforedestroy', this.onComponentDestroy, this);
			}

			// create a new keymap and register it on component
			this.keyMaps.add(elementId, new Zarafa.core.KeyMap(component, bindings, element));
		}
	},

	/**
	 * Function is used to get key bindings registered for a particular map id.
	 * It will recursively get bindings for every string separated using dot and then will
	 * combine and return the bindings.
	 * @param {String} mapId map id for which we need to get bindings
	 * @return {Array} array of key bindings that be added in keymap
	 */
	getKeyBindings: function(mapId)
	{
		// Get the bindings registered for the mapId. Also see if any other bindings have been
		// registered on mapIds that are hierarchically-speaking its parent. So
		// "contentpanel.record.message" will also get keys for "contentpanel.record" and
		// "contentpanel".
		var bindings = [];

		while(!Ext.isEmpty(mapId)) {
			// get binding for particular map id
			if(this.keys[mapId]) {
				bindings = bindings.concat(this.keys[mapId]);
			}

			// change map id to point to its parent
			mapId = mapId.substr(0, mapId.lastIndexOf('.'));
		}

		return bindings;
	},

	/**
	 * Handler function that will be called when any component registered with {@link Zarafa.core.KeyMapMgr KeyMapMgr}
	 * is going to be destroyed, so we can safely remove keymappings registered with that component.
	 * @param {Ext.Component} component component that is going to be destroyed
	 */
	onComponentDestroy: function(component)
 	{
		this.deactivate(component.getEl());
	},

	/**
	 * Disables the {@link Zarafa.core.KeyMap keymap} and removes it from list of {@link Zarafa.core.KeyMapMgr#keyMaps}.
	 * {@link Zarafa.core.KeyMap#disable} removes event listener which is used to check and fire handler events for bindings
	 * but it doesn't remove bindings registered with the {@link Zarafa.core.KeyMap keymap} that will be done only when
	 * element is destroyed from dom.
	 * @param {Ext.Element} element The element on which keymap is bound.
	 */
	deactivate: function(element)
	{
		element = Ext.get(element);
		var keymap = this.keyMaps.get(element.id);

		if(keymap) {
			keymap.disable();
			this.keyMaps.remove(keymap);
		}
	},

	/**
	 * Function returns {@link Zarafa.core.KeyMap keymap} bound to component or element.
	 * @param {Ext.Element} element The element which should be used to check for bound keymaps.
	 * @return {Zarafa.core.KeyMap} keymap object registered on component/element
	 */
	getActive: function(element)
	{
		// if element is passed as dom node then find its corresponding Ext.Element object
		element = Ext.get(element);

		return this.keyMaps.get(element.id);
	},

	/**
	 * Will enable the {@link Zarafa.core.KeyMap keymap} that is registered on the specified element.
	 * @param {Ext.Element} element The element on which keymap is bound.
	 */
	enableKeyMap: function(element)
	{
		// if element is passed as dom node then find its corresponding Ext.Element object
		element = Ext.get(element);

		var keymap = this.keyMaps.get(element.id);

		if(keymap) {
			if(this.isGloballyEnabled()) {
				// We need to check whether we are not in a state where the keymaps have been disabled globally.
				keymap.enable();
			} else {
				// Only set the originallyEnabled flag on the keymap when the keymaps have been disabled globally in the KeyMapMgr.
				keymap.originallyEnabled = true;
			}
		}
	},

	/**
	 * Will disable the {@link Zarafa.core.KeyMap keymap} that is registered on the specified element.
	 * @param {Ext.Element} element The element on which keymap is bound.
	 */
	disableKeyMap: function(element)
	{
		// if element is passed as dom node then find its corresponding Ext.Element object
		element = Ext.get(element);

		var keymap = this.keyMaps.get(element.id);

		if(keymap) {
			keymap.disable();

			// Only set the originallyEnabled flag on the keymap when the keymaps have been disabled globally in the KeyMapMgr.
			if(!this.isGloballyEnabled()) {
				keymap.originallyEnabled = false;
			}
		}
	},

	/**
	 * Can be used to enable the keymaps globally and works in conjucture with {@link #disable} that
	 * is able to disable all the keymaps globally.
	 * Will enable the all the registered keymaps that have an originallyEnabled flag set to true.
	 * After that each keymap, including the ones that have the originallyEnabled flag set to false,
	 * will have that flag cleared from the keymap object.
	 */
	enableAllKeymaps: function()
	{
		this.keyMaps.each(function(keymap) {
			if(keymap.originallyEnabled === true) {
				keymap.enable();
			}

			// Unset the originallyEnabled flag on the keymap object
			delete keymap.originallyEnabled;
		}, this);
	},

	/**
	 * Can be used to disable the keymaps globally and works in conjucture with {@link #enable} that
	 * is able to enable all the keymaps globally.
	 * Will enable the all the registered keymaps that have an originallyEnabled flag set to true.
	 * After that each keymap, including the ones that have the originallyEnabled flag set to false,
	 * will have that flag cleared from the keymap object.
	 */
	disableAllKeymaps: function()
	{
		this.keyMaps.each(function(keymap) {
			keymap.originallyEnabled = keymap.isEnabled();
			keymap.disable();
		}, this);
	},

	/**
	 * Checks if the {@link Zarafa.core.KeyMap keymap} is enabled on the specified element.
	 * @param {Ext.Element} element The element on which keymap is bound.
	 * @return {Boolean} True when enabled, false otherwise.
	 */
	isEnabled: function(element)
	{
		var keymap = this.getActive(element);

		return keymap && keymap.isEnabled();
	},

	/**
	 * Returns whether the keymaps are globally enabled or not.
	 * return {Boolean} True when enabled, false otherwise.
	 */
	isGloballyEnabled: function()
	{
		return container.getSettingsModel().get('zarafa/v1/main/keycontrols') !== Zarafa.settings.data.KeyboardSettings.NO_KEYBOARD_SHORTCUTS;
	},

	/**
	 * Format a keyboard shortcut hint for display in tooltips.
	 * Returns an empty string when the shortcut is not active
	 * under the current keyboard settings. Substitutes Cmd for
	 * Ctrl on macOS.
	 * @param {String} hint Human-readable shortcut (e.g. 'Ctrl + S')
	 * @param {Boolean} basic True when this is a basic shortcut
	 * @return {String} Formatted hint like ' (Ctrl + S)' or ''
	 */
	formatShortcutHint: function(hint, basic)
	{
		var setting = container.getSettingsModel().get('zarafa/v1/main/keycontrols');
		if (setting === 'disabled') {
			return '';
		}
		if (setting === 'basic' && basic !== true) {
			return '';
		}
		if (Ext.isMac) {
			hint = hint.replace(/Ctrl/g, 'Cmd');
		}
		return ' (' + hint + ')';
	}
});

Zarafa.core.KeyMapMgr = new Zarafa.core.KeyMapMgr();
Ext.namespace('Zarafa.core');

/**
 * @class Zarafa.core.MessageClass
 *
 * Special utility class for manipulation and comparisons
 * of Message Class Strings
 * @singleton
 */
Zarafa.core.MessageClass = {

	/**
	 * Check if the given messageclass property on this record matches the given 'expectedClass'.
	 * This comparison is done in a case-insensite way. This function can be used
	 * for comparing the value of 'message_class' safely.
	 *
	 * @param {String} className The className which should be checked,
	 * @param {String|Array} expectedClass The expected class against which the className
	 * must be compared
	 * @param {Boolean} baseOnly (optional) Only compare the start of the className,
	 * this can be used for partial comparison (e.g. record.isClass('message_class', 'IPM', true)
	 * will return true when the actual message_class is 'IPM.Note'). Defaults to false.
	 * @return {Boolean} True when the className matches.
	 */
	isClass: function(className, expectedClass, baseOnly)
	{
		if (Ext.isEmpty(className)) {
			return false;
		}

		// Convert the className to uppercase
		className = className.toUpperCase();

		// If the expectedClass is an array, we check if the className
		// is either one of the names in expectedClas
		if (Array.isArray(expectedClass)) {
			for (var i = 0, len = expectedClass.length; i < len; i++) {
				if (this.isClass(className, expectedClass[i], baseOnly)) {
					return true;
				}
			}
			return false;
		}

		// If the expectedClass length is larger then the className,
		// then it can never be equal.
		if (expectedClass.length > className.length) {
			return false;
		}

		// If baseOnly is true, we only want to match expectedClass against
		// the start of the className. Although we can use className.search()
		// it is faster to create a substring to ensure className and expectedClass
		// have the same length.
		if (baseOnly === true) {
			className = className.substring(0, expectedClass.length);
		}

		// Now the entire string must match
		return className == expectedClass.toUpperCase();
	},

	/**
	 * Test if the messageClass is compatible with the give containerClass.
	 * An IPM.Note is for example compatible with IPF.Note containerClass.
	 *
	 * @param {String} messageClass The Message Class to compare
	 * @param {String} containerClass The Container Class to compare
	 * @return {Boolean} True when the messageClass is compatible with the given containerClass
	 */
	isContainerClassCompatible: function(messageClass, containerClass)
	{
		messageClass = messageClass.toUpperCase();
		containerClass = containerClass.toUpperCase();

		if (Ext.isEmpty(containerClass)) {
			return true;
		}

		switch (messageClass) {
			case 'IPM.APPOINTMENT':
			case 'IPM.OLE.CLASS.{00061055-0000-0000-C000-000000000046}':
				return Zarafa.core.ContainerClass.isClass(containerClass, 'IPF.Appointment', true);
			case 'IPM.STICKYNOTE':
				return Zarafa.core.ContainerClass.isClass(containerClass, 'IPF.StickyNote', true);
			case 'IPM.CONTACT':
			case 'IPM.DISTLIST':
				return Zarafa.core.ContainerClass.isClass(containerClass, 'IPF.Contact', true);
			case 'IPM.TASK':
				return Zarafa.core.ContainerClass.isClass(containerClass, 'IPF.Task', true);
			case 'IPM.NOTE':
			case 'REPORT.IPM':
			case 'IPM.SCHEDULE':
			case "IPM.TASKREQUEST":
				// These are all normal messages, so those should be in IPF.Note containers,
				// however the IPF.Note.OutlookHomepage is special (as it is used for RSS feeds),
				// and should thus be excluded.
				return this.isClass(containerClass, 'IPF.Note', true) && !this.isClass(containerClass, 'IPF.Note.OutlookHomepage', true);
		}

		// Our fallthrough, the switch didn't catch any message class,
		// so perhaps we should cut of everything after the last dot (.)
		// and then we just retry by calling this function recursively.
		var index = messageClass.lastIndexOf('.');
		if (index > 0) {
			messageClass = messageClass.substr(0, index);
			return this.isContainerClassCompatible(messageClass, containerClass);
		}

		return false;
	},

	/**
	 * Function will return default foldertype from the hierarchy
	 * for the supplied message_class to the function.
	 *
	 * @param {String} messageClass The message_class of the mapi record.
	 * @return {String} The foldertype of the default folder for the supplied message_class
	 */
	getDefaultFolderTypeFromMessageClass: function(messageClass)
	{
		messageClass = messageClass.toUpperCase();

		switch (messageClass) {
			case 'IPM.APPOINTMENT':
			case 'IPM.OLE.CLASS.{00061055-0000-0000-C000-000000000046}':
				return 'calendar';
			case 'IPM.STICKYNOTE':
				return 'note';
			case 'IPM.CONTACT':
			case 'IPM.DISTLIST':
				return 'contact';
			case 'IPM.TASK':
				return 'task';
			case 'IPM.NOTE':
			case 'REPORT.IPM':
			case 'IPM.SCHEDULE':
			case "IPM.TASKREQUEST":
				return 'inbox';
		}

		// Our fallthrough, the switch didn't catch any message class,
		// so perhaps we should cut of everything after the last dot (.)
		// and then we just retry by calling this function recursively.
		var index = messageClass.lastIndexOf('.');
		if (index > 0) {
			messageClass = messageClass.substr(0, index);
			return this.getDefaultFolderTypeFromMessageClass(messageClass);
		}

		return '';
	}
};
Ext.namespace('Zarafa.core');

/**
 * @class Zarafa.core.ModuleNames
 * List of module names. Each module represents a server side component that can process actions. A list module
 * for instance will allow 'list' actions.
 * @singleton
 */
Zarafa.core.ModuleNames =
{
	/**
	 * Module information for the Address book.
	 * @property
	 * @type Mixed
	 */
	'ADDRESSBOOK': {
		list: 'addressbooklistmodule',
		item: 'addressbookitemmodule'
	},

	/**
	 * Module information for IPM.Appointment
	 * @property
	 * @type Mixed
	 */
	'IPM.APPOINTMENT': {
		list: 'appointmentlistmodule',
		item: 'appointmentitemmodule'
	},

	/**
	 * Module information for Appointment recurrence exceptions
	 * @property
	 * @type Mixed
	 */
	'IPM.OLE.CLASS.{00061055-0000-0000-C000-000000000046}': {
		list: 'appointmentlistmodule',
		item: 'appointmentitemmodule'
	},

	/**
	 * Module information for IPM.Task
	 * @property
	 * @type Mixed
	 */
	'IPM.TASK': {
		list: 'tasklistmodule',
		item: 'taskitemmodule'
	},

	/**
	 * Module information for IPM.TaskRequest
	 * @property
	 * @type Mixed
	 */
	'IPM.TASKREQUEST': {
		list: 'tasklistmodule',
		item: 'taskitemmodule'
	},

	/**
	 * contact list module.
	 * @property
	 * @type Mixed
	 */
	'IPM.CONTACT': {
		list: 'contactlistmodule',
		item: 'contactitemmodule'
	},

	/**
	 * Distribution list module.
	 * Here we use same module as contact module.
	 * @property
	 * @type Mixed
	 */
	'IPM.DISTLIST': {
		list: 'contactlistmodule',
		item: 'contactitemmodule'
	},

	/**
	 * Module information for hierarchy.
	 * The hierarchy is the set of stores a user can see, and the folders within those
	 * stores.
	 * @property
	 * @type Mixed
	 */
	'HIERARCHY': {
		list: 'hierarchymodule',
		item: 'hierarchymodule'
	},

	/**
	 * Module information for settings.
	 * stores.
	 * @property
	 * @type Mixed
	 */
	'SETTINGS': {
		list: 'settingsmodule'
	},

	/**
	 * Module information for IPM.Note
	 * @property
	 * @type Mixed
	 */
	'IPM.NOTE': {
		list: 'maillistmodule',
		item: 'createmailitemmodule'
	},

	/**
	 * Module information for IPM.StickyNote
	 * @property
	 * @type Mixed
	 */
	'IPM.STICKYNOTE': {
		list: 'stickynotelistmodule',
		item: 'stickynoteitemmodule'
	},

	/**
	 * Module information for freebusy
	 * @property
	 * @type Mixed
	 */
	'FREEBUSY': {
		list: 'freebusymodule'
	},

	/**
	 * Module information for busytime
	 * @property
	 * @type Mixed
	 */
	 'BUSYTIME': {
		 list: 'busytimelistmodule'
	 },

	/**
	 * Module information for freebusy
	 * @property
	 * @type Mixed
	 */
	'SUGGESTEMAILADDRESS': {
		list: 'suggestemailaddressmodule',
		item: 'suggestemailaddressmodule'
	},

	/**
	 * Module information for reminder
	 * @property
	 * @type Mixed
	 */
	'REMINDER': {
		list: 'reminderlistmodule',
		item: 'reminderitemmodule'
	},

	/**
	 * Module information for delegates
	 * @property
	 * @type Mixed
	 */
	'DELEGATES': {
		list: 'delegatesmodule',
		item: 'delegatesmodule'
	},

	/**
	 * Module information for Rules
	 * @property
	 * @type Mixed
	 */
	'RULES': {
		list: 'rulesmodule',
		item: 'rulesmodule'
	},

	/**
	 * Module information for Out of office settings
	 * @property
	 * @type Mixed
	 */
	'OUTOFOFFICESETTINGS': {
		list: 'outofofficesettingsmodule',
		item: 'outofofficesettingsmodule'
	},

	/**
	 * Module information for Restore Soft Deleted Items
	 * @property
	 * @type Mixed
	 */
	'RESTOREITEMS': {
		list: 'restoreitemslistmodule',
		item: 'restoreitemslistmodule'
	},

	/**
	 * Module information for advanced search.
	 * @property
	 * @type Mixed
	 */
	'IPM.SEARCH': {
		list: 'advancedsearchlistmodule',
		item: 'createmailitemmodule'
	},

	/**
	 * Obtain the moduleName for an appropriate key.
	 * The key could either be a component name, or a Message Class.
	 *
	 * @param {String} key The key for which the module must be found
	 * @param {Boolean} baseOnly (optional) When the the key is not exactly
	 * matched againsts a moduleName, remove everything after the last '.',
	 * defaults to false.
	 * @return {Object} An object containing a 'list' and 'item' key,
	 * for the moduleNames for the 'list' and 'item' module respectively.
	 */
	getModule: function(key, baseOnly)
	{
		key = key.toUpperCase();

		var module = this[key];
		if (!module && baseOnly === true) {
			var index = key.lastIndexOf('.');
			if (index > 0) {
				key = key.substr(0, index);
				module = this.getModule(key, baseOnly);
			}
		}

		return module;
	},

	/**
	 * Obtain the moduleName for the Item module for the appropriate key.
	 * This uses {@link #getModule} to obtain the module, and returns the
	 * itemModulename from the result.
	 *
	 * @param {String} key The key for which the module must be found
	 * @param {Boolean} baseOnly (optional) When the the key is not exactly
	 * matched againsts a moduleName, remove everything after the last '.',
	 * defaults to false.
	 * @return {String} The item moduleName for the requested module
	 */
	getItemName: function(key, baseOnly)
	{
		var module = this.getModule(key, baseOnly);
		if (module) {
			return module.item;
		}
	},

	/**
	 * Obtain the moduleName for the List module for the appropriate key.
	 * This uses {@link #getModule} to obtain the module, and returns the
	 * listModulename from the result.
	 *
	 * @param {String} key The key for which the module must be found
	 * @param {Boolean} baseOnly (optional) When the the key is not exactly
	 * matched againsts a moduleName, remove everything after the last '.',
	 * defaults to false.
	 * @return {String} The list moduleName for the requested module
	 */
	getListName: function(key, baseOnly)
	{
		var module = this.getModule(key, baseOnly);
		if (module) {
			return module.list;
		}
	}
};
Ext.namespace('Zarafa.core');

/**
 * @class Zarafa.core.PingService
 * @extends Ext.util.Observable
 *
 * Ping service which will periodically ping the server to determine
 * if the HTTP and Gromox server are both available again, so the user can
 * continue working with grommunio Web.
 */
Zarafa.core.PingService = Ext.extend(Ext.util.Observable, {

	/**
	 * @cfg {String} url
	 * The url used to send the requests to. defaults to grommunio.php.
	 */
	url: 'grommunio.php',

	/**
	 * @cfg {String} cmd
	 * The GET attribute to send to the server. defaults to 'ping'
	 */
	cmd: 'ping',

	/**
	 * @cfg {Object} headers
	 * The default headers to be applied to the request. defaults to
	 *   'Content-Type' => 'application/json; charset=utf-8;'
	 */
	headers: undefined,

	/**
	 * @cfg {Number} timeout The initial timeout value for the call
	 * to {@link #sendPing}. This will be incremented by {@link #getNextTimeout}.
	 */
	timeout: 1000,

	/**
	 * @cfg {Number} maxTimeout The maximum timeout value which can be used
	 * and returned by {@link #getNextTimeout}.
	 */
	maxTimeout: 300000,

	/**
	 * The DelayedTask which will be used to defer the {@link #sendPing}
	 * function call to periodically poll the server for availability.
	 * @property
	 * @type Ext.util.DelayedTask
	 * @private
	 */
	pingTask: undefined,

	/**
	 * The current timeout value for the {@link #pingTask}.
	 * This is obtained (and incremented) through {@link #getNextTimeout}.
	 * @property
	 * @type Number
	 * @private
	 */
	currentTimeout: undefined,

	/**
	 * True if the Ping has been send to the server, and the PingService
	 * is currently awaiting the response from the server.
	 * @property
	 * @type Boolean
	 * @private
	 */
	pingPending: false,

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

		Ext.apply(config, {
			// Apply here instead of class prototype, to prevent
			// accidental sharing of object between all instances.
			headers: {
				'Content-Type': 'application/json; charset=utf-8;'
			}
		});

		Ext.apply(this, config);

		this.addEvents(
			/**
			 * @event start
			 * Fired during {@link #start} to indicate that the Ping Service will start
			 * polling the server for link connectivity.
			 * @param {Zarafa.core.PingService} service This object
			 * @param {Number} timeout The timeout for the next ping which will be send
			 * to the server
			 */
			'start',
			/**
			 * @event stop
			 * Fired during {@link #stop} to indicate that the Ping Service will stop
			 * polling the server for link connectivity. This doesn't imply that the
			 * Service has been stopped before or after a successful response was returned.
			 * @param {Zarafa.core.PingService} service This object
			 */
			'stop',
			/**
			 * @event send
			 * Fired during {@link #sendPing} to indicate that a new Ping request will
			 * be made to the server.
			 * @param {Zarafa.core.PingService} service This object
			 * @param {XMLHttpRequest} xhrObj The XMLHttpRequest which is send to the server
			 */
			'send',
			/**
			 * @event retry
			 * Fired when a ping request was completed, but the connectivity has failed,
			 * a new attempt will be made after a specific timeout.
			 * @param {Zarafa.core.PingService} service This object
			 * @param {Object} response The response, if any, as send by the server
			 * @param {Number} timeout The timeout after which the next ping will be send
			 */
			'retry',
			/**
			 * @event restored
			 * Fired when a ping request was completed, and the connectivity has been restored.
			 * This means connectivity is restored, but the user might no longer have an active
			 * session. No new attempts will be made after this.
			 * @param {Zarafa.core.PingService} service This object
			 * @param {Object} response The response as send by the server
			 */
			'restored'
		);

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

		// Instantiate the delayed task for the sendPing function
		this.pingTask = new Ext.util.DelayedTask(this.sendPing, this);
	},

	/**
	 * Start the Ping Service and schedule the first run of {@link #pingTask}.
	 */
	start: function()
	{
		// reset the current timeout
		delete this.currentTimeout;

		// Obtain a new timeout and start polling
		var timeout = this.getNextTimeout();
		this.fireEvent('start', this, timeout);
		this.pingTask.delay(timeout);
	},

	/**
	 * Stop the Ping Service and cancel the {@link #pingTask}.
	 */
	stop: function()
	{
		this.pingTask.cancel();
		this.fireEvent('stop', this);
	},

	/**
	 * Interrupt the current timeout for {@link #pingTask} and manually
	 * invoke {@link #sendPing} causing a new request to be send out right now.
	 */
	retry: function()
	{
		this.pingTask.cancel();
		this.sendPing();
	},

	/**
	 * Obtain the next timeout value for the {@link #pingTask}. If
	 * {@link #currentTimeout} is initialized this will double the value
	 * (restricted by {@link #maxTimeout}, or otherwise it will take {@link #timeout}.
	 * @return {Number} The timeout for the {@link #pingTask}
	 * @private
	 */
	getNextTimeout: function()
	{
		this.currentTimeout = this.currentTimeout ? (2 * this.currentTimeout) : this.timeout;
		this.currentTimeout = Math.min(this.maxTimeout, this.currentTimeout);
		return this.currentTimeout;
	},

	/**
	 * Send a Ping request to the configured {@link #url} with the given {@link #cmd GET action}.
	 * {@link #onStateChange} will handle the response as received from the server.
	 * @private
	 */
	sendPing: function()
	{
		// A Ping request was already send,
		// we will not send another one simultaneously.
		if (this.pingPending) {
			return;
		}

		var xmlHttpRequest = new XMLHttpRequest();

		// Open the HTTP request object
		var url = this.url;
		url = Ext.urlAppend(url, this.cmd);
		xmlHttpRequest.open('GET', url, true);

		// Apply the headers
		for (var key in this.headers) {
			xmlHttpRequest.setRequestHeader(key, this.headers[key]);
		}

		// Register statechange callback function
		xmlHttpRequest.onreadystatechange = this.onStateChange.createDelegate(this, [ xmlHttpRequest ]);

		// Mark that the Ping request is currently pending.
		this.pingPending = true;

		// Send the request
		xmlHttpRequest.send();

		this.fireEvent('send', this, xmlHttpRequest);
	},

	/**
	 * Called by {@link XMLHttpRequest} when a response from the server has been received.
	 * This will determine if the link connection to the server has been restored or not.
	 * @param {XMLHttpRequest} xmlHttpRequest The request object
	 * @private
	 */
	onStateChange: function(xmlHttpRequest)
	{
		var response;

		// The readyState can be 4 values:
		// 0 - Object is created, but not initialized
		// 1 - Request has been opened, but send() has not been called yet
		// 2 - send() has been called, no data available yet
		// 3 - Some data has been received, responseText nor responseBody are available
		// 4 - All data has been received
		//
		// readyState 0 - 3 can be completely ignored by us, as they are only updates
		// about the current progress. Only on readyState 4, should we continue and
		// start checking for the response status.
		if (xmlHttpRequest.readyState != 4) {
			return;
		}

		// Regardless of the state, the Ping request
		// is no longer pending.
		this.pingPending = false;

		// HTTP request must have succeeded
		if (xmlHttpRequest.status !== 200) {
			this.failure();
			return;
		}

		// Depending on the response type, convert it into a data Object.
		if (xmlHttpRequest.responseText) {
			// JSON response
			response = Ext.decode(xmlHttpRequest.responseText);
		} else {
			// XML response is not supported
			this.failure();
			return;
		}

		// Determine if the response indicates that a link connection
		// exists, and call the proper handler.
		if (response.success) {
			this.restored(response);
		} else {
			this.failure(response);
		}
	},

	/**
	 * Called when the link connection has been restored. This will fire
	 * the {@link #restored} event.
	 * @param {Object} response The response as received by the server
	 * @private
	 */
	restored: function(response)
	{
		this.fireEvent('restored', this, response);
	},

	/**
	 * Called when the link connection has not been restored and will be
	 * retried later. This will fire the {@link #retry} event.
	 * @param {Object} response The response, if any, as received by the server
	 * @private
	 */
	failure: function(response)
	{
		var timeout = this.getNextTimeout();
		this.fireEvent('retry', this, response, timeout);
		this.pingTask.delay(timeout);
	}
});
Ext.namespace('Zarafa.core');

/**
 * @class Zarafa.core.PluginMetaData
 * @extends Object
 *
 * The Meta Data object containing the registration details
 * of a {@link Zarafa.core.Plugin}. An instance of this object
 * must be passed to {@link Zarafa.core.Container#registerPlugin}.
 */
Zarafa.core.PluginMetaData = Ext.extend(Object, {
	/**
	 * @cfg {String} name (required) The unique name for this plugin.
	 * For a user-friendly name for UI components, see {@link #displayName}
	 */
	name: '',

	/**
	 * @cfg {String} displayName The display name for this plugin. This
	 * will be used in places where the plugin is referenced in UI components.
	 * If not provided, {@link #name} will be used.
	 */
	displayName: '',

	/**
	 * @cfg {String} settingsName Alternative name for the plugin as used
	 * in the {@link Zarafa.settings.SettingsModel settings} in which the settings
	 * for this {@link Zarafa.core.Plugin plugin} are being saved. If not provided,
	 * then {@link #name} will be used.
	 */
	settingsName: '',

	/**
	 * @cfg {String} iconCls The icon to be used in places where the plugin is referenced
	 * in UI components.
	 */
	iconCls: '',

	/**
	 * @cfg {String} about The about text. If provided, {@link Zarafa.core.Plugin#registerAboutText}
	 * will be automatically called during {@link Zarafa.core.Plugin#initPlugin initialization}.
	 */
	about: undefined,

	/**
	 * @cfg {Boolean} allowUserDisable True if the user is allowed to enable/disable
	 * the plugin through the settings. To obtain the enabled status, the function
	 * {@link #isEnabled} should always be referenced.
	 */
	allowUserDisable: true,

	/**
	 * @cfg {Boolean} allowUserVisible True if the user is allowed to see the plugin
	 * in the settings. To obtain the visibility status, the function
	 * {@link #isPrivate} should always be referenced.
	 */
	allowUserVisible: true,

	/**
	 * @cfg {Constructor} pluginConstructor (required) The constructor of the
	 * {@link Zarafa.core.Plugin} which is described by this PluginMetaData instance.
	 */
	pluginConstructor: undefined,

	/**
	 * The instance of the {@link Zarafa.core.Plugin} (instantiated using the
	 * {@link #pluginConstructor}). This is obtained using the {@link #getInstance}
	 * function.
	 * @property
	 * @type Zarafa.core.Plugin
	 * @private
	 */
	instance: undefined,

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

		Ext.apply(this, config);

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

		// Initialize displayName if not initialized from config
		if (Ext.isEmpty(this.displayName)) {
			this.displayName = this.name;
		}

		// Initialize settingsName if not initialized from config
		if (Ext.isEmpty(this.settingsName)) {
			this.settingsName = this.name;
		}
	},

	/**
	 * Determine if the plugin is enabled.
	 * @return {Boolean} True if the plugin is enabled
	 */
	isEnabled: function()
	{
		if ( !this.allowUserDisable ){
			return true;
		} else {
			return container.getSettingsModel().get(this.getSettingsBase() + '/enable') === true;
		}
	},

	/**
	 * Determine if the plugin should be considered private.
	 * @return {Boolean} True if the plugin is private
	 */
	isPrivate: function()
	{
		return this.allowUserVisible === false;
	},

	/**
	 * Obtain the unique name for this plugin
	 * @return {String} The unique name for this plugin
	 */
	getName: function()
	{
		return this.name;
	},

	/**
	 * Obtain the display name for this plugin
	 * @return {String} The display name for this plugin
	 */
	getDisplayName: function()
	{
		return this.displayName;
	},

	/**
	 * Obtain the CSS classname for this plugin
	 * @return {String} The CSS classname for this plugin
	 */
	getIconCls: function()
	{
		return this.iconCls;
	},

	/**
	 * Obtain the About text containing the copyright and other disclaimers.
	 * @return {String} The about text for this plugin
	 */
	getAbout: function()
	{
		return this.about;
	},

	/**
	 * Obtain the base path for the {@link Zarafa.settings.SettingsModel settings} in which the settings
	 * for this plugin can be found. This uses the {@link #settingsName} within the special 'plugins' section
	 * of the settings.
	 * @return {String} The settings path
	 */
	getSettingsBase: function()
	{
		return 'zarafa/v1/plugins/' + this.settingsName;
	},

	/**
	 * Obtain the instance of the {@link Zarafa.core.Plugin} which is instantiated
	 * using the {@link #pluginConstructor}. This uses single-instancing using the {@link #instance}.
	 * Before calling this, the {@link #isEnabled} function should have been used to determine if the
	 * {@link Zarafa.core.Plugin} is allowed to be instantiated.
	 * property.
	 * @return {Zarafa.core.Plugin} The Plugin instance
	 */
	getInstance: function()
	{
		if (!this.instance) {
			this.instance = new this.pluginConstructor({ info: this });
		}
		return this.instance;
	}
});
Ext.namespace('Zarafa.core');

/**
 * @class Zarafa.core.PresenceManager
 * @extends Ext.util.Observable
 *
 * Has functions to handle presence of users
 *
 * @singleton
 */
Zarafa.core.PresenceManager = Ext.extend(Ext.util.Observable, {
	/**
	 * The stores that have been registered with the {@link Zarafa.core.PresenceManager PresenceManager}
	 * @property
	 * @private
	 */
	registeredStores: [],

	/**
	 * An array with userIdObjects for which a presence status
	 * will be requested from all {#presencePlugins}
	 * @property
	 * @private
	 */
	requestQueue: [],

	/**
	 * The polling interval in milliseconds. This is the interval with which
	 * the presence status of the users in the registered stores will be
	 * updated
	 * @property
	 * @private
	 */
	pollingInterval: 20000,

	/**
	 * The constructor
	 */
	constructor: function()
	{
		var me = this;

		Zarafa.onReady(function(){
			setInterval(function(){
				me.pollForUpdates();
			}, me.pollingInterval);
		});
	},

	/**
	 * Returns an array with all registered
	 * {@link Zarafa.core.PresencePlugin PresencePlugins}
	 * @return {Zarafa.core.PresencePlugin[]}
	 */
	getPresencePlugins: function()
	{
		return container.getPlugins().filter(function(plugin){
			return plugin instanceof Zarafa.core.PresencePlugin;
		});
	},

	/**
	 * Returns the presence status for the users for whom the status has been requested
	 * @param {Zarafa.core.data.UserIdObject[]} users An array of userIdObjects
	 * for whom a presence status is requested.
	 * @return {Object[]} An array of objects that contain a
	 * {@link Zarafa.core.data.PresenceStatus presence status} for
	 * each presence plugin
	 */
	getPresenceStatusForUsers: function(users)
	{
		users = [].concat(users);

		if ( users.length === 0 ){
			return [];
		}

		var presenceStatuses = users.map(function() {
			return {};
		});

		var presencePlugins = this.getPresencePlugins();
		if ( presencePlugins.length === 0 ){
			// No presence plugins available, so let's return an array with empty objects
			return presenceStatuses;
		}

		// Request the statuses from the presence plugins.
		Ext.each(presencePlugins, function(presencePlugin){
			var pluginName = presencePlugin.getName();
			var pluginPresenceStatuses = presencePlugin.getPresenceStatuses(users);

			// Fill the statuses in the array
			// XXX: required?
			Ext.each(users, function(user, userIndex){
				presenceStatuses[userIndex][pluginName] = pluginPresenceStatuses[userIndex];
			});
		});

		return presenceStatuses;
	},

	/**
	 * Returns the presence status for a user. If the user is not available in the
	 * cache, a status for the user will be requested from the presence plugins.
	 * @param {Zarafa.core.data.UserIdObject} user The user for whom a presence
	 * status is requested.
	 * @return {Zarafa.core.data.PresenceStatus} The squashed presence status
	 */
	getPresenceStatusForUser: function(user)
	{
		if ( Ext.isEmpty(user) ){
			return Zarafa.core.data.PresenceStatus.UNKNOWN;
		}

		var statusByPlugin = this.getPresenceStatusForUsers([user])[0];
		// Squash the status
		var presenceStatus = Zarafa.core.data.PresenceStatus.UNKNOWN;
		Ext.iterate(statusByPlugin, function(pluginName){
			if ( statusByPlugin[pluginName] > presenceStatus ){
				presenceStatus = statusByPlugin[pluginName];
			}
		});
		return presenceStatus;
	},

	/**
	 * Add a {@link Zarafa.core.data.UserIdObject user} to the user queue for which
	 * a presence status will be requested. The queue will be resolved in a
	 * delayedTask of 500ms.
	 * @param {Zarafa.core.data.UserIdObject} user An object that identifies the user for which
	 * a presence status will be requested.
	 */
	queuePresenceRequest: function(user)
	{
		// If the user is already in the request queue, we can simply return.
		var userAlreadyinRequestQueue = false;
		Ext.each(this.requestQueue, function(queuedUser){
			if ( user.equals(queuedUser) ){
				userAlreadyinRequestQueue = true;
				return false;
			}
		}, this);
		if ( userAlreadyinRequestQueue ){
			return;
		}

		this.requestQueue.push(user);

		// If the task has not yet been created, do it now
		this.task = this.task || new Ext.util.DelayedTask(this.doQueuedRequests, this);

		// Delay the task with 500ms to buffer the requests
		this.task.delay(500);
	},

	/**
	 * {@link Ext.util.DelayedTask} handler that requests a
	 * {@link Zarafa.core.data.PresenceStatus presence status} for all
	 * users in the {#requestQueue queue}.
	 */
	doQueuedRequests: function()
	{
		var statuses = this.getPresenceStatusForUsers(this.requestQueue);

		// Store the updates to the cache and update the ui
		var statusesByPlugin = this.rearangeStatuses(this.requestQueue, statuses);
		Ext.iterate(statusesByPlugin, function(pluginName){
			this.updateStatuses(pluginName, statusesByPlugin[pluginName]);
		}, this);

		// Empty the request queue
		this.requestQueue = [];
	},

	/**
	 * Rearanges an array with presence statuses that is originally orderded by user
	 * (e.g. the return value of {#link getPresenceStatusForUsers}) to an array
	 * that is ordered by pluginName)
	 * @param {Zarafa.core.data.UserIdObject[]} users An array with users
	 * @param {Object[]} statuses An array with objects that contain a presence
	 * status per presence plugin. The entries in the statuses array correspond
	 * to the entries in the given users array.
	 * @return {Object} An object that contains an array for each presence plugin.
	 * The arrays contain objects that contain a
	 * {@link Zarafa.core.data.UserIdObject user} and a status.
	 */
	rearangeStatuses: function(users, statuses)
	{
		var statusesByPlugin = {};
		Ext.each(statuses, function(statusByPlugin, index){
			Ext.iterate(statusByPlugin, function(pluginName){
				if ( !Ext.isDefined(statusesByPlugin[pluginName]) ){
					statusesByPlugin[pluginName] = [];
				}
				statusesByPlugin[pluginName].push({
					user: users[index],
					status: statusByPlugin[pluginName]
				});
			}, this);
		}, this);

		return statusesByPlugin;
	},

	/**
	 * Registers a store with the {@link Zarafa.core.PresenceManager}. The
	 * {@link Zarafa.core.PresenceManager} will then make sure that presence statuses
	 * are fetched when the store loads or when the {@link Zarafa.core.PresenceManager}
	 * polls for updates.
	 * @param {Zarafa.core.data.MAPIStore|Zarafa.core.data.MAPISubStore} store The store
	 * that will be registered.
	 * will be added to the records. (e.g.: 'sender')
	 */
	registerStore: function(store)
	{
		if (this.getPresencePlugins().length === 0) {
			return;
		}

		var storeRegistered = Ext.each(this.registeredStores, function(registeredStore){
			if ( registeredStore.store === store ){
				return false;
			}
		}, this);

		if ( Ext.isDefined(storeRegistered) ){
			// Store was already registered
			return;
		}

		this.registeredStores.push({
			store: store
		});

		// Add the current users to the cache by calling the onLoad listener
		this.onStoreLoad(store, store.getRange(), null);

		// Register an event handler for the load event of this store
		store.on('load', this.onStoreLoad, this);
	},

	/**
	 * Unregisters a store that was previously registered with the {#registerStore}.
	 * @param {Zarafa.core.data.MAPIStore|Zarafa.core.data.MAPISubStore} store The store
	 * that will be unregistered.
	 */
	unregisterStore: function(store)
	{
		for ( var i=0; i<this.registeredStores.length; i++) {
			if ( this.registeredStores[i].store === store ){
				this.registeredStores.splice(i, 1);
				return;
			}
		}
	},

	/**
	 * Event handler for the load event of a registered store. If records are loaded that contain
	 * users for which the {@link Zarafa.core.data.PresenceCache} does not have an
	 * entry yet, then a status for these users is requested from the presence plugins.
	 * @param {Zarafa.core.data.MAPIStore|Zarafa.core.data.MAPISubStore} store The store
	 * that contains records for which presence statuses are needed.
	 * @param {Zarafa.core.data.MAPIRecord[]} records The records that are being loaded into
	 * this store.
	 * @param {Object} options The loading options that were specified
	 */
	onStoreLoad: function(store, records, options)
	{
		// Create an array with user info objects to send to the PresenceManager
		var users = Zarafa.core.data.UserIdObjectFactory.createFromStore(store);

		// Check if we already have an entry for these users in the cache
		// because otherwise we must add it and make a request to the plugins
		Ext.each(users, function(user){
			if (!Ext.isDefined(Zarafa.core.data.PresenceCache.getUser(user))){
				// Ask the presence plugins about this user
				this.queuePresenceRequest(user);
			}
		}, this);
	},

	/**
	 * Processes status updates from presence plugins. The updates will be stored
	 * in the {@link Zarafa.core.data.PresenceCache} and the UI will be updated.
	 * @param {Object} pluginName The name of the plugin from which the updates come
	 * @param {Array} updates An array with objects that contain a
	 * {Zarafa.core.data.UserIdObject UserIdObject} and a
	 * {@link Zarafa.core.data.PresenceStatus} for that user
	 */
	updateStatuses: function(pluginName, updates) {
		// Make sure we have an array
		if ( !Array.isArray(updates) ){
			updates = [updates];
		}

		// First add the statuses to the cache, and check if the UI needs to be updated
		var realUpdates = [];
		Ext.each(updates, function(update){
			var statusBeforeUpdate = Zarafa.core.data.PresenceCache.getStatusForUser(update.user);
			Zarafa.core.data.PresenceCache.addStatusForUser(pluginName, update.user, update.status);
			var statusAfterUpdate = Zarafa.core.data.PresenceCache.getStatusForUser(update.user);
			if ( statusAfterUpdate !== statusBeforeUpdate ){
				realUpdates.push(update);
			}
		});

		// Now update the stores that have records for these users
		Ext.each(this.registeredStores, function(registeredStore){
			var records = registeredStore.store.getRange();
			var modified = [];
			Ext.each(records, function(record) {
				if ( modified.indexOf(record)>=0 ){
					return;
				}

				var recordAdded = Ext.each(realUpdates, function(update) {
					var user = Zarafa.core.data.UserIdObjectFactory.createFromRecord(record);
					if ( user && user.equals(update.user) ){
						modified.push(record);
						return false;
					}
				}, this);

				if ( Ext.isDefined(recordAdded) ){
					return false;
				}
			}, this);

			// Fire update events for all modified records, so components that display these records
			// update their UI.
			if ( modified.length ){
				Ext.each( modified, function(record) {
					registeredStore.store.fireEvent('update', registeredStore.store, record, Ext.data.Record.COMMIT);
				});

				// Recipient links don't get updated when we fire an update event, so we fire a datachanged event
				if ( registeredStore.store instanceof Zarafa.core.data.IPMRecipientStore ){
					registeredStore.store.fireEvent('datachanged', registeredStore.store);
				}
			}
		}, this);
	},

	/**
	 * Requests a status from all presence plugins for all users in the
	 * registered stores.
	 */
	pollForUpdates: function()
	{
		var users = Zarafa.core.data.PresenceCache.getUserInfoList();
		var statuses = this.getPresenceStatusForUsers(users);
		var statusesByPlugin = this.rearangeStatuses(users, statuses);
		Ext.iterate(statusesByPlugin, function(pluginName){
			this.updateStatuses(pluginName, statusesByPlugin[pluginName]);
		}, this);
	}
});

// Make a singleton of this class
Zarafa.core.PresenceManager = new Zarafa.core.PresenceManager();
Ext.namespace('Zarafa.core');

/**
 * @class Zarafa.core.Request
 * @extends Ext.util.Observable
 *
 * Request object for asynchronous communication with the server. The request object
 * automatically serialises single or multiple action requests into corresponding
 * JSON and provides a callback system to allow asynchronous handling of the
 * response from the server.
 * <p>
 * The request object supports global events surrounding the sending of the data
 * to the server. For handling the responses it communicates directly with the
 * {@link Zarafa.core.ResponseRouter ResponseRouter} on which events are provided
 * surrounding the receiving of the Responses (as well as handling of the Notifications).
 * <p>
 * You shouldn't have to instantiate this class yourself as a global instance
 * can be obtained from the globally available {@link Zarafa.core.Container container}
 * object.
 * <p>
 * The structure of data that will be used for communication will be like
 * <pre><code>
 * zarafa: {
 *	module_name: {
 *		module_id: {
 *			action_type: {
 *				// data
 *			}
 *		}
 *	}
 * }
 * </code></pre>
 */
Zarafa.core.Request = Ext.extend(Ext.util.Observable, (function() {
	/**
	 * True if the connection has been {@link #paralyze}. This will
	 * prevent any requests from being send to the server, or any responses
	 * from being processed.
	 * @property
	 * @type Boolean
	 * @private
	 */
	var paralyzed = false;

	/**
	 * True if the Webapp has been {@link #interrupt}. This will
	 * queue all requests which will be send to the server, until the connection
	 * is restored again.
	 * @property
	 * @type Boolean
	 * @private
	 */
	var interrupted = false;

	/**
	 * The current base number for the generating new requestsIds
	 * using the {@link #getRequestId} function.
	 * @property
	 * @type Number
	 * @private
	 */
	var requestIdBase = 0;

	/**
	 * The current JSON object containing all requests which
	 * should be send to the server on the next call to
	 * {@link #send}. Will be reset on {@link #reset}.
	 * @property
	 * @type Object
	 * @private
	 */
	var zarafaTag;

	/**
	 * The flag to indicate if zarafaTag holds JSON data.
	 * This influences if the contents of zarafaTag will be encoded or not.
	 * @property
	 * @type Boolean
	 * @private
	 */
	var hasJson = false;

	/**
	 * The flag to indicate if zarafaTag holds RAW data.
	 * This influences if the contents of zarafaTag will be encoded or not.
	 * @property
	 * @type Boolean
	 * @private
	 */
	var hasData = false;

	/**
	 * The list of request ids as generated by {@link #getRequestId} of all
	 * requests which are added after a {@link #reset} and before {@link #send}.
	 * These will be merged into {@link #activeRequests} on {@link #send}.
	 * @property
	 * @type Array
	 * @private
	 */
	var queuedRequests = [];

	/**
	 * Key-value list of request ids and the corresponding {@link XMLHttpRequest} objects
	 * which was used to send the request to the server. These requests are all pending
	 * on the PHP side. These will be cleared as soon as the response has been received.
	 * As long as a request is listed here, it can be canceled using {@link #cancelActiveRequest}.
	 * @property
	 * @type Array
	 * @private
	 */
	var activeRequests = {};

	/**
	 * The list of {@link #XMLHttpRequest} objects which are queued while the
	 * {@link #interrupted connection is interrupted}. These will be dequeued
	 * later when the connection is restored.
	 * @property
	 * @type Array
	 * @private
	 */
	var queuedInterruptedHttpRequests = [];

	/**
	 * The unique id for subsystem which will be used to differentiate between two sessions of same user
	 * using same browser but in different tabs.
	 * @property
	 * @type String
	 * @private
	 */
	var subSystemId;

	return {
		// public variables
		/**
		 * @cfg {String} defaultUrl
		 * The url used to send the requests to. defaults to grommunio.php.
		 */
		defaultUrl: 'grommunio.php',

		/**
		 * @cfg {Object} defaultHeaders
		 * The default headers to be applied to the request. defaults to
		 *	'Content-Type' => 'application/json; charset=utf-8;'
		 */
		defaultHeaders: undefined,

		/**
		 * @cfg {String} subSystemPrefix string that will be used to generate session filename which
		 * will be used to store session data in php. current timestamp will be appended to this string
		 * so different tabs opened in same browser will have different session data. So every request object
		 * corresponds to one unique subSystem string id (accuracy upto milliseconds).
		 */
		subSystemPrefix: 'webapp',

		/**
		 * @cfg {Object} requestHeaders
		 * Headers that should be sent with every request.
		 */
		requestHeaders: undefined,

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

			Ext.apply(config, {
				// Apply here instead of class prototype, to prevent
				// accidental sharing of object between all instances.
				defaultHeaders: {
					'Content-Type': 'application/json; charset=utf-8;'
				},
				requestHeaders: {}
			});

			Ext.apply(this, config);

			this.addEvents(
				/**
				 * @event connectionparalyzed
				 * Fired when the window is about to be unloaded. At this moment
				 * grommunio Web will shutdown and start dropping all communication with the PHP
				 * @param {Zarafa.core.Request} request
				 * @param {Zarafa.core.data.ParalyzeReason} reason The reason to paralyze the grommunio Web
				 */
				'connectionparalyzed',
				/**
				 * @event connectioninterrupted
				 * Fired when the connection to the server has been lost, but is likely
				 * to be restored soon. grommunio Web will start pausing all communication with
				 * the PHP until the connection has been restored.
				 * @param {Zarafa.core.Request} request
				 * @param {Zarafa.core.PingService} service The ping service which will
				 * be used to ping for the connection restoration.
				 */
				'connectioninterrupted',
				/**
				 * @event connectionrestored
				 * Fired when the connection to the server has been restored. This will
				 * resend any requests to the PHP which were scheduled while the connection
				 * was lost.
				 * @param {Zarafa.core.Request} request
				 */
				'connectionrestored',
				/**
				 * @event beforesend
				 * Fires before the XmlHttpRequest sends a request to the server.
				 * Return false to not send request to server.
				 * @param {Zarafa.core.Request} request
				 * @param {window.XMLHttpRequest} xmlHttpRequest XmlHttpRequest object
				 */
				'beforesend',
				/**
				 * @event aftersend
				 * Fires after the XmlHttpRequest sent a request to the server.
				 * @param {Zarafa.core.Request} request
				 * @param {window.XMLHttpRequest} xmlHttpRequest XmlHttpRequest object
				 */
				'aftersend'
			);

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

			// Initialize all private variables
			this.initialize();
		},

		/**
		 * Initialize all private variables of the Request object
		 * @private
		 */
		initialize: function()
		{
			paralyzed = false;
			interrupted = false;
			requestIdBase = 0;
			zarafaTag = undefined;
			hasJson = false;
			hasData = false;
			queuedRequests = [];
			activeRequests = {};
			queuedInterruptedHttpRequests = [];
			subSystemId = this.subSystemPrefix + '_' + new Date().getTime();
		},

		/**
		 * Set the {@link #paralyzed} property which will prevent any requests from
		 * being send out to the server. This will fire the {@link #connectionparalyzed} event.
		 * @param {Zarafa.core.data.ParalyzeReason} reason The reason to paralyze grommunio Web
		 */
		paralyze: function(reason)
		{
			if (this.isParalyzed()) {
				return;
			}

			paralyzed = true;
			this.fireEvent('connectionparalyzed', this, reason);
		},

		/**
		 * @return {Boolean} True if the connection is {@link #paralyzed}.
		 */
		isParalyzed: function()
		{
			return paralyzed;
		},

		/**
		 * Set the {@link #interrupted} property which will pause all requests to be send out
		 * to the server. These will be send when the connection is {@link #restore restored}.
		 * @private
		 */
		interrupt: function()
		{
			if (this.isInterrupted() || this.isParalyzed()) {
				return;
			}

			interrupted = true;

			// Instantiate the PingService for polling the
			// server for the recovery of the connection.
			var service = new Zarafa.core.PingService({
				url: this.defaultUrl,
				headers: this.defaultHeaders
			});

			// Add event handler which will restore interaction with
			// the PHP server when the link has been restored.
			service.on('restored', this.restore, this);

			// Add some event handlers which will stop the PingService
			// as soon as the connection is restored, or if grommunio Web
			// is being paralyzed. We defer the connectionrestored by 1 ms,
			// so we force the 'stop' event from the service to be fired after
			// all 'restored' event handlers from the service have been handled.
			this.on('connectionparalyzed', service.stop, service);
			this.on('connectionrestored', service.stop, service, { delay: 1 });

			// Fire the event, allow interested parties to register
			// for events on the service before we start it.
			this.fireEvent('connectioninterrupted', this, service);

			// start the service
			service.start();
		},

		/**
		 * Clear the {@link #interrupted} property which will restore all requests which were
		 * paused while the connection was interrupted.
		 * @param {Zarafa.core.PingService} service This object
		 * @param {Object} response The response as send by the server
		 * @private
		 */
		restore: function(service, response)
		{
			if (!this.isInterrupted()) {
				return;
			}

			// If the response object indicates that our session
			// is no longer active, we must paralyze grommunio Web
			// as no further requests can be send to the server.
			if (response && response.active === false) {
				this.paralyze(Zarafa.core.data.ParalyzeReason.SESSION_EXPIRED);
				return;
			} else {
				interrupted = false;
				this.fireEvent('connectionrestored', this);

				// If there was a HTTP request queued, we should
				// send the first one now. The next one will be
				// send when the first one has responded. (This prevents
				// a DDOS if the connection was lost for a long time,
				// and many clients are connected to the server).
				if (this.hasQueuedHttpRequests()) {
					this.dequeueHttpRequest();
				}
			}
		},

		/**
		 * @return {Boolean} True if the connection is {@link #interrupted}.
		 */
		isInterrupted: function()
		{
			return interrupted;
		},

		/**
		 * Sets request headers that will be sent with every request made through {@link Zarafa.core.Request} object.
		 * @param {String} key header key.
		 * @param {String} value header value.
		 */
		setRequestHeader: function(key, value)
		{
			this.requestHeaders[key] = value;
		},

		/**
		 * Generates a new unique Request ID, which can be used to match
		 * the request with a response.
		 * @param {String} prefix (optional) The prefix for the unique request id.
		 * @return {String} request ID
		 */
		getRequestId: function(prefix)
		{
			return (prefix || 'z-gen') + (++requestIdBase);
		},

		/**
		 * Function will be used to cancel a previously generated request which is waiting for the server request.
		 * When doing multiple requests we need to make sure that previous requests are cancelled otherwise data
		 * can be overwritten by previous requests. Special requirement was that we can't do {@link window.XMLHttpRequest#abort}
		 * as it will abort the whole request and discard data that was added to response by server side, which is not
		 * good when server has added any notification data to response (like new mail), so we will need to overwrite
		 * responseHandler of this particular request with {@link Zarafa.core.data.DummyResponseHandler}, which will
		 * have empty functions for discarding response came from server and notifications will be handled normally.
		 * @param {String} requestId The request id.
		 */
		cancelActiveRequest: function(requestId)
		{
			var responseRouter = container.getResponseRouter();

			// If the request is still queued, we have an opportunity to
			// prevent the entire request. However this is only possible
			// if no other request was queued, as then we can cancel all the
			// pending data. If that is not the case, we have to register the
			// DummyResponseHandler
			if (queuedRequests.indexOf(requestId) >= 0) {
				if (queuedRequests.length === 1) {
					// Reset environment
					this.reset();
					responseRouter.removeRequestResponseHandler(requestId);
				} else {
					responseRouter.addRequestResponseHandler(requestId, new Zarafa.core.data.DummyResponseHandler());
				}
				return;
			}

			// If there is a link interruption, then the request might still be queued.
			// This means we can interrupt the request before it is being send to the
			// server.
			if (this.hasQueuedHttpRequests()) {
				// Search through the list of pending HTTP requests
				// to find the one which holds our cancelled request id.
				for (var i = 0; i < queuedInterruptedHttpRequests.length; i++) {
					var xhrObj = queuedInterruptedHttpRequests[i];
					var index = xhrObj.queuedRequests.indexOf(requestId);

					if (index >= 0) {
						// We found the request, but now comes the problem.
						// We can only cancel the entire HTTPRequest if this
						// was the only request on the object. Because we don't
						// know which part of the JSON corresponds to this request.
						if (xhrObj.queuedRequests.length === 1) {
							queuedInterruptedHttpRequests.splice(i, 1);
							responseRouter.removeRequestResponseHandler(requestId);
							return;
						}

						// If we can't cancel the request, we can break
						// the loop and just change the response handler.
						break;
					}
				}

				// We didn't find the request to which the request corresponds,
				// or we couldn't cancel the entire request. In either case,
				// we have to register the DummyResponseHandler.
				responseRouter.addRequestResponseHandler(requestId, new Zarafa.core.data.DummyResponseHandler());
				return;
			}

			// If the request has been send out to the server, we cannot cancel
			// it and thus we will have to register the DummyResponseHandler to
			// prevent the response from being processed. However we can mark
			// the corresponding xmlHttpRequest so we will not retry it if all
			// requests have been cancelled.
			var xhrObj = activeRequests[requestId];
			if (Ext.isDefined(xhrObj)) {
				// Only prevent the retry if all requests attached
				// to the xmlHttpRequest have been cancelled.
				if (xhrObj.queuedRequests.length === 1) {
					xhrObj.preventRetry = true;
				}
				responseRouter.addRequestResponseHandler(requestId, new Zarafa.core.data.DummyResponseHandler());
				return;
			}
		},

		/**
		 * Queue a request to {@link #queuedRequests}. This can be dequeued
		 * in {@link #dequeueRequests}.
		 * @param {String} The request to be queued
		 * @private
		 */
		queueRequest: function(request)
		{
			queuedRequests.push(request);
		},

		/**
		 * Obtain and dequeue any requests from {@link #queuedRequests}. Use
		 * {@link #activateRequests} to add them the the {@link #activeRequests} array
		 * once the request has been send out.
		 * @return {Array} The array of previously queued requests
		 * @private
		 */
		dequeueRequests: function()
		{
			var requests = queuedRequests;
			queuedRequests = [];
			return requests;
		},

		/**
		 * Move all requests into {@link #activeRequests}. The provided requests
		 * should previously have been obtained through {@link #dequeueRequests}.
		 * @param {Array} requests The list of requests to activate.
		 * @private
		 */
		activateRequests: function(requests, xmlHttpRequest)
		{
			for (var i = 0; i < requests.length; i++) {
				activeRequests[requests[i]] = xmlHttpRequest;
			}
		},

		/**
		 * Remove all given requests from the {@link #activeRequests} queue.
		 * @param {Array} requests The list of requests to complete
		 * @private
		 */
		completeRequests: function(requests)
		{
			for (var i = 0, len = requests.length; i < len; i++) {
				delete activeRequests[requests[i]];
			}
		},

		/**
		 * Prepare a {@link XMLHttpRequest} object which will be used to transmit data to the server for processing.
		 * it also registers a callback function for onreadystatechange event that will be called when server sends response.
		 * @param {Mixed} requestData data that will be send using this request object, for XML request it
		 * will be XML document or else it will be JSON string.
		 * @param {Boolean} encoded True of the requestData is encoded, and must be decoded to be used.
		 * @param {String} url (optional) url to post to. Defaults to {@link #defaultUrl}
		 * @param {Object} headers (optional) headers to apply to the request. Defaults to {@link #defaultHeaders}
		 * @return {XMLHttpRequest} The initialized XMLHttpRequest
		 * @private
		 */
		prepareHttpRequest: function(requestData, encoded, url, headers)
		{
			var xmlHttpRequest = new XMLHttpRequest();

			if (!url) {
				url = this.defaultUrl;
				url = Ext.urlAppend(url, 'subsystem=' + subSystemId);
			}

			xmlHttpRequest.open('POST', url, true);
			xmlHttpRequest.requestUrl = url;

			// Apply header from argument, or apply the default headers
			headers = Ext.apply({}, this.requestHeaders, headers || this.defaultHeaders);
			for (var key in headers) {
				xmlHttpRequest.setRequestHeader(key, headers[key]);
			}
			xmlHttpRequest.requestHeaders = headers;

			// Store requestData into the xmlHttpRequest, when the request failed,
			// we can still determine report the failure back to the requestee.
			xmlHttpRequest.requestData = requestData;
			xmlHttpRequest.requestDataEncoded = encoded;
			xmlHttpRequest.queuedRequests = this.dequeueRequests();

			return xmlHttpRequest;
		},

		/**
		 * Clone a {@link XMLHttpRequest} instance which was prepared by {@link #prepareHttpRequest} and
		 * has previously been attempted to be {@link #sendHttpRequest send out} but failed due to a broken
		 * connection. This will clone the instance, so the request can be retried when the connection
		 * returns.
		 * @param {XMLHttpRequest} xmlHttpRequest The request object to clone
		 * @return {XMLHttpRequest} The cloned XMLhttpRequest object
		 * @private
		 */
		cloneRequest: function(xmlHttpRequest)
		{
			var cloneRequest = new XMLHttpRequest();
			cloneRequest.open('POST', xmlHttpRequest.requestUrl, true);
			cloneRequest.requestUrl = xmlHttpRequest.requestUrl;

			var headers = xmlHttpRequest.requestHeaders;
			for (var key in headers) {
				cloneRequest.setRequestHeader(key, headers[key]);
			}
			cloneRequest.requestHeaders = headers;

			cloneRequest.requestData = xmlHttpRequest.requestData;
			cloneRequest.requestDataEncoded = xmlHttpRequest.requestDataEncoded;
			cloneRequest.queuedRequests = xmlHttpRequest.queuedRequests;

			return cloneRequest;
		},

		/**
		 * Send out a {@link XMLHttpRequest} which was initialized by {@link #prepareHttpRequest}. This will
		 * perform the final step for transmitting the request, by firing the {@link #beforesend} event,
		 * and {@link #activeRequests activating the queued requests}.
		 * @param {XMLHttpRequest} xmlHttpRequest The request object
		 * @private
		 */
		sendHttpRequest: function(xmlHttpRequest)
		{
			var requestData = xmlHttpRequest.requestData;
			var requests = xmlHttpRequest.queuedRequests;

			// When the connection paralyzed, we cannot handle any incoming data anymore.
			// So all outgoing requests will be dropped.
			if (this.isParalyzed()) {
				return;
			}

			if (this.fireEvent('beforesend', this, xmlHttpRequest) === false) {
				return;
			}

			// Register the onready StateChange event handler
			xmlHttpRequest.onreadystatechange = this.stateChange.createDelegate(this, [xmlHttpRequest]);

			// Move the queued requests to the active list.
			this.activateRequests(requests, xmlHttpRequest);

			// send request
			xmlHttpRequest.send(requestData);

			this.fireEvent('aftersend', this, xmlHttpRequest);
		},

		/**
		 * {@link #queuedInterruptedHttpRequests Queue} a {@link XMLHttpRequest} which was initialized by
		 * {@link #prepareHttpRequest}. This will keep the request instance until it can be send
		 * to the server at a {@link #dequeueHttpRequest later time}.
		 * @param {XMLHttpRequest} xmlHttpRequest The request object
		 * @private
		 */
		queueHttpRequest: function(xmlHttpRequest)
		{
			queuedInterruptedHttpRequests.push(xmlHttpRequest);
		},

		/**
		 * Check if there are queued {@link #queuedInterruptedHttpRequests HTTP requests}
		 * @return {Boolean} True if there are queued HTTP requests
		 * @private
		 */
		hasQueuedHttpRequests: function()
		{
			return !Ext.isEmpty(queuedInterruptedHttpRequests);
		},

		/**
		 * Dequeue a {@link #queuedInterruptedHttpRequests HTTP request} and {@link #sendHttpRequest send it} to the server
		 * @private
		 */
		dequeueHttpRequest: function()
		{
			var xmlHttpRequest = queuedInterruptedHttpRequests.shift();
			if (xmlHttpRequest) {
				this.sendHttpRequest(xmlHttpRequest);
			}
		},

		/**
		 * Called by XMLHttpRequest when a response from the server is coming in. When the entire response has been
		 * completed it calls {@link Zarafa.core.ResponseRouter#receive receive}} which handles the rest of the process.
		 * @param {Object} xmlHttpRequest The raw HTTP request object that is used for communication.
		 * @private
		 */
		stateChange: function(xmlHttpRequest)
		{
			var requestIds = xmlHttpRequest.queuedRequests;
			var responseRouter = container.getResponseRouter();
			var response;

			// When the connection is paralyzed, we cannot handle any incoming data anymore.
			// All incoming responses from the server will be dropped.
			if (this.isParalyzed()) {
				return;
			}

			// The readyState can be 4 values:
			// 0 - Object is created, but not initialized
			// 1 - Request has been opened, but send() has not been called yet
			// 2 - send() has been called, no data available yet
			// 3 - Some data has been received, responseText nor responseBody are available
			// 4 - All data has been received
			//
			// readyState 0 - 3 can be completely ignored by us, as they are only updates
			// about the current progress. Only on readyState 4, should we continue and
			// start checking for the response status.
			if (xmlHttpRequest.readyState != 4) {
				return;
			}

			// The transaction is complete, all requests must now be cleared from the list.
			// Note that when we got a HTTP error, the requests are still removed, since
			// the HTTP request is no longer pending.
			this.completeRequests(requestIds);

			// HTTP request must have succeeded
			switch (xmlHttpRequest.status) {
				case 401: /* Unauthorized */
					// Indicate that the user is no longer logged in, and
					// he must re-authenticate. This must be done through the
					// normal logon page, so here we just paralyze the Request.
					// The exact reason for the paralyzation can be found in the
					// headers.
					var reason = xmlHttpRequest.getResponseHeader('X-grommunio-Hresult');
					if (reason === 'MAPI_E_INVALID_WORKSTATION_ACCOUNT') {
						this.paralyze(Zarafa.core.data.ParalyzeReason.SESSION_INVALID);
					} else {
						this.paralyze(Zarafa.core.data.ParalyzeReason.SESSION_EXPIRED);
					}
					return;
				case 500: /* Internal Server Error */
					// The connection is present, if there still are queued
					// HTTP requests, we can send the next one right now.
					if (this.hasQueuedHttpRequests()) {
						this.dequeueHttpRequest();
					}

					// Indicate that the request failed
					// inside the server and exit the function.
					this.receiveFailure(xmlHttpRequest);
					return;
				case 200: /* OK */
					// The connection is present, if there still are queued
					// HTTP requests, we can send the next one right now.
					if (this.hasQueuedHttpRequests()) {
						this.dequeueHttpRequest();
					}
					break;
				default: /* Connection errors */
					// Interrupt the connection
					this.interrupt();

					// Clone the XMLHttpRequest and queue it
					// for when the connection is restored.
					if (xmlHttpRequest.preventRetry !== true) {
						var clone = this.cloneRequest(xmlHttpRequest);
						this.queueHttpRequest(clone);
					}
					return;
			}

			// Depending on the response type, convert it into a data Object.
			if (xmlHttpRequest.responseText) {
				// JSON response
				response = Ext.decode(xmlHttpRequest.responseText);
			} else {
				// XML response is not supported
				this.receiveFailure(xmlHttpRequest);
				return;
			}

			// Check for empty response, sometimes the PHP server doesn't bother with
			// responding with any data.
			if (Ext.isEmpty(response) || Ext.isEmpty(response.zarafa)) {
				this.receiveFailure(xmlHttpRequest);
				return;
			}

			responseRouter.receive(response);
		},

		/**
		 * Called when the {@link XMLHttpRequest} object indicates a problem
		 * in the returned data from the server. This will call
		 * {@link Zarafa.core.ResponseRouter#receiveFailure receiveFailure} on
		 * the {@link Zarafa.core.ResponseRouter ResponseRouter}.
		 * @param {XMLHttpRequest} xmlHttpRequest The xmlHttpRequest which contains the problem
		 * @private
		 */
		receiveFailure: function(xmlHttpRequest)
		{
			var responseRouter = container.getResponseRouter();
			var requestData = xmlHttpRequest.requestData;
			if (xmlHttpRequest.requestDataEncoded) {
				requestData = Ext.decode(requestData);
			}
			responseRouter.receiveFailure(requestData, xmlHttpRequest);
		},

		/**
		 * Resets the {@link Zarafa.core.Request Request} object and prepares it for
		 * accepting new calls to {@link #addRequest} or {@link #singleRequest}.
		 */
		reset: function()
		{
			queuedRequests = [];

			zarafaTag = {
				'zarafa': {}
			};
			hasJson = false;
			hasData = false;
		},

		/**
		 * Adds a single request to the Request object. This is a combination of a module name, id and action.
		 * The parameters objected will be encoded and added into the action tag.
		 * The callbacks are used when the responds for this specific request has returned.
		 * @param {String} moduleName name of the module to communicate with (i.e. 'addressbooklistmodule')
		 * @param {String} actionType action to perform (i.e. 'list' or 'globaladdressbook')
		 * @param {Object} actionData data that will included in the action tag (i.e. { restriction: { name: 'piet' } })
		 * @param {Zarafa.core.data.AbstractResponseHandler} responseHandler The response handler which must be
		 * used for handling the responds for this specific request.
		 * @return {String} The unique Request ID which was assigned to this request.
		 */
		addRequest: function(moduleName, actionType, actionData, responseHandler)
		{
			if (Ext.isEmpty(zarafaTag)) {
				throw 'Request object not initialised. Call reset() first';
			}
			if (hasData) {
				throw 'Request object initialized with RAW data';
			}

			var requestId = this.getRequestId(moduleName);
			this.queueRequest(requestId);

			if (Ext.isDefined(responseHandler)) {
				container.getResponseRouter().addRequestResponseHandler(requestId, responseHandler);
			}

			actionData = Ext.value(actionData, {});

			// create new module tag if not present
			if (!Ext.isDefined(zarafaTag.zarafa[moduleName])) {
				zarafaTag.zarafa[moduleName] = {};
			}

			// create new module id tag if not present
			if (!Ext.isDefined(zarafaTag.zarafa[moduleName][requestId])) {
				zarafaTag.zarafa[moduleName][requestId] = {};
			}

			// add action data
			zarafaTag.zarafa[moduleName][requestId][actionType] = actionData;

			// JSON data was added
			hasJson = true;

			return requestId;
		},

		/**
		 * Adds a single request to the Request object. Opposed to {@link #addRequest} this will be raw data.
		 * The parameters object will be placed to the action tag.
		 * The callbacks are used when the responds for this specific request has returned.
		 * @param {String} moduleName name of the module to communicate with (i.e. 'addressbooklistmodule')
		 * @param {String} actionType action to perform (i.e. 'list' or 'globaladdressbook')
		 * @param {Object} actionData data that will included in the action tag
		 * @param {Zarafa.core.data.AbstractResponseHandler} responseHandler The response handler which must be
		 * used for handling the responds for this specific request.
		 * @return {String} The unique Request ID which was assigned to this request.
		 */
		addDataRequest: function(moduleName, actionType, actionData, responseHandler)
		{
			if (Ext.isEmpty(zarafaTag)) {
				throw 'Request object not initialised. Call reset() first';
			}
			if (hasJson) {
				throw 'Request object initialized with JSON data';
			}
			if (hasData) {
				throw 'Request object already contains RAW data';
			}

			var requestId = this.getRequestId(moduleName);
			this.queueRequest(requestId);

			if (Ext.isDefined(responseHandler)) {
				container.getResponseRouter().addRequestResponseHandler(requestId, responseHandler);
			}

			// add action data
			zarafaTag = actionData;

			// Raw data was added
			hasData = true;

			return requestId;
		},

		/**
		 * Calls {@link #prepareHttpRequest} to actually create the request using passed data,
		 * and calls {@link #sendHttpRequest} to send request.
		 * @param {String} url (optional) url to post to. Defaults to {@link #defaultUrl}
		 * @param {Object} headers (optional) headers to apply to the request. Defaults to {@link #defaultHeaders}
		 */
		send: function(url, headers)
		{
			if (Ext.isEmpty(zarafaTag)) {
				throw 'Request object not initialised. Call reset() first';
			}

			if (Ext.isEmpty(queuedRequests)) {
				throw 'No requests have been added. Use addRequest()';
			}

			if (!this.isParalyzed()) {
				var xmlHttpRequest = this.prepareHttpRequest(hasJson ? Ext.encode(zarafaTag) : zarafaTag, hasJson, url, headers);
				if (this.isInterrupted() || this.hasQueuedHttpRequests()) {
					this.queueHttpRequest(xmlHttpRequest);
				} else {
					this.sendHttpRequest(xmlHttpRequest);
				}
			}

			// All data has been send, reset the zarafaTag to
			// prevent sending the same request twice.
			zarafaTag = undefined;
			hasJson = false;
			hasData = false;
		},

		/**
		 * Convenience method for performing a single JSON request to the server.
		 * It calls {@link #reset reset()}, {@link #addRequest addRequest()}, and {@link #send send()} in turn.
		 * @param {String} moduleName name of the module to communicate with (i.e. 'addressbooklistmodule')
		 * @param {String} actionType action to perform (i.e. 'list' or 'globaladdressbook')
		 * @param {Object} actionData data that will included in the action tag (i.e. { restriction: { name: 'piet' } })
		 * @param {Zarafa.core.data.AbstractResponseHandler} responseHandler The response handler which must be
		 * used for handling the responds for this specific request.
		 * @return {String} The unique request ID which was assigned to this transaction object.
		 */
		singleRequest: function(moduleName, actionType, actionData, responseHandler)
		{
			var requestId;

			this.reset();
			requestId = this.addRequest(moduleName, actionType, actionData, responseHandler);
			this.send();

			return requestId;
		},

		/**
		 * Method to {@link window.XMLHttpRequest#abort} given request discarding whatever the data
		 * added to response by server.
		 * @param {XMLHttpRequest} xhrObj Object of the request made previously.
		 */
		abortRequest: function(xhrObj)
		{
			xhrObj.preventRetry = true;
			xhrObj.abort();
		},

		/**
		 * Method gives active {@link XMLHttpRequest request} object based on given requestId which
		 * was used to send the request to server.
		 * @param {String} requestId Unique identifier of the request made previously.
		 */
		getActiveRequest: function(requestId)
		{
			return activeRequests[requestId];
		}
	};
})());
Ext.namespace('Zarafa.core');

/**
 * @class Zarafa.core.ResponseRouter
 * @extends Ext.util.Observable
 *
 * The router for Responses to requests made by the {@link Zarafa.core.Request Request} object.
 * Each response is delivered to its destination {@link Ext.data.Store store} through the
 * {@link Zarafa.core.data.AbstractResponseHandler ResponseHandler}.
 * Upon receiving a response, the ResponseRouter will determine if it is a response to
 * a direct request from the {@link Zarafa.core.Request Request Object} or a notification
 * generated by the PHP-side.
 * If the response came from a request from the {@link Zarafa.core.Request Request Object} a
 * {@link Zarafa.core.data.AbstractResponseHandler ResponseHandler} will have been registered
 * by the {@link Ext.data.DataProxy Proxy} which made the request. If the
 * {@link Zarafa.core.data.AbstractResponseHandler ResponseHandler} is not available the
 * response is considered a Notification, in which case the
 * {@link Zarafa.core.data.NotificationResolver NotificationResolver} is used to generate
 * a special {@link Zarafa.core.data.AbstractNotificationResponseHandler ResponseHandler} which can
 * update the {@link Ext.data.Store stores} which contain the updated {@link Ext.data.Record records}.
 */
Zarafa.core.ResponseRouter = Ext.extend(Ext.util.Observable, {
	/**
	 * The collection of {@link Zarafa.core.data.AbstractResponseHandler ResponseHandlers}
	 * stored by using the moduleid of the outgoing request.
	 * @property
	 * @type Object
	 */
	responseHandlers: undefined,

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

		Ext.applyIf(config, {
			responseHandlers: {}
		});

		this.addEvents(
			/**
			 * @event beforereceive
			 * Main event which is triggered when data has been received from
			 * the PHP server, and is about to be processed by the router.
			 * @param {Object} data The data which was received by the router.
			 */
			'beforereceive',
			/**
			 * @event afterreceive
			 * Main event which is triggered when the data which has been received from
			 * the PHP server has been processed.
			 * @param {Object} data The data which was received by the router.
			 */
			'afterreceive',
			/**
			 * @event receiveexception
			 * Main event which is triggered when a Request has failed, or the response
			 * doesn't contain sufficient data to handle it.
			 * @param {Object} requestdata The request data which was send to the server.
			 * @param {Object} xmlHttpRequest The raw browser response object.
			 */
			'receiveexception',
			/**
			 * @event response
			 * Main event which is triggered when a response has been received
			 * from the PHP server.
			 * @param {String} module The module name for this response
			 * @param {String} id The module id for this response
			 * @param {Object} response The response which was received.
			 * @param {Number} timestamp The {@link Date#getTime timestamp} on which the response was received
			 * @return {Boolean} False to cancel the handling of the response by ResponseHandlers
			 */
			'response'
		);

		Ext.apply(this, config);

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

	/**
	 * Register a {@link Zarafa.core.data.AbstractResponseHandler ResponseHandler} to the
	 * Response Router. This handler will be used to handle the Response for the request
	 * with the given identifier.
	 * @param {String} id The unique request identifier on which the ResponseHandler
	 * must be registerdd.
	 * @param {Zarafa.core.data.AbstractResponseHandler} handler The ResponseHandler
	 * which must be registered for the given id.
	 */
	addRequestResponseHandler: function(id, handler)
	{
		this.responseHandlers[id] = handler;
	},

	/**
	 * Get a {@link Zarafa.core.data.AbstractResponseHandler ResponseHandler} from
	 * the {@link #responseHandlers} which has been registered for the given module identifier.
	 * This will automatically deregister the handler to prevent it being used twice.
	 * @param {String} id The unique request identifier on which the ResponseHandler
	 * could be registered.
	 * @return {Zarafa.core.data.AbstractResponseHandler} The registered response handler.
	 * @private
	 */
	getRequestResponseHandler: function(id)
	{
		var handler = this.responseHandlers[id];
		this.removeRequestResponseHandler(id);
		return handler;
	},

	/**
	 * Removes a {@link Zarafa.core.data.AbstractResponseHandler ResponseHandler} from the
	 * {@link Zarafa.core.ResponsRouter ResponsRouter} to prevent it being called twice.
	 * @param {String} id The unique request identifier of the ResponseHandler which will be
	 * deregistered from {@link Zarafa.core.ResponsRouter ResponsRouter}.
	 */
	removeRequestResponseHandler: function(id)
	{
		delete this.responseHandlers[id];
	},

	/**
	 * Get a {@link Zarafa.core.data.AbstractResponseHandler ResponseHandler} from
	 * the {@link Zarafa.core.data.NotificationResolver NotificationResolver}. This
	 * will construct a special {@link Zarafa.core.data.AbstractResponseHandler ResponseHandler}
	 * which is dedicated to handling this specific notification.
	 * @param {String} module The module from which the notification originated.
	 * @param {Object} data The response data which was send as notification, this is used
	 * to determine which {@link Ext.data.Store stores} are affected by this notification.
	 * @private
	 */
	getNotificationResponseHandler: function(module, data)
	{
		return container.getNotificationResolver().getHandlerForResponse(module, data);
	},

	/**
	 * This will have a Response from the PHP server and will process it
	 * by delivering the Response over the configured route to the destination.
	 *
	 * @param {Object} data The data object which was received and
	 * must be processed.
	 */
	receive: function(data)
	{
		this.fireEvent('beforereceive', data);

		this.processResponse(data);

		this.fireEvent('afterreceive', data);
	},

	/**
	 * This will report a Receive failure which can be triggered when
	 * the request failed with HTTP-error (e.g. 404) or when the response data
	 * was incomplete. This will go through all
	 * {@link Zarafa.core.data.AbstractResponseHandlers ResponseHandlers} which
	 * are affected by this error to report the error using:
	 * {@link Zarafa.core.data.AbstractResponseHandlers#responseFailure responseFailure}.
	 * @param {Object} requestdata The request data which was send to the server.
	 * @param {Object} xmlHttpRequest The raw browser response object.
	 */
	receiveFailure: function(requestData, xmlHttpRequest)
	{
		this.fireEvent('receiveexception', requestData, xmlHttpRequest);

		// Without requestData we cannot report the request failure
		// back to the requestee.
		if (!Ext.isObject(requestData)) {
			return;
		}

		// Find all registered ResponseHandlers which are affected by this
		// failure and propagate the responseFailure to them,
		Ext.iterate(requestData.zarafa, function(moduleName, modules) {
			Ext.iterate(modules, function(moduleId, moduleData) {
				var handler = this.getRequestResponseHandler(moduleId);
				if (!Ext.isEmpty(handler)) {
					handler.responseFailure(xmlHttpRequest);
				}
			}, this);
		}, this);
	},

	/**
	 * Resolve all response data into a collection of {@link Zarafa.core.data.AbstractResponseHandlers}
	 * which will be in charge of handling all responses. The responsehandlers will be returned into
	 * an array which is sorted on priority, meaning that the response handlers should be called in
	 * the order in which they are listed in the array.
	 * @param {Object} data The data from the response
	 * @return {Array} Array of objects containing the data
	 * @private
	 */
	resolveResponseHandlers: function(data)
	{
		var responses = [];
		var notifications = [];

		// Iterate over all modules and ids, and obtain the corresponding
		// ResponseHandlers. We separate the RequestResponses from the notifications.
		Ext.iterate(data, function(moduleName, modules) {
			// iterate over module ids
			Ext.iterate(modules, function(moduleId, moduleData) {
				var handler = {
					moduleName: moduleName,
					moduleId: moduleId,
					moduleData: moduleData
				};

				// Check if a RequestResponse Handler is registered for this moduleId
				handler.handler = this.getRequestResponseHandler(moduleId);
				if (!Ext.isEmpty(handler.handler)) {
					responses.push(handler);
					return;
				}

				// No RequestResponse was available, this is a notification
				handler.handler = this.getNotificationResponseHandler(moduleName, moduleData);
				if (!Ext.isEmpty(handler.handler)) {
					notifications.push(handler);
					return;
				}
			}, this);
		}, this);

		// Return the objects as a single array, the RequestResponses have highest priority,
		// followed by the notifications.
		return responses.concat(notifications);
	},

	/**
	 * Perform a transaction through the {@link Zarafa.core.data.AbstractResponseHandler ResponseHandler}.
	 *
	 * @param {Zarafa.core.data.AbstractResponseHandler} handler The handler which should be used
	 * for handling the response.
	 * @param {String} moduleName The name of the module from which the response was received
	 * @param {String} moduleId The unique id which is used to correlate the response to a request
	 * @param {Object} moduleData The data which was provided for the given moduleName/moduleId
	 * @param {Number} timestamp The {@link Date#getTime timestamp} on which the response was received
	 * @private
	 */
	handleResponse: function(handler, moduleName, moduleId, moduleData, timestamp)
	{
		var success = true;

		// Begin the Response transaction. When the transaction cannot
		// be started, we bail out immediately.
		if (handler.start(moduleName, moduleId, moduleData, timestamp) === false) {
			return;
		}

		if (Ext.isObject(moduleData)) {
			// Iterate over each action, and start handling them with
			// the corresponding actionData. If one of the handlers indicate
			// failure, we only change the 'success' status, but continue
			// with the other handlers. The 'success' status itself will
			// be used when the transaction is being completed.
			Ext.iterate(moduleData, function(actionType, actionData) {
				if (handler.handle(actionType, actionData) === false) {
					success = false;
				}
			}, this);
		}

		// Complete the transaction.
		handler.done(success);
	},

	/**
	 * Processes a response from the server. the data is examined for
	 * any error tags and call error listeners.
	 * @param {Object} jsonData A JSON object containing server response.
	 * @private
	 */
	processResponse: function(jsonData)
	{
		// check for errors, these are global errors which can be generated from grommunio.php
		// file, module level errors will be handled by module callback functions.
		if (!Ext.isEmpty(jsonData.zarafa.error)) {
			// Fire the exception event on the DataProxy like this, as the response cannot be matched to a specific proxy.
			Ext.data.DataProxy.fireEvent('exception', Ext.data.DataProxy, 'remote', null, null, jsonData.zarafa, null);
			return;
		}

		// Create the timestamp which is used as receive date for the current response
		var timestamp = new Date().getTime();

		// when all's fine, unpack the server response and obtain the responseHandlers
		var handlers = this.resolveResponseHandlers(jsonData.zarafa);

		for (var i = 0, len = handlers.length; i < len; i++) {
			var handler = handlers[i];
			var moduleName = handler.moduleName;
			var moduleId = handler.moduleId;
			var moduleData = handler.moduleData;
			var responseHandler = handler.handler;

			if (this.fireEvent('response', moduleName, moduleId, moduleData, timestamp) !== false) {
				this.handleResponse(responseHandler, moduleName, moduleId, moduleData, timestamp);
			}
		}
	}
});
Ext.namespace('Zarafa.core');
Ext.namespace('Zarafa.core.themes');

/**
 * Central theme registration for all available color themes.
 * This file registers all themes at once instead of requiring separate JS files per theme.
 *
 * @class Zarafa.core.Themes
 */
Zarafa.core.Themes = {
	/**
	 * List of all available unified themes (CSS-based, no separate theme files)
	 */
	themes: [
		{ name: 'purple', displayName: _('Purple') },
		{ name: 'orange', displayName: _('Orange') },
		{ name: 'lime', displayName: _('Lime') },
		{ name: 'magenta', displayName: _('Magenta') },
		{ name: 'highcontrast', displayName: _('High Contrast') },
		{ name: 'cyan', displayName: _('Cyan') },
		{ name: 'teal', displayName: _('Teal') },
		{ name: 'indigo', displayName: _('Indigo') },
		{ name: 'red', displayName: _('Red') },
		{ name: 'green', displayName: _('Green') },
		{ name: 'brown', displayName: _('Brown') }
	],

	/**
	 * Register all themes with the container
	 */
	registerAll: function()
	{
		Ext.each(this.themes, function(themeInfo) {
			// Create a proper class name (capitalize first letter of each word)
			var className = 'Theme' + themeInfo.name.charAt(0).toUpperCase() + themeInfo.name.slice(1);

			// Create a dynamic theme class for each theme
			Zarafa.core.themes[className] = Ext.extend(Zarafa.core.ThemePlugin, {});

			// Register the theme plugin
			container.registerPlugin(new Zarafa.core.PluginMetaData({
				name: themeInfo.name,
				displayName: themeInfo.displayName,
				allowUserDisable: false,
				allowUserVisible: false,
				pluginConstructor: Zarafa.core.themes[className]
			}));
		}, this);
	}
};

// Register all themes when ready
Zarafa.onReady(function() {
	Zarafa.core.Themes.registerAll();
});
Ext.namespace('Zarafa.core');

/**
 * @class Zarafa.core.URLActionMgr
 * @extends Object
 *
 * The {@link Zarafa.core.URLActionMgr URLActionMgr} singleton. This singleton will be
 * used to execute actions that are passed in URL, such as mailto action
 * @singleton
 */
Zarafa.core.URLActionMgr = Ext.extend(Ext.util.Observable, {
	/**
	 * The list of registered {@link Zarafa.core.URLAction action handlers}.
	 * Multiple action handlers can be registered for a single url action.
	 * @property
	 * @type Object
	 * @private
	 */
	mappings: undefined,

	/**
	 * @constructor
	 */
	constructor: function()
	{
		this.mappings = {};
	},

	/**
	 * Registers the url action configuration to the specified action.
	 * This will make sure if already a handler is registered for a particular action
	 * then another handler is appended, so both handlers will be executed.
	 *
	 * @param {String} action The name of the URL action to register on.
	 * @param {Object} handlerConfig The config containing handler info for action.
	 * In this object handler is a required key.
	 */
	register: function(action, handlerConfig)
	{
		if(!this.mappings[action]){
			this.mappings[action] = [];
		}

		// Register/Merge action configuration options to mappings array.
		this.mappings[action] = this.mappings[action].concat(handlerConfig);
	},

	/**
	 * Function will be called to execute any URL action, function will find proper handler
	 * based on action and will call handlers which are registered for the action.
	 *
	 * @param {Object} data The data containing action and action data.
	 */
	execute: function(data)
	{
		Ext.iterate(data, function(action, actionData) {
			// check if we have any handlers registered for this action
			var handlers = this.mappings[action];

			if(handlers) {
				Ext.each(handlers, function(handler) {
					// call handler to execute url action
					handler.handler.call(handler.scope || this, action, actionData, handler);
				});
			}
		}, this);
	}
});

Zarafa.core.URLActionMgr = new Zarafa.core.URLActionMgr();Ext.namespace('Zarafa.core');

/**
 * @class Zarafa.core.Util
 * Utility class
 * @singleton
 */
Zarafa.core.Util =
{
	/**
	 * @cfg {Boolean} skipRequester This flag specifies if confirm dialog will skip.
	 * If it is true then confirm dialog will not show otherwise it will show.
	 */
	skipRequester: false,

	/**
	 * Sort an array of objects
	 *
	 * @param {Array} list The array of objects which must be sorted
	 * @param {String} order The order in which the array must be sorted, can be either "ASC" or "DESC"
	 * @param {String/Function} sort (optional) Can either be a string, a function or nothing.
	 * A String argument will be used to sort the array on the given fieldname. A function will be used
	 * as comparison function for sorting. When not provided the default comparison function shall be
	 * used which compares items based on their value.
	 * @return {Array} The sorted array
	 * @method
	 */
	sortArray: function(list, order, sort)
	{
		var collection = new Ext.util.MixedCollection();
		var fn;
		collection.addAll(list);

		if (Ext.isFunction(sort)) {
			// Use given sort function
			fn = sort;
		} else if (Ext.isString(sort)) {
			// Sort on object attribute value, by default this is a
			// numeric sort. We need to wrap the function to
			// access the attribute value.
			fn = function(obj1, obj2) {
				return Zarafa.core.Util.numericComparison(obj1[sort], obj2[sort]);
			};
		} else {
			// Sort the object, by default this is a numeric sort
			fn = this.numericComparison;
		}

		collection.sort(order, fn);
		return collection.getRange();
	},

	/**
	 * Comparison function for comparing two numbers.
	 * This function can be used for sorting in for example the functions
	 * {@link Ext.util.MixedCollection#sort} and {@link #sortArray}.
	 *
	 * @param {Number} number1 The first number to compare
	 * @param {Number} number2 The second number to compare
	 * @return {Number} A positive value when number1 is greater then number2.
	 * A negative value when number2 is greater then number1. 0 when both objects are equal.
	 */
	numericComparison: function(number1, number2)
	{
		if(!Ext.isDefined(number2) || !Ext.isDefined(number1)) {
			return -1;
		}
		return number1 - number2;
	},

	/**
	 * Comparison function for comparing two strings using case sensitive comparison.
	 * This function can be used for sorting in for example the functions
	 * {@link Ext.util.MixedCollection#sort} and {@link #sortArray}.
	 *
	 * @param {String} string1 The first object to compare
	 * @param {String} string2 The second object to compare
	 * @return {Number} A positive value when string1 is greater then string2.
	 * A negative value when string2 is greater then string1. 0 when both objects are equal.
	 */
	caseSensitiveComparison: function(string1, string2)
	{
		return string1 > string2 ? 1 : (string1 < string2 ? -1 : 0);
	},

	/**
	 * Comparison function for comparing two strings using case insensitive comparison.
	 * This function can be used for sorting in for example the functions
	 * {@link Ext.util.MixedCollection#sort} and {@link #sortArray}.
	 *
	 * @param {String} string1 The first object to compare
	 * @param {String} string2 The second object to compare
	 * @return {Number} A positive value when string1 is greater then string2.
	 * A negative value when string2 is greater then string1. 0 when both objects are equal.
	 */
	caseInsensitiveComparison: function(string1, string2)
	{
		var v1 = String(string1).toUpperCase(), v2 = String(string2).toUpperCase();
		return Zarafa.core.Util.caseSensitiveComparison(v1, v2);
	},

	/**
	 * Remove all duplicate entries from an array of objects
	 *
	 * @param {Array} list The array of objects which must be filtered
	 * @param {String} attr (optional) The fieldname on which objects must be compared to detect duplicates
	 * If not provided the comparison is done on the object value.
	 * @return {Array} The array with only unique elements
	 * @method
	 */
	uniqueArray: function(list, attr)
	{
		var collection = new Ext.util.MixedCollection();

		Ext.each(list, function(item) {
			var value = attr ? item[attr] : item;

			if (!collection.containsKey(value)) {
				collection.add(value, item);
			}
		}, this);

		return collection.getRange();
	},

	/**
	 * This is a utility function to trim strings in a single or multi dimensional array
	 * this function should only be used with array which has only string values, otherwise
	 * this function will give unpredicated results without any error
	 * @param {Array} arrayToTrim array whose values should be trimmed
	 * @return {Array} trimmed array
	 */
	trimStringArray: function(arrayToTrim)
	{
		var tmpArray = arrayToTrim;
		arrayToTrim = [];				// reset array

		for(var index = 0, len = tmpArray.length; index < len; index++) {
			if(Array.isArray(tmpArray[index])) {
				// recursively call the same function
				arrayToTrim.push(Zarafa.core.Util.trimStringArray(tmpArray[index]));
			} else {
				if(Ext.isString(tmpArray[index]) && !Ext.isEmpty(tmpArray[index].trim())) {
					arrayToTrim.push(tmpArray[index].trim());
				}
			}
		}

		return arrayToTrim;
	},

	/**
	 * This is a utility function to check if any multi dimensional array contains any token
	 * @param {Array} multiDimArray single or multi-dimensional array
	 * @param {Mixed} tokenToSearch token to search in array
	 * @param {Boolean} caseInSensitive case sensitive match
	 * @param {Boolean} matchPartial comparison will also check for partial match
	 * @return {Boolean} true if token is found else false
	 */
	inArray: function(multiDimArray, tokenToSearch, caseInSensitive, matchPartial)
	{
		for(var index = 0, len = multiDimArray.length; index < len; index++) {
			if(Array.isArray(multiDimArray[index])) {
				// recursively call the same function
				if(Zarafa.core.Util.inArray(multiDimArray[index], tokenToSearch, caseInSensitive, matchPartial)) {
					return true;
				}
			} else {
				if(tokenToSearch) {
					if(matchPartial) {
						if(caseInSensitive) {
							if(multiDimArray[index].indexOf(tokenToSearch.toLowerCase()) != -1 || tokenToSearch.indexOf(multiDimArray[index].toLowerCase()) != -1) {
								return true;
							}
						} else {
							if(multiDimArray[index].indexOf(tokenToSearch) != -1) {
								return true;
							}
						}
					} else {
						if(caseInSensitive) {
							if(multiDimArray[index].toLowerCase() === tokenToSearch.toLowerCase()) {
								return true;
							}
						} else {
							if(multiDimArray[index] === tokenToSearch) {
								return true;
							}
						}
					}
				}
			}
		}

		return false;
	},

	/**
	 * This is a utility function to copy all the properties of config to obj recursively.
	 * Ext.apply() does the same thing but this extension preserves child object's previous values
	 * instead of overwriting it with new values.
	 * @param {Object} obj The receiver of the properties
	 * @param {Object} config The source of the properties
	 * @param {Object} defaults A different object that will also be applied for default values
	 * @return {Object} returns obj
	 */
	applyRecursive: function(obj, config, defaults)
	{
		if(defaults) {
			obj = Zarafa.core.Util.applyRecursive(obj, defaults);
		}

		if(obj && config && (Ext.isObject(config) || Array.isArray(config))) {
			for(var key in config) {
				if (Ext.isDefined(obj[key]) && Ext.isObject(obj[key])) {
					// object with child elements, so call this function recursively
					obj[key] = Zarafa.core.Util.applyRecursive(obj[key], config[key]);
				} else if (Ext.isObject(config[key])) {
					obj[key] = Zarafa.core.Util.applyRecursive({}, config[key]);
				} else {
					// normal copy
					obj[key] = config[key];
				}
			}
		}

		return obj;
	},

	/**
	 * This is a utility function to copy all the properties of config to obj recursively,
	 * if they don't already exist. Ext.appplyIf() does the same thing but this extension
	 * preserves child object's previous values instead of overwriting it with new values.
	 * @param {Object} obj The receiver of the properties
	 * @param {Object} config The source of the properties
	 * @param {Object} defaults A different object that will also be applied for default values
	 * @return {Object} returns obj
	 */
	applyIfRecursive: function(obj, config, defaults)
	{
		if(defaults) {
			obj = Zarafa.core.Util.applyIfRecursive(obj, defaults);
		}

		if(obj && config && (Ext.isObject(config) || Array.isArray(config))) {
			for(var key in config) {
				if(Ext.isDefined(obj[key]) && Ext.isObject(obj[key])) {
					// object with child elements, so call this function recursively
					obj[key] = Zarafa.core.Util.applyIfRecursive(obj[key], config[key]);
				} else if (Ext.isObject(config[key])) {
					obj[key] = Zarafa.core.Util.applyIfRecursive({}, config[key]);
				} else if(!Ext.isDefined(obj[key])) {
					// normal copy
					obj[key] = config[key];
				}
			}
		}

		return obj;
	},

	/**
	 * Recursively flattens a JSON object hierarchy into a flat list of key/value pairs.
	 *
	 * For example: The object:
	 *	{
	 *  	'zarafa': {
	 *  		'v1': {
	 *  			'main': {
	 *  				'settingA': 'value1',
	 *					'settingB': 'value2'
	 *  			}
	 *  		}
	 *  	}
	 *	}
	 *
	 * will be flattened to:
	 *	{
	 * 		'zarafa/v1/main/settingA': 'value1',
	 * 		'zarafa/v1/main/settingB': 'value2'
	 *	}
	 *
	 * @param {Object} obj The object to flatten.
	 * @param {String} sep The separator which must be applied between each path-key (e.g: '/')
	 * @param {String} path The basePath for the keys inside the object.
	 * @return {Object} The flattened object
	 */
	flattenObject: function(obj, sep, path)
	{
		var ret = {};

		var separator = '';
		if (Ext.isEmpty(path)) {
			path = '';
		} else {
			separator = sep;
		}

		if (Ext.isObject(obj)) {
			for (var key in obj) {
				Ext.apply(ret, Zarafa.core.Util.flattenObject(obj[key], sep, path + separator + key));
			}
		} else {
			ret[path] = obj;
		}

		return ret;
	},

	/**
	 * Function will return object which has all keys in lowercase
	 *
	 * @param {Object} obj The object.
	 * @return {Object} The object with all keys as lowercase
	 */
	objectKeysToLowerCase: function(object)
	{
		var key, keys = Object.keys(object);
		var newObject={};
		for (var i=0; i<keys.length; i++) {
			key = keys[i];
			newObject[key.toLowerCase()] = object[key];
		}
		return newObject;
	},

	/**
	 * Split a string in pieces based on whether each piece matches the passed
	 * pattern. It returns both the pieces that match and that do not match the
	 * pattern.
	 * @param {String} str The input string to be split up
	 * @param {RegExp} pattern The regex pattern used to be split the string
	 * @return {Array} The array of pieces
	 * @private
	 */
	splitStringByPattern: function(str, pattern)
	{
		var cutOffPoints = [0];
		var found;
		// Find the cutOffPoints in the str
		while((found = pattern.exec(str)) !== null){
			if(found.index!==0){
				cutOffPoints.push(found.index);
			}
			if(pattern.lastIndex < str.length){
				cutOffPoints.push(pattern.lastIndex);
			}
		}
		// Cut the string up into the pieces based on the cutOffPoints
		var parts = [];
		if(cutOffPoints.length > 1){
			for(var i=0;i<cutOffPoints.length;i++){
				// Use the current and the next cutOffPoint to calculate the number of character we need to extract.
				if(Ext.isDefined(cutOffPoints[i+1])){
					parts.push(str.slice(cutOffPoints[i], cutOffPoints[i+1]));
				} else {
					parts.push(str.slice(cutOffPoints[i]));
				}
			}
		} else {
			parts = [str];
		}
		return parts;
	},

	/**
	 * Convenience method to check if a given point (x,y) is inside a box (x,y,width,height)
	 * @param {Object} box a (x, y, with, height) tuple
	 * @param {Number} x point x component
	 * @param {Number} y point y component
	 * @return {Boolean} True if the given point is inside the box.
	 */
	inside: function(box, x, y)
	{
		return (x >= box.x && x < (box.x + box.width) && y >= box.y && y < (box.y + box.height));
	},

	/**
	 * Restrict a box containing 'x', 'y', 'width' and 'height' properties,
	 * to fall completely inside the given container box (which has the same properties).
	 * This ensures that the position of the box left-top corner will always be inside
	 * the container, and will attempt to move the x and y coordinates in such a way
	 * that the full width and height will fit inside the container box.
	 * @param {Object} container The container box
	 * @param {Object} box The box
	 * @return {Object} The updated box position
	 */
	restrictBox: function(container, box)
	{
		// Ensure we copy the box
		box = Ext.apply({}, box);

		// For all our calculations, we at least
		// want the top-left position to be inside
		// the container.
		box.x = Math.max(container.x, box.x);
		box.y = Math.max(container.y, box.y);

		// We can only correct the x-coordinate
		// if it doesn't equal are most-left position
		if (box.x > container.x) {
			var overflowX = Math.max(0, (box.x + box.width) - (container.x + container.width));
			box.x -= overflowX;
		}

		// We can only correct the x-coordinate
		// if it doesn't equal are most-upper position
		if (box.y > container.y) {
			var overflowY = Math.max(0, (box.y + box.height) - (container.y + container.height));
			box.y -= overflowY;
		}

		// Technically we could have moved the boxed
		// beyond our minimum top-left position. Fix
		// that here, and just accept that we will
		// overflow...
		box.x = Math.max(container.x, box.x);
		box.y = Math.max(container.y, box.y);

		return box;
	},

	/**
	 * Function will get the index of the start and end position of the current selection.
	 * @param {Ext.Element/HTMLElement} obj Reference to the textfield
	 * @return {Object} An object containing a 'start' and 'end' field which indicate
	 * the current position of the start and end of the selection. The fields will
	 * be -1 if there is no selection.
	 */
	getSelectionRange: function(obj)
	{
		obj = obj.dom || obj;

		if (obj.selectionStart || (obj.selectionStart == "0")) {
			return { start: obj.selectionStart, end: obj.selectionEnd };
		}
	},

	/**
	 * Function which will set the current caret position in the given textfield.
	 * @param {Ext.Element/HTMLElement} obj Reference to the textfield or the id of the textfield
	 * @param {Number} position The desired position for the caret
	 */
	setCaretPosition: function(obj, position)
	{
		Zarafa.core.Util.setSelectionRange(obj, position, position);
	},

	/**
	 * Function makes a selection in the textfield
	 * @param {Ext.Element/HTMLElement} obj Reference to the textfield or the id of the textfield
	 * @param {Number} selectionStart Index of the starting position of the selection
	 * @param {Number} selectionEnd Index of the ending position of the selection
	 */
	setSelectionRange: function(obj, selectionStart, selectionEnd)
	{
		obj = obj.dom || obj;

		if (obj && typeof obj == "object" && obj.setSelectionRange) {
			obj.focus();
			obj.setSelectionRange(selectionStart, selectionEnd);
		}
	},

	/**
	 * Checks whether a string is a valid email address using a regular expression. This check is
	 * not performed to the extend as the RFC 5322 specification describes the format.
	 * @param {String} str String to be validated
	 * @return {Boolean} Returns true when the email address is valid
	 */
	validateEmailAddress: function(str)
	{
		//TODO make a better check
		var filter = new RegExp(/^([^<]*<){0,1}(([a-z0-9\.\!\#\$\%\&\'\*\+\-\/\=\?\^\_\`\{\|\}\~])+\@(([a-z0-9\-])+\.)+([a-z0-9]{2,5})+)>{0,1}$|^\[[^\]]+\]$/i);
		if(Ext.isString(str) && str.length > 0 ){
			return filter.test(str);
		} else {
			return false;
		}
	},

	/**
	 * Merge 2 objects containing event handlers into a single object,
	 * while preserving scopes. This can be used when a {@link Ext.Component}
	 * receives a {@link Ext.Component#listeners} object while it also needs
	 * to add listeners in the same way (while it cannot use {@link Ext.Component#on}).
	 * By default the source listeners will {@link Function#createInterceptor intercept}
	 * the functions from the target.
	 *
	 * @param {Object} target The object with event handlers into which the new
	 * handlers will be merged.
	 * @param {Object} sourcec The object with event handlers which will be merged
	 * into the target
	 * @param {Boolean} intercept (optional) False to use {@link Function#createSequence}
	 * rather then {@link Function#createInterceptor}.
	 * @return {Object} The merged object
	 */
	mergeListeners: function(target, source, intercept)
	{
		// Make sure we have a target
		target = Ext.value(target, {});

		// Take the scope from our source, otherwise we default to the target
		var scope = source['scope'] || target['scope'];

		// Go over all listeners
		for (var key in source) {
			if (key === 'scope') {
				continue;
			}

			// Always create a delegate, the default scope inside the
			// target might might be equal to the scope of the source.
			var handler = source[key].createDelegate(scope);

			// Add the event handler
			if (Ext.isDefined(target[key])) {
				if (intercept !== false) {
					target[key] = target[key].createInterceptor(handler);
				} else {
					target[key] = target[key].createSequence(handler);
				}
			} else {
				target[key] = handler;
			}
		}

		return target;
	},

	/**
	 * Encode string in utf-16 hex format
	 * @param {String} str String to be converted into utf-16 encoded hex string
	 * @return {String} The utf-16 encoded string in hex format
	 */
	encode_utf16: function(str)
	{
		var num1, num2;
		var result = '';

		if(!Ext.isString(str)) {
		  str = String(str);
		}

		for (var i = 0, len = str.length; i < len; i++) {
		  num2 = (str.charCodeAt(i) >> 8).toString(16);
		  num1 = (str.charCodeAt(i) & 0xff).toString(16);

		  result += String.leftPad(String(num1), 2, '0');
		  result += String.leftPad(String(num2), 2, '0');
		}

		return result;
	},

	/**
	 * Function converts string in to hexadecimal string
	 * @param {String} str ASCII string to be converted into hexadecimal representation.
	 * @return {String} The hexadecimal representation as a string.
	 */
	stringToHex: function(string)
	{
		string = string.toUpperCase();
		var hexString = '';
		for(var i=0; i < string.length; i++) {
			hexString += '' + string.charCodeAt(i).toString(16);
		}

		return hexString;
	},

	/**
	 * Function converts hexadecimal string in to ASCII string
	 * @param {String} hexString The hexadecimal string
	 * @return {String} converted ASCII string
	 */
	hexToString: function(hexString)
	{
		hexString = hexString.toString();
		var string = '';
		for (var i = 0; i < hexString.length; i += 2) {
			string += String.fromCharCode(parseInt(hexString.substr(i, 2), 16));
		}
		return string.toLowerCase();
	},

	/**
	 * Function used to reload the webapp.
	 */
	reloadWebapp: function()
	{
		if ( container.fireEvent('beforewebappreload') !== false ){
			this.disableLeaveRequester();
			window.location.reload();
		}
	},

	/**
	 * Function is use to register onbeforeunload event to show confirm dialog
	 * when user trying to leave the page.
	 */
	enableLeaveRequester: function()
	{
		window.onbeforeunload = this.onBeforeUnload.createDelegate(this);
	},

	/**
	 * Function is use to deregistering onbeforeunload event.
	 */
	disableLeaveRequester: function()
	{
		window.onbeforeunload = null;
	},

	/**
	 * Check if one of the opened content panels contains unsaved user changes.
	 * @return {Boolean} true if there are unsaved changes.
	 */
	hasUnsavedContentPanels: function()
	{
		var contentPanels = Zarafa.core.data.ContentPanelMgr && Zarafa.core.data.ContentPanelMgr.contentPanels;

		if (!(contentPanels instanceof Ext.util.MixedCollection)) {
			return false;
		}

		return contentPanels.findIndexBy(function(contentPanel) {
			return (
				contentPanel &&
				contentPanel.isDestroyed !== true &&
				Ext.isFunction(contentPanel.isXType) &&
				contentPanel.isXType('zarafa.recordcontentpanel') &&
				contentPanel.recordComponentPlugin &&
				contentPanel.recordComponentPlugin.isChangedByUser === true
			);
		}) !== -1;
	},

	/**
	 * Function which is show confirm dialog when user trying to leave the page.
	 * It will also check the value of {#Zarafa.core.Util.skipRequester skipRequester}
	 * If it's true then it will not show the confirm dialog.
	 * It only shows the confirm dialog when there are unsaved changes.
	 */
	onBeforeUnload: function()
	{
		if(this.skipRequester) {
			this.skipRequester = false;
			return;
		}

		if (this.hasUnsavedContentPanels()) {
			return _('Your changes will be lost if you leave this page now.');
		}
	},

	/**
	 * Helper function which used to show the {@link Ext.MessageBox}
	 * and {@link window.Zarafa.core.Util#disableLeaveRequester disable Leave Requester}
	 *
	 * @param {Object} options The options object which used to construct the {@link Ext.MessageBox}.
	 * @param {boolean} custom True to show {@link Zarafa.common.dialogs.MessageBox.addCustomButtons messageBox}.
	 */
	showMessageBox: function(options, custom)
	{
		Zarafa.core.Util.disableLeaveRequester();
		if (custom){
			Zarafa.common.dialogs.MessageBox.addCustomButtons(options);
		} else {
			Ext.MessageBox.show(options);
		}
	},

	/**
	 * Helper function which add the given text into
	 * browser clipboard data.
	 *
	 * @param {String} text The text which is going to
	 * add in clipboard data.
	 */
	copyToClipboard: function (text)
	{
		var doc = document;
		if (Zarafa.core.BrowserWindowMgr.isMainWindowActive() === false) {
			doc = Zarafa.core.BrowserWindowMgr.getActive().document;
		}

		var textArea = doc.createElement("textarea");
		textArea.setAttribute("id", "copyTextArea");
		doc.body.appendChild(textArea);
		textArea.innerText = text;
		textArea.select();
		doc.execCommand("copy");
		doc.body.removeChild(textArea);
	},

	/**
	 * Function which will convert given input into Bytes.
	 * @param {Number} input the number which needs to be converted in bytes.
	 * @param {String} sizeUnit string which indicates given input is in which unit.
	 * And value must be 'KB' or 'MB'.
	 * @return {Number} input in Bytes.
	 */
	convertToBytes: function (input, sizeUnit)
	{
		sizeUnit = sizeUnit.toUpperCase();
		if (sizeUnit === 'MB') {
			input = input * Math.pow(1024,2);
		} else if (sizeUnit === 'KB') {
			input = input * 1024;
		}
		return input;
	},

	/**
	 * Function which will convert given input into
	 * KB or MB size unit.
	 * @param {Number} input the number in bytes which needs to be converted.
	 * @param {String} sizeUnit string which should indicate
	 * in which unit input is needed to be converted.
	 * And value must be 'KB' or 'MB'.
	 * @return {Number} input in KB or MB.
	 */
	convertBytesToKBorMB: function (input, sizeUnit)
	{
		sizeUnit = sizeUnit.toUpperCase();
		if (sizeUnit === 'MB') {
			input = input / Math.pow(1024,2);
		} else if (sizeUnit === 'KB') {
			input = input / 1024;
		}
		return input;
	},

	/**
	 * Function gets the formatted date according to the given/selected language, as formats differ
	 * according to languages.
	 * @param {String} value The input date value
	 * @param {String} language The current language
	 * @param {String} format The current format of the date
	 * @return {Array} newDate The date array which is to be set
	 */
	getDateByLanguageFormat: function(value, language, format)
	{
		if (!Ext.isDefined(language)) {
			language = container.getSettingsModel().get('zarafa/v1/main/language');
		}
		// We extract the language code from the language since the language retrieved
		// from the settings is in the format 'en_GB.UTF-8'
		language = language.split('.')[0];

		var rawValue = value;
		var formattedDate = [];
		var newDate = new Date();
		var regExSpecialChar = /[^0-9a-zA-Z]/g;

		switch (language) {
			case 'ja_JP':
			case 'hu_HU':
				// For format 'Y/m/d'
				if (regExSpecialChar.test(rawValue)) {
					rawValue = rawValue.split(regExSpecialChar);
					// Since the format is 'Y/m/d', the first part might be the year
					// In that case, we change the value to maintain the 'd/m/Y' format.
					if (rawValue[0].length === 4) {
						[rawValue[0], rawValue[2]] = [rawValue[2], rawValue[0]];
					}
					rawValue = rawValue.join('/');
				}
				formattedDate = this.getFormattedDate(rawValue, '/');
				break;
			case 'de_DE':
			case 'nb_NO':
			case 'fi_FI':
			case 'cs_CZ':
				// For format 'd.m.Y' and 'd. m. Y.'
				formattedDate = this.getFormattedDate(rawValue, '.');
				break;
			case 'ca_ES':
			case 'es_ES':
			case 'pl_PL':
				// For format 'd/m/aa' and 'd/m/r'
				// We remove alphabets part if any, to maintain the format.
				rawValue = rawValue.replace(/[a-zA-Z]/g, "");
				formattedDate = this.getFormattedDate(rawValue, '/');
				break;
			case 'nl_NL':
				// For format 'd-m-Y'
				formattedDate = this.getFormattedDate(rawValue, '-');
				break;
			case 'en_US':
				// For format 'm/d/Y', since this is a different format, we need to specifically format
				// the date as the 'getFormattedDate' function supports formats similar to 'd/m/Y'.
				var result = [];
				// If the selected language is English(US) but grommunio Web is using the local
				// format 'd/m/Y', then no need to format according to 'm/d/Y' format
				if (format === 'd/m/Y') {
					formattedDate = this.getFormattedDate(rawValue, '/');
					break;
				}
				rawValue = rawValue.replace(/[a-zA-Z\s]/g, "");
				// If the date value has a separator and is not a single digit or two digit day
				if (regExSpecialChar.test(rawValue) && rawValue.length > 2) {
					rawValue = rawValue.split(regExSpecialChar);
					// Since the format is 'm/d/Y', we interchange the day with the month.
					[result[0], result[1]] = [rawValue[1], rawValue[0]];
					if (rawValue[0] > 12) {
						result = rawValue.slice();
					}
					var year = rawValue[2];
					// Formatting the year if incomplete or incorrect
					if (year && year.length !== 4) {
						rawValue[2] = ('20' + year).length === 4 ? '20' + year : newDate.getFullYear().toString();
					}
					result[2] = rawValue[2];
				} else {
					// If there is no separator, we format the date and then
					// swap the month with the day if needed (according to 'm/d/Y').
					result = this.getFormattedDate(rawValue, '/');
					if (result[0] <= 12 && result[1] <= 12 && rawValue.length > 2) {
						[result[0], result[1]] = [result[1], result[0]];
					}
				}
				formattedDate = result.slice();
				break;
			case 'en_GB':
			case 'ru_RU':
			case 'it_IT':
			case 'pt_BR':
			case 'tr_TR':
			case 'sl_SI':
			case 'zh_TW':
			case 'fr_FR':
			case 'da_DR':
			default:
				// For format 'd/m/Y'
				formattedDate = this.getFormattedDate(rawValue, '/');
				break;
		}
		return formattedDate;
	},

	/**
	 * Function corrects the date string if it has no separator or it is incomplete/incorrect.
	 * @param {String} rawValue The input date value
	 * @param {String} separator The separator of the date according to the format
	 * @return {Array} dateArray The date array which is to be set
	 */
	getFormattedDate: function(rawValue, separator)
	{
		var dateArray, hasNoSeparator = false;
		var currentFullDate = new Date();
		var currentYear = currentFullDate.getFullYear();
		var currentMonth = currentFullDate.getMonth() + 1;
		var currentDate = currentFullDate.getDate();

		var regExSeparator = /[^0-9a-zA-Z]/g;
		if (!regExSeparator.test(rawValue)) {
			hasNoSeparator = true;
		}

		if (hasNoSeparator) {
			rawValue = rawValue.replace(/[a-zA-Z]/g, "");
			switch (rawValue.length) {
				// Example: '06062020' -> '06/06/2020'
				case 8:
					rawValue = rawValue.slice(0, 2) + separator + rawValue.slice(2, 4) + separator + rawValue.slice(4);
					break;
				// Example: '662020' -> '06/06/2020'
				case 6:
					rawValue = rawValue.slice(0, 1) + separator + rawValue.slice(1, 2) + separator + rawValue.slice(2);
					break;
				// Example: '06' -> '06/02/2020'
				case 2:
					rawValue = rawValue + separator + currentMonth + separator + currentYear;
					break;
				// Example: '6' -> '06/02/2020'
				case 1:
					rawValue = '0' + rawValue + separator + currentMonth + separator + currentYear;
					break;
				default:
					rawValue = currentDate + separator + currentMonth + separator + currentYear;
					break;
			}
			dateArray = rawValue.split(separator);
		} else {
			rawValue = rawValue.replace(/[a-zA-Z\s]/g, "");
			dateArray = rawValue.split(regExSeparator);

			// Check for the year value
			if (dateArray.length > 1 && rawValue.length > 0) {
				var year = dateArray[2];
				if (year && year.length !== 4) {
					dateArray[2] = ('20' + year).length === 4 ? '20' + year : currentYear;
				}
			} else {
				dateArray = [currentDate, currentMonth, currentYear];
			}
		}
		// If value of month part is greater than 12, swap with day.
		if (dateArray[1] > 12) {
			[dateArray[0], dateArray[1]] = [dateArray[1], dateArray[0]];
		}
		return dateArray;
	},

	/**
	 * Function converts data into hexadecimal representation.
	 * @param {String} str string to be converted into hexadecimal representation.
	 * @return {String} An ASCII string containing the hexadecimal representation of string.
	 */
	bin2hex : function (str)
	{
		return str.split('').reduce((str, aChar) =>
			str += `0${aChar.charCodeAt().toString(16)}`.slice(-2), '');
	},

	/**
	 * Function decodes a hexadecimally encoded binary string.
	 * @param {String} str Hexadecimal representation of data.
	 * @return {String} Returns the binary representation of the given data.
	 */
	hex2bin : function (str)
	{
		return str.match(/.{1,2}/g).reduce((str, hex) =>
			str += String.fromCharCode(parseInt(hex, 16)), '');
	}
};
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.AbstractResponseHandler
 * @extends Object
 *
 * The main interface used by the {@link Zarafa.core.ResponseRouter ResponseRouter}
 * for handling responses received by the PHP-side.
 *
 * This class is used as 'scoped' object, containing all information for handling
 * a specific server-response or server-notification.
 *
 * The {@link #start} function will be called directly before the
 * {@link Zarafa.core.ResponseRouter ResponseRouter} starts processing
 * the Response from the server which is intended for this handler.
 *
 * After this function the {@link #handle} function will be invoked
 * for each object inside the responds. Subclasses should not overwrite
 * this function directly, instead they can implement the specific handles
 * they need. Each {@link Zarafa.core.Actions} action will invoke the handler
 * with the same name but with 'do' prefixed. For example, the action
 * {@link Zarafa.core.Actions#item} will cause the function 'doItem' to be
 * invoked. Likewise, the action {@link Zarafa.core.Actions#open} will cause
 * the function 'doOpen' to be invoked.
 *
 * Finally the {@link #done} function is called to indicate the entire
 * response has been processed. The handler can then complete the
 * transaction by updating the {@link Ext.data.Store}.
 */
Zarafa.core.data.AbstractResponseHandler = Ext.extend(Object, {
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		Ext.apply(this, config);
	},

	/**
	 * The handler which is invoked when no valid response was returned
	 * for the Request. This could be a HTTP-404 error, or the PHP-side returned
	 * an invalid object which could not be parsed by a {@link Ext.data.DataReader DataReader}.
	 * @param {Object} responseObject The raw browser response object (e.g.: XMLHttpRequest)
	 * @param {Object} args (optional) A Javascript error object if the response could not
	 * have been parsed by a {@link Ext.data.DataReader DataReader}.
	 */
	responseFailure: Ext.emptyFn,

	/**
	 * The main handler to begin a Response processing transaction.
	 * @param {String} moduleName The name of the PHP module from which this response originated.
	 * @param {String} moduleId The unique identifier for the PHP response.
	 * @param {Object} data The entire response object which will be processed during this transaction.
	 * @param {Number} timestamp The {@link Date#getTime timestamp} on which the response was received
	 * @return {Boolean} False when the given data object cannot be handled by this response handler,
	 * and the transaction must be canceled.
	 */
	start: Ext.emptyFn,

	/**
	 * The handler for handling the given command from a Response.
	 * @param {Zarafa.core.Actions} action The action which must be executed.
	 * @param {Object} data The response object belonging to the given command.
	 * @return {Boolean} False when action could not be handled successfully. This will
	 * not cancel the transaction itself, but rather causes the 'success' argument for the
	 * {@link #done} function to be false.
	 */
	handle: function(action, data)
	{
		var handler = this['do' + Ext.util.Format.capitalize(action)];
		if (Ext.isFunction(handler)) {
			return handler.call(this, data);
		}
	},

	/**
	 * The main handler to complete a Response processing transaction.
	 * @param {Boolean} success True if no errors were returned from the PHP-side.
	 */
	done: Ext.emptyFn
});
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.CallbackQueue
 * @extends Ext.util.Observable
 *
 * A Special queue containing callback functions which can be used
 * to serialize a series of actions/validations where it is possible
 * that each action/validation might use a {@link Ext.MessageBox MessageBox}
 * to request the user input. While the {@link Ext.MessageBox MessageBox}
 * is opened, the queue will be paused until the user has closed the message.
 */
Zarafa.core.data.CallbackQueue = Ext.extend(Ext.util.Observable, {
	/**
	 * The queue which contains all the tasks which should be run
	 * @property
	 * @type Array
	 * @private
	 */
	queue: undefined,

	/**
	 * Indicates that {@link #run} has been called, and the various callbacks
	 * in the {@link #queue} are being executed.
	 * @property
	 * @type Boolean
	 * @private
	 */
	running: false,

	/**
	 * Internal counter to keep track at which task is currently being executed,
	 * this will be reset when the {@link #run queue starts} and will be updated
	 * after the {@link #onCompleteTask completion of each task}.
	 * @property
	 * @type Number
	 * @private
	 */
	currentTask: 0,

	/**
	 * The function which is provided to {@link #run} which should be called as
	 * soon as the last task has been called. It will be called with the scope
	 * {@link #completionScope}.
	 * @property
	 * @type Function
	 * @private
	 */
	completionFn: undefined,

	/**
	 * The scope for the {@link #completionFn} which is provided to {@link #run}.
	 * @property
	 * @type Object
	 * @private
	 */
	completionScope: undefined,

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

		// Initialize the queue
		config.queue = [];

		this.addEvents(
			/**
			 * @event startqueue
			 * Event which is fired when the queue is about to start
			 * @param {Zarafa.core.data.CallbackQueue} queue The queue which is started
			 */
			'startqueue',
			/**
			 * @event completequeue
			 * Event which is fired when the last callback from the queue has been completed.
			 * @param {Zarafa.core.data.CallbackQueue} queue The queue which has completed
			 * @param {Boolean} success True if all callbacks were executed successfully
			 */
			'completequeue',
			/**
			 * @event starttask
			 * Event which is fired when a task has started
			 * @param {Zarafa.core.data.CallbackQueue} queue The queue to which the task belongs
			 * @param {Function} fn The task function which has been started
			 * @param {Object} scope The scope of the function which has started
			 */
			'starttask',
			/**
			 * @event completetask
			 * Event which is fired when a task has completed
			 * @param {Function} fn The task function which has been completed
			 * @param {Object} scope The scope of the function which has completed
			 * @param {Boolean} success True if the callback was executed successfully
			 */
			'completetask'
		);

		Ext.apply(this, config);

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

	/**
	 * Add a callback function to the end of the {@link #queue}. When {@link #run} is called,
	 * this function will be executed with the provided scope.
	 * @param {Function} fn The function which will be called
	 * @param {Object} scope The scope in which the function will be called
	 */
	add: function(fn, scope)
	{
		this.queue.push({ fn: fn, scope: scope });
	},

	/**
	 * Remove a callback function which was previously registered using {@link #add}.
	 * This will search for the task in the {@link #queue} which matches the given
	 * function and scope exactly, and removed it from the {@link #queue}.
	 * @param {Function} fn The function to remove
	 * @param {Object} scope The scope of the function
	 */
	remove: function(fn, scope)
	{
		var queue = this.queue;

		for (var i = 0; i < queue.length; i++) {
			var task = queue[i];

			// Check if this is the same function and scope,
			// if so remove it from the queue.
			if (task.fn === fn && task.scope === scope) {
				this.queue.splice(i, 1);
				return;
			}
		}
	},

	/**
	 * Run all Callback functions in the {@link #queue}. This will fire the {@link #start} event,
	 * and starts all tasks {@link #currentTask starting with} 0.
	 */
	run: function(fn, scope)
	{
		this.running = true;
		this.currentTask = 0;
		this.completionFn = fn;
		this.completionScope = scope;

		this.fireEvent('startqueue', this);

		this.doTask(this.currentTask);
	},

	/**
	 * @returns {Boolean} True if the queue is currently running
	 */
	isRunning: function()
	{
		return this.running;
	},

	/**
	 * Called to execute the task at the specified location in the {@link #queue}.
	 * This will execute the callback function, and pass the {@link #onCompleteTask} function
	 * as callback function.
	 * @param {Number} index The index in the queue of the callback to execute
	 * @private
	 */
	doTask: function(index)
	{
		var task = this.queue[index];
		this.fireEvent('starttask', this, task.fn, task.scope);
		task.fn.call(task.scope, this.onCompleteTask.createDelegate(this, [ task ], 1));
	},

	/**
	 * Callback function for the task which was executed using {@link #doTask}. This
	 * checks if the task was successfully completed and if so if this was the last task.
	 * If either the task has failed, or this was the last task, the queue will be stopped,
	 * and the {@link #complete} event will be fired. Otherwise {@link #doTask} will be
	 * called to execute the {@link #currentTask next task}.
	 * @param {Boolean} success True if the task completed successfully
	 * @param {Object} task The task which was completed successfully
	 * @private
	 */
	onCompleteTask: function(success, task)
	{
		// If not provided, then assume success
		success = success !== false;

		this.fireEvent('completetask', this, task.fn, task.scope, success);

		if (success && this.currentTask < (this.queue.length - 1)) {
			this.currentTask++;
			this.doTask(this.currentTask);
		} else {
			if (this.completionFn) {
				this.completionFn.call(this.completionScope, success);
				this.completionFn = undefined;
				this.completionScope = undefined;
			}
			this.fireEvent('completequeue', this, success);
			this.running = false;
		}
	}
});
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.ContentPanelMgr
 * @extends Ext.util.Observable
 * @singleton
 *
 * The {@link Zarafa.core.ui.ContentPanel ContentPanel} manager. Each
 * {@link Zarafa.core.ui.ContentPanel ContentPanel} which is created
 * must register itself to this manager.
 *
 * This manager can be used by Plugins to hook into
 * {@link Zarafa.core.ui.ContentPanel ContentPanel} from the moment they
 * are being displayed.
 */
Zarafa.core.data.ContentPanelMgr = Ext.extend(Ext.util.Observable, {
	/**
	 * The collection of {@link Zarafa.core.ui.ContentPanel contentPanels}
	 * which have been registered to this manager.
	 * @property
	 * @type Ext.util.MixedCollection
	 */
	contentPanels: undefined,
	/**
	 * @constructor
	 */
	constructor: function()
	{
		this.contentPanels = new Ext.util.MixedCollection();
		this.addEvents([
			/**
			 * @event createcontentpanel
			 * Fires when a {@link Zarafa.core.ui.ContentPanel contentPanel} is being created.
			 * @param {Zarafa.core.ui.ContentPanel} contentPanels The
			 * {@link Zarafa.core.ui.ContentPanel} which is being created.
			 */
			'createcontentpanel',
			/**
			 * @event destroycontentpanel
			 * Fires when a {@link Zarafa.core.ui.ContentPanel contentPanel} is being destroyed.
			 * @param {Zarafa.core.ui.ContentPanel} contentPanel The
			 * {@link Zarafa.core.ui.ContentPanel} which is being destroyed.
			 */
			'destroycontentpanel'
		]);

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

	/**
	 * Register a {@link Zarafa.core.ui.ContentPanel contentPanel} with the {@link Zarafa.core.data.ContentPanelMgr ContentPanelMgr}.
	 * @param {Zarafa.core.ui.ContentPanel} contentPanel the {@link Zarafa.core.ui.ContentPanel contentPanel} which must be registered.
	 */
	register: function(contentPanel)
	{
		contentPanel.on('show', this.onContentPanelShow, this);
		contentPanel.on('close', this.onContentPanelHide, this);
		contentPanel.on('hide', this.onContentPanelHide, this);

		this.contentPanels.add(contentPanel);
	},

	/**
	 * UnRegister a {@link Zarafa.core.ui.ContentPanel contentPanel} from the {@link Zarafa.core.data.ContentPanelMgr ContentPanelMgr}.
	 * @param {Zarafa.core.ui.ContentPanel} contentPanel the {@link Zarafa.core.ui.ContentPanel contentPanel} which must be unregistered.
	 */
	unregister: function(contentPanel)
	{
		contentPanel.un('show', this.onContentPanelShow, this);
		contentPanel.un('close', this.onContentPanelHide, this);
		contentPanel.un('hide', this.onContentPanelHide, this);

		this.contentPanels.remove(contentPanel);
	},

	/**
	 * Event handler which is raised before the {@link Zarafa.core.ui.ContentPanel contentPanel} is
	 * being shown. This will raise the {@link #createcontentpanel} event to allow any
	 * listeners to hook into further events coming from the given
	 * {@link Zarafa.core.ui.ContentPanel contentPanel}.
	 *
	 * @param {Ext.Container} contentPanel The contentPanel which is being rendered
	 * @private
	 */
	onContentPanelShow: function(contentPanel)
	{
		this.fireEvent('createcontentpanel', contentPanel);
	},

	/**
	 * Event handler which is raised when the {@link Zarafa.core.ui.ContentPanel contentPanel} is
	 * being hidden. This will raise the {@link #destroycontentpanel} event to inform
	 * any listeners that their {@link Zarafa.core.ui.ContentPanel contentPanel} is going to disappear.
	 *
	 * @param {Ext.Container} contentPanel The contentPanel which is being destroyed
	 * @private
	 */
	onContentPanelHide: function(contentPanel)
	{
		this.fireEvent('destroycontentpanel', contentPanel);
	},

	/**
	 * Find instances of {@link Zarafa.core.ui.ContentPanel contentPanel} from the {@link Zarafa.core.data.ContentPanelMgr ContentPanelMgr}.
	 * @param {Ext.Component} component the class name of the contentPanel for which we should perform the search.
	 * @return {Ext.util.MixedCollection} {@link Ext.util.MixedCollection MixedCollection} of contentPanels for specified
	 * component.
	 */
	getContentPanelInstances: function(component)
	{
		return this.contentPanels.filterBy(function(contentPanel) {
			return contentPanel instanceof component;
		});
	}
});

Zarafa.core.data.ContentPanelMgr = new Zarafa.core.data.ContentPanelMgr();
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.IPFStoreMgr
 * @extends Ext.util.Observable
 *
 * The {@link Zarafa.core.data.IPFStore IPFStore} manager. Each
 * {@link Zarafa.core.data.IPFStore IPFStore} which is created
 * must register itself to this manager.
 *
 * {@link Zarafa.core.data.IPFStoreMgr} will then handle inter-store
 * communications. Listening to {@link Zarafa.core.data.IPFStoreMgr IPFStoreMgr}
 * events allow UI components and {@link Zarafa.core.data.IPFStore stores} to
 * detect {@link Zarafa.core.data.IPFRecord record} editing in dialogs.
 * @singleton
 */
Zarafa.core.data.IPFStoreMgr = Ext.extend(Ext.util.Observable, {
		/**
		 * The collection of {@link Zarafa.core.data.IPFStore stores}
		 * which have been registered to this manager.
		 * @property
		 * @type Ext.util.MixedCollection
		 */
		IPFStores: undefined,
		/**
		 * @constructor
		 */
		constructor: function()
		{
			this.IPFStores = new Ext.util.MixedCollection();
			this.addEvents([
				/**
				 * @event storeexception
				 * Fires when the {@link Zarafa.core.data.IPFStore IPFStore} fired an {@link Zarafa.core.data.IPFStore#exception exception}.
				 * @param {Zarafa.core.data.IPFStore} store The store on which the exception occurred
				 * @param {Ext.data.DataProxy} proxy The proxy from where the exception event originated
				 * @param {String} type The value of this parameter will be either 'response' or 'remote'.
				 * @param {String} action Name of the action.
				 * @param {Object} options The options for the action that were specified in the request.
				 * @param {Object} response If the 'type' argument was 'response' this will be the raw browser response object,
				 * otherwise it will be the decoded response object
				 * @param {Mixed} arg (optional) Additional arguments for the exception
				 */
				'storeexception',
				/**
				 * @event beforerecordsave
				 * Fires before a Message {@link Zarafa.core.data.IPFRecord record} is being saved.
				 * @param {Zarafa.core.data.IPFStore} store The {@link Zarafa.core.data.IPFStore store} in which the
				 * {@link Zarafa.core.data.IPFRecord record} is located while being saved.
				 * @param {Object} data An object containing the data that is to be saved.
				 * The object will contain a key for each appropriate action, with an array
				 * of updated data for each record.
				 */
				'beforerecordsave',
				/**
				 * @event afterrecordsave
				 * Fires when a Message {@link Zarafa.core.data.IPFRecord record} has been saved.
				 * @param {Zarafa.core.data.IPFStore} store The {@link Zarafa.core.data.IPFStore store} in which the
				 * {@link Zarafa.core.data.IPFRecord record} is located while being saved.
				 * @param {Mixed} obj The object containing {@link Zarafa.core.data.IPFRecord record} which has been saved. This
				 * {@link Zarafa.core.data.IPFRecord record} is the most recent version which came from the server.
				 */
				'afterrecordsave',
				/**
				 * @event afterrecordupdate
				 * Fires when a Message {@link Zarafa.core.data.IPFRecord record} has been updated.
				 * @param {Zarafa.core.data.IPFStore} store The {@link Zarafa.core.data.IPFStore store} to which the
				 * {@link Zarafa.core.data.IPFRecord record} belongs.
				 * @param {Zarafa.core.data.IPFrecord} record The most recent version which came from the server.
				 * @param {String} operation The update operation being performed.
				 * ({@link Ext.data.Record#EDIT}, {@link Ext.data.Record#REJECT}, {@link Ext.data.Record#COMMIT}).
				 */
				'afterrecordupdate',
				/**
				 * @event recordremove
				 * Fires when a Message {@link Zarafa.core.data.IPFRecord record} has been removed from the server
				 * @param {Zarafa.core.data.IPFStore} store The {@link Zarafa.core.data.IPFStore store} to which the
				 * {@link Zarafa.core.data.IPFRecord record} belongs.
				 * @param {Zarafa.core.data.IPFrecord} record The most recent version which came from the server.
				 */
				'recordremove',
				/**
				 * @event afterrecordwrite
				 * Fires when {@link Zarafa.core.IPFRecord IPFRecords} are modified (created, updated, destroyed, opened)
				 * on {@link Zarafa.core.data.IPFStore IPFStore}.
				 * @param {Zarafa.core.data.IPFStore} store The {@link Zarafa.core.data.IPFStore store} to which the
				 * {@link Zarafa.core.data.IPFRecord record} belongs.
				 * @param {String} action [Ext.data.Api.actions.create|update|destroy|open]
				 * @param {Object} result The 'data' picked-out out of the response for convenience.
				 * @param {Ext.Direct.Transaction} res
				 * @param {Zarafa.core.data.IPFrecord[]} records The most recent version of the records
				 * which came from the server.
				 */
				'afterrecordwrite'
			]);

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

		/**
		 * Register a {@link Zarafa.core.data.IPFStore IPFStore} with the {@link Zarafa.core.data.IPFStoreMgr IPFStoreMgr}.
		 * @param {Zarafa.core.data.IPFStore} IPFStore the {@link Zarafa.core.data.IPFStore IPFStore} which must be registered.
		 * @param {Boolean} serverOnly True to register the {@link Zarafa.core.data.IPFStore IPFStore} only for
		 * events originating directly from the server.
		 */
		register: function(IPFStore, serverOnly)
		{
			if (!serverOnly) {
				IPFStore.on('beforesave', this.onBeforeSave, this);
				IPFStore.on('save', this.onSave, this);
				IPFStore.on('update', this.onUpdate, this);
				IPFStore.on('remove', this.onRemove, this);
			}
			IPFStore.on('write', this.onWrite, this);
			IPFStore.on('exception', this.onException, this);

			this.IPFStores.add(IPFStore);
		},

		/**
		 * Unregister a {@link Zarafa.core.data.IPFStore IPFStore} from the {@link Zarafa.core.data.IPFStoreMgr IPFStoreMgr}.
		 * @param {Zarafa.core.data.IPFStore} IPFStore the {@link Zarafa.core.data.IPFStore IPFStore} which must be unregistered.
		 * @param {Boolean} serverOnly True if the {@link Zarafa.core.data.IPFStore IPFStore} was {@link #register registered}
		 * with the serverOnly argument.
		 */
		unregister: function(IPFStore, serverOnly)
		{
			if (!serverOnly) {
				IPFStore.un('beforesave', this.onBeforeSave, this);
				IPFStore.un('save', this.onSave, this);
				IPFStore.un('update', this.onUpdate, this);
				IPFStore.un('remove', this.onRemove, this);
			}
			IPFStore.un('write', this.onWrite, this);
			IPFStore.un('exception', this.onException, this);

			this.IPFStores.remove(IPFStore);
		},

		/**
		 * Filter the list of {@link Zarafa.core.data.IPFRecord records} to contain
		 * only those which can be {@link Zarafa.core.data.IPFRecord#eventPropagation propagated}
		 * over a new event to other {@link Zarafa.core.data.IPFStore stores}.
		 *
		 * @param {Zarafa.core.data.IPFRecord/Array} records The record or records to filter
		 * out the non-propagatable records.
		 * @return {Zarafa.core.data.IPFRecord/Array} All propagatable records
		 * @private
		 */
		getPropagatableRecords: function(records)
		{
			var propagateRecords = [];

			if (!Array.isArray(records)) {
				records = [ records ];
			}

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

				if (record instanceof Zarafa.core.data.IPFRecord && record.hasEventPropagation()) {
					propagateRecords.push(record);
				}
			}

			return propagateRecords;
		},

		/**
		 * Event handler which is triggered when a {@link Zarafa.core.data.IPFRecord record} is about to
		 * be saved in a {@link Zarafa.core.data.IPFStore store}. This function will inform the
		 * {@link Zarafa.core.data.IPFStore IPFStores} about the event through the
		 * {@link #beforerecordsave} event.
		 *
		 * @param {Zarafa.core.data.IPFStore} IPFStore
		 * @param {Object} data An object containing the data that is to be saved.
		 * The object will contain a key for each appropriate action, with an array
		 * of updated data for each record.
		 * @private
		 */
		onBeforeSave: function(IPFStore, data)
		{
			var propagateData = {};

			for (var key in data) {
				var propagateRecords = this.getPropagatableRecords(data[key]);
				if (!Ext.isEmpty(propagateData)) {
					propagateData[key] = propagateRecords;
				}
			}

			this.fireEvent('beforerecordsave', IPFStore, propagateData);
		},

		/**
		 * Event handler which is triggered when a {@link Zarafa.core.data.IPFRecord record} has been
		 * saved in a {@link Zarafa.core.data.IPFStore store}. This function will inform the
		 * {@link arafa.core.data.IPFStore IPFStores} about the event through the
		 * {@link #afterrecordsave} event.
		 *
		 * @param {Zarafa.core.data.IPFStore} IPFStore
		 * @param {Number} batch The identifier for the batch that was saved.
		 * @param {Object} data An object containing the data that is to be saved.
		 * The object will contain a key for each appropriate action, with an array
		 * of updated data for each record.
		 * @private
		 */
		onSave: function(IPFStore, batch, data)
		{
			this.fireEvent('afterrecordsave', IPFStore, data);
		},

		/**
		 * Event handler which is triggered when a {@link Zarafa.core.data.IPFRecord record} has been
		 * updated from the server. This function will inform the {@link Zarafa.core.data.IPFStore IPFStores}
		 * about the event through the {@link #afterrecordupdate} event.
		 *
		 * @param {Zarafa.core.data.IPFStore} IPFStore
		 * @param {Zarafa.core.data.IPFRecord} record The Record which has been updated
		 * @param {String} operation The update operation being performed.
		 * ({@link Ext.data.Record#EDIT}, {@link Ext.data.Record#REJECT}, {@link Ext.data.Record#COMMIT}).
		 * @private
		 */
		onUpdate: function(IPFStore, record, operation)
		{
			if (record.hasEventPropagation()) {
				this.fireEvent('afterrecordupdate', IPFStore, record, operation);
			}
		},

		/**
		 * Event handler which is triggered when a {@link Zarafa.core.data.IPFRecord record} has been
		 * removed from the server. This function will inform the {@link Zarafa.core.data.IPFStore IPFStores}
		 * about the event through the {@link #recordremove} event.
		 *
		 * @param {Zarafa.core.data.IPFStore} IPFStore
		 * @param {Zarafa.core.data.IPFRecord} record The Record which has been updated
		 * @param {String} The index at which the record was removed
		 * @private
		 */
		onRemove: function(IPFStore, record, index)
		{
			if (record.hasEventPropagation()) {
				this.fireEvent('recordremove', IPFStore, record);
			}
		},

		/**
		 * Event handler will be called on successful completion of any CRUD operation,
		 * the difference between this event and {@link #save} event is that {@link #save}
		 * event will pass only data set that is modified not the record that is modified.
		 * so this event removes burden of finding record from the record set.
		 *
		 * @param {Zarafa.core.data.IPFStore} store The {@link Zarafa.core.data.IPFStore store} to which the
		 * {@link Zarafa.core.data.IPFRecord record} belongs.
		 * @param {String} action [Ext.data.Api.actions.create|update|destroy|open]
		 * @param {Object} data The 'data' picked-out out of the response for convenience.
		 * @param {Ext.Direct.Transaction} res
		 * @param {Zarafa.core.data.IPFrecord[]} records The most recent version of the records
		 * which came from the server.
		 * @private
		 */
		onWrite: function(IPFStore, action, data, transaction, records)
		{
			var propagateRecords = this.getPropagatableRecords(records);
			if (!Ext.isEmpty(propagateRecords)) {
				this.fireEvent('afterrecordwrite', IPFStore, action, data, transaction, propagateRecords);
			}
		},

		/**
		 * Event handler which will be called when the store has fired the {@link Ext.data.Store#exception} event.
		 * This will look up which store has exactly fired the event, and will fire the {@link #exception} event.
		 * @param {Ext.data.DataProxy} proxy The proxy from where the exception event originated
		 * @param {String} type The value of this parameter will be either 'response' or 'remote'.
		 * @param {String} action Name of the action.
		 * @param {Object} options The options for the action that were specified in the request.
		 * @param {Object} response If the 'type' argument was 'response' this will be the raw browser response object,
		 * otherwise it will be the decoded response object
		 * @param {Mixed} arg (optional) Additional arguments for the exception
		 * @private
		 */
		onException: function(proxy, type, action, options, response, arg)
		{
			var proxies = Ext.pluck(this.IPFStores.items, 'proxy');
			if (!Ext.isEmpty(proxies)) {
				var storeIndex = proxies.indexOf(proxy);
				this.fireEvent('storeexception', this.IPFStores.get(storeIndex), proxy, type, action, options, response, arg);
			}
		},

		/**
		 * Executes the specified function once for every {@link Zarafa.core.data.IPFStore store} in the collection,
		 * passing the following arguments:
		 * <div class="mdetail-params"><ul>
		 * <li><b>item</b>: Zarafa.core.data.IPFStore<p class="sub-desc">The collection item</p></li>
		 * <li><b>index</b>: Number<p class="sub-desc">The item's index</p></li>
		 * <li><b>length</b>: Number<p class="sub-desc">The total number of items in the collection</p></li>
		 * </ul></div>
		 * The function should return a boolean value. Returning false from the function will stop the iteration.
		 * @param {Function} fn The function to execute for each item.
		 * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed.
		 * Defaults to the current item in the iteration.
		 */
		each: function(fn, scope)
		{
			this.IPFStores.each.apply(this, arguments);
		},

		/**
		 * Obtain a list of {@link Zarafa.core.data.IPFStore stores} which have one or more of the
		 * requested {@link Zarafa.core.data.IPFRecord folders} currently
		 * {@link Zarafa.core.data.IPFStore#containsStoreInLastLoad loaded}.
		 * @param {Array} folders The list of {@link Zarafa.core.data.IPFRecord folders} or
		 * {@link Zarafa.hierarchy.data.MAPIFolderRecord#getId folder entryIds}
		 * for which the {@link Zarafa.core.data.IPFStore stores} are requested.
		 * @return {Array} The array of {@link Zarafa.core.data.IPFStore stores} which have the
		 * one or more of the requested stores loaded.
		 */
		getStoresForStores: function(folders)
		{
			var stores = [];

			if (!Array.isArray(folders)) {
				folders = [ folders ];
			}

			if (folders[0] instanceof Zarafa.core.data.IPFRecord) {
				folders = Ext.pluck(folders, 'id');
			}

			this.IPFStores.each(function(store) {
				if (store.containsStoreInLastLoad(folders)) {
					stores.push(store);
				}
			}, this);

			return stores;
		}
});

Zarafa.core.data.IPFStoreMgr = new Zarafa.core.data.IPFStoreMgr();
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.IPMRecipientResolveRecord
 * Contains a description of what the data that will be used to resolve an user looks like. It is 
 * used by the reader in the Zarafa.core.data.CheckNamesResponseHandler.
 */
Zarafa.core.data.IPMRecipientResolveRecordFields = [
	{name: 'user_name'},
	{name: 'display_name'},
	{name: 'address_type'},
	{name: 'smtp_address'},
	{name: 'email_address'},
	{name: 'entryid'},
	{name: 'search_key'},
	{name: 'object_type', type: 'int'},
	{name: 'display_type', type: 'int'},
	{name: 'display_type_ex', type: 'int'}

];
Zarafa.core.data.IPMRecipientResolveRecord = Ext.data.Record.create(Zarafa.core.data.IPMRecipientResolveRecordFields);
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.IPMStoreMgr
 * @extends Ext.util.Observable
 *
 * The {@link Zarafa.core.data.IPMStore IPMStore} manager. Each
 * {@link Zarafa.core.data.IPMStore IPMStore} which is created
 * must register itself to this manager.
 *
 * {@link Zarafa.core.data.IPMStoreMgr} will then handle inter-store
 * communications. Listening to {@link Zarafa.core.data.IPMStoreMgr IPMStoreMgr}
 * events allow UI components and {@link Zarafa.core.data.IPMStore stores} to
 * detect {@link Zarafa.core.data.IPMRecord record} editing in dialogs.
 * @singleton
 */
Zarafa.core.data.IPMStoreMgr = Ext.extend(Ext.util.Observable, {
		/**
		 * The collection of {@link Zarafa.core.data.IPMStore stores}
		 * which have been registered to this manager.
		 * @property
		 * @type Ext.util.MixedCollection
		 */
		IPMStores: undefined,
		/**
		 * @constructor
		 */
		constructor: function()
		{
			this.IPMStores = new Ext.util.MixedCollection();
			this.addEvents([
				/**
				 * @event storeexception
				 * Fires when the {@link Zarafa.core.data.IPMStore IPMStore} fired an {@link Zarafa.core.data.IPMStore#exception exception}.
				 * @param {Zarafa.core.data.IPMStore} store The store on which the exception occurred
				 * @param {Ext.data.DataProxy} proxy The proxy from where the exception event originated
				 * @param {String} type The value of this parameter will be either 'response' or 'remote'.
				 * @param {String} action Name of the action.
				 * @param {Object} options The options for the action that were specified in the request.
				 * @param {Object} response If the 'type' argument was 'response' this will be the raw browser response object,
				 * otherwise it will be the decoded response object
				 * @param {Mixed} arg (optional) Additional arguments for the exception
				 */
				'storeexception',
				/**
				 * @event beforerecordsave
				 * Fires before a Message {@link Zarafa.core.data.IPMRecord record} is being saved.
				 * @param {Zarafa.core.data.IPMStore} store The {@link Zarafa.core.data.IPMStore store} in which the
				 * {@link Zarafa.core.data.IPMRecord record} is located while being saved.
				 * @param {Object} data An object containing the data that is to be saved.
				 * The object will contain a key for each appropriate action, with an array
				 * of updated data for each record.
				 */
				'beforerecordsave',
				/**
				 * @event afterrecordsave
				 * Fires when a Message {@link Zarafa.core.data.IPMRecord record} has been saved.
				 * @param {Zarafa.core.data.IPMStore} store The {@link Zarafa.core.data.IPMStore store} in which the
				 * {@link Zarafa.core.data.IPMRecord record} is located while being saved.
				 * @param {Mixed} obj The object containing {@link Zarafa.core.data.IPMRecord record} which has been saved. This
				 * {@link Zarafa.core.data.IPMRecord record} is the most recent version which came from the server.
				 */
				'afterrecordsave',
				/**
				 * @event afterrecordupdate
				 * Fires when a Message {@link Zarafa.core.data.IPMRecord record} has been updated.
				 * @param {Zarafa.core.data.IPMStore} store The {@link Zarafa.core.data.IPMStore store} to which the
				 * {@link Zarafa.core.data.IPMRecord record} belongs.
				 * @param {Zarafa.core.data.IPMrecord} record The most recent version which came from the server.
				 * @param {String} operation The update operation being performed.
				 * ({@link Ext.data.Record#EDIT}, {@link Ext.data.Record#REJECT}, {@link Ext.data.Record#COMMIT}).
				 */
				'afterrecordupdate',
				/**
				 * @event recordremove
				 * Fires when a Message {@link Zarafa.core.data.IPMRecord record} has been removed from the server
				 * @param {Zarafa.core.data.IPMStore} store The {@link Zarafa.core.data.IPMStore store} to which the
				 * {@link Zarafa.core.data.IPMRecord record} belongs.
				 * @param {Zarafa.core.data.IPMrecord} record The most recent version which came from the server.
				 */
				'recordremove',
				/**
				 * @event afterrecordwrite
				 * Fires when {@link Zarafa.core.IPMRecord IPMRecords} are modified (created, updated, destroyed, opened)
				 * on {@link Zarafa.core.data.IPMStore IPMStore}.
				 * @param {Zarafa.core.data.IPMStore} store The {@link Zarafa.core.data.IPMStore store} to which the
				 * {@link Zarafa.core.data.IPMRecord record} belongs.
				 * @param {String} action [Ext.data.Api.actions.create|update|destroy|open]
				 * @param {Object} result The 'data' picked-out out of the response for convenience.
				 * @param {Ext.Direct.Transaction} res
				 * @param {Zarafa.core.data.IPMrecord[]} records The most recent version of the records
				 * which came from the server.
				 */
				'afterrecordwrite'
			]);

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

		/**
		 * Register a {@link Zarafa.core.data.IPMStore IPMStore} with the {@link Zarafa.core.data.IPMStoreMgr IPMStoreMgr}.
		 * @param {Zarafa.core.data.IPMStore} IPMStore the {@link Zarafa.core.data.IPMStore IPMStore} which must be registered.
		 * @param {Boolean} serverOnly True to register the {@link Zarafa.core.data.IPMStore IPMStore} only for
		 * events originating directly from the server.
		 */
		register: function(IPMStore, serverOnly)
		{
			if (!serverOnly) {
				IPMStore.on('beforesave', this.onBeforeSave, this);
				IPMStore.on('save', this.onSave, this);
				IPMStore.on('update', this.onUpdate, this);
				IPMStore.on('remove', this.onRemove, this);
			}
			IPMStore.on('write', this.onWrite, this);
			IPMStore.on('exception', this.onException, this);

			this.IPMStores.add(IPMStore);
		},

		/**
		 * Unregister a {@link Zarafa.core.data.IPMStore IPMStore} from the {@link Zarafa.core.data.IPMStoreMgr IPMStoreMgr}.
		 * @param {Zarafa.core.data.IPMStore} IPMStore the {@link Zarafa.core.data.IPMStore IPMStore} which must be unregistered.
		 * @param {Boolean} serverOnly True if the {@link Zarafa.core.data.IPMStore IPMStore} was {@link #register registered}
		 * with the serverOnly argument.
		 */
		unregister: function(IPMStore, serverOnly)
		{
			if (!serverOnly) {
				IPMStore.un('beforesave', this.onBeforeSave, this);
				IPMStore.un('save', this.onSave, this);
				IPMStore.un('update', this.onUpdate, this);
				IPMStore.un('remove', this.onRemove, this);
			}
			IPMStore.un('write', this.onWrite, this);
			IPMStore.un('exception', this.onException, this);

			this.IPMStores.remove(IPMStore);
		},

		/**
		 * Filter the list of {@link Zarafa.core.data.IPMRecord records} to contain
		 * only those which can be {@link Zarafa.core.data.IPMRecord#eventPropagation propagated}
		 * over a new event to other {@link Zarafa.core.data.IPMStore stores}.
		 *
		 * @param {Zarafa.core.data.IPMRecord/Array} records The record or records to filter
		 * out the non-propagatable records.
		 * @return {Zarafa.core.data.IPMRecord/Array} All propagatable records
		 * @private
		 */
		getPropagatableRecords: function(records)
		{
			var propagateRecords = [];

			if (!Array.isArray(records)) {
				records = [ records ];
			}

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

				if (record instanceof Zarafa.core.data.IPMRecord && record.hasEventPropagation()) {
					propagateRecords.push(record);
				}
			}

			return propagateRecords;
		},

		/**
		 * Event handler which is triggered when a {@link Zarafa.core.data.IPMRecord record} is about to
		 * be saved in a {@link Zarafa.core.data.IPMStore store}. This function will inform the
		 * {@link Zarafa.core.data.IPMStore IPMStores} about the event through the
		 * {@link #beforerecordsave} event.
		 *
		 * @param {Zarafa.core.data.IPMStore} IPMStore
		 * @param {Object} data An object containing the data that is to be saved.
		 * The object will contain a key for each appropriate action, with an array
		 * of updated data for each record.
		 * @private
		 */
		onBeforeSave: function(IPMStore, data)
		{
			var propagateData = {};

			for (var key in data) {
				var propagateRecords = this.getPropagatableRecords(data[key]);
				if (!Ext.isEmpty(propagateData)) {
					propagateData[key] = propagateRecords;
				}
			}

			this.fireEvent('beforerecordsave', IPMStore, propagateData);
		},

		/**
		 * Event handler which is triggered when a {@link Zarafa.core.data.IPMRecord record} has been
		 * saved in a {@link Zarafa.core.data.IPMStore store}. This function will inform the
		 * {@link arafa.core.data.IPMStore IPMStores} about the event through the
		 * {@link #afterrecordsave} event.
		 *
		 * @param {Zarafa.core.data.IPMStore} IPMStore
		 * @param {Number} batch The identifier for the batch that was saved.
		 * @param {Object} data An object containing the data that is to be saved.
		 * The object will contain a key for each appropriate action, with an array
		 * of updated data for each record.
		 * @private
		 */
		onSave: function(IPMStore, batch, data)
		{
			this.fireEvent('afterrecordsave', IPMStore, data);
		},

		/**
		 * Event handler which is triggered when a {@link Zarafa.core.data.IPMRecord record} has been
		 * updated from the server. This function will inform the {@link Zarafa.core.data.IPMStore IPMStores}
		 * about the event through the {@link #afterrecordupdate} event.
		 *
		 * @param {Zarafa.core.data.IPMStore} IPMStore
		 * @param {Zarafa.core.data.IPMRecord} record The Record which has been updated
		 * @param {String} operation The update operation being performed.
		 * ({@link Ext.data.Record#EDIT}, {@link Ext.data.Record#REJECT}, {@link Ext.data.Record#COMMIT}).
		 * @private
		 */
		onUpdate: function(IPMStore, record, operation)
		{
			if (record.hasEventPropagation()) {
				this.fireEvent('afterrecordupdate', IPMStore, record, operation);
			}
		},

		/**
		 * Event handler which is triggered when a {@link Zarafa.core.data.IPMRecord record} has been
		 * removed from the server. This function will inform the {@link Zarafa.core.data.IPMStore IPMStores}
		 * about the event through the {@link #recordremove} event.
		 *
		 * @param {Zarafa.core.data.IPMStore} IPMStore
		 * @param {Zarafa.core.data.IPMRecord} record The Record which has been updated
		 * @param {String} The index at which the record was removed
		 * @private
		 */
		onRemove: function(IPMStore, record, index)
		{
			if (record.hasEventPropagation()) {
				this.fireEvent('recordremove', IPMStore, record);
			}
		},

		/**
		 * Event handler will be called on successful completion of any CRUD operation,
		 * the difference between this event and {@link #save} event is that {@link #save}
		 * event will pass only data set that is modified not the record that is modified.
		 * so this event removes burden of finding record from the record set.
		 *
		 * @param {Zarafa.core.data.IPMStore} store The {@link Zarafa.core.data.IPMStore store} to which the
		 * {@link Zarafa.core.data.IPMRecord record} belongs.
		 * @param {String} action [Ext.data.Api.actions.create|update|destroy|open]
		 * @param {Object} data The 'data' picked-out out of the response for convenience.
		 * @param {Ext.Direct.Transaction} res
		 * @param {Zarafa.core.data.IPMrecord[]} records The most recent version of the records
		 * which came from the server.
		 * @private
		 */
		onWrite: function(IPMStore, action, data, transaction, records)
		{
			var propagateRecords = this.getPropagatableRecords(records);
			if (!Ext.isEmpty(propagateRecords)) {
				this.fireEvent('afterrecordwrite', IPMStore, action, data, transaction, propagateRecords);
			}
		},

		/**
		 * Event handler which will be called when the store has fired the {@link Ext.data.Store#exception} event.
		 * This will look up which store has exactly fired the event, and will fire the {@link #exception} event.
		 * @param {Ext.data.DataProxy} proxy The proxy from where the exception event originated
		 * @param {String} type The value of this parameter will be either 'response' or 'remote'.
		 * @param {String} action Name of the action.
		 * @param {Object} options The options for the action that were specified in the request.
		 * @param {Object} response If the 'type' argument was 'response' this will be the raw browser response object,
		 * otherwise it will be the decoded response object
		 * @param {Mixed} arg (optional) Additional arguments for the exception
		 * @private
		 */
		onException: function(proxy, type, action, options, response, arg)
		{
			var proxies = Ext.pluck(this.IPMStores.items, 'proxy');
			if (!Ext.isEmpty(proxies)) {
				var storeIndex = proxies.indexOf(proxy);
				this.fireEvent('storeexception', this.IPMStores.get(storeIndex), proxy, type, action, options, response, arg);
			}
		},

		/**
		 * Executes the specified function once for every {@link Zarafa.core.data.IPMStore store} in the collection,
		 * passing the following arguments:
		 * <div class="mdetail-params"><ul>
		 * <li><b>item</b>: Zarafa.core.data.IPMStore<p class="sub-desc">The collection item</p></li>
		 * <li><b>index</b>: Number<p class="sub-desc">The item's index</p></li>
		 * <li><b>length</b>: Number<p class="sub-desc">The total number of items in the collection</p></li>
		 * </ul></div>
		 * The function should return a boolean value. Returning false from the function will stop the iteration.
		 * @param {Function} fn The function to execute for each item.
		 * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed.
		 * Defaults to the current item in the iteration.
		 */
		each: function(fn, scope)
		{
			this.IPMStores.each.apply(this, arguments);
		},

		/**
		 * Obtain a list of {@link Zarafa.core.data.IPMStore stores} which have one or more of the
		 * requested {@link Zarafa.core.date.IPFRecord folders} currently
		 * {@link Zarafa.core.data.IPMStore#containsFolderInLastLoad loaded}.
		 * @param {Array} folders The list of {@link Zarafa.core.data.IPFRecord folders} or
		 * {@link Zarafa.hierarchy.data.MAPIFolderRecord#getId folder entryIds}
		 * for which the {@link Zarafa.core.data.IPMStore stores} are requested.
		 * @return {Array} The array of {@link Zarafa.core.data.IPMStore stores} which have the
		 * one or more of the requested folders loaded.
		 */
		getStoresForFolders: function(folders)
		{
			var stores = [];

			if (!Array.isArray(folders)) {
				folders = [ folders ];
			}

			if (folders[0] instanceof Zarafa.core.data.IPFRecord) {
				folders = Ext.pluck(folders, 'id');
			}

			this.IPMStores.each(function(store) {
				if (store.containsFolderInLastLoad(folders)) {
					stores.push(store);
				}
			}, this);

			return stores;
		}
});

Zarafa.core.data.IPMStoreMgr = new Zarafa.core.data.IPMStoreMgr();
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.JsonReader
 * @extends Ext.data.JsonReader
 *
 * This extension of the {@link Ext.data.JsonReader} supports {@link Zarafa.core.data.IPMStore stores}
 * which can hold different type of {@link Zarafa.core.data.IPMRecord records}.
 *
 * If in the constructor no recordType is provided, dynamic {@link Zarafa.core.data.IPMRecord record} type
 * support is assumed. With dynamic types, the incoming response data is used to determine which
 * {@link Zarafa.core.data.MAPIRecord MAPIRecord} must be constructed for each individual root element.
 */
Zarafa.core.data.JsonReader = Ext.extend(Ext.data.JsonReader, {
	/**
	 * In {@link #getEfMapping} we generate a mapping of all Record Fields per ObjectType,
	 * all mappings are stored in a special cache to prevent them from being regenerated
	 * each time.
	 * @property
	 * @type Object
	 * @private
	 */
	efCache: undefined,

	/**
	 * @cfg {Boolean} dynamicRecord Enable dynamic detection of the records
	 * which are read by this JsonReader. When enabled this will prefer using
	 * the {@link Zarafa.core.data.RecordFactory} to detect the recordType rather
	 * then using the {@link #recordType} directly. (defaults to true)
	 */
	dynamicRecord: true,

	/**
	 * @constructor
	 * @param {Object} meta Metadata configuration options.
	 * @param {Object} recordType (optional) Optional Record type matches the type
	 * which must be read from response. If no type is given, {@link Zarafa.core.data.JsonReader}
	 * will dynamically detect the record type based on the response.
	 */
	constructor: function(meta, recordType)
	{
		meta = Ext.applyIf(meta || {}, {
			totalProperty: 'count',
			root: 'item',
			id: 'entryid',
			idProperty: 'entryid'
		});

		// Check if the meta object contained the successProperty.
		// Note that this must be called before the superclass constructor,
		// because the meta object will be altered there.
		var hasSuccessProperty = Ext.isDefined(meta.successProperty);

		// If no recordType is provided, enable the dynamic record handling
		// of the JsonReader.
		if (!Ext.isDefined(recordType)) {
			// FIXME: We shouldn't for IPM as base recordclass here, instead
			// this should be handled as configuration option, or something even smarter.
			recordType = Zarafa.core.data.RecordFactory.getRecordClassByMessageClass('IPM');
		}

		// Check we dynamic records are disabled.
		if (Ext.isDefined(meta.dynamicRecord)) {
			this.dynamicRecord = meta.dynamicRecord;
		}

		if (this.dynamicRecord !== false) {
			this.efCache = {};
		}

		Zarafa.core.data.JsonReader.superclass.constructor.call(this, meta, recordType);

		// This fixes a bug in the Ext.data.JsonReader. Even when no successProperty
		// is given, the getSuccess function will still be implemented after which
		// the function will often fail due to the lack of the success property within
		// the response data.
		if (!hasSuccessProperty) {
			this.getSuccess = function(o) { return true; };
		}
	},

	/**
	 * Build the extractors which are used when reading the Json data. This initialized
	 * functions like {@link #getTotal}, {@link #getSuccess}, {@link #getId}.
	 * @private
	 */
	buildExtractors: function()
	{
		var s = this.meta;

		Zarafa.core.data.JsonReader.superclass.buildExtractors.call(this);

		// Wrap the original getId function to check if the data is the raw
		// data which has wrapped the 'props' field, or if this is the unwrapped
		// data.
		if (s.id || s.idProperty) {
			var old = this.getId;
			this.getId = function(rec) {
				if (rec.props) {
					var id = old(rec.props);
					if (!Ext.isEmpty(id)) {
						return id;
					}
				}

				return old(rec);
			};
		}
	},

	/**
	 * Obtain the mapping between response objects and {@link Zarafa.core.data.IPMRecord record} fields for
	 * the given recordType. If no mapping yet exist for this recordType one will be constructed
	 * and added to the {@link Zarafa.core.data.IPMRecord.efCache cache}.
	 *
	 * @param {String} key The unique key for this record type (used for caching purposes).
	 * @param {Array} items The array of {@link Zarafa.core.data.IPMRecord record} items.
	 * @param {Number} len The length of the items array.
	 * @return {Array} The name/value list of response to {@link Zarafa.core.data.IPMRecord record} fields.
	 * @private
	 */
	getEfMapping: function(key, items, len)
	{
		if (Ext.isString(key)) {
			key = key.toUpperCase();
		}

		var ef = this.efCache[key];

		if (!Ext.isDefined(ef))
		{
			ef = [];
    	for(var i = 0; i < len; i++){
				var f = items[i];
				var map = (!Ext.isEmpty(f.mapping)) ? f.mapping : f.name;
				ef.push(this.createAccessor.call(this, map));
			}
			this.efCache[key] = ef;
		}

		return ef;
	},

	/**
	 * Type-casts a single row of raw-data from server
	 * @param {Object} data The data object which must be deserialized.
	 * @param {Array} items The {@link Ext.data.Field Field} used for deserialization.
	 * @param {Integer} len The length of the items array.
	 * @private
	 */
	extractValues: function(data, items, len)
	{
		var values = {};

		// If the data object is wrapped (it contains objects like 'props', 'attachments',
		// 'recipients', etc... Then we must call extractValues for each individual subobject.
		if (Ext.isDefined(data.props)) {
			values = Ext.apply({}, data);
			values.props = this.extractValues(data.props, items, len);
			return values;
		}

		if (this.dynamicRecord === true)
		{
			var recordType = Zarafa.core.data.RecordFactory.getRecordClassByRecordData(data);
			if (!Ext.isDefined(recordType)) {
				recordType = this.recordType;
			}

			items = recordType.prototype.fields.items;
			len = recordType.prototype.fields.length;

			// Normally the caller has initialized this.ef for us, but only at this time
			// do we know the real recordType. As such we have to override the previous
			// this.ef mapping.
			this.ef = this.getEfMapping(data.message_class || data.object_type, items, len);
		}

		// Extract the values per object which we want to deserialize.
		for (var j = 0; j < len; j++) {
			var f = items[j];
			var v = this.ef[j](data);
			if (Ext.isDefined(v)) {
				values[f.name] = f.convert(v, data);
			}
		}

		return values;
	},

	/**
	 * Returns extracted, type-cast rows of data. Iterates to call #extractValues for each row
	 *
	 * This function is exactly copied from {@link Ext.data.DataReader.extractData} with the only
	 * difference is using the RecordFactory for record allocation.
	 *
	 * @param {Object|Array} data-root from server response
	 * @param {Boolean} returnRecords [false] Set true to return instances of {@link Zarafa.core.data.MAPIRecord MAPIRecord}.
	 * @private
	 */
	extractData: function(root, returnRecords)
	{
		// A bit ugly this, too bad the Record's raw data couldn't be saved in a common property named "raw" or something.
		var rawName = (this instanceof Ext.data.JsonReader) ? 'json' : 'node';

		var rs = [];

		// Had to add Check for XmlReader, #isData returns true if root is an Xml-object. Want to check in order to re-factor
		// #extractData into DataReader base, since the implementations are almost identical for JsonReader, XmlReader
		if (this.isData(root) && !(this instanceof Ext.data.XmlReader)) {
			root = [root];
		}
		if (returnRecords === true) {
			for (var i = 0; i < root.length; i++) {
				var n = root[i];

				var record = undefined;
				var id = this.getId(n);
				var data = n.props || n;

				// Clear all data from the object which must be deserialized,
				// we only want the 'object_type' and 'message_class' properties.
				data = { message_class: data.message_class, object_type: data.object_type };

				if (this.dynamicRecord === true) {
					record = Zarafa.core.data.RecordFactory.createRecordObjectByRecordData(data, id);
				}

				if (!record) {
					record = new this.recordType({}, id);
				}

				// move primitive properties and identification properties at same level
				this.moveIdProperties(n, record.baseIdProperties);

				var f		= record.fields,
					fi		= f.items,
					fl		= f.length;
				this.update(record, this.extractValues(n, fi, fl));
				record[rawName] = n;  // <-- There's implementation of ugly bit, setting the raw record-data.
				rs.push(record);
			}
		} else {
			for (var i = 0; i < root.length; i++) {
				var n = root[i];

				var Record = undefined;
				if (this.dynamicRecord === true) {
					Record = Zarafa.core.data.RecordFactory.getRecordClassByRecordData(n.props || n);
				}

				// Fall back to specified record type if we can't get the type from the data
				if (!Record) {
					Record = this.recordType;
				}

				// move primitive properties and identification properties at same level
				this.moveIdProperties(n, Record.prototype.baseIdProperties);

				var f		= Record.prototype.fields,
					fi		= f.items,
					fl		= f.length;

				// here we can't do anything about complex structures so its ignored here
				var data = this.extractValues(n, fi, fl);
				data[this.meta.idProperty] = this.getId(n.props || n);
				rs.push(data);
			}
		}
		return rs;
	},

	/**
	 * Function will merge all identification properties and primitive properties
	 * to the props field and return the merged data. so {@link Zarafa.core.JsonReader JsonReader}
	 * can read the data and extract the properties.
	 * @param {Object} data data that is passed to {@link #extractData} to extract properties.
	 * @param {String|Array} idProperties The id properties which should be moved into the properties object.
	 * @return {Object} The updated data object.
	 */
	moveIdProperties: function(data, idProperties)
	{
		// If there is not data then return no data
		if (!data) {
			return data;
		}

		if (!data.props) {
			data.props = {};
		}

		// move the base identification property to <props> tag level
		var idProperty = this.meta.idProperty;
		if (idProperty) {
			var value = data[idProperty];
			if (Ext.isDefined(value)) {
				data.props[idProperty] = value;
				delete data[idProperty];
			}
		}

		// move all extra identification properties to <props> tag level
		if (Ext.isString(idProperties)) {
			var value = data[idProperties];
			if (Ext.isDefined(value)) {
				data.props[idProperties] = value;
				delete data[idProperties];
			}
		} else if (idProperties) {
			for (var i = 0, len = idProperties.length; i < len; i++) {
				var idProp = idProperties[i];
				var value = data[idProp];

				if(Ext.isDefined(value)) {
					data.props[idProp] = value;
					delete data[idProp];
				}
			}
		}

		return data;
	},

	/**
	 * Used for un-phantoming a record after a successful database insert.
	 * Sets the records pk along with new data from server.
	 * You must return at least the database pk using the idProperty defined in
	 * your DataReader configuration. The incoming data from server will be merged
	 * with the data in the local record. In addition, you must return record-data
	 * from the server in the same order received. Will perform a commit as well,
	 * un-marking dirty-fields. Store's "update" event will be suppressed.
	 *
	 * @param {Record/Record[]} record The phantom record to be realized.
	 * @param {Object/Object[]} data The new record data to apply. Must include the primary-key from database defined in idProperty field.
	 * @private
	 */
	realize: function(record, data)
	{
		// This function is copy & pasted from Ext.js Ext.data.JsonReader#realize.
		// Our only difference is the assignment of the record.data field.
		if (Array.isArray(record)) {
			for (var i = record.length - 1; i >= 0; i--) {
				// recurse
				if (Array.isArray(data)) {
					this.realize(record.splice(i,1).shift(), data.splice(i,1).shift());
				} else {
					// weird...record is an array but data isn't?? recurse but just send in the whole invalid data object.
					// the else clause below will detect !this.isData and throw exception.
					this.realize(record.splice(i,1).shift(), data);
				}
			}
		} else {
			// If records is NOT an array but data IS, see if data contains just 1 record. If so extract it and carry on.
			if (Array.isArray(data)) {
				data = data.shift();
			}
			if (!this.isData(data)) {
				// TODO: Let exception-handler choose to commit or not rather than blindly records.commit() here.
				// record.commit();
				throw new Ext.data.DataReader.Error('realize', record);
			}
			record.phantom = false; // <-- That's what it's all about
			record._phid = record.id; // <-- copy phantom-id -> _phid, so we can remap in Store#onCreateRecords
			record.id = this.getId(data);

			// And now the infamous line for which we copied this entire function.
			// Extjs expects that we transfer _all_ properties back from the server to the client after
			// a new item was created. Since this has a negative impact on the performance.
			//
			// This has been solved to let the server-side only return the properties which have
			// changed, or are new (like the entryid). We can then simply use Ext.apply to apply
			// the updated data over the already available data.
			//
			// But for those who have paid attention in the data flow of Extjs, know that this
			// sounds quite a lot like the function description of Ext.data.JsonReader#update.
			//
			// So to make everything even simpler, we don'e update the record.data object here,
			// but instead we simply continue to the Ext.data.JsonReader#update function to
			// handle the rest of the work.
			//
			//record.data = data;

			// Since we postpone the record.data update, there is no need to commit,
			// this too is done in update().
			//record.commit();

			// Time for the real work...
			this.update(record, data);

			// During realize the record might have received a new
			// id value. We have to reMap the store to update the keys.
			if (record.store) {
				record.store.reMap(record);
			}
		}
	},

	/**
	 * Used for updating a non-phantom or "real" record's data with fresh data from
	 * server after remote-save. If returning data from multiple-records after a batch-update,
	 * you must return record-data from the server in the same order received.
	 * Will perform a commit as well, un-marking dirty-fields. Store's "update" event
	 * will be suppressed as the record receives fresh new data-hash
	 *
	 * @param {Record/Record[]} record
	 * @param {Object/Object[]} data
	 * @private
	 */
	update: function(record, data)
	{
		// Recursively call into update to update each record individually.
		if (Array.isArray(record)) {
			for (var i = 0; i < record.length; i++) {
				if(Array.isArray(data)) {
					this.update(record[i], data[i]);
				} else {
					this.update(record[i], data);
				}
			}
			return;
		}

		// It can happen that the data is wrapped in a array of length 1.
		if (Array.isArray(data)) {
			data = data.shift();
		}

		if (this.isData(data)) {
			// move primitive properties and identification properties at same level
			data = this.moveIdProperties(data, record.baseIdProperties);

			// All preprocessing has been done. All remaining data
			// can be applied into the IPMRecord directly.
			record.data = Ext.apply(record.data, data.props || data);

			// scalar values from props are applied so remove it from data object
			delete data.props;

			// Put the action response from the server in the record
			if(data.action_response){
				record.action_response = data.action_response;
				delete data.action_response;
			}

			// If the record contains substores to store complex data then we have to first
			// serialize those data into its consecutive stores and then we can continue
			// with normal processing
			Ext.iterate(data, function(key, value) {
				if (Array.isArray(value) || Ext.isObject(value)) {
					var store;

					if (record.supportsSubStore(key)) {
						store = record.getSubStore(key);
						if (!store) {
							store = record.createSubStore(key);
						} else {
							store.removeAll(true);
						}

						// Load data into the SubStore, and remove
						// it from the data object.
						if (!Ext.isEmpty(value)) {
							store.loadData(value);
							delete data[key];
						}
					}
				}
			}, this);

			// Discard any additional data which was set on the data object,
			// this data has probably been set by plugins, but they have sufficient
			// alternatives to fit their custom data into the IPMRecord structure.
		}

		record.commit();
	}
});
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.JsonWriter
 * @extends Ext.data.JsonWriter
 */
Zarafa.core.data.JsonWriter = Ext.extend(Ext.data.JsonWriter, {
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			writeAllFields: false,
			// FIXME: Disable automatic encoding for now,
			// the MAPIProxy needs an individual encoded string
			// for each record in the request. We might want to
			// fix this in the future though.
			encode: false
		});

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

	/**
	 * Render the data in the data object which will be {@link Ext.encode encoded}
	 * and send over the protocol to the server after this function call. During
	 * rendering all {@link Date date} objects will be converted to UNIX timestamps.
	 * This will prevent ExtJs/JSON specific encoding functions to convert the
	 * date object into a "YYYY-MM-DDTHH:MM:SS" timestring.
	 * @param {Object/Array} data The object which musted be rendered
	 * @private
	 */
	renderData: function(data)
	{
		if (Array.isArray(data)) {
			for (var i = 0, len = data.length; i < len; i++) {
				this.renderData(data[i]);
			}
			return;
		}

		Ext.iterate(data, function(key, value) {
			if (Ext.isDate(value)) {
				data[key] = Math.floor(value.getTime() / 1000);
			}
			if (Ext.isObject(value)) {
				this.renderData(value);
			}
		}, this);
	},

	/**
	 * Final action of a write event. Apply the written data-object to params.
	 * This function is extended from {@link Ext.data.JsonWriter Extjs}, to use
	 * {@link #renderData} to add some extra data conversions before encoding
	 * the data by {@link Ext.encode Ext.encode}.
	 * @param {Object} http params-object to write-to.
	 * @param {Object} baseParams as defined by {@link Ext.data.Store#baseParams}.
	 * The baseParms must be encoded by the extending class, eg: {@link Ext.data.JsonWriter}, {@link Ext.data.XmlWriter}.
	 * @param {Object/Object[]} data Data-object representing compiled Store-recordset.
	 */
	render: function(params, baseParams, data)
	{
		// Apply the parameters into the data object, this allows
		// optional data to be send to the server.
		Ext.apply(data, baseParams, params);

		// Apply special rendering to convert all objects
		this.renderData(data);
		Zarafa.core.data.JsonWriter.superclass.render.call(this, params, baseParams, data);
	},

	/**
	 * Adds special function for serialization needed when opening
	 * a record. We can use the default {@link Zarafa.core.data.JsonWriter.toIdHash toIdHash}
	 * function.
	 *
	 * @param {Ext.data.Record} record
	 * @return {Object}
	 * @private
	 */
	openRecord: function(record)
	{
		return this.toIdHash(record);
	},

	/**
	 * Rather then using the regular {@link Ext.data.JsonWriter#toHash toHash}
	 * function, this will use the specialized {@link Zarafa.core.data.JsonWriter#toPropHash toPropHash}
	 * function.
	 *
	 * @param {Ext.data.Record} record
	 * @return {Object}
	 * @override
	 * @private
	 */
	createRecord: function(record)
	{
		return this.toPropHash(record);
	},

	/**
	 * Rather then using the regular {@link Ext.data.JsonWriter#toHash toHash}
	 * function, this will use the specialized {@link Zarafa.core.data.JsonWriter#toPropHash toPropHash}
	 * function.
	 *
	 * @param {Ext.data.Record} record
	 * @return {Object}
	 * @override
	 * @private
	 */
	updateRecord: function(record)
	{
		return this.toPropHash(record);
	},

	/**
	 * Use the {@link Zarafa.core.data.JsonWriter#toIdHash toIdHash} function for creating the hash.
	 *
	 * @param {Ext.data.Record} record
	 * @return {Object}
	 * @override
	 * @private
	 */
	destroyRecord: function(record)
	{
		return this.toIdHash(record);
	},

	/**
	 * Similar to {@link Ext.data.JsonWriter#toHash}
	 *
	 * This will limit the serialization to only the ID properties and message
	 * action commands for the given {@link Zarafa.core.data.IPMRecord record}.
	 *
	 * @param {Ext.data.Record} record The record to hash
	 * @param {Boolean} allowEmpty True to allow empty ID elements to be send
	 * @return {Object} The hashed object
	 * @private
	 */
	toIdHash: function(record, allowEmpty)
	{
		var hash = {};

		Ext.each(record.getIdProps(), function(idProp) {
			var id = record.get(idProp);
			if (allowEmpty || Ext.isDefined(id)) {
				hash[idProp] = id;
			}
		}, this);

		this.addMessageActionsHash(hash, record);
		hash.timezone_iana = Intl.DateTimeFormat().resolvedOptions().timeZone;

		return hash;
	},

	/**
	 * Similar to {@link Ext.data.JsonWriter#toHash}
	 *
	 * Besides serializing the data itself, it will insert
	 * the recipients, attachments and message action commands
	 * into the object data.
	 *
	 * @param {Ext.data.Record} record The record to hash
	 * @return {Object} The hashed object
	 * @private
	 */
	toPropHash: function(record)
	{
		var hash = this.toIdHash(record, false);

		// FIXME: How to pass on deleted properties?
		hash.props = this.toHash.call(this, record);

		// FIXME: remove identification entryids from props,
		// in the future Extjs will support the 'config'
		// argument to toHash which we can use the filter
		// out the ID properties...
		this.removeIdHashFromProps(hash, record);

		// Add additional information from the subStores into the hash
		for (var key in record.subStores) {
			if (record.supportsSubStore(key) === true) {
				var store = record.subStores[key];

				if (store && store.writer) {
					Ext.apply(hash, store.writer.toPropHash(record));
				}
			}
		}

		this.addMessageActionsHash(hash, record);

		return hash;
	},

	/**
	 * remove additional identification properties from the props using the
	 * {@link Zarafa.core.data.JsonWriter.idProperties idProperties}
	 * field.
	 *
	 * @param {Object} hash The hash into which the identification fields must be added
	 * @param {Zarafa.core.data.IPMrecord} record The record to serialize from
	 * @private
	 */
	removeIdHashFromProps: function(hash, record)
	{
		Ext.each(record.getIdProps(), function(idProp) {
			if (Ext.isDefined(hash.props) && Ext.isDefined(hash.props[idProp])) {
				delete hash.props[idProp];
			}
		}, this);
	},

	/**
	 * Add message actions into the hash. Message actions are not properties
	 * which come from the server, but are used to add an additional action
	 * instruction for the server to perform. As such the action needs to
	 * be serialized separately into the hash object.
	 *
	 * @param {Object} hash The hash into which the message actions must be added
	 * @param {Zarafa.core.data.IPMrecord} record The record to serialize from
	 * @private
	 */
	addMessageActionsHash: function(hash, record)
	{
		var actions = record.getMessageActions();
		var message_action = {};

		// No Message actions defined
		if (!Ext.isDefined(actions)) {
			return;
		}

		for (var key in actions) {
			if (Ext.isDefined(actions[key])) {
				message_action[key] = actions[key];
			}
		}

		hash.message_action = message_action;
	}
});
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.MAPIProxy
 * @extends Ext.data.DataProxy
 */
Zarafa.core.data.MAPIProxy = Ext.extend(Ext.data.DataProxy, {
	/**
	 * @cfg {String} listModuleName Name of the listModule on the server.
	 */
	listModuleName: undefined,
	/**
	 * @cfg {String} itemModuleName Name of the itemModule on the server.
	 */
	itemModuleName: undefined,

	/**
	 * Currently active requests for {@link Zarafa.core.data.MAPIProxy MAPIProxy} mapped by the
	 * action type used in requests, {@link Zarafa.core.data.MAPIProxy MAPIProxy} can use this
	 * active request data to abort any previous request and start a new request.
	 * @property
	 * @type Object
	 * @private
	 */
	activeRequestMapping: undefined,

	/**
	 * The {@link Date#getTime timestamps} for the last time a response was received for
	 * a given {@link Zarafa.core.Actions action}.
	 * @property
	 * @type Object
	 * @private
	 */
	lastResponseTime: undefined,

	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		Ext.apply(this, config);
		Zarafa.core.data.MAPIProxy.superclass.constructor.call(this, config);

		this.activeRequestMapping = {};
		this.lastResponseTime = {};
	},

	/**
	 * Check if the given action has registered requestIds in the {@link #activeRequestMapping}.
	 * When this is the case, the action is considered to be active. When no action is passed,
	 * this function will check if there are any requestsIds pending for any action.
	 *
	 * @param {Zarafa.core.Action} action The action which is to be checked.
	 * @return {Boolean} True if the given action has registered requestIds.
	 */
	isExecuting: function(action)
	{
		if (Ext.isEmpty(action)) {
			return !Ext.isEmpty(Object.keys(this.activeRequestMapping));
		} else {
			return !Ext.isEmpty(this.activeRequestMapping[action]);
		}
	},

	/**
	 * Register a requestId to a particular action. This will update {@link activeRequestMapping}
	 * to contain the requestId for the given action. By this registration it is possible to
	 * track all current outstanding requests, and it is possible to cancel them using {@link #cancelRequests}.
	 * @param {Zarafa.core.Actions} action The action for which this request id was generated
	 * @param {String} requestId The unique id which was given to the request
	 */
	addRequestId: function(action, requestId)
	{
		if (!Ext.isDefined(this.activeRequestMapping[action])) {
			this.activeRequestMapping[action] = [ requestId ];
		} else {
			this.activeRequestMapping[action].push(requestId);
		}
	},

	/**
	 * Remove a requestId from a particular action. This will update {@link activeRequestMapping}
	 * to remove the requestId from the given action.
	 * @param {Zarafa.core.Actions} requestId The unique id which was given to the request.
	 */
	deleteRequestId: function(requestId)
	{
		for (var key in this.activeRequestMapping) {
			if (Array.isArray(this.activeRequestMapping[key])) {
				this.activeRequestMapping[key].remove(requestId);
				if (Ext.isEmpty(this.activeRequestMapping[key])) {
					delete this.activeRequestMapping[key];
				}
			}
		}
	},

	/**
	 * Cancel all requests made by this proxy for a particular action.
	 * This will call {@link Zarafa.core.Request#cancelActiveRequest} to cancel
	 * the response handling of all requests which were send out by this proxy for
	 * the given action.
	 * @param {Zarafa.core.Actions} action The action
	 * @protected
	 */
	cancelRequests: function(action)
	{
		if (this.activeRequestMapping[action]) {
			var requests = this.activeRequestMapping[action];

			for (var i = 0, len = requests.length; i < len; i++) {
				container.getRequest().cancelActiveRequest(requests[i]);
			}

			delete this.activeRequestMapping[action];
		}
	},

	/**
	 * Update the {@link #lastResponseTime} with the {@link Date#getTime timestamp}
	 * of when the response for the given action was received.
	 * @param {Zarafa.core.Actions} action The action
	 * @param {Number} timestamp The timestamp
	 */
	updateExecutionTimestamp: function(action, timestamp)
	{
		this.lastResponseTime[action] = timestamp;
	},

	/**
	 * Obtain the {@link Date#getTime timestamp} of the last time
	 * the given {@link #updateExecutionTimestamp action was executed}.
	 * It will mark the time of when the action was completed (because the
	 * response was returned from the server).
	 * @param {Zarafa.core.Actions} action The action
	 * @return {Number} The timestamp of the last action
	 */
	lastExecutionTime: function(action)
	{
		return this.lastResponseTime[action] || 0;
	},

	/**
	 * Obtain the name of the listModule for communication with PHP.
	 * @param {Zarafa.core.data.MAPIRecord} record the record for which the listModuleName is requested
	 * @return {String} The name of the listModule.
	 * @private
	 */
	getListModuleName: function(record)
	{
		return this.listModuleName;
	},

	/**
	 * Obtain the name of the itemModule for communication with PHP.
	 * @param {Zarafa.core.data.MAPIRecord} record the record for which the itemModuleName is requested
	 * @return {String} The name of the itemModule.
	 * @private
	 */
	getItemModuleName: function(record)
	{
		return this.itemModuleName;
	},

	/**
	 * This will create a {@link Zarafa.core.data.ProxyResponseHandler ProxyResponseHandler} object
	 * which will be used by the {@link Zarafa.core.data.ResponseRouter ResponseRouter} when the
	 * response for the given request has returned.
	 *
	 * @param {String} modulename The modulename which is being accessed with this request
	 * @param {Zarafa.core.Actions} serverAction The action to perform on the server.
	 * @param {Ext.data.Api.action} action name of the action to perform.
	 * @param {Ext.data.Record[]} records list of records to operate on.
	 * @param {Object} parameters object containing user parameters such as range (pagination) information, sorting information, etc.
	 * @param {Ext.data.DataReader} reader data reader. Converts raw JavaScript objects (in our case) to instances of {@link Ext.data.Record}
	 * @param {Function} callback call back function to call when the request has finished successfully.
	 * @param {Object} scope scope for the call back function.
	 * @param {Object} args arguments object. This will be passed to the call back function on successful read.
	 * @return {Object} An instance of the {@link Zarafa.core.data.ProxyResponseHandler ProxyResponseHandler}
	 * which should be used for this request.
	 * @private
	 */
	getResponseHandlerForRequest: Ext.emptyFn,

	/**
	 * Performs a request for a store. This single entry point carries out all CRUD requests.
	 * @param {Ext.data.Api.action} action name of the action to perform. One of 'create', 'destroy', 'update', and 'read'.
	 * @param {Ext.data.Record[]} records list of records to operate on. In case of 'read' this will be ignored.
	 * @param {Object} parameters object containing user parameters such as range (pagination) information, sorting information, etc.
	 * @param {Ext.data.DataReader} reader data reader. Converts raw JavaScript objects (in our case) to instances of {@link Ext.data.Record}
	 * @param {Function} callback call back function to call when the request has finished successfully.
	 * @param {Object} scope scope for the call back function.
	 * @param {Object} args arguments object. This will be passed to the call back function on successful read.
	 */
	request: function(action, records, parameters, reader, callback, scope, args)
	{
		switch (action)
		{
			case 'update':
			case 'create':
				this.createUpdateAction(action, records, parameters, reader, callback, scope, args);
				break;
			case 'destroy':
				this.destroyAction(action, records, parameters, reader, callback, scope, args);
				break;
			case 'read':
				this.readAction(action, records, parameters, reader, callback, scope, args);
				break;
			case 'open':
				this.openAction(action, records, parameters, reader, callback, scope, args);
				break;
			case 'import':
				this.importAction(action, records, parameters, reader, callback, scope, args);
				break;
		}
	},

	/**
	 * @param {Ext.data.Api.action} action name of the action to perform. Either 'create' or 'update'.
	 * @param {Ext.data.Record[]} records list of records to operate on.
	 * @param {Object} parameters object containing user parameters such as range (pagination) information, sorting information, etc.
	 * @param {Ext.data.DataReader} reader data reader. Converts raw JavaScript objects (in our case) to instances of {@link Ext.data.Record}
	 * @param {Function} callback call back function to call when the request has finished successfully.
	 * @param {Object} scope scope for the call back function.
	 * @param {Object} args arguments object. This will be passed to the call back function on successful read.
	 * @private
	 */
	createUpdateAction: function(action, records, parameters, reader, callback, scope, args)
	{
		this.doRequests(args.actionType || Zarafa.core.Actions['save'], action, records, parameters, reader, callback, scope, args);
	},

	/**
	 * Performs a destroy action on one or more records.
	 * @param {Ext.data.Api.action} action name of the action to perform. Always 'destroy'.
	 * @param {Ext.data.Record[]} records list of records to operate on.
	 * @param {Object} parameters object containing user parameters such as range (pagination) information, sorting information, etc.
	 * @param {Ext.data.DataReader} reader data reader. Converts raw JavaScript objects (in our case) to instances of {@link Ext.data.Record}
	 * @param {Function} callback call back function to call when the request has finished successfully.
	 * @param {Object} scope scope for the call back function.
	 * @param {Object} args arguments object. This will be passed to the call back function on successful read.
	 * @private
	 */
	destroyAction: function(action, records, parameters, reader, callback, scope, args)
	{
		this.doRequests(args.actionType || Zarafa.core.Actions['delete'], action, records, parameters, reader, callback, scope, args);
	},

	/**
	 * Performs a read action on one or more records.
	 * @param {Ext.data.Api.action} action name of the action to perform. Always 'open'.
	 * @param {Ext.data.Record[]} records list of records to operate on.
	 * @param {Object} parameters object containing user parameters such as range (pagination) information, sorting information, etc.
	 * @param {Ext.data.DataReader} reader data reader. Converts raw JavaScript objects (in our case) to instances of {@link Ext.data.Record}
	 * @param {Function} callback call back function to call when the request has finished successfully.
	 * @param {Object} scope scope for the call back function.
	 * @param {Object} args arguments object. This will be passed to the call back function on successful read.
	 * @private
	 */
	openAction: function(action, records, parameters, reader, callback, scope, args)
	{
		this.doRequests(args.actionType || Zarafa.core.Actions['open'], action, records, parameters, reader, callback, scope, args);
	},

	/**
	 * Performs an import action on one or more records.
	 * @param {Ext.data.Api.action} action name of the action to perform. Always 'import'.
	 * @param {Ext.data.Record[]} records list of records to operate on.
	 * @param {Object} parameters object containing user parameters such as range (pagination) information, sorting information, etc.
	 * @param {Ext.data.DataReader} reader data reader. Converts raw JavaScript objects (in our case) to instances of {@link Ext.data.Record}
	 * @param {Function} callback call back function to call when the request has finished successfully.
	 * @param {Object} scope scope for the call back function.
	 * @param {Object} args arguments object. This will be passed to the call back function on successful read.
	 * @private
	 */
	importAction: function(action, records, parameters, reader, callback, scope, args)
	{
		this.doRequests(args.actionType || Zarafa.core.Actions['import'], action, records, parameters, reader, callback, scope, args);
	},

	/**
	 * Performs a read action on a Folder/Store to load all records.
	 * @param {Ext.data.Api.action} action name of the action to perform. Always 'read'.
	 * @param {Ext.data.Record[]} records list of records to operate on. In case of 'read' this will be ignored.
	 * @param {Object} parameters object containing user parameters such as range (pagination) information, sorting information, etc.
	 * @param {Ext.data.DataReader} reader data reader. Converts raw JavaScript objects (in our case) to instances of{@link Ext.data.Record}
	 * @param {Function} callback call back function to call when the request has finished successfully.
	 * @param {Object} scope scope for the call back function.
	 * @param {Object} args arguments object. This will be passed to the call back function on successful read.
	 * @private
	 */
	readAction: function(action, records, parameters, reader, callback, scope, args)
	{
		this.doRequests(args.actionType || Zarafa.core.Actions['list'], action, records, parameters, reader, callback, scope, args);
	},

	/**
	 * Initialize the the {@link Zarafa.core.Request request} structure. The initial
	 * {@link Zarafa.core.Request request} will be reset and new requests will be added
	 * for each individual {@link Zarafa.core.date.MAPIRecord record} which was provided.
	 * @param {Zarafa.core.Actions} serverAction The action to perform on the server.
	 * @param {Ext.data.Api.action} action name of the action to perform.
	 * @param {Ext.data.Record[]} records list of records to operate on.
	 * @param {Object} parameters object containing user parameters such as range (pagination) information, sorting information, etc.
	 * @param {Ext.data.DataReader} reader data reader. Converts raw JavaScript objects (in our case) to instances of {@link Ext.data.Record}
	 * @param {Function} callback call back function to call when the request has finished successfully.
	 * @param {Object} scope scope for the call back function.
	 * @param {Object} args arguments object. This will be passed to the call back function on successful read.
	 * @private
	 */
	doRequests: function(serverAction, action, records, parameters, reader, callback, scope, args)
	{
		// Check if the previous request needs to be cancelled.
		if (args.cancelPreviousRequest === true) {
			this.cancelRequests(serverAction);
		}

		// reset the request object, starts a new composite request
		container.getRequest().reset();

		// If records are provided, we must perform a 'itemmodule' action on each of the given records
		if (records && args.listRequest !== true) {
			var items = parameters.jsonData[reader.meta.root];

			// Force the records object to be an array
			if (!Array.isArray(records)) {
				records = [ records ];
			}

			// Force the serialized data to be an array
			if (!Array.isArray(items)) {
				items = [ items ];
			}

			for (var i = 0; i < records.length; i++) {
				var record = records[i];
				var data = items[i];
				var module = this.getItemModuleName(record);
				var handler = this.getResponseHandlerForRequest(module, serverAction, action, record, parameters, reader, callback, scope, args);

				// Add the request
				var requestId = container.getRequest().addRequest(module, serverAction, data, handler);

				// store reference of transaction id to active request mapping
				this.addRequestId(serverAction, requestId);
			}
		} else {
			// No records were provided, we must perform a 'listmodule' action
			var module = this.getListModuleName(records);
			var handler = this.getResponseHandlerForRequest(module, serverAction, action, records, parameters, reader, callback, scope, args);

			// Add the request
			var requestId = container.getRequest().addRequest(module, serverAction, parameters, handler);

			// store reference of transaction id to active request mapping
			this.addRequestId(serverAction, requestId);
		}

		// send out the request
		container.getRequest().send();
	}
});
Ext.namespace('Zarafa.core.data');

// Extend Ext.data.Field documentation with a single config option.
/**
 * @class Ext.data.Field
 * @extends Object
 *
 * @cfg {Boolean} forceProtocol Used to determine if this field must
 * always be send to the server when {@link Zarafa.core.data.MAPIRecord#set record.set}
 * has been called. By default a field is only send to the server when it has been
 * modified, but with this option enabled, it will also be send when it has simply been set
 * with the same value as before.
 */

/**
 * @class Zarafa.core.data.MAPIRecord
 * @extends Ext.data.Record
 *
 * An extension to the {@link Ext.data.Record Record} that adds the open() method for loading
 * the 'full' contents of a MAPI item. The list modules on the server side only return partial records,
 * omitting, for example, the body of email messages. The open() method can be used to retrieve
 * these fields.
 *
 */
Zarafa.core.data.MAPIRecord = Ext.extend(Ext.data.Record, {
	/**
	 * The base array of ID properties which is copied to the {@link #idProperties}
	 * when the record is being created.
	 * @property
	 * @type Array
	 * @private
	 */
	baseIdProperties: [ 'entryid', 'store_entryid', 'parent_entryid' ],

	/**
	 * The array of properties which must be used to uniquely
	 * identify this record on the server (See {@link #baseIdProperties}.
	 * @property
	 * @type Array
	 * @private
	 */
	idProperties: undefined,

	/**
	 * The key-value object of {@link Zarafa.core.data.MAPISubStore MAPISubStore} which stores
	 * data of the complex properties like recipients, attachments etc. This container is mainly
	 * created to hold all {@link Zarafa.core.data.MAPISubStore MAPISubStore} at one place so
	 * {@link Zarafa.core.data.JsonWriter JsonWriter} can determine which {@link Zarafa.core.data.MAPISubStore MAPISubStore}
	 * it needs to serialize when serializing an {@link Zarafa.core.data.MAPIRecord MAPIRecord}.
	 * @property
	 * @type Object
	 * @private
	 */
	subStores: undefined,

	/**
	 * The key-value object of Booleans which stores the name of each SubStore which is supported
	 * by this Record. For each subStore name, the matching SubStore Type is provided which must be
	 * used to allocate the subStore. If no type is provided, then the given SubStore is not supported
	 * by this record.
	 * @property
	 * @type Object
	 * @private
	 */
	subStoresTypes: undefined,

	/**
	 * Used by {@link #setEventPropagation} and {@link #hasEventPropagation}, using this a record
	 * can be configured to prevent any events from the {@link Zarafa.core.data.IPMStore store} to
	 * be propagated through the {@link Zarafa.core.data.IPMStoreMgr IPMStoreMgr}
	 * @property
	 * @type Boolean
	 * @private
	 */
	eventPropagation: true,

	/**
	 * True to enable fine-grained modifications tracking between individual {@link Ext.data.Store#update} events.
	 * When trackUpdateModifications is true, {@link #updateModifications} will keep track of all properties
	 * which were updated since the last {@link Ext.data.Store#update} event.
	 * Must be updated through {@link #setUpdateModificationsTracking}.
	 * @property
	 * @type Boolean
	 * @private
	 */
	trackUpdateModifications: false,

	/**
	 * If {@link #trackUpdateModifications} is true, this field will contain all changes since the
	 * last {@link Ext.data.Store#update} event. When the next {@link Ext.data.Store#update} event
	 * has been fired, this object will be cleared again.
	 * @property
	 * @type Object
	 * @private
	 */
	updateModifications: undefined,

	/**
	 * If {@link #trackUpdateModifications} is true, this field will contains all substores which have
	 * fired the 'update' event since the last {@link Ext.data.Store#update} event. When the next
	 * {@link Ext.data.Store#update} event has been fired, this object will be cleared again.
	 * @property
	 * @type Object
	 * @private
	 */
	updateSubStoreModifications: undefined,

	/**
	 * The number of editors working on this records. The {@link #beginEdit} and {@link #endEdit}
	 * support nested editing blocks. This means that {@link #update} will not be fired until
	 * this counter drops to 0.
	 * @property
	 * @type Number
	 * @private
	 */
	editingCount: 0,

	/**
	 * The property will contain list of sub action types that will be sent to server when saving/deleting
	 * this record.
	 * @property
	 * @type Object
	 */
	actions: undefined,

	/**
	 * True if record is used for modal dialog. This is required because modal dialog will be the second dialog,
	 * with the same record (even though they are copies the entryid will be the same for both)
	 *
	 * @property
	 * @type Boolean
	 * @private
	 */
	isModalDialogRecord: false,

	/**
	 * @constructor
	 * @param {Object} data The data which must be applied to this record
	 * @param {Object} id The unique id for this record
	 * @param {Zarafa.core.data.RecordDefinition} definition The record definition used to
	 * construct this record
	 */
	constructor: function(data, id, definition)
	{
		Zarafa.core.data.MAPIRecord.superclass.constructor.call(this, data, id);

		this.idProperties = this.baseIdProperties.clone();

		this.actions = {};

		// initialize substore container
		this.subStores = {};
		this.subStoresTypes = {};

		if (definition) {
			this.subStoresTypes = definition.getSubStores();
		}
	},

	/**
	 * Copy the {@link Zarafa.core.data.MAPIRecord Record} to a new instance
	 * @param {String} newId (optional) A new Record id, defaults to the id of the record being copied. See id.
	 * @return {Zarafa.core.data.MAPIRecord} The copy of the record.
	 */
	copy: function(newId)
	{
		var copy = Zarafa.core.data.RecordFactory.createRecordObjectByRecordData(this.data, newId || this.id);

		copy.idProperties = this.idProperties.clone();
		copy.phantom = this.phantom;

		return copy.applyData(this, true);
	},

	/**
	 * Applies all data from an {@link Zarafa.core.data.MAPIRecord Record}
	 * to this instance. This will update all data, attachments, recipients, etc..
	 *
	 * @param {Zarafa.core.data.MAPIRecord} record The record to apply to this
	 * @param {Boolean} cheapCopy True to allow cheap assignment rather then the more
	 * expensive copying of all data.
	 * @return {Zarafa.core.data.MAPIRecord} this
	 */
	applyData: function(record, cheapCopy)
	{
		this.beginEdit();

		// This has to be synced before calling set(), as this field will
		// be used inside that function.
		this.trackUpdateModifications = record.trackUpdateModifications;

		// For each key in the remote data set, we are going to manually set
		// it inside our own dataset. This ensures that the 'modified' and
		// 'updateModifications' fields will automatically be updated to contain
		// the correct set of data.
		// if record is modal dialog record then instead of all merge only those properties which was changed
		var data = record.isModalDialogRecord ? record.modified : record.data;
		for (var key in data) {
			if(key === 'message_class') {
				// Don't update record's message_class while updating the record
				// As it is it's identity of record type and fields assigned to it.
				continue;
			}
			if (Ext.isDate(record.data[key])) {
				this.set(key, record.data[key].clone());
			} else {
				this.set(key, record.data[key]);
			}
		}

		// The actions are separate from the 'data', so we must copy it
		// separately. Note that we have no change or event mechanism for
		// this field, so bluntly copying the object is sufficient.
		this.actions = Ext.apply({}, record.actions);

		// Trigger a fake 'open' event, when the original
		// record is open and the id properties indicate that
		// both instances are referring to the exact same instance
		if (this.idProperties.equals(record.idProperties)) {
			// Create all substores & merge the contents into
			// the new substores. Perform this action before
			// calling afterOpen() as that function expects
			// the contents of the substores to be properly
			// initialized, and might add additional records
			// into the substores
			this.createSubStores();
			this.mergeSubStores(record.subStores, cheapCopy);

			// We are done with merging everything,
			// call afterOpen() if needed to ensure the
			// last initialization of the record occurs,
			// and if record which referred by "this"
			// is not opened.
			if (record.isOpened() && !this.isOpened()) {
				this.afterOpen();
			}
		}

		this.endEdit();

		return this;
	},

	/**
	 * Save all changes inside this record to the store.
	 * This will call {@link Zarafa.core.data.MAPIStore#save} with itself
	 * as argument.
	 */
	save: function()
	{
		this.getStore().save(this);
	},

	/**
	 * Compare this {@link Zarafa.core.data.MAPIRecord record} instance with another one to see
	 * if they are the same MAPI Item from the server (i.e. The entryid matches).
	 *
	 * @param {Zarafa.core.data.MAPIRecord} record The MAPIRecord to compare with
	 * @return {Boolean} True if the records are the same.
	 */
	equals: Ext.emptyFn,

	/**
	 * Enable/Disable event {@link #eventPropagation propagation} from the
	 * {@link Zarafa.core.data.IPMStore store} regarding this record by the
	 * {@link Zarafa.core.data.IPMStoreMgr IPMStoreMgr}.
	 * @param {Boolean} eventPropagation True if events can be propagated.
	 */
	setEventPropagation: function(eventPropagation)
	{
		this.eventPropagation = eventPropagation;
	},

	/**
	 * Checks if events can from the {@link Zarafa.core.data.IPMStore store} regarding
	 * this record can be {@link #eventPropagation propagated} further by the
	 * {@link Zarafa.core.data.IPMStoreMgr IPMStoreMgr}.
	 * @return {Boolean} True if events can be propagated.
	 */
	hasEventPropagation: function()
	{
		return this.eventPropagation;
	},

	/**
	 * Enable/disable UpdateModifications tracking. This will keep track of the
	 * exact modifications between two {@link Ext.data.Store#update} events. This allows
	 * finegrained tuning of UI components which constantly listen to
	 * {@link Ext.data.Store#update} events and only require the modifications since the
	 * last update call.
	 * @param {Boolean} enable Enable updatemodification tracking
	 */
	setUpdateModificationsTracking: function(enable)
	{
		this.trackUpdateModifications = enable;
	},

	/**
	 * Get the updateModifications tracking status
	 * @return {Boolean} True when update Modifications tracking has been enabled..
	 */
	getUpdateModificationsTracking: function()
	{
		return this.trackUpdateModifications;
	},

	/**
	 * @return {Ext.data.Store} data store this record belongs to.
	 */
	getStore: function()
	{
		return this.store;
	},

	/**
	 * Opens the record, loading all fields.
	 * @param {Object} options Extra options to be used for loading the record
	 */
	open: function(options)
	{
		if (this.isOpened() && (!options || options.forceLoad !== true)) {
			return;
		}
		this.store.open(this, options);
	},

	/**
	 * Called by the store after the record was opened successfully.
	 * @private
	 */
	afterOpen: function()
	{
		this.opened = true;
		this.createSubStores();
		delete this.updateModifications;
		delete this.updateSubStoreModifications;
	},

	/**
	 * @return {Boolean} true iff the record has been fully loaded.
	 */
	isOpened: function()
	{
		return this.opened === true;
	},

	/**
	 * Set a value for the given fieldname.
	 * @param {String} name The {@link Ext.data.Field#name name of the field} to set.
	 * @param {String/Object/Array} value The value to set the field to.
	 * @param {Boolean} force (optional) True to force the property to be changed in
	 * the modified array. Setting this argument will effectively override the
	 * {@link Ext.data.Field#forceProtocol forceProtocol} option for the property we are modifying here.
	 */
	set: function(name, value, force)
	{
		var forceProtocol = force;
		if (!Ext.isDefined(forceProtocol)) {
			var field = this.fields.get(name);
			forceProtocol = Ext.isDefined(field) ? field.forceProtocol : false;
		}

		if (this.trackUpdateModifications === true) {
			var encode = Ext.isPrimitive(value) ? String : Ext.encode;

			if (encode(this.data[name]) !== encode(value)) {
				// If the value is different, then we have to update
				// the 'updateModifications'. This is slightly different
				// behavior then the 'modified' array, as that array will
				// be updated even when the value is the same but
				// 'forceProtocol' was enabled.
				if (!this.updateModifications) {
					this.updateModifications = {};
				}

				if (this.updateModifications[name] === undefined) {
					this.updateModifications[name] = this.data[name];
				}
			} else if (forceProtocol !== true) {
				// No changes were made, nor is this property being
				// forced to be transmitted to the server...
				return;
			}
		}

		Zarafa.core.data.MAPIRecord.superclass.set.call(this, name, value);
		this.modified = this.modified || {};
		if (forceProtocol === true && this.modified[name] === undefined) {
			this.modified[name] = this.data[name];
		}
	},

	/**
	 * Called after a record has been edited
	 * @private
	 */
	afterEdit: function()
	{
		Zarafa.core.data.MAPIRecord.superclass.afterEdit.call(this);
		delete this.updateModifications;
		delete this.updateSubStoreModifications;
	},

	/**
	 * Called after a record modifications have been rejected
	 * @private
	 */
	afterReject: function()
	{
		Zarafa.core.data.MAPIRecord.superclass.afterReject.call(this);
		delete this.updateModifications;
		delete this.updateSubStoreModifications;
	},

	/**
	 * Begin an edit. While in edit mode, no events (e.g.. the <code>update</code> event)
	 * are relayed to the containing store.
	 * See also: <code>{@link #endEdit}</code> and <code>{@link #cancelEdit}</code>.
	 *
	 * This function is overridden from {@link Ext.data.Record#beginEdit} and adds
	 * support for nested editing blocks by using the {@link #editingCount}.
	 */
	beginEdit: function()
	{
		// Increase editing counter, if it is a negative value, it means that
		// it has been corrupted and we must force it to something valid.
		this.editingCount++;
		if (this.editingCount < 1) {
			this.editingCount = 1;
		}

		// If this is not a nested call, we can direct the call to the superclass.
		if (this.editingCount === 1) {
			Zarafa.core.data.MAPIRecord.superclass.beginEdit.call(this);
			this.updateModifications = this.updateModifications || {};
			this.updateSubStoreModifications = this.updateSubStoreModifications || {};
		}
	},

	/**
	 * Cancels all changes made in the current edit operation.
	 *
	 * This function is overridden from {@link Ext.data.Record#cancelEdit} and adds
	 * support for nested editing blocks by using the {@link #editingCount}.
	 */
	cancelEdit: function()
	{
		// Increase editing counter, if it is a negative value, it means that
		// it has been corrupted and we must force it to something valid.
		this.editingCount--;
		if (this.editingCount < 0) {
			this.editingCount = 0;
		}

		// If this is not a nested call, we can direct the call to the superclass.
		if (this.editingCount === 0) {
			Zarafa.core.data.MAPIRecord.superclass.cancelEdit.call(this);
			delete this.updateModifications;
			delete this.updateSubStoreModifications;
		}
	},

	/**
	 * Ends all editing made in the current edit operation and calls {@link #afterEdit}.
	 *
	 * This function is overridden from {@link Ext.data.Record#endEdit} and adds
	 * support for nested editing blocks by using the {@link #editingCount}.
	 */
	endEdit: function()
	{
		// Increase editing counter, if it is a negative value, it means that
		// it has been corrupted and we must force it to something valid.
		this.editingCount--;
		if (this.editingCount < 0) {
			this.editingCount = 0;
		}

		// If this is not a nested call, we can direct the call to the superclass.
		if (this.editingCount === 0) {
			Zarafa.core.data.MAPIRecord.superclass.endEdit.call(this);
		}
	},

	/**
	 * Usually called by the {@link Ext.data.Store} which owns the Record.
	 * Commits all changes made to the Record since either creation, or the last commit operation.
	 * <p>Developers should subscribe to the {@link Ext.data.Store#update} event
	 * to have their code notified of commit operations.</p>
	 * @param {Boolean} silent (optional) True to skip notification of the owning
	 * store of the change (defaults to false)
	 */
	commit: function(silent)
	{
		// Delete the local modification tracking
		delete this.updateModifications;
		delete this.updateSubStoreModifications;

		// Commit changes to substores
		for (var key in this.subStores) {
			this.subStores[key].commitChanges();
		}

		Zarafa.core.data.MAPIRecord.superclass.commit.call(this, silent);
	},

	/**
	 * When UpdateModifications tracking has been enabled, this function will return
	 * true if the given field has been modified since the last {@link Ext.data.Store#update}
	 * event. If UpdateModifications tracking has been disabled, this function will return
	 * the same value as {@link #isModified}.
	 * @param {String} fieldName The fieldname which has been modified
	 * @return {Boolean} True if the field has been modified
	 */
	isModifiedSinceLastUpdate: function(fieldName)
	{
		if (this.trackUpdateModifications === true) {
			return !!(this.updateModifications && this.updateModifications.hasOwnProperty(fieldName));
		} else {
			return this.isModified(fieldName);
		}
	},

	/**
	 * When updateModifications tracking has been enabled, this function will return
	 * true if the given substore has been modified since the last {@link Ext.data.Store#update}
	 * event. UpdateModifications tracking has been disabled, this function will return
	 * if the subStore has been modified since the subStore was created.
	 * @param {String} subStore Name of the subStore
	 * @return {Boolean} True if the field has been modified
	 */
	isSubStoreModifiedSincelastUpdate: function(subStore)
	{
		if (this.trackUpdateModifications === true) {
			return !!(this.updateSubStoreModifications && this.updateSubStoreModifications.hasOwnProperty(subStore));
		} else {
			subStore = this.getSubStore(subStore);
			return (!Ext.isEmpty(subStore.modified) || !Ext.isEmpty(subStore.removed));
		}
	},

	/**
	 * Returns the list of all added/modified/deleted records inside the subStore since the last
	 * {@link Ext.data.Store#update} event.
	 * @param {String} subStore Name of the subStore
	 * @return {Array} The array of the records which were changed since the last update.
	 */
	getSubStoreChangesSinceLastUpdate: function(subStore)
	{
		if (this.trackUpdateModifications === true) {
			if (this.updateSubStoreModifications && this.updateSubStoreModifications[subStore]) {
				return this.updateSubStoreModifications[subStore].changes;
			}
		} else {
			subStore = this.getSubStore(subStore);
			return [].concat(subStore.modified, subStore.removed);
		}
	},

	/**
	 * Get the Message Action list for the {@link Zarafa.core.data.MAPIRecord record}.
	 * @return {Mixed} The Message Action list.
	 */
	getMessageActions: function()
	{
		return this.actions;
	},

	/**
	 * Get Message Action for the {@link Zarafa.core.data.MAPIRecord record}.
	 * @param {String} actionName The name of action.
	 * @return {Mixed} The Message Action.
	 */
	getMessageAction: function(actionName)
	{
		if(this.actions[actionName]) {
			return this.actions[actionName];
		} else {
			return false;
		}
	},

	/**
	 * Add action to Message Action list
	 * @param {String} name The action name to add to the list.
	 * @param {String} value The value attached to the action name
	 */
	addMessageAction: function(name, value)
	{
		this.actions[name] = value;

		// @todo we don't want to send updates at this point since the record may not be complete. Needs a begin/end construct
		// to work properly

		// Notify modification change, but do not send a notification to the UI (since no UI has changed)
		if(this.store && this.store.modified && this.store.modified.indexOf(this) == -1){
			this.store.modified.push(this);
		}
	},

	/**
	 * Delete action from the Message Action list
	 * @param {String} name The action name to delete from the list.
	 */
	deleteMessageAction: function(name)
	{
		delete this.actions[name];
	},

	/**
	 * @param {String} name name of message action.
	 * @return {Boolean} True if a {@link #actions message action} with the given name exists.
	 */
	hasMessageAction: function(name)
	{
		return Ext.isDefined(this.actions[name]);
	},

	/**
	 * Clear all Message Actions.
	 */
	clearMessageActions: function()
	{
		this.actions = {};
	},

	/**
	 * Clear Action Response.
	 */
	clearActionResponse: function()
	{
		delete this.action_response;
	},

	/**
	 * Get requested data from the Action Response.
	 * @param {String} key Requested action response property
	 * @return {Mixed} The corresponding data
	 */
	getActionResponse: function(key)
	{
		if(this.action_response){
			return this.action_response[key];
		}
	},

	/**
	 * Unset private the {@link Zarafa.core.data.MAPIRecord record} to a different
	 * {@link Zarafa.hierarchy.data.MAPIFolderRecord folder}.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder The folder to copy/move the record to
	 */
	unsetPrivate: function()
	{
		this.addMessageAction('unset_Private', true);
	},

	/**
	 * Copy the {@link Zarafa.core.data.MAPIRecord record} to a different
	 * {@link Zarafa.hierarchy.data.MAPIFolderRecord folder}.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder The folder to copy the record to
	 */
	copyTo: function(folder)
	{
		this.addMessageAction('action_type', 'copy');
		this.addMessageAction('destination_parent_entryid', folder.get('entryid'));
		this.addMessageAction('destination_store_entryid', folder.get('store_entryid'));
	},

	/**
	 * Move the {@link Zarafa.core.data.MAPIRecord record} to a different
	 * {@link Zarafa.hierarchy.data.MAPIFolderRecord folder}.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder The folder to copy the record to
	 */
	moveTo: function(folder)
	{
		this.addMessageAction('action_type', 'move');
		this.addMessageAction('destination_parent_entryid', folder.get('entryid'));
		this.addMessageAction('destination_store_entryid', folder.get('store_entryid'));
	},

	/**
	 * Adds message action 'suppress_exception' which suppresses an exception pop-up.
	 */
	suppressException: function()
	{
		this.addMessageAction('suppress_exception', true);
	},

	/**
	 * Checks if the SubStore with the given name is supported by this record.
	 * @param {String} name The name of the subStore to check
	 * @return {Boolean} True if the given SubStore is supported by this Record.
	 */
	supportsSubStore: function(name)
	{
		return Ext.isFunction(this.subStoresTypes[name]);
	},

	/**
	 * This will create a new {@link #subStores SubStore} for the given name
	 * (if this is {@link #supportsSubStore supported}). The new substore will
	 * automatically be {@link #setSubStore set} on this record.
	 *
	 * @param {String} name The name of the subStore to create
	 * @return {Zarafa.core.data.MAPISubStore} The new substore.
	 */
	createSubStore: function(name)
	{
		if (this.supportsSubStore(name)) {
			var store = this.getSubStore(name);
			if (!Ext.isDefined(store)) {
				store = this.setSubStore(name, new this.subStoresTypes[name]());
				store.on('update', this.onSubStoreUpdate, this);
				store.on('add', this.onSubStoreChange, this);
				store.on('remove', this.onSubStoreChange, this);
			}
			return store;
		}
	},

	/**
	 * Create all {@link #subStores} which are {@link #subStoresTypes supported}
	 * by this record.
	 */
	createSubStores: function()
	{
		for (var key in this.subStoresTypes) {
			if (!this.getSubStore(key)) {
				this.createSubStore(key);
			}
		}
	},

	/**
	 * Get the SubStore for a particular name. This will get the SubStore from the {@link #subStores} field.
	 * @param {String} name The name of the substore to get
	 * @return {Zarafa.core.data.MAPISubStore} The substore.
	 */
	getSubStore: function(name)
	{
		if (this.subStores !== null) {
			return this.subStores[name];
		}
		return undefined;
	},

	/**
	 * Set the SubStore for a particular name. This will set the SubStore on the {@link #subStores} field.
	 * @param {String} name The name of the subStore to set
	 * @param {Zarafa.core.data.MAPISubStore} store the subStore.
	 * @return {Zarafa.core.data.MAPISubStore} The substore.
	 */
	setSubStore: function(name, store)
	{
		if (this.subStores === null) {
			this.subStores = {name: store};
		} else {
			this.subStores[name] = store;
		}
		store.setParentRecord(this);
		return store;
	},

	/**
	 * Merge a substore into the substore inside this record.
	 * @param {String} name The name of the subStore to merge
	 * @param {Zarafa.core.data.MAPISubStore} remoteSubStore The store to merge
	 * @param {Boolean} cheapCopy Use the cheap assignment rather then the more expensive copying
	 * of all records
	 */
	mergeSubStore: function(name, remoteSubStore, cheapCopy)
	{
		var subStore = this.getSubStore(name);

		if (subStore && remoteSubStore) {
			if (cheapCopy !== true ) {
					// When we are not performing a cheap copy we wish to preserve
					// the "add", "modify" and "delete" changes in the subStore.

					var prop = name === 'attachments' ? 'attach_id' : 'entryid';
					var isPermissionSubStore = name === 'permissions';
					// Go over the current store, and start searching for the corresponding
					// record in the remote store.
					subStore.each(function(record) {
						var remoteRecordIndex = remoteSubStore.findBy(function (remoteRecord) {
							var isRecordEqual = this.idComparison(record.get(prop), remoteRecord.get(prop));
							// If substore is permissions substore then check both the copy of record 
							// having same rights if not it means substore has old copy of user permission record 
							// which needs to remove from substore.
							if (isPermissionSubStore && isRecordEqual) {
								return record.get("rights") === remoteRecord.get("rights");
							}
							return isRecordEqual;
						}, this);

						if (remoteRecordIndex < 0) {
							// The other store doesn't contain this record,
							// remove it from the current store.
							subStore.remove(record);
						}
					}, this);

					// Go over the remote store to search for any new records which were added
					remoteSubStore.each(function(record) {
						var origRecordIndex = subStore.findBy(function (storeRecord) {
							return this.idComparison(record.get(prop), storeRecord.get(prop));
						}, this);

						if (origRecordIndex < 0) {
							// New record, add it to the current store.
							subStore.add(record.copy());
						}
					}, this);
			} else {
				// A cheap copy is nothing more that destroy all
				// currently available data and move all records
				// from the remote store into the current store.
				// We fire the 'datachanged' event to inform the
				// UI of the bulk change which has been performed.
				subStore.removeAll(true);
				subStore.add(remoteSubStore.getRange(), true);
				subStore.fireEvent('datachanged', subStore);
			}
		}
	},

	/**
	 * Function which is used to compare two entry ids also
	 * it is take care of comparing local contact items.
	 *
	 * @param {String} entryIdOne The first id to compare
	 * @param {String} entryIdTwo The second id to compare
	 * @return {Boolean} return true if entryId is same else false.
	 * @protected
	 */
	idComparison: function(entryIdOne, entryIdTwo)
	{
		entryIdOne = Zarafa.core.EntryId.hasContactProviderGUID(entryIdOne) ?
			Zarafa.core.EntryId.unwrapContactProviderEntryId(entryIdOne): entryIdOne;

		entryIdTwo = Zarafa.core.EntryId.hasContactProviderGUID(entryIdTwo) ?
			Zarafa.core.EntryId.unwrapContactProviderEntryId(entryIdTwo): entryIdTwo;

		return Zarafa.core.EntryId.compareEntryIds(entryIdOne, entryIdTwo);
	},

	/**
	 * Merge all data from the object containing subStores into this record.
	 * This will call {@link #mergeSubStore} for each subStore found in the SubStores object,
	 * note that only stores which are {@link #supportsSubStore supported} will be merged.
	 * @param {Object} subStores The key-value array containing all subStores which must
	 * be applied to the record.
	 * @param {Boolean} cheapCopy Use the cheap assignment rather then the more expensive copying
	 * of all records
	 */
	mergeSubStores: function(subStores, cheapCopy)
	{
		for (var key in subStores) {
			if (this.supportsSubStore(key)) {
				this.mergeSubStore(key, subStores[key], cheapCopy);
			}
		}
	},

	/**
	 * Event handler which is fired when data in this subStore has changed. This will markt the subStore as
	 * changed and force the {@link Ext.data.Store#update} event of the store of this record.
	 * @param {Zarafa.core.data.MAPISubStore} store The store which was changed
	 * @param {Zarafa.core.data.MAPIRecord[]} records The records which were added/modified/removed
	 * @private
	 */
	onSubStoreChange: function(store, records)
	{
		for (var key in this.subStores) {
			if (this.subStores[key] === store) {
				if (this.trackUpdateModifications === true) {
					if (!this.updateSubStoreModifications) {
						this.updateSubStoreModifications = {};
					}

					if (!Ext.isDefined(records)) {
						records = store.getRange();
					}

					var changes = this.updateSubStoreModifications[key];
					if (!changes) {
						changes = {
							store: store,
							changes: [].concat(records)
						};
						this.updateSubStoreModifications[key] = changes;
					} else {
						changes.changes = changes.changes.concat(records);
					}
				}

				this.dirty = true;
				// Because we manually force the 'update' event to be fired,
				// we must create the local modified array. ExtJs demands
				// that afterEdit() must only be called when the modified
				// array exists.
				this.modified = this.modified || {};
				if (!this.editing) {
					this.afterEdit();
				}
				break;
			}
		}
	},

	/**
	 * Event handler which is fired when data in this subStore has been updated. This will check
	 * if the given action is {@link Ext.data.Record#COMMIT}, in that case no action is taken,
	 * in other cases {@link #onSubStoreChange} is called to act upon a change inside the substore
	 * contents.
	 * @param {Zarafa.core.data.MAPISubStore} store The store which fired the event
	 * @param {Zarafa.core.data.MAPIRecord[]} records The records which were modified
	 * @param {String} action The action which was performed (could be
	 * {@link Ext.data.Record#EDIT}, {@link Ext.data.Record#REJECT}, {@link Ext.data.Record#COMMIT}).
	 * @private
	 */
	onSubStoreUpdate: function(store, records, action)
	{
		if (action !== Ext.data.Record.COMMIT) {
			this.onSubStoreChange(store, records);
		}
	},

	/**
	 * Add extra Identification property to the {@link #idProperties} array.
	 * This will serialize the given property into the identification section
	 * when communication with the server.
	 *
	 * @param {String} prop The propertyname to be added
	 */
	addIdProp: function(prop)
	{
		if (!this.hasIdProp(prop)) {
			this.idProperties.push(prop);
		}
	},

	/**
	 * Check if the given property is an {@link #idProperties id prop}.
	 * @param {String} prop The name of the property
	 * @return {Boolean} True if the given prop is an ID property
	 */
	hasIdProp: function(prop)
	{
		return this.idProperties.indexOf(prop) > -1;
	},

	/**
	 * Remove extra Identification property from the {@link #idProperties} array.
	 * @param {String} prop The name of the property
	 */
	removeIdProp: function(prop)
	{
		if(this.hasIdProp(prop)) {
			this.idProperties.splice(this.idProperties.indexOf(prop), 1);
		}
	},

	/**
	 * Obtain the list of of identification properties ({@link #idProperties}).
	 * These properties will be placed inside the identification section of
	 * the protocol during the communication with the server.
	 *
	 * @return {array} the array of identification properties.
	 */
	getIdProps: function()
	{
		return this.idProperties;
	},

	/**
	 * Convenience method to get {@link Zarafa.core.mapi.DisplayType} or {@link Zarafa.core.mapi.DisplayTypeEx}
	 * property value from {@link Zarafa.core.data.IPMRecord}.
	 *
	 * @return {Zarafa.core.mapi.DisplayType|Zarafa.core.mapi.DisplayTypeEx} The display type value.
	 */
	getDisplayType: function()
	{
		var displayType = this.get('display_type');
		var displayTypeEx = this.get('display_type_ex');
		var returnValue;

		switch(displayType) {
			case Zarafa.core.mapi.DisplayType.DT_MAILUSER:
			case Zarafa.core.mapi.DisplayType.DT_DISTLIST:
				returnValue = displayTypeEx & ~Zarafa.core.mapi.DisplayTypeEx.DTE_FLAG_ACL_CAPABLE;
				break;
			default:
				returnValue = displayType;
				break;
		}

		return returnValue;
	},

	/**
	 * Destroy the record, this will destroy the record and the record data
	 * ensuring that all references are lost.
	 */
	destroy: function()
	{
		// Destroy all substores
		for (var key in this.subStores) {
			this.subStores[key].destroy();
		}
		this.subStores = null;
	},

	/**
	 * @returns {boolean} return true only if record has delete access else false.
	 */
	hasDeleteAccess: function ()
	{
		return (this.get('access') & Zarafa.core.mapi.Access.ACCESS_DELETE) > 0;
	},

	/**
	 * @returns {boolean} True if the store granted MAPI_ACCESS_MODIFY on this item.
	 */
	hasModifyAccess: function ()
	{
		return (this.get('access') & Zarafa.core.mapi.Access.ACCESS_MODIFY) > 0;
	},

	/**
	 * Determine if this is a persisted item which the user is not allowed to modify.
	 * Phantom (new, unsaved) records are always editable. Items opened from a
	 * read-only shared folder (and embedded sub-messages, which carry no
	 * PR_ACCESS) lack MAPI_ACCESS_MODIFY and are therefore read-only.
	 * @returns {boolean} True if the record may not be modified.
	 */
	isReadOnlyRecord: function ()
	{
		return !this.phantom && !this.hasModifyAccess();
	},

	/**
	 * Event handler for the 'datachanged' event of {@link Ext.data.Store store}
	 * When we have modal dialog open and if we receive a new email then modified, store
	 * and sub store of the selected records are not accessible anymore,
	 * so we have to set the modified things from the record along with updated store in modal record.
	 *
	 * @param {Zarafa.core.data.ListModuleStore} store This store
	 * @private
	 */
	onDataChange: function (store)
	{
		var modalRecord = this.modalRecord;
		if (Ext.isEmpty(modalRecord.getStore()) && this.isModal()) {
			modalRecord.applyData(this.record);
			modalRecord.store = store;
		}
	}
});
Ext.namespace('Zarafa.core.data');

Ext.data.Api.actions.open = 'open';

/**
 * @class Zarafa.core.data.MAPIStore
 * @extends Ext.data.GroupingStore
 * @xtype zarafa.mapistore
 *
 * Extension of the Extjs store which adding support for the 'open' command,
 * which is used by MAPI to request additional data for a record.
 */
Zarafa.core.data.MAPIStore = Ext.extend(Ext.data.GroupingStore, {
	/**
	 * @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,

	/**
	 * Set to true when the {Zarafa.core.data.MAPIStore} starts saving, set to false when done.
	 * @property
	 * @type Boolean
	 * @private
	 */
	isSaving: false,

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

		Ext.applyIf(config, {
			// Don't automatically update changes to records to the server.
			autoSave: false,

			// When autoSave is false, indicates that CRUD operations are batched into a single request.
			batch: true
		});

		this.addEvents(
			/**
			 * @event open
			 * Fires when the {@link Zarafa.core.data.MAPIStore MAPIStore} gets extra data for a specific
			 * {@link Zarafa.core.data.MAPIRecord MAPIRecord}.
			 * @param {Zarafa.core.data.MAPIStore} store The {@link Zarafa.core.data.MAPIStore MAPIStore} which issues
			 * open request to get extra data for specific record.
			 * @param {Zarafa.core.data.MAPIRecord} record Record which is being opened to get extra information.
			 */
			'open'
		);

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

		// Update the getKey function inside the MixedCollection to match
		// the one provided getRecordKey.
		this.data.getKey = this.getRecordKey;

		this.initEvents();
	},

	/**
	 * Initialize all events which Zarafa.core.data.MAPIStore MAPIStore} will listen to.
	 * @protected
	 */
	initEvents: function()
	{
		this.on('beforeload', this.onBeforeLoad, this);
		this.on('add', this.onAdd, this);
		this.on('remove', this.onRemove, this);
		this.on('write', this.onWrite, this);

		this.on('beforesave', function(){ this.isSaving = true; }, this);
		this.on('save', function(){ this.isSaving = false; }, this);
	},

	/**
	 * The {@link Ext.util.MixedCollection#getKey} function which must be
	 * applied to the {@link #data}{@link Ext.util.MixedCollection#getKey #getKey}
	 * function. This is assigned by the constructor and allows subclasses to
	 * simply override this function rather then apply it manually to {@link #data}
	 * themselves.
	 * @param {Ext.data.Record} o The record for which the key is requested
	 * @return {String} The key by which the record must be saved into the {@link Ext.util.MixedCollection}.
	 * @protected
	 */
	getRecordKey: Ext.util.MixedCollection.prototype.getKey,

	/**
	 * Check if a particular {@link Zarafa.core.Action action} is being executed
	 * by the {@link #proxy} of this store. When no action is given, this function
	 * will check if the proxy is busy with any action.
	 *
	 * @param {Zarafa.core.Action} action The action which is being checked
	 * @return {Boolean} True if the given action is being executed by the proxy
	 */
	isExecuting: function(action)
	{
		return this.proxy.isExecuting(action);
	},

	/**
	 * Determine if a {@link #isExecuting 'list'} or {@link #isExecuting 'open'}
	 * request is still pending. And if so
	 * {@link Zarafa.core.data.MAPIProxy#cancelRequest cancel} those requests.
	 */
	cancelLoadRequests: function()
	{
		// If we are loading data, we want to cancel
		// the request as we don't want the data anymore.
		if (this.isExecuting('list')) {
			this.proxy.cancelRequests('list');
		}

		// If we are opening the record, we want to cancel
		// the request as we don't want the data anymore.
		if (this.isExecuting('open')) {
			this.proxy.cancelRequests('open');
		}

		// Saving is still interesting as the user might
		// not expect that action to be still pending.
	},

	/**
	 * Get the {@link Date#getTime timestamp} of the last time a response was given
	 * for the given action.
	 * @param {Zarafa.core.Action} action The action which is being checked
	 * @return {Number} The timestamp of the last action time
	 */
	lastExecutionTime: function(action)
	{
		return this.proxy.lastExecutionTime(action);
	},

	/**
	 * <p>Reloads the Record cache from the configured Proxy. See the superclass {@link Ext.data.Store#reload documentation}
	 * for more detaiils.
	 * During reload we add an extra option into the {@link #load} argument which marks the action as a reload
	 * action.
	 */
	reload: function(options)
	{
		options = Ext.applyIf(options || {}, { reload: true });
		Zarafa.core.data.MAPIStore.superclass.reload.call(this, options);
	},

	/**
	 * Saves all pending changes to the store. See the superclass {@link Ext.data.Store#save documentation}
	 * for more details. Where the superclass saves all {@link #removed} and {@link #modified} records,
	 * this function will only save the records which are passed as argument.
	 *
	 * @param {Zarafa.core.data.MAPIRecord/Zarafa.core.data.MAPIRecord[]} records The records which
	 * must be saved to the server.
	 * @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(records) {
		// When no records are provided, fall back to the default behavior of the superclass.
		if (!Ext.isDefined(records)) {
			return Zarafa.core.data.MAPIStore.superclass.save.call(this);
		}

		if (!Array.isArray(records)) {
			records = [ records ];
		}

		if (!this.writer) {
			throw new Ext.data.Store.Error('writer-undefined');
		}

		var destroyed = [],
			created = [],
			updated = [],
			queue = [],
			trans,
			batch,
			data = {};

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

			if (this.removed.indexOf(record) >= 0) {
				// Check for removed records first, a record located in this.removed is
				// guaranteed to be a non-phantom. See store.remove().
				destroyed.push(record);
			} else if (this.modified.indexOf(record) >= 0) {
				// Only accept valid records.
				if (record.isValid()) {
					if (record.phantom) {
						created.push(record);
					} else {
						updated.push(record);
					}
				}
			}
		}

		if (destroyed.length > 0) {
			queue.push(['destroy', destroyed]);
		}
		if (created.length > 0) {
			queue.push(['create', created]);
		}
		if (updated.length > 0) {
			queue.push(['update', updated]);
		}

		var len = queue.length;
		if(len){
			batch = ++this.batchCounter;
			for(var i = 0; i < len; ++i){
				trans = queue[i];
				data[trans[0]] = trans[1];
			}
			if(this.fireEvent('beforesave', this, data) !== false){
				for(var i = 0; i < len; ++i){
					trans = queue[i];
					this.doTransaction(trans[0], trans[1], batch);
				}
				return batch;
			}
		}
		return -1;
	},

	/**
	 * Event handler which is fired when we are about to (re)load the store.
	 * When this happens we should cancel all pending {@link #open} requests,
	 * as they cannot be completed anymore (the record will have been deleted,
	 * so the opened record has become useless.
	 * @private
	 */
	onBeforeLoad: function()
	{
		if (this.isExecuting('open')) {
			this.proxy.cancelRequests('open');
		}
	},

	/**
	 * Event handler which is raised when a {@link Zarafa.core.data.MAPIRecord MAPIRecord} has been added
	 * to this {@link Zarafa.core.data.MAPIStore MAPIStore}.
	 *
	 * @param {Zarafa.core.data.MAPIStore} store The {@link Zarafa.core.data.MAPIStore MAPIStore} to which the store was added.
	 * @param {Zarafa.core.data.MAPIRecord[]} records The array of {@link Zarafa.core.data.MAPIRecord records} which have been added.
	 * @param {Number} index The index at which the record(s) were added
	 * @private
	 */
	onAdd: function(store, records, index)
	{
		this.setRecordsStore(store, records);
	},

	/**
	 * Event handler which is raised when a {@link Zarafa.core.data.MAPIRecord MAPIRecord} has been removed
	 * from this {@link Zarafa.core.data.MAPIStore MAPIStore}.
	 *
	 * @param {Zarafa.core.data.MAPIStore} store The {@link Zarafa.core.data.MAPIStore MAPIStore} from which the records were removed.
	 * @param {Zarafa.core.data.MAPIRecord[]} records The array of {@link Zarafa.core.data.MAPIRecord records} which have been removed.
	 * @param {Number} index The index at which the record(s) were removed.
	 * @private
	 */
	onRemove: function(store, records, index)
	{
		this.setRecordsStore(undefined, records);
	},

	/**
	 * Event handler which is raised when the {@link #write} event has been fired. This will clear
	 * all {@link Zarafa.core.data.MAPIRecord#actions Message Actions} from the given records.
	 *
	 * @param {Zarafa.core.data.MAPIStore} store The store which fired the event
	 * @param {String} action [Ext.data.Api.actions.create|update|destroy]
	 * @param {Object} result The 'data' picked-out out of the response for convenience
	 * @param {Ext.Direct.Transaction} res The transaction
	 * @param {Record/Record[]} records The records which were written to the server
	 * @private
	 */
	onWrite: function(store, action, result, res, records)
	{
		if (!Array.isArray(records)) {
			records = [ records ];
		}

		for (var i = 0, len = records.length; i < len; i++) {
			records[i].clearMessageActions();
			records[i].clearActionResponse();
		}
	},

	/**
	 * Iterates through all {@link Zarafa.core.data.MAPIRecord records} and sets the
	 * reference to the {@link Zarafa.core.data.MAPIStore MAPIStore} to which it belongs.
	 *
	 * @param {Zarafa.core.data.MAPIStore} store The {@link Zarafa.core.data.MAPIStore MAPIStore} to which the
	 * {@link Zarafa.core.data.MAPIRecord records} must be assigned.
	 * @param {Zarafa.core.data.MAPIRecord[]} records The array of
	 * {@link Zarafa.core.data.MAPIRecord records} which must be updated.
	 * @private
	 */
	setRecordsStore: function(store, records)
	{
		records = Array.isArray(records) ? records : [ records ];
		Ext.each(records, function(record) { record.join(store); }, this);
	},

	/**
	 * Function is used to get extra properties from the server, which are not received in
	 * 'list' action. function will call {@link #execute} event, which is entry point for every
	 * CRUD operation, {@link #execute} will internal call {@link #createCallback} to create a
	 * callback function based on operation type ('open' -> onOpenRecords).
	 * @param {Zarafa.core.data.MAPIRecord[]} records records for which we need extra properties.
	 * @param {Object} options Extra options which can be used for opening the records
	 * @return {Boolean|undefined} false when this.execute fails
	 */
	open: function(records, options)
	{
		try {
			return this.execute('open', records, options);
		} catch (e) {
			this.handleException(e);
			return false;
		}
	},

	/**
	 * Function will work as callback function for 'open' operation, and update the
	 * existing records with the new data that is received from server.
	 * @param {Boolean} success true if operation completed successfully else false.
	 * @param {Zarafa.core.data.MAPIRecord|Zarafa.core.dataMAPIRecord[]} records updated records.
	 * @param {Object|Array} data properties of records which is received from server (in key/value pair).
	 */
	onOpenRecords: function(success, records, data)
	{
		if (success !== true) {
			return;
		}

		if (!Array.isArray(records)) {
			records = [ records ];
		}

		records.forEach(function(record) {
			// Check against the full record set (snapshot when a filter is
			// active): records hidden by a filter (e.g. collapsed conversation
			// items) must still receive their opened data.
			var collection = this.snapshot || this.data;
			if (collection.indexOf(record) === -1) {
				return;
			}

			// Opening items in batch passes multiple records but only one data object per onOpenRecords
			// call, therefore the entryid has to be compared to update the correct record.
			// Also opening meeting requests passes the task request as record and expects the actual task to
			// be applied onto the record, here the entryids don't match so just check if records length is not 1.
			if (records.length !== 1 && Array.isArray(data) && data.length !== 0 &&
				!Zarafa.core.EntryId.compareEntryIds(record.get('entryid'), data[0].entryid)) {
				return;
			}

			try {
				// call reader to update record data
				var oldRecord = record;

				this.reader.update(record, data);
				record.afterOpen();

				this.fireEvent('open', this, record, oldRecord);
			} catch (e) {
				this.handleException(e);
			}
		}, this);
	},

	/**
	 * 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.MAPIStore.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 = this.findBy(function(record) { return this.idComparison(id, record.id); }, this);
			if (index >= 0) {
				item = this.getAt(index);
			}
		}

		return item;
	},

	/**
	 * Compare a {@link Ext.data.Record#id ids} to determine if they are equal.
	 * @param {String} a The first id to compare
	 * @param {String} b The second id to compare
	 * @protected
	 */
	idComparison: function(a, b)
	{
		return a === b;
	},

	/**
	 * 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: function(fn, scope)
	{
		// Save the function for later usage.
		this.filterFn = fn;
		this.filterScope = scope;

		Zarafa.core.data.MAPIStore.superclass.filterBy.apply(this, arguments);
	},

	/**
	 * 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: function()
	{
		// Reset the filter
		delete this.filterFn;
		delete this.filterScope;

		Zarafa.core.data.MAPIStore.superclass.clearFilter.apply(this, arguments);
	},

	/**
	 * 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)
	{
		Zarafa.core.data.MAPIStore.superclass.loadRecords.apply(this, arguments);

		if (this.persistentFilter === true && !this.isDestroyed && (!options || options.add !== true)) {
			if (this.filterFn) {
				this.filterBy(this.filterFn, this.filterScope);
			}
		}
	},

	/**
	 * Clear all data in the store
	 * @private
	 */
	clearData: function()
	{
		this.data.each(function(rec) {
			rec.destroy();
		});
		Zarafa.core.data.MAPIStore.superclass.clearData.apply(this, arguments);
	},

	/**
	 * 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);
	},

	/**
	 * Clears any existing grouping and refreshes the data using the default sort.
	 */
	clearGrouping: function()
	{
		// Only clear grouping when
		// grouping was previously applied
		if (this.groupField) {
			Zarafa.core.data.MAPIStore.superclass.clearGrouping.apply(this, arguments);
		}
	},

	/**
	 * Destroys the store
	 */
	destroy: function()
	{
		// Make sure we cancel all load requests
		// to the server as we are no longer
		// interested in the results.
		if (!this.isDestroyed) {
			this.cancelLoadRequests();
		}

		Zarafa.core.data.MAPIStore.superclass.destroy.apply(this, arguments);
	}
});

Ext.reg('zarafa.mapistore', Zarafa.core.data.MAPIStore);
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.NoSyncStore
 * @extends Ext.util.Observable
 *
 * The {@link Zarafa.core.data.NoSyncStore NoSyncStore} represents the collection of
 * {@link Ext.data.Record records}. It offers the same interface
 * as {@link Zarafa.core.data.Store Store} without any CRUD operations being
 * send to the server. This implies that the {@link Zarafa.core.data.NoSyncStore NoSyncStore}
 * will only work for working on {@link Ext.data.Record records} offline.
 */
Zarafa.core.data.NoSyncStore = Ext.extend(Ext.util.Observable, {
	/**
	 * The {@link Ext.data.Record Record} constructor as supplied to (or created by) the
	 * {@link Ext.data.DataReader Reader}. Read-only.
	 * <p>If the Reader was constructed by passing in an Array of {@link Ext.data.Field} definition objects,
	 * instead of a Record constructor, it will implicitly create a Record constructor from that Array (see
	 * {@link Ext.data.Record}.{@link Ext.data.Record#create create} for additional details).</p>
	 * <p>This property may be used to create new Records of the type held in this Store
	 * @property recordType
	 * @type Function
	 */
	recordType: undefined,

	/**
	 * A {@link Ext.util.MixedCollection MixedCollection} containing the defined {@link Ext.data.Field Field}s
	 * for the {@link Ext.data.Record Records} stored in this Store. Read-only.
	 * @property fields
	 * @type Ext.util.MixedCollection
	 */
	fields: undefined,

	/**
	 * True if this store is currently sorted by more than one field/direction combination.
	 * @property
	 * @type Boolean
	 */
	hasMultiSort: false,

	/**
	 * Object containing the current sorting information.
	 * @property
	 * @type Object
	 */
	sortToggle: undefined,

	/**
	 * @cfg {String} sortField
	 * (optional) Initial column on which to sort.
	 */
	sortField: undefined,

	/**
	 * @cfg {String} sortDir
	 * (Optional) Initial direction to sort (<code>"ASC"</code> or <code>"DESC"</code>). Defaults to
	 * <code>"ASC"</code>.
	 */
	sortDir: 'ASC',

	/**
	 * @cfg {Object} sortInfo A config object to specify the sort order in the request of a Store's load operation.
	 * Note that for local sorting, the direction property is case-sensitive.
	 */
	sortInfo: undefined,

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

		Ext.apply(this, config);

		// If the recordType is provided, we can obtain the fields.
		if (this.recordType) {
			this.fields = this.recordType.prototype.fields;
		}

		this.addEvents(
			/**
			 * @event add
			 * Fires when Records have been {@link #add}ed to the Store
			 * @param {Store} this
			 * @param {Ext.data.Record[]} records The array of Records added
			 * @param {Number} index The index at which the record(s) were added
			 */
			'add',
			/**
			 * @event remove
			 * Fires when a Record has been {@link #remove}d from the Store
			 * @param {Store} this
			 * @param {Ext.data.Record} record The Record that was removed
			 * @param {Number} index The index at which the record was removed
			 */
			'remove',
			/**
			 * @event update
			 * Fires when a Record has been updated
			 * @param {Store} this
			 * @param {Ext.data.Record} record The Record that was updated
			 * @param {String} operation The update operation being performed. Value may be one of:
			 * <pre><code>
	Ext.data.Record.EDIT
	Ext.data.Record.REJECT
	Ext.data.Record.COMMIT
			 * </code></pre>
			 */
			'update',
			/**
			 * @event clear
			 * Fires when the data cache has been cleared.
			 * @param {Store} this
			 * @param {Record[]} The records that were cleared.
			 */
			'clear'
		);

		this.sortToggle = {};
		if (this.sortField){
			this.setDefaultSort(this.sortField, this.sortDir);
		} else if(this.sortInfo) {
			this.setDefaultSort(this.sortInfo.field, this.sortInfo.direction);
		}

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

		this.initEvents();
		this.initData();
	},

	/**
	 * Initialize data structures in which the {@link Ext.data.Record records} are stored.
	 * @private
	 */
	initData: function()
	{
		this.data = new Ext.util.MixedCollection(false);
		this.data.getKey = function(o){
			return o.id;
		};

		this.removed = [];
		this.modified = [];
	},

	/**
	 * Initialize events which can be raised by the {@link Zarafa.core.data.NoSyncStore NoSyncStore}
	 * @private
	 */
	initEvents: function()
	{
		this.on({
			scope: this,
			add: this.createRecords,
			remove: this.destroyRecord,
			clear: this.onClear
		});
	},

	/**
	 * Destroys the store.
	 */
	destroy: function()
	{
		if (!this.isDestroyed) {
			this.clearData();
			this.data = null;
			this.purgeListeners();
			this.isDestroyed = true;
		}
	},

	/**
	 * Add Records to the Store and fires the {@link #add} event.
	 * See also <code>{@link #insert}</code>.
	 * @param {Ext.data.Record[]} records An Array of Ext.data.Record objects
	 * to add to the cache.
	 * @param {Boolean} silent [false] Defaults to <tt>false</tt>.
	 * Set <tt>true</tt> to not fire add event.
	 */
	add: function(records, silent)
	{
		records = [].concat(records);
		if(records.length < 1) {
			return;
		}

		for (var i = 0, len = records.length; i < len; i++) {
			records[i].join(this);
		}

		var index = this.data.length;
		this.data.addAll(records);

		if(this.snapshot){
			this.snapshot.addAll(records);
		}

		if (silent !== true) {
			this.fireEvent('add', this, records, index);
		}
	},

	/**
	 * Remove Records from the Store and fires the {@link #remove} event.
	 * @param {Ext.data.Record/Ext.data.Record[]} record The record object or array of records to remove from the cache.
	 * @param {Boolean} silent [false] Defaults to <tt>false</tt>. Set <tt>true</tt> to not fire remove event.
	 */
	remove: function(record, silent)
	{
		if (Array.isArray(record)) {
			Ext.each(record, function(r){
				this.remove(r, silent);
			}, this);
		}

		var index = this.data.indexOf(record);
		if(this.snapshot){
			this.snapshot.remove(record);
		}
		if (index > -1) {
			record.join(null);

			this.data.removeAt(index);

			if (silent !== true) {
				this.fireEvent('remove', this, record, index);
			}
		}
	},

	/**
	 * Remove a Record from the Store at the specified index. Fires the {@link #remove} event.
	 * @param {Number} index The index of the record to remove.
	 * @param {Boolean} silent [false] Defaults to <tt>false</tt>. Set <tt>true</tt> to not fire remove event.
	 */
	removeAt: function(index, silent)
	{
		this.remove(this.getAt(index), silent);
	},

	/**
	 * Remove all Records from the Store and fires the {@link #clear} event.
	 * @param {Boolean} silent [false] Defaults to <tt>false</tt>. Set <tt>true</tt> to not fire clear event.
	 */
	removeAll: function(silent)
	{
		var items = [];
		this.each(function(rec){
			items.push(rec);
		});

		this.clearData();
		if(this.snapshot){
			this.snapshot.clear();
		}
		this.modified = [];
		this.removed = [];

		if (silent !== true) {
			this.fireEvent('clear', this, items);
		}
	},

	/**
	 * Remove all Records for which the callback returns true
	 * from the Store and fires the {@link #remove} event.
	 *
	 * @param {Function} callback The callback function which is used to determine
	 * if a record must be removed. Function must accept a {@link Ext.data.Record}
	 * as argument.
	 * @param {Object} scope The scope which must be used for the callback function
	 */
	removeIf: function(callback, scope)
	{
		this.each(function(record) {
			if (callback.call(scope || this, record)) {
				this.remove(record);
			}
		}, this);
	},

	/**
	 * Inserts Records into the Store at the given index and fires the {@link #add} event.
	 * See also <code>{@link #add}</code>.
	 * @param {Number} index The start index at which to insert the passed Records.
	 * @param {Ext.data.Record[]} records An Array of Ext.data.Record objects to add to the cache.
	 */
	insert: function(index, records)
	{
		records = [].concat(records);
		for (var i = 0, len = records.length; i < len; i++) {
			this.data.insert(index, records[i]);
			records[i].join(this);
		}

		if(this.snapshot){
			this.snapshot.addAll(records);
		}

		this.fireEvent('add', this, records, index);
	},

	/**
	 * Get the index within the cache of the passed Record.
	 * @param {Ext.data.Record} record The Ext.data.Record object to find.
	 * @return {Number} The index of the passed Record. Returns -1 if not found.
	 */
	indexOf: function(record)
	{
		return this.data.indexOf(record);
	},

	/**
	 * Get the index within the cache of the Record with the passed id.
	 * @param {String} id The id of the Record to find.
	 * @return {Number} The index of the Record. Returns -1 if not found.
	 */
	indexOfId: function(id)
	{
		return this.data.indexOfKey(id);
	},

	/**
	 * Get the Record at the specified index.
	 * @param {Number} index The index of the Record to find.
	 * @return {Ext.data.Record} The Record at the passed index. Returns undefined if not found.
	 */
	getAt: function(index)
	{
		return this.data.itemAt(index);
	},

	/**
	 * Returns a range of Records between specified indices.
	 * @param {Number} startIndex (optional) The starting index (defaults to 0)
	 * @param {Number} endIndex (optional) The ending index (defaults to the last Record in the Store)
	 * @return {Ext.data.Record[]} An array of Records
	 */
	getRange: function(start, end)
	{
		return this.data.getRange(start, end);
	},

	/**
	 * Gets the number of cached records
	 * @return {Number} The number of records
	 */
	getCount: function()
	{
		return this.data.length;
	},

	/**
	 * Gets the total number of records in the dataset.
	 * @return {Number} The number of records
	 */
	getTotalCount: function()
	{
		return this.getCount();
	},

	/**
	 * Calls the specified function for each of the {@link Ext.data.Record Records} in the cache.
	 * @param {Function} fn The function to call. The {@link Ext.data.Record Record} is passed as the first parameter.
	 * Returning <tt>false</tt> aborts and exits the iteration.
	 * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed.
	 * Defaults to the current {@link Ext.data.Record Record} in the iteration.
	 */
	each: function(fn, scope)
	{
		this.data.each(fn, scope);
	},

	/**
	 * Gets all {@link Ext.data.Record records} modified since the last commit.
	 * <b>Note</b>: deleted records are not included.
	 * See also {@link Ext.data.Record}<tt>{@link Ext.data.Record#markDirty markDirty}.</tt>.
	 * @return {Ext.data.Record[]} An array of {@link Ext.data.Record Records} containing outstanding
	 * modifications. To obtain modified fields within a modified record see
	 *{@link Ext.data.Record}<tt>{@link Ext.data.Record#modified modified}.</tt>.
	 */
	getModifiedRecords: function()
	{
		return this.modified;
	},

	/**
	 * Gets all {@link Ext.data.Record records} removed since the last commit.
	 * @return {Ext.data.Record[]} An array of {@link Ext.data.Record Records} which have
	 * been marked as removed.
	 */
	getRemovedRecords: function()
	{
		return this.removed;
	},

	/**
	 * Called by {@link Ext.data.Record}
	 * @param {Ext.data.Record} The record which has been edited.
	 * @private
	 */
	afterEdit: function(record)
	{
		if(this.modified.indexOf(record) === -1) {
			this.modified.push(record);
		}
		this.fireEvent('update', this, record, Ext.data.Record.EDIT);
	},

	/**
	 * Called by {@link Ext.data.Record}
	 * @param {Ext.data.Record} The record which has been rejected.
	 * @private
	 */
	afterReject: function(record)
	{
		this.modified.remove(record);
		this.fireEvent('update', this, record, Ext.data.Record.REJECT);
	},

	/**
	 * Called by {@link Ext.data.Record}
	 * @param {Ext.data.Record} The record which has been committed.
	 * @private
	 */
	afterCommit: function(record)
	{
		this.modified.remove(record);
		this.fireEvent('update', this, record, Ext.data.Record.COMMIT);
	},

	/**
	 * 'Commit' outstanding changes. Since {@link Zarafa.core.data.NoSyncStore NoSyncStore}
	 * has no commit capability, changes are not actually sent, but are only cleared.
	 */
	commitChanges: function()
	{
		var m = this.modified.slice(0);
		for(var i = 0, len = m.length; i < len; i++){
			var mi = m[i];
			// Committing means unphantoming.
			mi.phantom = false;
			mi.commit();
		}

		this.modified = [];
		this.removed = [];
	},

	/**
	 * Clear the data within this store
	 * @private
	 */
	clearData: function()
	{
		this.data.clear();
	},

	/**
	 * Should not be used directly. Store#add will call this automatically
	 * @param {Object} store
	 * @param {Object} rs
	 * @param {Object} index
	 * @private
	 */
	createRecords: function(store, rs, index)
	{
		for (var i = 0, len = rs.length; i < len; i++) {
			if (rs[i].phantom && rs[i].isValid()) {
				rs[i].markDirty(); // <-- Mark new records dirty
				this.modified.push(rs[i]); // <-- add to modified
			}
		}
	},

	/**
	 * Destroys a record or records. Should not be used directly. It's called by Store#remove automatically
	 * @param {Store} store
	 * @param {Ext.data.Record} record
	 * @param {Number} index
	 * @private
	 */
	destroyRecord: function(store, record, index)
	{
		var modifiedRecord = this.modified.find(function(item){
			return item.get("entryid") === record.get("entryid") && item.phantom === record.phantom;
		});
			 
		if (Ext.isDefined(modifiedRecord)) {
			this.modified.remove(modifiedRecord);
		}
 
		if (!record.phantom) {
			this.removed.push(record);
		}
	},

	/**
	 * Clears all records. Show not be used directly. It's called by Store#removeAll automatically
	 * @param {Store} store
	 * @param {Ext.data.Record/Ext.data/Record[]} records
	 * @private
	 */
	onClear: function(store, records)
	{
		Ext.each(records, function(rec, index) {
			this.destroyRecord(this, rec, index);
		}, this);
	},

	/**
	 * Returns an object describing the current sort state of this Store.
	 * @return {Object} The sort state of the Store. An object with two properties:
	 * field: String The name of the field by which the Records are sorted.
	 * direction: String The sort order, 'ASC' or 'DESC' (case-sensitive).
	 *
	 * Added for grid support with store, grid's store needs sortinfo.
	 *
	 * See <tt>{@link #sortInfo}</tt> for additional details.
	 */
	getSortState: Ext.data.Store.prototype.getSortState,

	/**
	 * Invokes sortData if we have sortInfo to sort on and are not sorting remotely
	 * @private
	 */
	applySort: Ext.data.Store.prototype.applySort,

	/**
	 * Performs the actual sorting of data. This checks to see if we currently have a multi sort or not. It applies
	 * each sorter field/direction pair in turn by building an OR'ed master sorting function and running it against
	 * the full dataset
	 * @private
	 */
	sortData: Ext.data.Store.prototype.sortData,

	/**
	 * Creates and returns a function which sorts an array by the given field and direction
	 * @param {String} field The field to create the sorter for
	 * @param {String} direction The direction to sort by (defaults to "ASC")
	 * @return {Function} A function which sorts by the field/direction combination provided
	 * @private
	 */
	createSortFunction: Ext.data.Store.prototype.createSortFunction,

	/**
	 * Sets the default sort column and order to be used by the next {@link #load} operation.
	 * @param {String} fieldName The name of the field to sort by.
	 * @param {String} dir (optional) The sort order, 'ASC' or 'DESC' (case-sensitive, defaults to <tt>'ASC'</tt>)
	 */
	setDefaultSort: Ext.data.Store.prototype.setDefaultSort,

	/**
	 * Sort the Records.
	 * If remote sorting is used, the sort is performed on the server, and the cache is reloaded. If local
	 * sorting is used, the cache is sorted internally. See also {@link #remoteSort} and {@link #paramNames}.
	 * This function accepts two call signatures - pass in a field name as the first argument to sort on a single
	 * field, or pass in an array of sort configuration objects to sort by multiple fields.
	 * Single sort example:
	 * store.sort('name', 'ASC');
	 * Multi sort example:
	 * store.sort([
	 *  {
	 *   field : 'name',
	 *   direction: 'ASC'
	 *  },
	 *  {
	 *   field : 'salary',
	 *   direction: 'DESC'
	 *  }
	 * ], 'ASC');
	 * In this second form, the sort configs are applied in order, with later sorters sorting within earlier sorters' results.
	 * For example, if two records with the same name are present they will also be sorted by salary if given the sort configs
	 * above. Any number of sort configs can be added.
	 * @param {String/Array} fieldName The name of the field to sort by, or an array of ordered sort configs
	 * @param {String} dir (optional) The sort order, 'ASC' or 'DESC' (case-sensitive, defaults to <tt>'ASC'</tt>)
	 */
	sort: Ext.data.Store.prototype.sort,

	/**
	 * Sorts the store contents by a single field and direction. This is called internally by {@link sort} and would
	 * not usually be called manually
	 * @param {String} fieldName The name of the field to sort by.
	 * @param {String} dir (optional) The sort order, 'ASC' or 'DESC' (case-sensitive, defaults to <tt>'ASC'</tt>)
	 */
	singleSort: Ext.data.Store.prototype.singleSort,

	/**
	 * Sorts the contents of this store by multiple field/direction sorters. This is called internally by {@link sort}
	 * and would not usually be called manually.
	 * Multi sorting only currently applies to local datasets - multiple sort data is not currently sent to a proxy
	 * if remoteSort is used.
	 * @param {Array} sorters Array of sorter objects (field and direction)
	 * @param {String} direction Overall direction to sort the ordered results by (defaults to "ASC")
	 */
	multiSort: Ext.data.Store.prototype.multiSort,

	/**
	 * Sums the value of property for each record between start and end and returns the result
	 * @param {String} property A field in each record
	 * @param {Number} start (optional) The record index to start at (defaults to 0)
	 * @param {Number} end (optional) The last record index to include (defaults to length - 1)
	 * @return {Number} The sum
	 */
	sum: Ext.data.Store.prototype.sum,

	/**
	 * Returns a filter function used to test a the given property's value. Defers most of the work to
	 * Ext.util.MixedCollection's createValueMatcher function
	 * @param {String} property The property to create the filter function for
	 * @param {String/RegExp} value The string/regex to compare the property value to
	 * @param {Boolean} anyMatch True if we don't care if the filter value is not the full value (defaults to false)
	 * @param {Boolean} caseSensitive True to create a case-sensitive regex (defaults to false)
	 * @param {Boolean} exactMatch True to force exact match (^ and $ characters added to the regex). Defaults to false. Ignored if anyMatch is true.
	 * @private
	 */
	createFilterFn: Ext.data.Store.prototype.createFilterFn,

	/**
	 * Filters an array of elements to only include matches of a simple selector (e.g. div.some-class or span:first-child)
	 * @param {Array} el An array of elements to filter
	 * @param {String} selector The simple selector to test
	 * @param {Boolean} nonMatches If true, it returns the elements that DON'T match
	 * the selector instead of the ones that match
	 * @return {Array} An Array of DOM elements which match the selector. If there are
	 * no matches, and empty Array is returned.
	 */
	filter: Ext.data.Store.prototype.filter,

	/**
	 * 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: Ext.data.Store.prototype.filterBy,

	/**
	 * Query the records by a specified property.
	 * @param {String} field A field on your records
	 * @param {String/RegExp} value Either a string that the field
	 * should begin with, or a RegExp to test against the field.
	 * @param {Boolean} anyMatch (optional) True to match any part not just the beginning
	 * @param {Boolean} caseSensitive (optional) True for case sensitive comparison
	 * @return {MixedCollection} Returns an Ext.util.MixedCollection of the matched records
	 */
	query: Ext.data.Store.prototype.query,

	/**
	 * Query the cached records in this Store using a filtering function. The specified function
	 * will be called with each record in this Store. If the function returns <tt>true</tt> the record is
	 * included in the results.
	 * @param {Function} fn The function to be called. It will be passed the following parameters:<ul>
	 * <li><b>record</b>: Ext.data.Record<p class="sub-desc">The {@link Ext.data.Record record}
	 * to test for filtering. Access field values using {@link Ext.data.Record#get}.</p></li>
	 * <li><b>id</b>: Object<p class="sub-desc">The ID of the Record passed.</p></li>
	 * </ul>
	 * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this Store.
	 * @return {MixedCollection} Returns an Ext.util.MixedCollection of the matched records
	 **/
	queryBy: Ext.data.Store.prototype.queryBy,

	/**
	 * Finds the index of the first matching Record in this store by a specific field value.
	 * @param {String} fieldName The name of the Record field to test.
	 * @param {String/RegExp} value Either a string that the field value
	 * should begin with, or a RegExp to test against the field.
	 * @param {Number} startIndex (optional) The index to start searching at
	 * @param {Boolean} anyMatch (optional) True to match any part of the string, not just the beginning
	 * @param {Boolean} caseSensitive (optional) True for case sensitive comparison
	 * @return {Number} The matched index or -1
	 */
	find: Ext.data.Store.prototype.find,

	/**
	 * Finds the index of the first matching Record in this store by a specific field value.
	 * @param {String} fieldName The name of the Record field to test.
	 * @param {Mixed} value The value to match the field against.
	 * @param {Number} startIndex (optional) The index to start searching at
	 * @return {Number} The matched index or -1
	 */
	findExact: Ext.data.Store.prototype.findExact,

	/**
	 * Find the index of the first matching Record in this Store by a function.
	 * If the function returns <tt>true</tt> it is considered a match.
	 * @param {Function} fn The function to be called. It will be passed the following parameters:<ul>
	 * <li><b>record</b>: Ext.data.Record<p class="sub-desc">The {@link Ext.data.Record record}
	 * to test for filtering. Access field values using {@link Ext.data.Record#get}.</p></li>
	 * <li><b>id</b>: Object<p class="sub-desc">The ID of the Record passed.</p></li>
	 * </ul>
	 * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this Store.
	 * @param {Number} startIndex (optional) The index to start searching at
	 * @return {Number} The matched index or -1
	 */
	findBy: Ext.data.Store.prototype.findBy,

	/**
	 * Get the Record with the specified id.
	 * @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: Ext.data.Store.prototype.getById,

	/**
	 * 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: Ext.data.Store.prototype.clearFilter,

	/**
	 * Returns true if this store is currently filtered
	 * @return {Boolean}
	 */
	isFiltered: Ext.data.Store.prototype.isFiltered,

	/**
	 * Collects unique values for a particular dataIndex from this store.
	 * @param {String} dataIndex The property to collect
	 * @param {Boolean} allowNull (optional) Pass true to allow null, undefined or empty string values
	 * @param {Boolean} bypassFilter (optional) Pass true to collect from all records, even ones which are filtered
	 * @return {Array} An array of the unique values
	 **/
	collect: Ext.data.Store.prototype.collect,

	/**
	 * When store's reader provides new metadata (fields) this function is called.
	 * @param {Object} meta The JSON metadata
	 * @private
	 */
	onMetaChange: Ext.data.Store.prototype.onMetaChange
});
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.NotificationResolver
 * @extends Ext.util.Observable
 *
 * The NotificationResolver is used when the {@link Zarafa.core.ResponseRouter ResponseRouter}
 * finds Responses which were not a response to a direct Request. This is usually the case,
 * when the PHP-side generated a Notification to update particular UI components based on
 * a server-side change.
 *
 * This resolver will read the response to determine which
 * {@link Zarafa.core.data.AbstractNotificationResponseHandler ResponseHandler} will
 * be needed to handle the response correctly.
 */
Zarafa.core.data.NotificationResolver = Ext.extend(Ext.util.Observable, {
	/**
	 * @cfg {Array} IPFNotificationModules The IPFNotificationModules contains the IPF notification
	 * module names.
	 */
	IPFNotificationModules: ['hierarchynotifier', 'newmailnotifier', 'addressbooknotifier', 'newtodotasknotifier'],

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

		Ext.apply(this, config);

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

	/**
	 * Function which used to add the IPF notification module name to {@link #IPFNotificationModules}.
	 * function mainly used by the external as well as internal plugins when plugins required to
	 * implement server side notification.
	 *
	 * @param {String} module The module is name of IPF notification module.
	 */
	addIPFNotificationModule: function(module)
	{
		this.IPFNotificationModules.push(module);
	},

	/**
	 * Obtain the {@link Zarafa.core.data.AbstractNotificationResponseHandler ResponseHandler}
	 * which can be used for handling the given response in a correct way. This will look into
	 * the response data to determine what kind of Notifications are in there, and will look
	 * up the most suitable {@link Zarafa.core.data.AbstractNotificationResponseHandler ResponseHandler}.
	 *
	 * @param {String} moduleName The Module which generated the notification.
	 * @param {Object} response The Json response data for which the responseHandler is needed
	 * @return {Zarafa.core.data.AbstractResponseHandler} The response handler
	 * which is suitable for handling the given response object.
	 */
	getHandlerForResponse: function(moduleName, response)
	{
		var handlers;

		if (!Ext.isObject(response)) {
			return null;
		}

		if (this.IPFNotificationModules.indexOf(moduleName) !== -1) {
			handlers = this.getHandlersForIPFResponse(response);
		} else {
			handlers = this.getHandlersForIPMResponse(response);
		}

		if (Array.isArray(handlers)) {
			if (handlers.length > 1) {
				return new Zarafa.core.data.CompositeResponseHandler({
					handlers: handlers
				});
			} else {
				return handlers[0];
			}
		} else {
			return handlers;
		}
	},

	/**
	 * Helper function for {@link #getHandlerForResponse}, this will construct
	 * the {@link Zarafa.hierarchy.data.HierarchyNotificationResponseHandler Response Handlers}
	 * for the {@link Zarafa.core.data.IPFStore IPFStores}.
	 *
	 * @param {Object} response The Json response data for which the responseHandler is needed
	 * @return {Zarafa.core.data.AbstractResponseHandler[]} The response handlers
	 * which are suitable for handling the given response object.
	 */
	getHandlersForIPFResponse: function(response)
	{
		var folderParents = [];
		var folderStores = [];

		// get handlers for folder's notifications.
		if(response['folders'] || response['newmail']) {
			var folders = response['folders'];
			if (Ext.isDefined(folders) && !Ext.isEmpty(folders['item'])) {
				folderParents = folderParents.concat(Ext.pluck(folders['item'], 'store_entryid'));
			}

			folderStores = Zarafa.core.data.IPFStoreMgr.getStoresForStores(folderParents);

			var newMail = response['newmail'];
			if (Ext.isDefined(newMail)) {
				folderStores.push(container.getHierarchyStore());
			}

			if (Array.isArray(folderStores)) {
				var responseHandlers = [];
				for (var i = 0, len = folderStores.length; i < len; i++) {
					responseHandlers.push(new Zarafa.hierarchy.data.HierarchyNotificationResponseHandler({
						store: folderStores[i],
						reader: folderStores[i].reader,
						notifyObject: folderStores[i]
					}));
				}

				return responseHandlers;
			}
		} else if (response['stores']) {
			// get handlers for stores's notifications.
			var hierarchyStore = container.getHierarchyStore();
			return new Zarafa.hierarchy.data.HierarchyNotificationResponseHandler({
				store: hierarchyStore,
				reader: hierarchyStore.reader,
				notifyObject: hierarchyStore
			});
		} else if(response['addressbook']) {
			var addressBookStore = Zarafa.addressbook.AddressBookHierarchyStore;
			return new Zarafa.addressbook.AddressBookHierarchyNotificationResponseHandler({
				store: addressBookStore,
				notifyObject: addressBookStore
			});
		} else if(response['newtodotask']) {
			var taskStore = container.getContextByName('task').getModel().getStore();
			return new Zarafa.task.data.TodoTaskListNotificationResponseHandler({
				store: taskStore,
				notifyObject: taskStore
			});
		}
	},

	/**
	 * Helper function for {@link #getHandlerForResponse}, this will construct
	 * the {@link Zarafa.core.data.IPMNotificationResponseHandler Response Handlers}
	 * for the {@link Zarafa.core.data.IPMStore IPMStores}.
	 *
	 * @param {Object} response The Json response data for which the responseHandler is needed
	 * @return {Zarafa.core.data.AbstractResponseHandler[]} The response handlers
	 * which are suitable for handling the given response object.
	 */
	getHandlersForIPMResponse: function(response)
	{
		var messageParents = [];
		var messageStores;

		var creates = response['newobject'];
		if (Ext.isDefined(creates) && Array.isArray(creates.item)) {
			messageParents = messageParents.concat(Ext.pluck(creates.item, 'entryid'));
		}

		var updates = response['update'];
		if (Ext.isDefined(updates) && Array.isArray(updates.item)) {
			messageParents = messageParents.concat(Ext.pluck(updates.item, 'parent_entryid'));
		}

		var deletes = response['delete'];
		if (Ext.isDefined(deletes) && Array.isArray(deletes.item)) {
			messageParents = messageParents.concat(Ext.pluck(deletes.item, 'parent_entryid'));
		}

		messageStores = Zarafa.core.data.IPMStoreMgr.getStoresForFolders(messageParents);

		if (Array.isArray(messageStores)) {
			var responseHandlers = [];
			for (var i = 0, len = messageStores.length; i < len; i++) {
				responseHandlers.push(new Zarafa.core.data.IPMNotificationResponseHandler({
					store: messageStores[i],
					reader: messageStores[i].reader,
					notifyObject: messageStores[i]
				}));
			}

			return responseHandlers;
		} else {
			return new Zarafa.core.data.IPMNotificationResponseHandler({
				store: messageStores,
				reader: messageStores.reader,
				notifyObject: messageStores
			});
		}
	}
});
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.Notifications
 * List of valid notification types.
 * @singleton
 */
Zarafa.core.data.Notifications =
{
	/**
	 * The 'New Mail' notification to indicate a new Mail has
	 * been delivered to the user.
	 * @property
	 * @type String
	 */
	newMail: 'newMail',

	/**
	 * The 'Object Created' notification to indicate that a new
	 * object has been added to the {@link Zarafa.core.MAPIStore Store}
	 * or {@link Zarafa.hierarchy.data.MAPIFolderRecord Folder}.
	 * @property
	 * @type String
	 */
	objectCreated: 'objectCreated',

	/**
	 * The 'Object Deleted' notification to indicate that an
	 * object has been deleted from the {@link Zarafa.core.MAPIStore Store}
	 * or {@link Zarafa.hierarchy.data.MAPIFolderRecord Folder}.
	 * @property
	 * @type String
	 */
	objectDeleted: 'objectDeleted',

	/**
	 * The 'Object Modified' notification to indicate that an
	 * object has been modified in the {@link Zarafa.core.MAPIStore Store}
	 * or {@link Zarafa.hierarchy.data.MAPIFolderRecord Folder}.
	 * @property
	 * @type String
	 */
	objectModified: 'objectModified'
};
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.PresenceCache
 * @extends Object
 *
 * Has functions to handle presence of users
 *
 * @singleton
 */
Zarafa.core.data.PresenceCache = Ext.extend(Object, {
	/**
	 * Holds the cached statuses for users. It is an object that
	 * contain an entryid to Object mapping. The object contains the {@link
	 * Zarafa.core.data.UserIdObject user}, the statusByPlugin for the
	 * user, and a squashed {@link Zarafa.core.data.PresenceStatus status}.
	 * @property
	 * @private
	 */
	cache: {},

	/**
	 * Returns the entry of the user in the {#cache presence cache} for the passed user, or
	 * undefined if the user is not found.
	 * @param {Zarafa.core.data.UserIdObject} user The user for which the entry in the
	 * {#cache presence cache} will be returned.
	 * @param {Boolean} noSync If set to true then the given user will not be used to
	 * update the cache. Note: this is a very ugly way to prevent infinite recursion
	 * when this function is called from {#syncUsers}
	 * @return {Object|undefined}
	 * @private
	 */
	getUser: function(user, noSync) {
		if (!user.hasOwnProperty('entryid')) {
			return;
		}

		// Check if we can update the cached user data
		if (user.entryid in this.cache) {
			// (IPMRecipientStores might change recipients from SMTP to
			// ZARAFA after the names have been resolved)
			if ( noSync !== true ){
				this.syncUsers([user]);
			}
			return this.cache[user.entryid];
		}
	},

	/**
	 * Synchronizes the given {@link Zarafa.core.data.UserIdObject users} with the cache.
	 * @param {Zarafa.core.data.UserIdObject[]} users The users that need to be synced
	 * with the cached users.
	 * @return {Zarafa.core.data.UserIdObject[]}
	 */
	syncUsers: function(users) {
		Ext.each( users, function(user, index){
			var syncedUser = this.getUser(user, true);
			if (Ext.isDefined(syncedUser)) {
				// Check if we can update the cache
				this.cache[user.entryid].user.syncWithUser(user);
				syncedUser = this.cache[user.entryid].user;
			} else {
				// Add the new user to the cache, so it will be updated during polling
				this.cache[user.entryid] = {
					user: user,
					statusByPlugin: {},
					status: Zarafa.core.data.PresenceStatus.UNKNOWN
				};
				syncedUser = this.cache[user.entryid].user;
			}

			// Update the return value
			users[index] = syncedUser;
		}, this);

		return users;
	},

	/**
	 * Returns the cached presence status for the given user or
	 * undefined if the user was not found in the cache.
	 * @param {Zarafa.core.data.UserIdObject} userInfo The user for which
	 * a {@link Zarafa.core.data.PresenceStatus presence status} is
	 * requested.
	 * @return {Zarafa.core.data.PresenceStatus|undefined}
	 */
	getStatusForUser: function(userInfo) {
		// Distlists don't have userInfos but will send null
		if ( !userInfo ){
			return Zarafa.core.data.PresenceStatus.UNKNOWN;
		}

		var user = this.getUser(userInfo);
		return user && user.status;
	},

	/**
	 * Adds the status for the given user for the given plugin to the cache.
	 * @param {String} pluginName Name of the plugin for which a status is added/updated
	 * @param {Zarafa.core.data.UserIdObject} user The user for which the status is added
	 * @param {Zarafa.core.data.PresenceStatus} status The status for the given user according to the
	 * given plugin
	 */
	addStatusForUser: function(pluginName, user, status){
		// First see if we already have a cached status for this user
		var cacheUser = this.getUser(user);

		// Add the status of the user for this plugin
		if ( Ext.isDefined(cacheUser) ){
			cacheUser.statusByPlugin[pluginName] = status;
		} else {
			var statusByPlugin = {};
			statusByPlugin[pluginName] = status;
			this.cache[user.entryid] = {
				user: user,
				statusByPlugin: statusByPlugin
			};
		}

		// Add a squashed status
		var presenceUser = this.cache[user.entryid];
		presenceUser.status = Zarafa.core.data.PresenceStatus.UNKNOWN;
		Ext.iterate(presenceUser.statusByPlugin, function(pluginName){
			if ( presenceUser.statusByPlugin[pluginName] > presenceUser.status ){
				presenceUser.status = presenceUser.statusByPlugin[pluginName];
			}
		}, this);
	},

	/**
	 * Returns a list of {Zarafa.core.data.UserIdObject} objects in the cache.
	 * @return {Array} of {@link Zarafa.core.data.UserIdObject UserIdObjects}
	 */
	getUserInfoList: function() {
		return Object.keys(this).map(function(key) {
			return this.cache[key].user;
		}, this);
	}
});

// Make a singleton of this class
Zarafa.core.data.PresenceCache = new Zarafa.core.data.PresenceCache();
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.Record
 * @singleton
 */
Zarafa.core.data.Record = {
	/**
	 * Create subclass of a {@link Ext.data.Record record} using the
	 * default list of fields.
	 * @param {Object} fields The fields which must be added to the subclass.
	 * @param {Type} base The base type from which the subclass must be derived.
	 * @return {Object} The type of the subclass.
	 */
	create: function(fields, base)
	{
		var subclass = Ext.extend(base || Ext.data.Record, {});
		var proto = subclass.prototype;

		proto.fields = new Ext.util.MixedCollection(false, function(field) {
			return field.name;
		});

		this.addFields(proto, fields || []);

		subclass.getField = function(name)
		{
			return proto.fields.get(name);
		};

		return subclass;
	},

	/**
	 * Add fields to an object
	 * @param {Object} proto The object to update the fields to
	 * @param {Object} fields The array of fields which must be added
	 * @private
	 */
	addFields: function(proto, fields)
	{
		for(var i = 0, len = fields.length; i < len; i++) {
			if (Array.isArray(fields[i])) {
				this.addFields(proto, fields[i]);
			} else {
				proto.fields.add(new Ext.data.Field(fields[i]));
			}
		}
	}
};
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.RecordDefinition
 * @extends Ext.util.Observable
 * The definition for a {@link Ext.data.Record record}
 * for a particular message class. Each message class can have its
 * own set of fields and default values. Within this class, the
 * specific values are kept. By default all fields and default values
 * are inherited from the parent definition. So each definition only
 * contains the data which is new for this particular message class.
 *
 * NOTE: This class should never be used outside of the
 * {@link Zarafa.core.data.RecordFactory RecordFactory}.
 */
Zarafa.core.data.RecordDefinition = Ext.extend(Ext.util.Observable, {
	/**
	 * @cfg {Zarafa.core.data.RecordDefinition} parent The parent record definition for this definition.
	 */
	parent: undefined,

	/**
	 * The object class definition for the record.
	 * This type is always a subclass of the {@link Zarafa.core.data.RecordDefinition.base base},
	 * field within this {@link Zarafa.core.data.RecordDefinition definition}.
	 * @property
	 * @type Class
	 * @private
	 */
	type: undefined,

	/**
	 * The configuration object in where all options for the Record instances are configured.
	 *
	 * This is editable as long as no record has been created using this definition yet, after
	 * that this property can no longer be used.
	 * @property
	 * @type Object
	 * @private
	 */
	cfg: undefined,

	/**
	 * @cfg {Object} base The object class definition which must be used as base for the record.
	 * In most cases this base must be extended from {@link Ext.data.Record record}.
	 *
	 * This is applied on the {@link #cfg} object and can be changed as long as no record has been
	 * created using this definition yet, after that this property can no longer be used.
	 */
	base: undefined,

	/**
	 * @cfg {Object} subStores The key-value array where for each subStore type is provided with the type
	 * of the {@link Zarafa.core.data.SubStore SubStore} which must be used for the given subStore.
	 *
	 * This is applied on the {@link #cfg} object and can be changed as long as no record has been
	 * created using this definition yet, after that this property can no longer be used.
	 */
	subStores: undefined,

	/**
	 * @cfg {Ext.data.Field[]} fields The array of fields which belong to this {@link Ext.data.Record record}.
	 * This is mapped on the server to MAPI properties which are stored in the zarafa-server.
	 *
	 * This is applied on the {@link #cfg} object and can be changed as long as no record has been
	 * created using this definition yet, after that this property can no longer be used.
	 */
	fields: undefined,

	/**
	 * @cfg {Object} defaults The key-value array where the default values for particular fields is provided.
	 * When a new {@link Ext.data.Record record} is created (one that does
	 * not yet exist on the server) all default values will be applied to the new
	 * {@link Ext.data.Record record}.
	 *
	 * This is applied on the {@link #cfg} object and can be changed as long as no record has been
	 * created using this definition yet, after that this property can no longer be used.
	 */
	defaults: undefined,

	/**
	 * @cfg {Object} createDefaults The key-value array for default values that should be applied
	 * when a record is created (either a phantom or non-phantom).
	 *
	 * This is applied on the {@link #cfg} object and can be changed as long as no record has been
	 * created using this definition yet, after that this property can no longer be used.
	 */
	createDefaults: undefined,

	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		this.addEvents([
			/**
			 * @event createphantom
			 * Fires when the {@link Zarafa.core.data.RecordFactory RecordFactory} has
			 * created a new phantom record.
			 * @param {Ext.data.Record} record The created phantom record
			 * @param {Object} data The data used to initialize the record
			 */
			'createphantom',
			/**
			 * @event createrecord
			 * Fires when the {@link Zarafa.core.data.RecordFactory RecordFactory} has
			 * created a new record.
			 * @param {Ext.data.Record} record The created record
			 * @param {Object} data The data used to initialize the record
			 */
			'createrecord'
		]);

		// Only the parent is applied on the current
		// class. the rest can be applied to the cfg object.
		this.parent = config.parent;
		delete config.parent;

		this.cfg = Ext.apply({}, config);

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

	/**
	 * Create a new {@link Ext.data.Record record} object
	 * based on this {@link Zarafa.core.data.RecordDefinition definition}.
	 *
	 * The data object used to instantiate the new Record will be created using the
	 * default values from {@link Ext.data.Field field} definitions as obtained by
	 * {@link #getFieldDefaultValues}, the default values configured through
	 * the {@link Zarafa.core.data.RecordFactory Record Factory} which are obtained
	 * by the {@link #getDefaultValues} function. And finally the data passed as
	 * argument will be applied. This ordering implies that the {@link Ext.data.Field field}
	 * default values are most likely to be overridden by the other values.
	 *
	 * @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 which was created
	 */
	createRecord: function(data, id)
	{
		var RecordClass = this.getType();
		var applyData = {};
		Ext.apply(applyData, this.getFieldDefaultValues());
		if (!Ext.isDefined(id)) {
			Ext.apply(applyData, this.getDefaultValues());
		}
		if (data) {
			Ext.apply(applyData, data);
		}

		// Apply the createDefaults object to initialize the record with.
		var record = new RecordClass(applyData, id, this);

		if (record.phantom) {
			this.fireEvent('createphantom', record, data);
		} else {
			// field default values will be applied by the jsonreader
			this.fireEvent('createrecord', record, data);
		}

		// All changes which were made should be committed,
		// as only after this function returns should the changes
		// for the record be kept.
		record.commit();

		return record;
	},

	/**
	 * Obtain the {@link #type object class} definition which can be used for constructing
	 * new {@link Ext.data.Record records}. The type will be a subclass of
	 * {@link Zarafa.core.data.RecordDefinition#base base}.
	 * @return {Class} The object class definition for this record definition
	 */
	getType: function()
	{
		if (!Ext.isDefined(this.type)) {
			var baseClass = this.getBaseClass();
			var fields = this.getFields();
			this.type = Zarafa.core.data.Record.create(fields, baseClass);
		}
		return this.type;
	},

	/**
	 * Set the {@link #base} class for the {@link Zarafa.core.data.RecordDefinition definition}.
	 *
	 * NOTE: If {@link #createRecord} has been called, then changes made by this function will
	 * no longer have effect.
	 *
	 * @param {Class} baseClass The base class to set.
	 */
	setBaseClass: function(baseClass)
	{
		this.cfg.base = baseClass;
	},

	/**
	 * Obtain the {@link #base} class which must be used when creating a new record
	 * from this {@link Zarafa.core.data.RecordDefinition definition}. If
	 * no base class is yet provided, this function will call recursively
	 * into its {@link #parent parents} until a valid base class has been found.
	 * @return {Class} The base class to be used for creating a new record.
	 * @private
	 */
	getBaseClass: function()
	{
		if (!Ext.isDefined(this.base)) {
			if (this.cfg.base) {
				this.base = this.cfg.base;
			} else if (Ext.isDefined(this.parent)) {
				this.base = this.parent.getBaseClass();
			} else {
				this.base = Ext.data.Record;
			}
		}
		return this.base;
	},

	/**
	 * Set the type which must be used for a particular SubStore.
	 * When a SubStore type has been set on a RecordType, then the
	 * subStore is considered 'supported' by the Record.
	 *
	 * NOTE: If {@link #createRecord} has been called, then changes made by this function will
	 * no longer have effect.
	 *
	 * @param {String} name The name of the SubStore (This matches the name how the
	 * data for this subStore is send through Json).
	 * @param {Type} type The Object type which must be used to allocate the subStore
	 */
	setSubStore: function(name, type)
	{
		this.cfg.subStores = Ext.value(this.cfg.subStores, {});
		this.cfg.subStores[name] = type;
	},

	/**
	 * Obtain the key-value array of subStore types which are active for
	 * this {@link Zarafa.core.data.RecordDefinition definition}. This object
	 * is used to {@link Zarafa.core.data.MAPIRecord#setSubStoreTypes set} on
	 * the {@link Zarafa.core.data.MAPIRecord MAPIRecord} which is created.
	 * @return {Object} key-value array of all subStore Types
	 * @private
	 */
	getSubStores: function()
	{
		if (!this.subStores) {
			this.subStores = {};
			if (Ext.isDefined(this.parent)) {
				Ext.apply(this.subStores, this.parent.getSubStores());
			}
			Ext.apply(this.subStores, this.cfg.subStores);
		}

		return Ext.apply({}, this.subStores);
	},

	/**
	 * Add a {@link Ext.data.Field field} to the {@link Zarafa.core.data.RecordDefinition definition}.
	 *
	 * NOTE: If {@link #createRecord} has been called, then changes made by this function will
	 * no longer have effect.
	 *
	 * @param {Ext.data.Field} field The field to add.
	 */
	addField: function(field)
	{
		this.cfg.fields = Ext.value(this.cfg.fields, []);

		if (!Array.isArray(field)) {
			this.cfg.fields.push(field);
		} else {
			this.cfg.fields = this.cfg.fields.concat(field);
		}
	},

	/**
	 * Obtain the list of {@link Ext.data.Field fields} which are active
	 * for this {@link Zarafa.core.data.RecordDefinition definition}.
	 * These fields must be used to create a record, to indicate which
	 * {@link Ext.data.Field fields} are allowed.
	 * This function will recursively call into its parents to obtain
	 * all fields from the parents.
	 *
	 * @return {Ext.data.Field[]} All fields which are active for
	 * this definition.
	 * @private
	 */
	getFields: function()
	{
		if (!this.fields) {
			this.fields = [];

			if (Ext.isDefined(this.parent)) {
				this.fields = this.fields.concat(this.parent.getFields());
			}
			if (this.cfg.fields) {
				this.fields = this.fields.concat(this.cfg.fields);
			}
		}

		return this.fields;
	},

	/**
	 * Add a default value for a {@link Ext.data.Field field} to the
	 * {@link Zarafa.core.data.RecordDefinition definition}.
	 *
	 * NOTE: If {@link #createRecord} has been called, then changes made by this function will
	 * no longer have effect.
	 *
	 * @param {Ext.data.Field} field The field for which the default value applies
	 * @param {Mixed} value The default value for the field
	 */
	addDefaultValue: function(field, value)
	{
		this.cfg.defaults = Ext.value(this.cfg.defaults, {});
		var name = Ext.isString(field) ? field : field.name;
		this.cfg.defaults[name] = value;
	},

	/**
	 * Apply all default values for the {@link Ext.data.Field field}
	 * which have been set for the {@link Zarafa.core.data.RecordDefinition definition}.
	 *
	 * This function will recursively call into its parents to apply
	 * all default values from the parents.
	 * @private
	 */
	getDefaultValues: function()
	{
		if (!this.defaults) {
			this.defaults = {};

			if (Ext.isDefined(this.parent)) {
				Ext.apply(this.defaults, this.parent.getDefaultValues());
			}

			if (this.cfg.defaults) {
				Ext.apply(this.defaults, this.cfg.defaults);
			}
		}

		return this.defaults;
	},

	/**
	 * Apply all default values from the {@link Ext.data.Field field}
	 * definitions. These are applied by the {@link Ext.data.DataReader DataReader}
	 * when reading {@link Ext.data.Record records} from the server, but not for
	 * {@link Ext.data.Record#phantom phantom} records.
	 * @private
	 */
	getFieldDefaultValues: function()
	{
		if (!this.fieldDefaults) {
			var fields = this.getFields();

			this.fieldDefaults = {};
			for (var i = 0, len = fields.length; i < len; i++) {
				var field = fields[i];
				var value = field.defaultValue;
				if (!Ext.isDefined(value)) {
					value = Ext.data.Field.prototype.defaultValue;
				}

				this.fieldDefaults[field.name] = value;
			}
			Ext.apply(this.fieldDefaults, this.cfg.createDefaults);
		}

		return this.fieldDefaults;
	},

	/**
	 * Fires the specified event with the passed parameters (minus the event name).
	 *
	 * This function will recursively call into its parents to fire the event
	 * from the parents.
	 * @param {String} eventName The name of the event to fire.
	 * @param {Object...} args The arguments for the event
	 * @return {Boolean} returns false if any of the handlers return false otherwise it returns true.
	 */
	fireEvent: function()
	{
		if (Ext.isDefined(this.parent)) {
			this.parent.fireEvent.apply(this.parent, arguments);
		}
		Zarafa.core.data.RecordDefinition.superclass.fireEvent.apply(this, arguments);
	}
});
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);
	}
};
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.ServerConfig
 * @extends Object
 *
 * An object which represents the server
 * configuration. To obtain the instance
 * of this object, use {@link Zarafa.core.Container#getServerConfig}.
 */
Zarafa.core.data.ServerConfig = Ext.extend(Object, {

	/**
	 * Object containing all meta data for this server configuration
	 * @property
	 * @type Object
	 */
	meta: undefined,

	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		this.meta = config;
	},

	/**
	 * @return {String} the base url of grommunio Web
	 */
	getBaseUrl: function()
	{
		return this.meta.base_url;
	},

	/**
	 * @return {String} the title of grommunio Web
	 */
	getWebappTitle: function()
	{
		return this.meta.webapp_title;
	},

	/**
	 * @return {Boolean} True if the GAB list should only be enabled when searching
	 */
	isFullGabDisabled: function()
	{
		return this.meta.disable_full_gab === true;
	},

	/**
	 * @return {Boolean} True if it should be possible to set rules on the store
	 * of other users.
	 */
	isSharedRulesEnabled: function()
	{
		return this.meta.enable_shared_rules === true;
	},

	/**
	 * @return {Boolean} True if the conversation view may be used
	 * (administrative kill-switch, see ENABLE_CONVERSATION_VIEW in config.php)
	 */
	isConversationViewEnabled: function()
	{
		return this.meta.enable_conversation_view !== false;
	},

	/**
	 * @return {Boolean} True if dragging attachments out of grommunio Web into a
	 * cooperating web application is enabled (administrative kill-switch, see
	 * ENABLE_ATTACHMENT_DRAG_OUT in config.php)
	 */
	isAttachmentDragOutEnabled: function()
	{
		return this.meta.enable_attachment_drag_out !== false;
	},

	/**
	 * @return {Number} The maximum attachment size (in bytes) whose content is
	 * embedded in a drag operation for the attachment drag-out feature (see
	 * ATTACHMENT_DRAG_OUT_MAX_SIZE in config.php). Defaults to 25 MiB when the
	 * server did not provide a (valid) value.
	 */
	getAttachmentDragOutMaxSize: function()
	{
		var size = parseInt(this.meta.attachment_drag_out_max_size, 10);
		return isNaN(size) || size < 0 ? 26214400 : size;
	},

	/**
	 * @return {Boolean} True if grommunio Web is using Single Sign-On to login
	 */
	usingSSO: function()
	{
		return this.meta.using_sso;
	},

	/**
	 * @return {Boolean} True if Plugins are enabled
	 */
	isPluginsEnabled: function()
	{
		return this.meta.enable_plugins;
	},

	/**
	 * @return {String} A semicolon separated list of plugins that cannot be
	 * disabled by the user.
	 */
	getAlwaysEnabledPluginsList: function()
	{
		return this.meta.always_enabled_plugins || '';
	},

	/**
	 * @return {Boolean} True if Advanced Settings are enabled
	 */
	isAdvancedSettingsEnabled: function()
	{
		return this.meta.enable_advanced_settings;
	},

	/**
	 * @return {Number} The maximum number of allowed attachments in a single message
	 */
	getMaxAttachments: function()
	{
		return this.meta.max_attachments;
	},

	/**
	 * @return {String} The base url for webapp help manual plugin.
	 */
	getWebappManualUrl: function()
	{
		return this.meta.plugin_webappmanual_url;
	},

	/**
	 * @return {Number} The maximum number of files that can be uploaded via a single request.
	 */
	getMaxFileUploads: function()
	{
		return this.meta.max_file_uploads;
	},

	/**
	 * @reutn {Number} The maximum attachment size allowed to attach in single request.
	 */
	getMaxPostRequestSize: function()
	{
		return this.meta.post_max_size;
	},

	/**
	 * @return {Number} The maximum size of a single attachment
	 */
	getMaxAttachmentSize: function()
	{
		return this.meta.max_attachment_size;
	},

	/**
	 * @return {Number} The maximum size of all attachments in a single message combined
	 */
	getMaxAttachmentTotalSize: function()
	{
		return this.meta.max_attachment_total_size;
	},

	/**
	 * @return {Number} The start offset to use when loading freebusy data
	 */
	getFreebusyLoadStartOffset: function()
	{
		return this.meta.freebusy_load_start_offset;
	},

	/**
	 * @return {Number} The end offset to use when loading freebusy data
	 */
	getFreebusyLoadEndOffset: function()
	{
		return this.meta.freebusy_load_end_offset;
	},

	/**
	 * @return {Number} The upper limit of the eml files allowed to be included in single ZIP archive
	 */
	getMaxEmlFilesInZIP: function()
	{
		return this.meta.maximum_eml_files_in_zip;
	},

	/**
	 * @return {Mixed} The client timeout time (in seconds) if set or false otherwise.
	 */
	getClientTimeout: function()
	{
		return this.meta.client_timeout;
	},

	/**
	 * @return {String} The active theme selected by admin or user.
	 */
	getActiveTheme: function()
	{
		return this.meta.active_theme;
	},

	/**
	 * @return {Array} The installed json themes
	 */
	getJsonThemes: function()
	{
		return this.meta.json_themes;
	},

	/**
	 * @return {String} The active iconset selected by admin or user.
	 */
	getActiveIconset: function()
	{
		return this.meta.active_iconset;
	},

	/**
	 * @return {Array} The installed iconsets
	 */
	getIconsets: function()
	{
		return this.meta.iconsets;
	},

	/**
	 * @return {String|Boolean} The primary color for SVG icons if defined by the active theme,
	 * or false otherwise
	 */
	getPrimaryIconColor: function()
	{
		return this.meta.icons_primary_color;
	},

	/**
	 * @return {String|Boolean} The secondary color for SVG icons if defined by the active theme,
	 * or false otherwise
	 */
	getSecondaryIconColor: function()
	{
		return this.meta.icons_secondary_color;
	},

	/**
	 * @return {Object} The about texts of iconsets
	 */
	getIconsetAbouts: function()
	{
		return this.meta.iconsets_about;
	},

	/**
 	 * @return {Array} returns the installed plugins version information array.
	 */
	getPluginsVersion: function()
	{
		return this.meta.version_info;
	},

	/**
	 * @return {Boolean} True if VCF import functionality is supported on backend, false otherwise.
	 */
	isVCfImportSupported: function()
	{
		return this.meta.is_vcfimport_supported;
	},

	/**
	 * @return {Boolean} True if ICS and VCS import functionality is supported on backend, false otherwise.
	 */
	isICSImportSupported: function()
	{
		return this.meta.is_icsimport_supported;
	},

	/**
	 * @return {Array} returns the color schemes defined in config.php/default.php.
	 */
	getColorSchemes: function()
	{
		return this.meta.color_schemes;
	},

	/**
	 * @return {Array} returns the additional color schemes defined in config.php/default.php.
	 */
	getAdditionalColorSchemes: function()
	{
		return this.meta.additional_color_schemes;
	},

	/**
	 * @return {Array} returns the categories defined in config.php/default.php.
	 */
	getDefaultCategories: function()
	{
		return this.meta.default_categories;
	},

	/**
	 * @return {Array} returns the additional categories defined in config.php/default.php.
	 */
	getAdditionalDefaultCategories: function()
	{
		return this.meta.additional_default_categories;
	},

	/**
	 * @return {Array} returns the contact prefix defined in config.php.
	 */
	getContactPrefix: function ()
	{
		return this.meta.contact_prefix;
	},

	/**
	 * @return {Array} returns the contact suffix defined in config.php.
	 */
	getContactSuffix: function ()
	{
		return this.meta.contact_suffix;
	},

	/**
	 * @return {Array} returns the powerpaste config defined in config.php.
	 */
	getPowerpasteConfig: function ()
	{
		return this.meta.powerpaste;
	},

	/**
	 * @return {Number} return the shared stores polling interval in microseconds
	 */
	getSharedStorePollingInterval: function()
	{
		return this.meta.shared_store_polling_interval * 60000;
	},

	/**
	 * @returns {Boolean} True if DOMPurify is enabled by admin(from config.php) else false.
	 */
	getDOMPurifyEnabled: function ()
	{
		return this.meta.enable_dompurify;
	},

	/**
	 * @returns {Boolean} True if file previewer is enabled by admin(from config.php) else false.
	 */
	isFilePreviewerEnabled: function ()
	{
		return this.meta.enable_file_previewer;
	},

	/**
	 * @returns {Boolean} True if theming is enabled by admin(from config.php) else false.
	 */
	isThemingEnabled: function ()
	{
		return this.meta.enable_themes;
	},

	/**
	 * @returns {Boolean} True if iconsets are enabled by admin(from config.php) else false.
	 */
	isIconSetsEnabled: function ()
	{
		return this.meta.enable_iconsets;
	},

	/**
	 * @returns {Boolean} True if widgets are enabled by admin(from config.php) else false.
	 */
	isWidgetEnabled : function()
	{
		return this.meta.enable_widgets;
	},

	/**
	 * @return {Boolean} True if grommunio Web shows the logout button
	 */
	showLogoutButton: function()
	{
		return this.meta.show_logout_button;
	},

	/**
	 * @return {String} Comma-separated list of attachment reminder keywords,
	 * or empty string if the feature is disabled.
	 */
	getAttachmentReminderKeywords: function()
	{
		return this.meta.attachment_reminder_keywords || '';
	},

	/**
	 * @return {String} The authentication method used: 'oidc' or 'basic'.
	 */
	getAuthMethod: function()
	{
		return this.meta.auth_method || 'basic';
	}
});
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.SettingsStateProvider
 * @type Ext.state.Provider
 *
 * A special {@link Ext.state.Provider} for the {@link Ext.state.Manager}
 * which stores the state of {@link Zarafa.core.ui.ContentPanel content panels}, {@link Ext.Panel panels}
 * and {@link Ext.grid.GridPanel grids} into the {@link Zarafa.settings.SettingsModel settings}.
 */
Zarafa.core.data.SettingsStateProvider = Ext.extend(Ext.state.Provider, {

	/**
	 * @cfg {String} basePath The base path of the {@link Zarafa.settings.SettingsModel settings}
	 * in which all State settings must be saved. The complete name for a settings path is generated
	 * by {@link #getStatePath}.
	 */
	basePath: 'zarafa/v1/state',

	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		Ext.apply(this, config);

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

	/**
	 * Create the path of the {@link Zarafa.settings.SettingsModel settings}
	 * in which the {@link Ext.Component#getState state} for the given {@link Ext.Component component}
	 * must be saved. This adds the {@link Ext.Component#getStateName State name} to the
	 * {@link #basePath}.
	 * @return {String} The Settings path in which the state for the component must be saved.
	 */
	getStatePath: function(component)
	{
		return this.basePath + '/' + component.getStateName();
	},

	/**
	 * Encodes a value including type information. Decode with {@link #decodeValue}.
	 * @param {Mixed} value The value to encode
	 * @param {String} name The property name (if this function was called recursively) of the property being encoded
	 * @param {Ext.Component} component The component for which the settings are being encoded
	 * @return {String} The encoded value
	 */
	encodeValue: function(value, name, component)
	{
		if (Array.isArray(value)) {
			for (var i = 0, len = value.length; i < len; i++) {
				value[i] = this.encodeValue(value[i], name, component);
			}
		} else if (Ext.isObject(value)) {
			for (var key in value) {
				value[key] = this.encodeValue(value[key], key, component);
			}
		} else if ((name === 'height' || name === 'width') && component.statefulRelativeDimensions === true) {
			value = this.scaleSizeToPercentage(component, value, name);
		}

		return value;
	},

	/**
	 * Decodes a string previously encoded with {@link #encodeValue}.
	 * @param {Mixed} value The value to decode
	 * @param {String} name The property name (if this function was called recursively) of the property being decoded
	 * @param {Ext.Component} component The component for which the settings are being decoded
	 * @return {Mixed} The decoded value
	 */
	decodeValue: function(value, name, component)
	{
		if (Array.isArray(value)) {
			for (var i = 0, len = value.length; i < len; i++) {
				value[i] = this.decodeValue(value[i], name, component);
			}
		} else if (Ext.isObject(value)) {
			for (var key in value) {
				value[key] = this.decodeValue(value[key], key, component);
			}
		} else if ((name === 'height' || name === 'width') && value <= 1.0) {
			value = this.scaleSizeToBody(component, value, name);
		}

		return value;
	},

	/**
	 * Convert a 'width' or 'height' value into a percentage of the current size of the body.
	 * This will ensure that when grommunio Web is reloaded on a new display (with different resolution)
	 * the panel is scaled accordingly.
	 * @param {Ext.Component} component The component for which the percentages are calculated
	 * @param {Number} value The value to convert
	 * @param {String} type The type of the value (can be 'width' or 'height')
	 * @return {Number} The converted value
	 * @private
	 */
	scaleSizeToPercentage: function(component, value, type)
	{
		type = Ext.util.Format.capitalize(type);

		var body = window['inner' + type];
		value = parseFloat((value / body).toFixed(2));

		// Limit the value to 1, we don't accept it when the
		// component is resized to beyond the maximum of the
		// body element.
		return Math.min(value, 1);
	},

	/**
	 * Convert a 'width' or 'height' percentage into a real size depending on the size of the body.
	 * This will ensure that when grommunio Web is reloaded on a new display (with different resolution)
	 * the panel is scaled accordingly. It will check the {@link Ext.Component#minWidth}/{@link Ext.Panel#maxWidth}
	 * and {@link Ext.Panel#minHeight}/{@link Ext.Panel#maxHeight} properties in the component to prevent
	 * it from becoming too big.
	 * @param {Ext.Component} component The component for which the real dimensions are determined.
	 * @param {Number} value The value to convert
	 * @param {String} type The type of the value (can be 'width' or 'height')
	 * @return {Number} The converted value
	 * @private
	 */
	scaleSizeToBody: function(component, value, type)
	{
		type = Ext.util.Format.capitalize(type);

		// Convert a 'width' or 'height' percentage into a real size depending on the size of active browser window body.
		var browserWindow = Zarafa.core.BrowserWindowMgr.getActive();
		var body = browserWindow['inner' + type];
		var minSize = component['min' + type];
		var maxSize = component['max' + type];

		value = Math.round(value * body);

		// Collapsible components with a SplitBar have a minSize/maxSize properties,
		// while normal panels have the minWidth/maxWidth & minHeight/maxHeight properties.
		if (component.minSize || component.maxSize) {
			value = Math.min(Math.max(value, component.minSize || 0), component.maxSize || value);
		} else if (minSize || maxSize) {
			value = Math.min(Math.max(value, minSize || 0), maxSize || value);
		}

		if (type === 'Width') {
			value = this.clampSidebarWidthToViewport(component, value, body);
		}

		return value;
	},

	/**
	 * Keep main sidebars in a usable range when restoring state on smaller screens.
	 * @param {Ext.Component} component The component for which the width is restored.
	 * @param {Number} value The calculated width.
	 * @param {Number} viewportWidth Width of the browser viewport.
	 * @return {Number} The clamped width.
	 * @private
	 */
	clampSidebarWidthToViewport: function(component, value, viewportWidth)
	{
		if (!component || !Ext.isFunction(component.isXType) || !component.isXType('mainviewsidebar')) {
			return value;
		}

		// Keep at least this much room for center content.
		var minCenterWidth = 320;
		var maxSidebarWidth = Math.max(component.minSize || 0, viewportWidth - minCenterWidth);

		return Math.min(value, maxSidebarWidth);
	},

	/**
	 * Sets the value for a key
	 * @param {String} name The key name
	 * @param {Mixed} value The value to set
	 */
	set: function(name, value)
	{
		var component = Ext.state.Manager.getComponent(name);
		container.getSettingsModel().set(this.getStatePath(component), this.encodeValue(value, undefined, component));
		this.fireEvent('statechange', this, name, value);
	},

	/**
	 * Returns the current value for a key
	 * @param {String} name The key name
	 * @param {Mixed} defaultValue A default value to return if the key's value is not found
	 * @return {Mixed} The state data
	 */
	get: function(name, defaultValue)
	{
		var component = Ext.state.Manager.getComponent(name);
		var value = container.getSettingsModel().get(this.getStatePath(component), true);

		if (Ext.isObject(defaultValue)) {
			value = Ext.apply(defaultValue, value);
		}

		return this.decodeValue(value, undefined, component);
	},

	/**
	 * Clears a value from the state
	 * @param {String} name The key name
	 */
	clear: function(name)
	{
		var component = Ext.state.Manager.getComponent(name);
		// Call restore rather then remove, to ensure the default values
		// are applied again.
		container.getSettingsModel().restore(this.getStatePath(component));
		this.fireEvent('statechange', this, name, null);
	}
});
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.StatefulObservable
 * @extends Ext.util.Observable
 *
 * Extends the {@link Ext.util.Observable} and add {@link #stateful}
 * properties to the object allowing similar features as the
 * {@link Ext.Component}#{@link Ext.Component#stateful}.
 */
Zarafa.core.data.StatefulObservable = Ext.extend(Ext.util.Observable, {
	/**
	 * @cfg {Boolean} stateful A flag which causes the Component to attempt to restore the state of internal properties
	 * from a saved state on startup. The component must have a {@link #stateId} for state to be managed.
	 */
	stateful: false,

	/**
	 * @cfg {String} stateId The unique id for this component to use for state management purposes
	 * See {@link #stateful} for an explanation of saving and restoring Component state.
	 */
	stateId: undefined,

	/**
	 * @cfg {Array} stateEvents An array of events that, when fired, should trigger this component to save its state
	 * (defaults to {@link #datamodechange}). stateEvents may be any type of event supported by this component.
	 * See {@link #stateful} for an explanation of saving and restoring Component state.
	 */
	stateEvents: undefined,

	/**
	 * @cfg {String} statefulName The unique name for this component by which the {@link #getState state}
	 * must be saved into the {@link Zarafa.settings.SettingsModel settings}.
	 * This option is only used when the {@link Zarafa.core.data.SettingsStateProvider SettingsStateProvider} is
	 * used in the {@link Ext.state.Manager}.
	 */
	statefulName: undefined,

	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		Ext.apply(this, config);

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

		// If stateful, generate a stateId if not provided manually,
		// and register the object to the Ext.state.Manager.
		if (this.stateful !== false) {
			if (!this.stateId) {
				this.stateId = Ext.id(null, 'state-');
			}

			Ext.state.Manager.register(this);
			this.initStateEvents();
		}
	},

	/**
	 * Obtain the path in which the {@link #getState state} must be saved.
	 * This option is only used when the {@link Zarafa.core.data.SettingsStateProvider SettingsStateProvider} is
	 * used in the {@link Ext.state.Manager}. This returns {@link #statefulName} if provided, or else generates
	 * a custom name.
	 * @return {String} The unique name for this component by which the {@link #getState state} must be saved.
	 */
	getStateName: function()
	{
		return this.statefulName;
	},

	/**
	 * Obtain the {@link #stateId} from the component
	 * @return {String} The state ID
	 * @private
	 */
	getStateId: function()
	{
		return this.stateId || this.id;
	},

	/**
	 * Register the {@link #stateEvents state events} to the {@link #saveState} callback function.
	 * @protected
	 */
	initStateEvents: function()
	{
		if (this.stateEvents) {
			for (var i = 0, e; e = this.stateEvents[i]; i++) {
				this.on(e, this.saveState, this, {delay:100});
			}
		}
	},

	/**
	 * Intialialize the component with {@link #applyState} using the state which has
	 * been {@link Ext.state.Manager#get obtained} from the {@link Ext.state.Manager State Manager}.
	 * @private
	 */
	initState: function()
	{
		if (Ext.state.Manager) {
			var id = this.getStateId();
			if (id) {
				var state = Ext.state.Manager.get(id);
				if (state) {
					if (this.fireEvent('beforestaterestore', this, state) !== false) {
						this.applyState(Ext.apply({}, state));
						this.fireEvent('staterestore', this, state);
					}
				}
			}
		}
	},

	/**
	 * Apply the given state to this object activating the properties which were previously
	 * saved in {@link Ext.state.Manager}.
	 * @param {Object} state The state object
	 * @protected
	 */
	applyState: function(state)
	{
		if (state) {
			Ext.apply(this, state);
		}
	},

	/**
	 * When {@link #stateful} the State object which should be saved into the
	 * {@link Ext.state.Manager}.
	 * @return {Object} The state object
	 * @protected
	 */
	getState: function()
	{
		return null;
	},

	/**
	 * Obtain the {@link #getState current state} and {@link Ext.state.Manager#set save} it
	 * to the {@link Ext.state.Manager State Manager}.
	 * @private
	 */
	saveState: function()
	{
		if (Ext.state.Manager && this.stateful !== false) {
			var id = this.getStateId();
			if (id) {
				var state = this.getState();
				if (this.fireEvent('beforestatesave', this, state) !== false) {
					Ext.state.Manager.set(id, state);
					this.fireEvent('statesave', this, state);
				}
			}
		}
	}
});
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.UIFactory
 * @extends Object
 * @singleton
 *
 *
 * The UI Factory is responsible for opening {@link Zarafa.core.ui.ContentPanel panels} in {@link Zarafa.core.data.UIFactoryLayer layers}.
 * The UI Factory decides the type of layer, based on cascading rules.
 * Types of layers are registered via {@link #registerLayer}.
 * First the type of layer is taken from settings, and then from a config object passed when creating the panel.
 */
Zarafa.core.data.UIFactory = {

	/**
	 * The array of {@link #registerLayer registered} {@link Zarafa.core.data.UIFactoryLayer layers}.
	 * @property
	 * @type Array
	 * @private
	 */
	layers: [],

	/**
	 * Register a layer a layer to the {@Link #layers} array.
	 * @param {Zarafa.core.data.UIFactoryLayer} layer The layer to register
	 */
	registerLayer: function(layer)
	{
		var index = layer.index || 0;

		if (this.layers.length > 0) {
			for (var i = 0, len = this.layers.length; i < len; i++) {
				if (this.layers[i].index > index) {
					this.layers.splice(i, 0, layer);
					return;
				}
			}

		}

		this.layers.push(layer);
	},

	/**
	 * Method to determine the {@link Zarafa.core.data.UIFactoryLayer layer} to use
	 *
	 * @param {Object} config Configuration object
	 * @param {Zarafa.core.data.MAPIRecord} record The record(s) loaded in the component
	 * @return {Zarafa.core.data.UIFactoryLayer} The Layer in which to place a component
	 */
	getPreferredLayer: function(config, record)
	{
		var layers = this.layers;

		// By default the bottom layout is the default,
		// this might be overridden from the settings.
		var layerIndex = 0;

		// First, we must check the special situations which are provided
		// by the configuration object.
		if (config) {
			// The first case checks if a manager was configured, if so the layer
			// which utilizes this manager get preference for the layer in which
			// the component is going to be installed.
			if (config.manager) {
				for (var i = 0, len = layers.length; i < len; i++) {
					if (config.manager === layers[i].manager) {
						layerIndex = i;
						break;
					}
				}
			}

			// The second case is if the new component has the 'modal' property,
			// not all layers accept this, so starting from the layerIndex,
			// we have to search upwards to find the layer which allows the
			// 'modal' property to be set.
			if (config.modal === true) {
				for (var i = layerIndex, len = layers.length; i < len; i++) {
					if (layers[i].allowModal === true) {
						layerIndex = i;
					}
				}
			}
		}

		// The settings might have a different idea on the layer in which
		// the component must be placed. Request the 'type' which should be the
		// layer in which components should be placed.
		var baseContentLayer;
		if(config && config.layerType) {
			baseContentLayer = config.layerType;
		} else {
			// Currently the support of popout feature is restricted to mail context only.
			// So, honor the configured value of base_content_layer settings for mail context only.
			if (Ext.isDefined(record) && record !== null && Ext.isFunction(record.isMessageClass) && record.isMessageClass(['IPM.Note', 'IPM.Schedule.Meeting', 'REPORT.IPM', 'REPORT.IPM.Note'], true)) {
				baseContentLayer = container.getSettingsModel().get('zarafa/v1/main/base_content_layer');
			}
		}

		// Go for popout only if supported.
		if (Zarafa.supportsPopOut() && !Ext.isEmpty(baseContentLayer) && layerIndex === 0 && !(config && config.searchText)) {
			for (var i = 0, len = layers.length; i < len; i++) {
				if (layers[i].type === baseContentLayer) {
					layerIndex = i;
					break;
				}
			}
		}

		return layers[layerIndex];
	},

	/**
	 * Open a {@link Ext.Component component} in a chosen layer.
	 *
	 * @param {Number} componentType A component type taken from the enumeration {@link Zarafa.core.data.SharedComponentType}
	 * @param {Zarafa.core.data.MAPIRecord} record The record(s) loaded in the component
	 * @param {Object} config Configuration object
	 */
	openLayerComponent: function(componentType, records, config)
	{
		var ComponentConstructor = container.getSharedComponent(componentType, records);
		if (ComponentConstructor) {
			if (ComponentConstructor.prototype instanceof Ext.Component) {
				var layer = this.getPreferredLayer(config, records);

				// FIXME: This shouldn't be here, the caller should have
				// applied the record and closable information.
				config = Ext.applyIf(config || {}, {
					record: records,
					closable: true,
					plugins: []
				});
				config.plugins = config.plugins.concat(layer.plugins);

				layer.create(ComponentConstructor, config);
			} else {
				ComponentConstructor.doOpen(records);
			}
		}
	},

	/**
	 * Will open the view action for the passed record.
	 * @param {Zarafa.core.data.MAPIRecord|Zarafa.core.data.MAPIRecord[]} records
	 * The record/records which will be loaded in dialog.
	 * @param {Object} config configuration object.
	 */
	openViewRecord: function(records, config)
	{
		var componentType = Zarafa.core.data.SharedComponentType['common.view'];
		this.openLayerComponent(componentType, records, config);
	},

	/**
	 * Will open the create action for the passed record.
	 * @param {Zarafa.core.data.MAPIRecord|Zarafa.core.data.MAPIRecord[]} records
	 * The record/records which will be loaded in dialog.
	 * @param {Object} config configuration object.
	 */
	openCreateRecord: function(records, config)
	{
		var componentType = Zarafa.core.data.SharedComponentType['common.create'];
		this.openLayerComponent(componentType, records, config);
	},

	/**
	 * Will open a contextmenu for the passed record/records
	 * This method uses the default componentType for context menus and calls {@link #openContextMenu} with it.
	 * @param {Zarafa.core.data.MAPIRecord|Zarafa.core.data.MAPIRecord[]} records
	 * The record/records for which the contextmenu should be shown
	 * @param {Object} position The X and Y coordinate where the contextmenu was requested
	 * @param {Object} configuration object which should be applied to the contextmenu.
	 */
	openDefaultContextMenu: function(records, config)
	{
		var componentType = Zarafa.core.data.SharedComponentType['common.contextmenu'];
		this.openContextMenu(componentType, records, config);
	},

	/**
	 * Open a context menu with supplied componentType
	 *
	 * @param {Number} componentType Shared component type taken from {@link Zarafa.core.data.SharedComponentType}
	 * @param {Zarafa.core.data.MAPIRecord|Zarafa.core.data.MAPIRecord[]} records The record(s) for which the contextmenu will be shown
	 * @param {Object} config Configuration object
	 */
	openContextMenu: function(componentType, records, config)
	{
		var ComponentConstructor = container.getSharedComponent(componentType, records);
		if (ComponentConstructor) {
			new ComponentConstructor(Ext.applyIf(config || {}, {records: records })).showAt(config.position);
		}
	},

	/**
	 * Open a hover card with supplied componentType
	 *
	 * @param {Zarafa.core.data.MAPIRecord|Zarafa.core.data.MAPIRecord[]} records The record(s) for which the hover card will be shown
	 * @param {Object} config Configuration object
	 */
	openHoverCard: function (records, config)
	{
		// Close all existing hover card
		Ext.WindowMgr.getBy(function (win) {
			if (win instanceof Zarafa.common.recipientfield.ui.RecipientHoverCardView) {
				win.hide();
			}
		}, this);
		var componentType = Zarafa.core.data.SharedComponentType['common.hovercard'];
		this.openContextMenu(componentType, records, config);
	}
};
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.UIFactoryLayer
 * @extends Object
 *
 * A Layer object which must be {@link Zarafa.core.data.UIFactory#registerLayer registered}}
 * to the {@link Zarafa.core.data.UIFactory UIFactory}. This object describes the
 * Layer on which a {@link Zarafa.core.ui.ContentPanel Content Panel} can be placed.
 */
Zarafa.core.data.UIFactoryLayer = Ext.extend(Object, {
	/**
	 * @cfg {String} type The name of the Content Layer
	 * which uniqely identifies this layer.
	 */
	type: '',

	/**
	 * @cfg {Number} index The index of the Content Layer. See
	 * {@link Zarafa.core.data.UIFactory#getPreferredLayer} for
	 * the ordering logic which is applied.
	 */
	index: undefined,

	/**
	 * @cfg {Array} plugins The plugins which must be installed
	 * on the Panel which is going to be installed on the Content Layer
	 */
	plugins: undefined,

	/**
	 * @cfg {Boolean} allowModal True if this Content Layer
	 * supports 'modal' components to be added.
	 */
	allowModal: false,

	/**
	 * @cfg {Object} manager The manager instance which is managing
	 * the Content Layer (e.g. {@link Ext.WindowGroup} or {@link Ext.TabPanel}.
	 */
	manager: undefined,

	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		Ext.apply(this, config);
	},

	/**
	 * The create function which is invoked when a component needs to be added to the Container using
	 * this Layer. This must be overridden by subclasses.
	 * @param {Function} Constructor The constructor of the component which has to be created in the container layer.
	 * @param {Object} config The configuration object which must be
	 * passed to the constructor when creating the component
	 * @protected
	 */
	create: Ext.emptyFn
});
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.User
 * @extends Object
 *
 * An object which represents a logged
 * on user in grommunio Web environment.
 * To obtain the instance of this object
 * for the currently logged in user,
 * refer to {@link Zarafa.core.Container#getUser}
 */
Zarafa.core.data.User = Ext.extend(Object, {

	/**
	 * Object containing all meta data for
	 * this user.
	 * @property
	 * @type Object
	 */
	meta: undefined,

	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		this.meta = config;
	},

	/**
	 * @return {String} The entryid for the user
	 */
	getEntryId: function()
	{
		return this.meta.entryid;
	},

	/**
	 * @return {String} The searchkey for the user
	 */
	getSearchKey: function()
	{
		return this.meta.search_key;
	},

	/**
	 * The display name for the user, this can be either
	 * the {@link #getFullName} or {@link #getUserName}
	 * depending which one is a non-empty string.
	 * @return {String} The displayname for the user
	 */
	getDisplayName: function()
	{
		return this.meta.fullname || this.meta.username;
	},
	
	/**
	 * @return {String} The thumbnail photho for this user
	 */
	getUserImage : function()
	{
		return this.meta.user_image || Ext.BLANK_IMAGE_URL;
	},
	
	setUserImage : function(image_data)
	{
		this.meta.user_image = image_data;
	},

	/**
	 * @return {String} The fullname for this user
	 */
	getFullName: function()
	{
		return this.meta.fullname;
	},

	/**
	 * @return {String} The username for this user
	 */
	getUserName: function()
	{
		return this.meta.username;
	},

	/**
	 * @return {String} The emailaddress for this user
	 */
	getEmailAddress: function()
	{
		return this.meta.email_address;
	},

	/**
	 * @return {String} The email address for this user
	 */
	getSMTPAddress: function()
	{
		return this.meta.smtp_address;
	},

	/**
	 * @deprecated 2.2.0 This function only exists for backward compatibility with
	 * 		 that want to send the session id as a GET parameter with requests that
	 * 		 they make to grommunio.php. Currently grommunio.php does not expect this
	 * 		 parameter anymore, but plugins that have not been updated might still
	 * 		 call this function.
	 * @return {String} Always empty
	 */
	getSessionId: function()
	{
		return '';
	},

	/**
	 * @return {String} The first name for this user
	 */
	getFirstName: function()
	{
		return this.meta.given_name;
	},

	/**
	 * @return {String} The initials for this user
	 */
	getInitials: function()
	{
		return this.meta.initials;
	},

	/**
	 * @return {String} The surname for this user
	 */
	getLastName: function()
	{
		return this.meta.surname;
	},

	/**
	 * @return {String} The street address for this user
	 */
	getAddress: function()
	{
		return this.meta.street_address;
	},

	/**
	 * @return {String} The city for this user
	 */
	getCity: function()
	{
		return this.meta.locality;
	},

	/**
	 * @return {String} The state or province for this user
	 */
	getState: function()
	{
		return this.meta.state_or_province;
	},

	/**
	 * @return {String} The zipcode for this user
	 */
	getZipCode: function()
	{
		return this.meta.postal_code;
	},

	/**
	 * @return {String} The country for this user
	 */
	getCountry: function()
	{
		return this.meta.country;
	},

	/**
	 * @return {String} The title for this user
	 */
	getTitle: function()
	{
		return this.meta.title;
	},

	/**
	 * @return {String} The company for this user
	 */
	getCompany: function()
	{
		return this.meta.company_name;
	},

	/**
	 * @return {String} The department for this user
	 */
	getDepartment: function()
	{
		return this.meta.department_name;
	},

	/**
	 * @return {String} The office location for this user
	 */
	getOffice: function()
	{
		return this.meta.office_location;
	},

	/**
	 * @return {String} The assistant for this user
	 */
	getAssistant: function()
	{
		return this.meta.assistant;
	},

	/**
	 * @return {String} The business phone number for this user
	 */
	getPhone: function()
	{
		return this.getPhoneBusiness();
	},

	/**
	 * @return {String} The business phone number for this user
	 */
	getPhoneBusiness: function()
	{
		return this.meta.business_telephone_number || this.meta.office_telephone_number;
	},

	/**
	 * @return {String} The second business phone number for this user
	 */
	getPhoneBusiness2: function()
	{
		return this.meta.business2_telephone_number;
	},

	/**
	 * @return {String} The fax number for this user
	 */
	getFax: function()
	{
		return this.meta.primary_fax_number;
	},

	/**
	 * @return {String} The phone number of the assistant for this user
	 */
	getPhoneAssistant: function()
	{
		return this.meta.assistant_telephone_number;
	},

	/**
	 * @return {String} The home phone number for this user
	 */
	getPhoneHome: function()
	{
		return this.meta.home_telephone_number;
	},

	/**
	 * @return {String} The second home phone number for this user
	 */
	getPhoneHome2: function()
	{
		return this.meta.home2_telephone_number;
	},

	/**
	 * @return {String} The mobile phone number for this user
	 */
	getPhoneMobile: function()
	{
		return this.meta.mobile_telephone_number;
	},

	/**
	 * @return {String} The pager phone number for this user
	 */
	getPhonePager: function()
	{
		return this.meta.pager_telephone_number;
	}
});
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.UserIdObject
 * @extends Object
 *
 * An object that identifies a user and has some functions to create and
 * compare.
 */
Zarafa.core.data.UserIdObject = Ext.extend(Object, {
	/**
	 * The type of this user. Can be ZARAFA or SMTP
	 * @property
	 */
	type: '',

	/**
	 * The display name of this user. Only added as a property to make
	 * easier for humans to recognize the user. Should not be used to
	 * identify the user!
	 * @property
	 */
	display_name: '',

	/**
	 * The email address of this user. Not always available.
	 * @property
	 */
	email_address: '',

	/**
	 * The username of this user. Only available for ZARAFA type users.
	 * @property
	 */
	username: '',

	/**
	 * The entryid of this user. Only available for ZARAFA type users.
	 * @property
	 */
	entryid: '',

	/**
	 * Constructor.
	 * @param {Object} idObject An object with properties
	 * that identify a user.
	 */
	constructor: function(idObject)
	{
		if ( idObject.type ) {
			this.type = idObject.type;
		}
		if ( idObject.display_name ) {
			this.display_name = idObject.display_name;
		}
		if ( idObject.email_address ) {
			this.email_address = idObject.email_address;
		}
		if ( idObject.username ) {
			this.username = idObject.username;
		}
		if ( idObject.entryid ) {
			this.entryid = idObject.entryid;
		}
	},

	/**
	 * Compares the {@link Zarafa.core.data.UserIdObject UserIdObject} to the passed
	 * {@link Zarafa.core.data.UserIdObject}. Returns true if both objects
	 * identify the same user.
	 * @param {Zarafa.core.data.UserIdObject} userIdObject A user of whom the id data
	 * will be compared to this users id data.
	 * @return {Boolean}
	 */
	equals: function(userIdObject)
	{
		// First we will compare the entryids if they are set
		if ( !Ext.isEmpty(this.entryid) && !Ext.isEmpty(userIdObject.entryid) ) {
			return Zarafa.core.EntryId.compareEntryIds(this.entryid, userIdObject.entryid);
		}

		// Second we will compare usernames. If a username is set, then an entryId should also be set,
		// so this comparison should not be necessary. However, this makes it possible to request
		// presence status with only a username, so let's include it.
		if ( !Ext.isEmpty(this.username) && this.username === userIdObject.username ) {
			return true;
		}

		// Third we will compare email addresses
		if ( !Ext.isEmpty(this.email_address) && this.email_address === userIdObject.email_address ) {
			return true;
		}

		//Last we will take care of when a username field is filled with the email address
		if (
			( !Ext.isEmpty(this.username) && this.username === userIdObject.email_address ) ||
			( !Ext.isEmpty(this.email_address) && this.email_address === userIdObject.username )
		){
			return true;
		}

		return false;
	},

	/**
	 * Synchronizes the data of this user with the given user.
	 * @param {Zarafa.core.data.UserIdObject} user A user whose id data will be used
	 * to update the id data of this user if possible.
	 */
	syncWithUser: function(user)
	{
		if ( user.type === 'EX' ){
			if ( this.type !== 'EX' ){
				this.type = user.type;
				this.display_name = user.display_name;
				this.username = user.username;
				this.entryid = user.entryid;
			}
			// Only store the email_address if we don't already have one. (Sometime we don't get an
			// email address for ZARAFA users, so we shouldn't overwrite if we have one)
			if ( Ext.isEmpty(this.email_address) ){
				this.email_address = user.username.indexOf('@')>=0 ? user.username : user.smtp_address;
			}
		}
	}
});
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.UserIdObjectFactory
 * @extends Object
 *
 * An factory object that can be used to create
 * {@link Zarafa.core.data.UserIdObject UserIdObjects}
 *
 * @singleton
 */
Zarafa.core.data.UserIdObjectFactory = {
	/**
	 * Creates a {@link Zarafa.core.data.UserIdObject} from the data in the record.
	 * @param {Zarafa.core.data.IPMRecord} record The record from which the data for the
	 * userIdObject is taken.
	 * @return {Zarafa.core.data.UserIdObject|null}
	 */
	createFromRecord: function(record)
	{
		// Return null for distlists
		if ( !Ext.isFunction(record.get) || !Ext.isEmpty(record.get('object_type')) && record.get('object_type') === Zarafa.core.mapi.ObjectType.MAPI_DISTLIST ) {
			return null;
		}

		// Add the general fields to the object
		var user = {};

		// Try to be smart to find a display_name
		user.display_name = record.get('display_name');

		user.type = record.get('address_type');

		if ( user.type === 'EX' ){

			// Add ZARAFA specific fields to the object
			var recordEntryId = record.get('entryid');
			var recordUsername = record.get('username');
			var recordEmailAddress = record.get('email_address');
			var recordSmtpAddress = record.get('smtp_address');

			user.entryid = recordEntryId;

			// Try to be smart to find the username and email address.
			// Sometimes the username is available in the email_address field,
			// sometimes the email address is available in the username field,
			// and sometimes it is available in the smtp_address field.
			if ( Ext.isString(recordUsername) && recordUsername.indexOf('@')==-1 ){
				user.username = recordUsername;
			} else if ( Ext.isString(recordEmailAddress) && recordEmailAddress.indexOf('@')==-1 ){
				user.username = recordEmailAddress;
			}
			if ( Ext.isString(recordEmailAddress) && recordEmailAddress.indexOf('@')>0 ){
				user.email_address = recordEmailAddress;
			} else if ( Ext.isString(recordSmtpAddress) && recordSmtpAddress.indexOf('@')>0 ){
				user.email_address = recordSmtpAddress;
			} else if ( Ext.isString(recordUsername) && recordUsername.indexOf('@')>0 ){
				user.email_address = recordUsername;
			}
		} else if ( !Ext.isEmpty(user.type) ) {

			// Add the field for non-ZARAFA users to the object
			// Try to be smart to find the email address
			user.email_address = record.get('email_address') || record.get('smtp_address');
			user.entryid = record.get('entryid');
		} else {
			return null;
		}

		var userIdObject = new Zarafa.core.data.UserIdObject(user);

		// If possible sync the userInfo with the cached one
		return Zarafa.core.data.PresenceCache.syncUsers([userIdObject])[0];
	},

	/**
	 * Returns an array with {@link Zarafa.core.data.UserIdObject UserIdObjects} for all
	 * users in the passed store.
	 * @param {Zarafa.core.data.MAPIStore|Zarafa.core.data.MAPISubStore} store The store
	 * from which {@link Zarafa.core.data.UserIdObject UserIdObjects} will be created.
	 * @return {Zarafa.core.data.UserIdObject[]} An array with userInfo
	 * objects.
	 */
	createFromStore: function(store)
	{
		if ( !store.data ) {
			return [];
		}

		var userIdObjects = [];
		var records = store.getRange();

		Ext.each(records, function(record){
			var userIdObject = Zarafa.core.data.UserIdObjectFactory.createFromRecord(record);
			if ( userIdObject ) {
				userIdObjects.push(userIdObject);
			}
		}, this);

		return userIdObjects;
	}
};
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.Version
 * @extends Object
 *
 * An object which represents the versioning
 * information of grommunio Web environment.
 * To obtain the instance of this object
 * refer to {@link Zarafa.core.Container#getVersion}
 */
Zarafa.core.data.Version = Ext.extend(Object, {

	/**
	 * Object containing all meta data for
	 * this user.
	 * @property
	 * @type Object
	 */
	meta: undefined,

	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		this.meta = config;
	},

	/**
	 * @return {String} Return grommunio Web version number
	 */
	getWebApp: function()
	{
		return this.meta.webapp;
	},

	/**
	 * @return {String} Return the Gromox version number
	 */
	getZCP: function()
	{
		return this.meta.zcp;
	},

	/**
	 * @return {String} Return the current Git branch
	 */
	getGit: function()
	{
		return this.meta.git;
	},

	/**
	 * @return {String} Return the cache buster string
	 */
	getCachebuster: function()
	{
		return this.meta.cachebuster;
	},

	/**
	 * Compares two version strings.
	 * @param {String} version1 The first version string for the comparison
	 * @param {String} version2 The second version string for the comparison
	 * @return {Integer} -1 if version1 is lower than version2,
	 * 1 if version2 is lower, 0 if the versions are equal.
	 */
	versionCompare: function(version1, version2)
	{
		// First remove unnecessary parts
		if ( version1.indexOf('-') > -1 ){
			version1 = version1.split('-')[0];
		}
		if ( version2.indexOf('-') > -1 ){
			version2 = version2.split('-')[0];
		}

		version1 = version1.split('.');
		version2 = version2.split('.');

		// Only compare major, minor, patch version.
		if (version1.length > 3) {
			version1 = version1.splice(0,3);
		}

		if (version2.length > 3) {
			version2 = version2.splice(0,3);
		}

		for ( var i=0; i<version1.length; i++ ){
			if ( !Ext.isDefined(version2[i]) ){
				return 1;
			}

			var v1 = parseInt(version1[i], 10);
			var v2 = parseInt(version2[i], 10);

			if ( v1 < v2 ){
				return -1;
			} else if ( v1 > v2 ){
				return 1;
			}
		}

		if ( version2[version1.length] ){
			return -1;
		}

		// When we get here the versions are considered the same
		return 0;
	}
});
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.ZarafaCustomEventDispatcher
 * @extends Ext.util.Observable
 * @singleton
 *
 * This singleton can be used to register events that have to be available in the whole Webapp.
 */
Zarafa.core.data.ZarafaCustomEventDispatcher = Ext.extend(Ext.util.Observable, {
	/**
	 * @constructor
	 * @param config
	 */
	constructor: function(config) {
		// Call our superclass constructor to complete construction process.
		Zarafa.core.data.ZarafaCustomEventDispatcher.superclass.constructor.call(this, config);
	}
});

// make it a singleton
Zarafa.core.data.ZarafaCustomEventDispatcher = new Zarafa.core.data.ZarafaCustomEventDispatcher();
Ext.namespace('Zarafa.core.plugins');

/**
 * @class Zarafa.core.plugins.AutoSaveMessagePlugin
 * @extends Object
 * @ptype zarafa.autosavemessageplugin
 *
 * Special plugin which must be used in combination with the
 * {@link Zarafa.core.plugins.RecordComponentPlugin}. This will
 * save message periodically after certain time.
 */
Zarafa.core.plugins.AutoSaveMessagePlugin = Ext.extend(Object, {
	/**
	 * The component which has been {@link #init initialized} on
	 * this plugin.
	 * @property
	 * @type Ext.Component
	 */
	field: undefined,

	/**
	 * The record which has been {@link #onSetRecord set} on the
	 * {@link #field}.
	 * @property
	 * @type Zarafa.core.data.MAPIRecord
	 */
	record: undefined,

	/**
	 * Timer that is used to save message after specified minutes
	 * @property
	 * @type Number
	 */
	messageAutoSaveTimer: undefined,

	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		Ext.apply(this, config);
	},

	/**
	 * Initializes the {@link Ext.Component Component} to which this plugin has been hooked.
	 * @param {Ext.Component} field The component on which the plugin is installed
	 */
	init: function(field)
	{
		this.field = field;
		field.messageAutoSavePlugin = this;

		this.field.on('render', this.onRender, this);
	},

	/**
	 * Event handler for the {@link Ext.Component#render render} event
	 * on the {@link #field}. This will register the event handlers which
	 * are needed to listen for record changes.
	 */
	onRender: function()
	{
		// Initialize necessary events based on ContentPanel and RecordComponentPlugin.
		this.field.on({
			'setrecord': this.onSetRecord,
			'userupdaterecord': this.onUserUpdateRecord,
			'saverecord': this.onSaveRecord,
			'close': this.onDestroy,
			'destroy': this.onDestroy,
			'scope': this
		});

		var fields = this.field.find('enableKeyEvents', true);

		// Register keypress event on every input field of the ContentPanel.
		Ext.each(fields, function(inputField) {
			this.field.mon(inputField, {
				'keypress': this.onFieldKeyPress,
				'scope': this
			});
		}, this);
	},

	/**
	 * Event handler for the {@link Zarafa.core.plugins.RecordComponentPlugin#setrecord setrecord} event
	 * @param {Ext.Component} field The component which fired the event
	 * @param {Zarafa.core.data.MAPIRecord} record The new record which is being set
	 * @param {Zarafa.core.data.MAPIRecord} oldrecord The old record which was previously set
	 * @private
	 */
	onSetRecord: function(field, record, oldrecord)
	{
		// Only IPMRecords will be handled by this plugin,
		// all other records will be discarded.
		if (record instanceof Zarafa.core.data.IPMRecord) {
			this.record = record;
		} else {
			this.record = undefined;
		}
	},

	/**
	 * Event handler for the {@link Zarafa.core.plugins.RecordComponentPlugin#userupdaterecord userupdaterecord} event
	 * on the {@link #field}. This will {@link #startMessageAutoSaveTimer start} the {@link #messageAutoSaveTimer}.
	 * @param {Ext.Component} field The component which fired the event
	 * @param {Zarafa.core.data.MAPIRecord} record The record which was updated
	 * @param {Boolean} isChangedByUser Indicates if the record has been changed by the user since it has been loaded.
	 * @private
	 */
	onUserUpdateRecord: function(field, record, isChangedByUser)
	{
		if (isChangedByUser) {
			this.startMessageAutoSaveTimer();
		}
	},

	/**
	 * Event handler for the input fields of the {@link #field}.
	 * This will {@link #startMessageAutoSaveTimer start} the {@link #messageAutoSaveTimer}.
	 * @param {HtmlElement} element The target of the event
	 * @param {Ext.EventObject} event The Ext.EventObject encapsulating the DOM event
	 * @param {Object} options The options configuration passed to the addListener call
	 * @private
	 */
	onFieldKeyPress: function(element, event, options)
	{
		this.startMessageAutoSaveTimer();
	},

	/**
	 * Start the {@link #messageAutoSaveTimer} if it was not yet started already.
	 * This will check the settings for the desired interval, and delay the call
	 * to {@link #messageAutoSave} as configured.
	 * @private
	 */
	startMessageAutoSaveTimer: function()
	{
		if (!this.messageAutoSaveTimer && this.record && this.record.isUnsent()) {
			var timeout = container.getSettingsModel().get('zarafa/v1/contexts/mail/autosave_time') * 1000;
			this.messageAutoSaveTimer = this.messageAutoSave.defer(timeout, this);
		}
	},

	/**
	 * Stop the {@link #messageAutoSaveTimer} if it is still pending.
	 * This will clear the interval and delete the {@link #messageAutoSaveTimer} making it
	 * available again for rescheduling.
	 * @private
	 */
	resetMessageAutoSaveTimer: function()
	{
		if (this.messageAutoSaveTimer) {
			window.clearTimeout(this.messageAutoSaveTimer);
			this.messageAutoSaveTimer = null;
		}
	},

	/**
	 * Event handler for the {@link #messageAutoSaveTimer}, this will
	 * {@link Zarafa.common.Actions.messageAutoSave autosave} the {@link #record}.
	 * @private
	 */
	messageAutoSave: function()
	{
		if (this.record) {

			// Check if the response received after resolve-attempt is ambiguous or not, if this is the case then "check names" dialog is still opened,
			// just halt the auto-saving mechanism until user select any record from suggestion.
			var recipientSubStore = this.record.getSubStore('recipients');
			var ambiguityDetected = false;
			recipientSubStore.each(function(record){
				if(record.resolveAttemptAmbiguous) {
					ambiguityDetected = true;
				}
			});

			if(!ambiguityDetected && this.field.autoSave){
				this.field.saveRecord();
			} else {
				this.resetMessageAutoSaveTimer();
				this.startMessageAutoSaveTimer();
			}
		}
	},

	/**
	 * When the record is about to be saved on the {@link Zarafa.core.ui.RecordContentPanel RecordContentPanel}
	 * we should {@link #resetMessageAutoSaveTimer cancel} {@link #messageAutoSaveTimer}
	 * @param {Zarafa.core.ui.RecordContentPanel} contentpanel The contentpanel which invoked the event
	 * @param {Zarafa.core.data.IPMRecord} record The record which is being saved.
	 * @private
	 */
	onSaveRecord: function(contentpanel, record)
	{
		this.resetMessageAutoSaveTimer();
	},

	/**
	 * Event handler for the {@link Ext.Container#destroy destroy} event.
	 * This will {@link #resetMessageAutoSaveTimer cancel} the {@link #messageAutoSaveTimer}
	 * @private
	 */
	onDestroy: function()
	{
		this.resetMessageAutoSaveTimer();
		this.record = undefined;
	}
});

Ext.preg('zarafa.autosavemessageplugin', Zarafa.core.plugins.AutoSaveMessagePlugin);
Ext.ns('Zarafa.core.plugins');

/**
 * @class Zarafa.core.plugins.ComboAutoWidth
 * @extends Ext.util.Observable
 * @ptype zarafa.comboautowidth
 *
 * this plugin will set the width of the combo box to the width of the maximum text in list
 */
Zarafa.core.plugins.ComboAutoWidth = Ext.extend(Ext.util.Observable, {
	/**
	 * Initialize the plugin for the given {@link Ext.form.Field field}
	 * @param {Ext.form.ComboBox} comboBox on which the plugin is placed
	 */
	init: function(combo)
	{
		this.container = combo;

		combo.mon(combo.store, 'load', this.resizeToFitContent, this);
		combo.on('render', this.resizeToFitContent, this);
	},

	/**
	 * Called in response to a load event from the addressbook comboBox store.
	 * resize the combobox innerlist according to the widest list item content
	 * @param {Zarafa.core.data.IPMStore} store store that fired the event.
	 * @private
	 */
	resizeToFitContent: function()
	{
		var combo = this.container;
		var store = combo.getStore();
		var listWidth = 0;
		var textMetrics = Ext.util.TextMetrics.createInstance(combo.el);

		store.each(function(record) {
			var curWidth = textMetrics.getWidth(record.get(combo.displayField));
			if (curWidth > listWidth) {
				listWidth = curWidth;
			}
		});

		if (listWidth > 0) {
			listWidth = Math.max(combo.minListWidth, listWidth);

			listWidth += combo.getTriggerWidth(); // to accommodate combo's down arrow.
			if (combo.getWidth() < listWidth) {
				if (!combo.list) {
					combo.listWidth = listWidth;
				} else {
					combo.list.setWidth(listWidth);
				}
			}
		}
	}
});

Ext.preg('zarafa.comboautowidth', Zarafa.core.plugins.ComboAutoWidth);
Ext.namespace('Zarafa.core.plugins');

/**
 * @class Zarafa.core.plugins.ComponentTooltipPlugin
 * @extends Object
 * @ptype zarafa.componenttooltipplugin
 *
 * This plugin is use to set the tooltip on {@link Ext.Component Component}.
 */
Zarafa.core.plugins.ComponentTooltipPlugin = Ext.extend(Object,{

	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};
		Ext.apply(this, config);
	},

	/**
	 * Initializes the {@link Ext.Component Component} to which this plugin has been hooked.
	 * @param {Ext.menu.Item} field The field on which the plugin is installed.
	 */
	init: function(field)
	{
		this.field = field;
	}
});

Ext.preg('zarafa.componenttooltipplugin', Zarafa.core.plugins.ComponentTooltipPlugin);Ext.namespace('Zarafa.core.plugins');

/**
 * @class Zarafa.core.plugins.ContentLayerPlugin
 * @extends Object
 * @ptype zarafa.contentlayerplugin
 *
 * The Plugin which must be installed on the {@link Zarafa.core.ui.ContentPanel}
 * to glue it together with the Content Layer on which the component is being
 * installed. This ensures that events and actions performed on the
 * {@link Zarafa.core.ui.ContentPanel panel} are forwarded correctly to the content layer.
 */
Zarafa.core.plugins.ContentLayerPlugin = Ext.extend(Object, {
	/**
	 * The Content Panel on which this plugin is installed.
	 * @property
	 * @type Zarafa.core.ui.ContentPanel
	 * @protected
	 */
	field: undefined,

	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		Ext.apply(this, config);
	},

	/**
	 * Initialize the plugin with the corresponding field.
	 * @param {Zarafa.core.ui.ContentPanel} field The panel on which the plugin is installed
	 */
	init: function(field)
	{
		this.field = field;

		// Listen for the moment the field was added to the parent container,
		// at that moment we can continue initializing.
		this.field.on('added', this.onAdded, this, { single: true });
	},

	/**
	 * This is called after {@link #init} when the {@link #field} has been fully initialized,
	 * and the owner of the component is known. This allows the plugin to access the parent.
	 *
	 * This will also {@link #focus} the component, to ensure it is visible for the user.
	 *
	 * @protected
	 */
	initPlugin: function()
	{
		this.focus();

		this.field.on('titlechange', this.onTitleChange, this);
		this.field.on('iconchange', this.onIconChange, this);
		this.field.on('hide', this.onHide, this);
		this.field.on('close', this.onClose, this);
	},

	/**
	 * Event handler for the {@link Ext.Component#added 'added'} event. At this moment
	 * we have access to the parent container in which the item was placed, but not
	 * all initialization might have been done yet. So this will register the
	 * {@link #onContainerAdd} event handler for the {@Link Ext.Component#add} event
	 * which is fired when the component is completely initialized.
	 * @param {Ext.Component} item The item which fired the event
	 * @param {Ext.Container} container The container on which the item was placed
	 * @private
	 */
	onAdded: function(item, container)
	{
		container.on('add', this.onContainerAdd, this);
	},

	/**
	 * Event handler for the {@link Ext.Container#add 'add'} event. At this moment
	 * the component must have been fully initialized so we can {@link #initPlugin finalize plugin initialization}.
	 * @param {Ext.Container} container The container which fired the event
	 * @param {Ext.Component} component The component which was added
	 * @private
	 */
	onContainerAdd: function(container, item)
	{
		if (this.field === item) {
			container.un('add', this.onContainerAdd, this);
			this.initPlugin();
		}
	},

	/**
	 * Event handler for the 'titlechange' event, fired by a {@link Ext.Component} that is managed by this plugin.
	 * @param {Zarafa.core.ui.ContentPanel} field The component that triggered the event
	 * @param {String} newTitle Title to set
	 * @param {String} oldTitle Previous title
	 * @private
	 */
	onTitleChange: function(field, newTitle, oldTitle)
	{
		this.setTitle(newTitle);
	},

	/**
	 * Event handler for the 'iconchange' event
	 * @param {Zarafa.core.ui.ContentPanel} field The component that triggered the event
	 * @param {String} newTitle Newly set title
	 * @param {String} oldTitle The previous title
	 * @private
	 */
	onIconChange: function(field, newIconCls, oldIconCls)
	{
		this.setIconClass(newIconCls);
	},

	/**
	 * Event handler for the 'hide' event
	 * @private
	 */
	onHide: function()
	{
		this.hide();
	},

	/**
	 * Event handler for the 'close' event
	 * @private
	 */
	onClose: function()
	{
		this.close();
	},

	/**
	 * This will apply the required title onto the {@link #field}
	 *
	 * Must be overridden by the subclasses
	 *
	 * @param {String} title The title to apply
	 * @protected
	 */
	setTitle: Ext.emptyFn,

	/**
	 * This will apply the required Icon Class onto the {@link #field}
	 *
	 * Must be overridden by the subclasses
	 *
	 * @param {String} iconCls The Icon Class to apply
	 * @protected
	 */
	setIconClass: Ext.emptyFn,

	/**
	 * This will hide panel containing the {@link #field}
	 *
	 * Must be overridden by the subclasses
	 *
	 * @protected
	 */
	hide: Ext.emptyFn,

	/**
	 * This will close panel containing the {@link #field}
	 *
	 * Must be overridden by the subclasses
	 *
	 * @protected
	 */
	close: Ext.emptyFn,

	/**
	 * This will bring focus to the Container by bringing it
	 * to the attention of the user.
	 *
	 * Must be overridden by subclasses
	 *
	 * @protected
	 */
	focus: Ext.emptyFn
});

Ext.preg('zarafa.contentlayerplugin', Zarafa.core.plugins.ContentLayerPlugin);
Ext.namespace('Zarafa.core.plugins');

/**
 * @class Zarafa.core.plugins.EnableFocusPlugin
 * @extends Object
 * @ptype zarafa.enablefocusplugin
 *
 * Normally div elements or span elements are not able to listen keyboard events because they
 * are non focusable elements,
 * So this plugin will add anchor element under the component's element, Which will get focus
 * when user clicks on the field, and anchor field will listen key events and will relay it to
 * parent component.
 * Adding this plugin to {@link Ext.Container} will intercept {@link Ext.Container#focus} event and
 * do focus handling itself.
 */
Zarafa.core.plugins.EnableFocusPlugin = Ext.extend(Object, {
	/**
	 * We are creating this element as anchor element, div elements can't get focus so we won't be able to
	 * handle keyboard in the panel, so when panel gets clicked we will set focus on this anchor element,
	 * to get keyboard events.
	 * @property
	 * @type Ext.Element
	 */
	focusEl: undefined,

	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		Ext.apply(this, config);
	},

	/**
	 * Initializes the {@link Ext.Component Component} to which this plugin has been hooked.
	 * @param {Ext.Component} field The component on which the plugin is installed
	 */
	init: function(field)
	{
		this.field = field;
		field.enableFocusPlugin = this;

		// override focus method of field to our own implementation
		// so focusing field should also transfer focus to the focusEl
		// and we can just ignore handling of focus in field
		this.field.focus = this.field.focus.createInterceptor(this.focus, this);

		this.field.on('render', this.onRender, this);
	},

	/**
	 * Function will intercept focus of the {@link Ext.Component Component} and will set it to
	 * {@link #focusEl} so we can listen for the key events and do keyboard handling.
	 * This function will return boolean value to indicate that intercepted function should be called or not,
	 * If the plugin is able to focus then it will return false and field's focus method will not be called
	 * and if plugin is not able to do focus then it will return true so field's focus method will be called.
	 * This return value is only used when this function is called as interceptor function.
	 * @private
	 */
	focus: function()
	{
		if(this.field.rendered && !this.field.isDestroyed) {
			if(this.focusEl) {
				// Creating a selection will also be seen as a click (because it is), but
				// Firefox will drop the current selection when we set the focus to the focusEl.
				// Fortunately FireFox has implemented most of the Selection api. So we can get
				// the current selection and put it back after we focus the focusEl.
				var selection = this.field.getEl().dom.ownerDocument.getSelection();
				var ranges = [];
				// Get all selection ranges
				if ( selection && selection.isCollapsed===false && selection.getRangeAt && selection.addRange ){
					for ( var i=0; i<selection.rangeCount; i++ ){
						ranges.push(selection.getRangeAt(i));
					}
				}

				this.focusEl.focus();

				// Add all ranges to the selection again
				Ext.each(ranges, function(range){
					selection.addRange(range);
				});

				// we have handled focus here so don't allow intercepted function to do its handling
				return false;
			}
		}

		// intercepted function will do its default handling
		return true;
	},

	/**
	 * Event handler for the {@link Ext.Component#render render} event
	 * on the {@link #field}. This will add anchor element under the component's element.
	 * This will add a mouse-click listener on field when it is clicked it will set focus
	 * on {@link #focusEl}, so once focus is set on {@link #focusEl} keyboard events can be
	 * listened by the components which register this plugin.
	 */
	onRender: function()
	{
		if(this.field) {
			var element = this.field.getEl();

			// this element allows the panel to be focused for keyboard events
			this.focusEl = element.createChild({
				tag: 'a',
				href: '#',
				// Disable tab-index, and position it somewhere where it cannot be seen
				// by the user. This will make the element completely invisible for the
				// user while we can benefit from the focus capabilities.
				tabindex: -1,
				style: 'position: absolute; left:-10000px; top:-10000px;'
			});

			this.focusEl.swallowEvent('click', true);

			this.field.mon(element, {
				click: this.onFieldClick,
				scope: this
			});
		}
	},

	/**
	 * Event handler which is fired when user clicks anywhere on panel,
	 * this will set the focus on {@link #focusEl} so that keyboard events can be fired on the component.
	 * @param {Ext.EventObject} eventObj The {@link Ext.EventObject} encapsulating the DOM event.
	 * @param {HtmlElement} element The target of the event.
	 * @param {Object} opt The options configuration passed to the event call.
	 * @private
	 */
	onFieldClick: function(eventObj, element, opt)
	{
		var focusedElement = Ext.get(document.activeElement);
		var clickedElement = Ext.get(element);

		/*
		 * Here we need to handle Keyevents that are fired on this component so we
		 * need to set focus on this element, now if any of the sub-component
		 * already has focus then we don't need to set focus on this component,
		 * as Keyevents will be propagated to this component, So here we are checking
		 * that whether user has clicked on any input field e.g. textfield, or focus
		 * is set on any sub-component.
		 */
		if(focusedElement != clickedElement) {
			var fieldEl = this.field.getEl();

			if(!fieldEl.contains(focusedElement)) {
				this.focus();
			}
		}
	}
});

Ext.preg('zarafa.enablefocusplugin', Zarafa.core.plugins.EnableFocusPlugin);
Ext.namespace('Zarafa.core.plugins');

/**
 * @class Zarafa.core.plugins.InputAutoFocusPlugin
 * @extends Object
 * @ptype zarafa.inputautofocusplugin
 *
 * A special plugin which, when placed on a {@link Ext.Panel} will
 * automatically select the first available Input element it can find.
 */
Zarafa.core.plugins.InputAutoFocusPlugin = Ext.extend(Object, {
	/**
	 * @cfg {Ext.Component} autoFocus The component which should
	 * be focused when the {@link #field} has been {@link Ext.Container#afterlayout laid out}
	 * or {@link Ext.Container#activate activated}.
	 */
	autoFocus: undefined,

	/**
	 * The field on which this plugin has been installed
	 * @property
	 * @type Ext.Container
	 */
	field: undefined,

	/**
	 * The extra {@link Ext.Element} which is inserted at the start of the {@link #field container},
	 * this is used to apply a {@link #doCyclicFocus cyclic focus} to prevent the focus
	 * to leave the {@link #field container}.
	 * @property
	 * @type Ext.Element
	 */
	beginFocusEl: undefined,

	/**
	 * The extra {@link Ext.Element} which is inserted at the end of the {@link #field container},
	 * this is used to apply a {@link #doCyclicFocus cyclic focus} to prevent the focus
	 * to leave the {@link #field container}.
	 * @property
	 * @type Ext.Element
	 */
	endFocusEl: undefined,

	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		Ext.apply(this, config);
	},

	/**
	 * Called by the {@link Ext.Container} when the plugin is being registered.
	 * @param {Ext.Container} field The field into which this plugin is installed
	 */
	init: function(field)
	{
		this.field = field;
		field.inputAutoFocusPlugin = this;

		this.field.on('afterrender', this.onAfterRender, this);
		this.field.on('afterlayout', this.onAfterFirstLayout, this, { single: true });
		this.field.on('destroy', this.onDestroy, this);
	},

	/**
	 * Event handler which is fired after the {@link #field} has been {@link Ext.Container#afterrender rendered}.
	 * This will create the {@link #beginFocusEl} and {@link #endFocusEl} elements to enable {@link #doCyclicFocus}
	 * cyclic focussing.
	 * @private
	 */
	onAfterRender: function()
	{
		this.beginFocusEl = Ext.DomHelper.insertBefore(this.field.el, {
			tag: 'a',
			href: '#',
			style: 'position: absolute; text-decoration: none; font-height: 1px; width: 1px; height: 1px; left:-10000px; top:-10000px;'
		}, true);
		this.beginFocusEl.dom.innerHTML = "&nbsp;";
		this.beginFocusEl.on('focus', this.onLimitFocussed, this);

		this.endFocusEl = Ext.DomHelper.insertAfter(this.field.el, {
			tag: 'a',
			href: '#',
			style: 'position: absolute; text-decoration: none; font-height: 1px; width: 1px; height: 1px; left:-10000px; top:-10000px;'
		}, true);
		this.endFocusEl.dom.innerHTML = "&nbsp;";
		this.endFocusEl.on('focus', this.onLimitFocussed, this);
		// Check if the field is a child of the Ext.Window, if a focus element exists on it,
		// then we need to intercept its focus and move it to the form
		var win = this.field.findParentByType('window');
		if (win && win.focusEl) {
			win.focusEl.on('focus', this.onDialogFocussed, this);
		}

	},

	/**
	 * Event handler which is fired after the {@link #field} has been {@link Ext.Container#afterlayout laid out}.
	 * This will check if the {@link #field} is a tab in a {@link Ext.TabPanel} or a normal {@link Ext.Container}.
	 * When it is a tab, it will wait for the {@link Ext.Container#activate tab-activation}, otherwise it will
	 * start the {@link #doAutoFocus}.
	 * @private
	 */
	onAfterFirstLayout: function()
	{
		if (Ext.isDefined(this.field.tabEl)) {
			this.field.on('activate', this.onActivate, this);
		} else {
			// This field isn't a tab, but perhaps one of the child elements is...
			var tabs = this.field.findBy(function(cmp) { return Ext.isDefined(cmp.tabEl); });
			if (!Ext.isEmpty(tabs)) {
				for (var i = 0, len = tabs.length; i < len; i++) {
					this.field.mon(tabs[i], 'activate', this.onActivate, this);
				}
			}
		}
	},

	/**
	 * Event handler which is fired after the {@link #field} has been {@link Ext.Container#activate activated}.
	 * This will start the {@link #doAutoFocus}.
	 * @private
	 */
	onActivate: function()
	{
		// Apply a 1ms delay, otherwise we cannot detect which input field
		// is hidden or not.
		this.doAutoFocus.defer(1, this);
	},

	/**
	 * Called when either the {@link #beginFocusEl} or {@link #endFocusEl} has been focussed.
	 * This will start the {@link #doCyclicFocus}.
	 * @param {Ext.EventObject} event The event object
	 * @param {HTMLElement} element The element which was focussed
	 * @private
	 */
	onLimitFocussed: function(event, element)
	{
		this.doCyclicFocus(this.beginFocusEl.dom === element);
	},

	/**
	 * Event handler which is called when the {@link #field} is being destroyed.
	 * This will automatically destroy the {@link #beginFocusEl} and {@link #endFocusEl}.
	 * @private
	 */
	onDestroy: function()
	{
		Ext.destroy(this.beginFocusEl);
		Ext.destroy(this.endFocusEl);
	},

	/**
	 * Change the {@link #autoFocus} property and automatically
	 * have the focus {@link #doAutoFocus} updated to this component.
	 * @param {Mixed} autoFocus The replacement for the {@link #autoFocus} object
	 */
	setAutoFocus: function(autoFocus)
	{
		this.autoFocus = autoFocus;
		this.doAutoFocus();
	},

	/**
	 * Called by {@link #onAfterFirstLayout} and {@link #onActivate}. If {@link #autoFocus} has been
	 * provided, this will apply the focus to that component, otherwise it will search for the first
	 * {@link #isFocusElement focussable element} inside the {@link #field container} (and the sub-containers).
	 * @private
	 */
	doAutoFocus: function()
	{
		var focusCmp;

		if (this.autoFocus) {
			if (Ext.isString(this.autoFocus)) {
				// Perhaps the string is a property on our field
				focusCmp = this.field[this.autoFocus];
				if (!focusCmp) {
					// Perhaps the string is a xtype of a child component
					var children = this.field.findByType(this.autoFocus);
					if (!Ext.isEmpty(children)) {
						focusCmp = children[0];
					}
				}
				if (!focusCmp) {
					// Perhaps the string is an ID of the HTML element
					focusCmp = Ext.get(this.autoFocus);
				}
			} else if (Ext.isElement(this.autoFocus) || Ext.isFunction(this.autoFocus.focus)) {
				focusCmp = this.autoFocus;
			}
		} else {
			focusCmp = this.findFocusElement(this.field);
			// If no focus element was found, then lets try
			// if we can focus on a button instead.
			if (!focusCmp) {
				focusCmp = this.findFocusElement(this.field, false, true);
			}
		}

		if (focusCmp) {
			focusCmp.focus.defer(1, focusCmp);
			if(focusCmp.events['setAutoFocusCursor']){
				focusCmp.fireEvent('setAutoFocusCursor', focusCmp.getEditor());
			}
		}
	},

	/**
	 * Called when the {@link #beginFocusEl} or {@link #endFocusEl} elements have been focussed.
	 * This will apply a cycle to the focus, making sure the focus will not leave the {@link #field}
	 * in which this plugin is installed.
	 * @param {Boolean} inverse Search for the last focussable item in the container rather then
	 * the first.
	 * @private
	 */
	doCyclicFocus: function(inverse)
	{
		var focusCmp;

		focusCmp = this.findFocusElement(this.field, inverse, true);
		if (focusCmp) {
			focusCmp.focus.defer(1, focusCmp);
		}
	},

	/**
	 * Search through the given container to search for the first {@link #isFocusElement focussable element}
	 * inside the given container (and the sub-containers).
	 * @param {Ext.Container} container The container through which we must search to find the first
	 * focussable element
	 * @param {Boolean} inverse True to search for the last Focus Element
	 * @param {Boolean} allowButton Allow a button to be considered an focussable input element
	 * @return {Ext.Component} The focussable component which was found in this container
	 * @private
	 */
	findFocusElement: function(container, inverse, allowButton)
	{
		var objects = [];

		if (container.topToolbar) {
			objects = objects.concat(container.topToolbar.items.items);
		}
		if (container.items) {
			objects = objects.concat(container.items.items);
		}
		if (container.buttons) {
			objects = objects.concat(container.buttons);
		}

		if (inverse) {
			objects.reverse();
		}

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

			if (this.isContainer(item)) {
				if (item.isVisible()) {
					focus = this.findFocusElement(item, inverse, allowButton);
				}
			} else if (this.isFocusElement(item, allowButton)) {
				focus = item;
			}

			if (focus) {
				return focus;
			}
		}
	},

	/**
	 * Check if the given {@link Ext.Component} is a container which contains items through which
	 * we can search for a {@link #isFocusElement focussable element}.
	 * @param {Ext.Component} item The component we are checking if it is a container
	 * @return {Boolean} True if the item is a container, false otherwise
	 * @private
	 */
	isContainer: function(item)
	{
		return (item instanceof Ext.Container || item instanceof Ext.form.CompositeField);
	},

	/**
	 * Check if the given {@link Ext.Component} is a visible input-element which can be focussed.
	 * This includes that the size of the component is non-0, the element itself is visible and
	 * not readonly input element.
	 * @param {Ext.Component} item The component we are checking
	 * @param {Boolean} allowButton Allow a button to be considered an focussable input element
	 * @return {Boolean} True if the item is focussable, false otherwise
	 */
	isFocusElement: function(item, allowButton)
	{
		var el = item.btnEl || item.el;

		if (!el) {
			return false;
		}

		var disallowedType = (allowButton === true) ? /submit|reset|hidden/i : /button|submit|reset|hidden/i;
		var allowedTagname = (allowButton === true) ? /button|input|textarea|select/i : /input|textarea|select/i;

		return (el.dom.style.height !== '0px' &&
				el.dom.style.width !== '0px' &&
				el.dom.style.display !== 'none' &&
				el.dom.style.visibility !== 'hidden' &&
				!el.dom.disabled &&
				!el.dom.getAttribute('readonly') &&
				!disallowedType.test(el.dom.type) &&
				allowedTagname.test(el.dom.tagName));
	},

	/**
	 * Call this when Ext.JS moves the focus to its own focus element a#x-dlg-focus (see source of {@link Ext.Window#render})
	 * Call {@link #doAutoFocus} to move focus to the first form field
	 */
	onDialogFocussed: function()
	{
		this.doAutoFocus();
	}
});

Ext.preg('zarafa.inputautofocusplugin', Zarafa.core.plugins.InputAutoFocusPlugin);
Ext.namespace('Zarafa.core.plugins');

/**
 * @class Zarafa.core.plugins.MarkAsReadPlugin
 * @extends Object
 * @ptype zarafa.markasreadplugin
 *
 * Special plugin which must be used in combination with the
 * {@link Zarafa.core.plugins.RecordComponentPlugin}. This will
 * mark the message as read after a certain time.
 */
Zarafa.core.plugins.MarkAsReadPlugin = Ext.extend(Object, {
	/**
	 * The component which has been {@link #init initialized} on
	 * this plugin.
	 * @property
	 * @type Ext.Component
	 */
	field: undefined,

	/**
	 * The record which has been {@link #onSetRecord set} on the
	 * {@link #field}.
	 * @property
	 * @type Zarafa.core.data.MAPIRecord
	 */
	record: undefined,

	/**
	 * timer that is used to set message as read after specified seconds
	 * @property
	 * @type Number
	 */
	readFlagTimer: undefined,

	/**
	 * Indicate that setting/starting up the {@link #readFlagTimer} will be
	 * ignored and record will be marked as read directly.
	 * @property
	 * @type Boolean
	 */
	ignoreReadFlagTimer: undefined,

	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		Ext.apply(this, config);
	},

	/**
	 * Initializes the {@link Ext.Component Component} to which this plugin has been hooked.
	 * @param {Ext.Component} field The component on which the plugin is installed
	 */
	init: function(field)
	{
		this.field = field;
		field.markAsReadPlugin = this;

		this.field.on('render', this.onRender, this);

		if (field.addInternalAction) {
			field.addInternalAction('send_read_receipt');
		}

		// When the host component has a model (e.g. PreviewPanel) and
		// preview is hidden (NO_PREVIEW mode), the normal setrecord /
		// loadrecord events never fire because the panel skips loading.
		// Listen on the model directly so the read-flag timer starts
		// regardless of preview visibility.
		if (field.model) {
			field.on('afterrender', function() {
				this.field.mon(this.field.model, 'previewrecordchange',
					this.onPreviewRecordChangeNoPanel, this);
			}, this, { single: true });
		}
	},

	/**
	 * Event handler for the {@link Ext.Component#render render} event
	 * on the {@link #field}. This will register the event handlers which
	 * are needed to listen for record changes.
	 */
	onRender: function()
	{
		this.field.on({
			'setrecord': this.onSetRecord,
			'beforesetrecord': this.onBeforeSetRecord,
			'beforeloadrecord': this.onBeforeLoadRecord,
			'loadrecord': this.onLoadRecord,
			'close': this.onDestroy,
			'destroy': this.onDestroy,
			'scope': this
		});
	},

	/**
	 * Event handler for the {@link Zarafa.core.plugins.RecordComponentPlugin#setrecord setrecord} event
	 * on the {@link #field}. This will {@link #resetReadFlagTimer stop} the {@link #readFlagTimer}.
	 * @param {Ext.Component} field The component which fired the event
	 * @param {Zarafa.core.data.MAPIRecord} record The new record which is being set
	 * @param {Zarafa.core.data.MAPIRecord} oldrecord The old record which was previously set
	 * @private
	 */
	onSetRecord: function(field, record, oldrecord)
	{
		// If the record is the same, the data of the record was just updated. No need to mark it as
		// read/unread then.
		if ( record && oldrecord && record.equals(oldrecord) ){
			return;
		}

		this.resetReadFlagTimer();

		// here, parameter named 'record' is the instance of shadowstore record.
		if(record && record.isOpened()) {
			// if shadowstore record is opened then fire load event on original record(record of list view)
			// as we are using that record to mark it as read.
			this.onLoadRecord(this.field, this.record);
		}
	},

	/**
	 * Event handler for the {@link Zarafa.core.plugins.RecordComponentPlugin#beforesetrecord beforesetrecord} event
	 * on the {@link #field}. This will get the original record because it is {@link Ext.data.Record#copy copied} within
	 * {@link Zarafa.core.plugins.RecordComponentPlugin#setRecord setRecord} method to start shadow editing.
	 * @param {Ext.Component} field The component which fired the event
	 * @param {Zarafa.core.data.MAPIRecord} record The new record which is being set
	 * @param {Zarafa.core.data.MAPIRecord} oldrecord The old record which was previously set
	 * @private
	 */
	onBeforeSetRecord: function(field, record, oldrecord)
	{

		this.resetReadFlagTimer();

		// Only IPMRecords will be handled by this plugin,
		// all other records will be discarded.
		if (record instanceof Zarafa.core.data.IPMRecord) {
			this.record = record;

			var store = this.record.getStore();

			if(Ext.isDefined(store)) {
				this.field.mon(store, 'load', this.onStoreLoad, this);
				this.field.mon(store, 'remove', this.onStoreRemove, this);
			}

		} else {
			this.record = undefined;
		}
	},

	/**
	 * Called when {@link Zarafa.mail.ui.MailGrid#store store} is loading new data.
	 * This will {@link Zarafa.core.plugins.MarkAsReadPlugin#resetReadFlagTimer reset}
	 * the {@link Zarafa.core.plugins.MarkAsReadPlugin#readFlagTimer}.
	 *
	 * @param {Zarafa.core.data.MAPIStore} store The store which being loaded.
	 * @param {Ext.data.Record[]} records The records which have been loaded from the store
	 * @param {Object} options The loading options that were specified (see {@link Ext.data.Store#load load} for details)
	 * @private
	 */
	onStoreLoad: function(store, records, options)
	{
		this.resetReadFlagTimer();
	},

	/**
	 * Event handler which is fired when the {@link Zarafa.mail.ui.MailGrid#store store} fires the
	 * remove event. This will {@link Zarafa.core.plugins.MarkAsReadPlugin#resetReadFlagTimer reset}
	 * the {@link Zarafa.core.plugins.MarkAsReadPlugin#readFlagTimer} if
	 * {@link Zarafa.core.plugins.MarkAsReadPlugin#record record} is removed
	 * from {@link Zarafa.mail.ui.MailGrid#store store}
	 *
	 * @param {Zarafa.hierarchy.data.HierarchyStore} store The store which fired the event
	 * @param {Zarafa.hierarchy.data.MAPIStoreRecord} record The record which was deleted
	 * @private
	 */
	onStoreRemove: function(store, record)
	{
		// Check if the removed record is the one which referred by the markAsRead plugin
		if (this.record === record) {
			this.resetReadFlagTimer();
		}
	},

	/**
	 * Event handler for the {@link Zarafa.core.plugins.RecordComponentPlugin#beforeloadrecord beforeloadrecord}
	 * event on the {@link #field}. When the record is unread and about to be
	 * opened, this piggybacks a mark_read message action onto the open request
	 * so the server can set the read flag in the same round-trip. This avoids
	 * a separate save request from {@link #onLoadRecord}/{@link #markAsRead}.
	 * @param {Ext.Component} field The component which fired the event
	 * @param {Zarafa.core.data.MAPIRecord} record The (shadow) record about to be loaded
	 * @private
	 */
	onBeforeLoadRecord: function(field, record)
	{
		if (!record || record.phantom || record.isRead() || record.isOpened()) {
			return;
		}

		// If mark_read was already added (e.g. by openMessageContent), skip.
		if (record.getMessageAction('mark_read')) {
			return;
		}

		// Task requests are excluded because they are transformed into
		// task records by openMessageContent.
		if (record.isMessageClass('IPM.TaskRequest', true)) {
			return;
		}

		// When a read-flag delay is configured and we are not in
		// immediate mode, do not piggyback mark_read on the open
		// request. The timer in onLoadRecord will handle it after
		// the configured delay has elapsed.
		if (!this.ignoreReadFlagTimer) {
			var delay = container.getSettingsModel().get('zarafa/v1/contexts/mail/readflag_time');
			if (delay > 0) {
				return;
			}
		}

		if (!record.needsReadReceipt()) {
			record.addMessageAction('mark_read', true);
		} else {
			var handling = container.getSettingsModel().get('zarafa/v1/contexts/mail/readreceipt_handling');
			if (handling === 'never') {
				record.addMessageAction('mark_read', true);
				record.addMessageAction('send_read_receipt', false);
			} else if (handling === 'always') {
				record.addMessageAction('mark_read', true);
				record.addMessageAction('send_read_receipt', true);
			}
			// 'ask': don't piggyback, let onLoadRecord handle it so
			// the user gets the read-receipt confirmation dialog.
		}
	},

	/**
	 * Event handler for the {@link Zarafa.core.plugins.RecordComponentPlugin#loadrecord loadrecord} event
	 * on the {@link #field}. This will {@link #startReadFlagTimer start} the {@link #readFlagTimer}.
	 * If the record was already marked as read by a piggybacked mark_read action
	 * on the open request, this synchronizes the read flag to the original record
	 * in the list store so that the grid updates without a separate server request.
	 * @param {Ext.Component} field The component which fired the event
	 * @param {Zarafa.core.data.MAPIRecord} record The record which was updated
	 * @private
	 */
	onLoadRecord: function(field, record)
	{
		if (record && !record.phantom && !record.isRead()) {
			// decide based on flag if we want to instantly mark as read or start the timer
			if (this.ignoreReadFlagTimer) {
				this.markAsRead();
			} else {
				this.startReadFlagTimer();
			}
		} else if (record && record.isRead() && this.record &&
			Ext.isFunction(this.record.isRead) && !this.record.isRead()) {
			// The server already marked the message as read (via
			// piggybacked mark_read on the open request).  Sync the
			// read flag to the original record so the list grid
			// reflects the new state without a separate save request.
			var flags = this.record.get('message_flags') | Zarafa.core.mapi.MessageFlags.MSGFLAG_READ;
			this.record.data['message_flags'] = flags;
			if (this.record.store) {
				this.record.store.fireEvent('update', this.record.store, this.record, Ext.data.Record.COMMIT);
			}
		}
	},

	/**
	 * Event handler for the model's previewrecordchange event. This is
	 * only relevant when the preview panel is hidden (NO_PREVIEW mode).
	 * When the panel is visible the normal setrecord/loadrecord flow
	 * handles everything, so this handler bails out in that case.
	 * @param {Zarafa.core.ContextModel} model The context model
	 * @param {Zarafa.core.data.IPMRecord} record The selected record
	 * @private
	 */
	onPreviewRecordChangeNoPanel: function(model, record)
	{
		// When the panel is visible the regular event flow handles
		// marking as read — avoid duplicate timers.
		if (this.field.isVisible()) {
			return;
		}

		this.resetReadFlagTimer();

		if (record instanceof Zarafa.core.data.IPMRecord) {
			this.record = record;
		} else {
			this.record = undefined;
			return;
		}

		if (record && !record.phantom && !record.isRead()) {
			this.startReadFlagTimer();
		}
	},

	/**
	 * Start the {@link #readFlagTimer} if it was not yet fired already.
	 * This will check the settings for the desired timeout, and delay the call
	 * to {@link #markAsRead} as configured.
	 * @private
	 */
	startReadFlagTimer: function()
	{
		if (!this.readFlagTimer) {
			var timeout = container.getSettingsModel().get('zarafa/v1/contexts/mail/readflag_time') * 1000;
			this.readFlagTimer = this.markAsRead.defer(timeout, this);
		}
	},

	/**
	 * Stop the {@link #readFlagTimer} if it is still pending.
	 * This will clear the timeout and delete the {@link #readFlagTimer} making it
	 * available again for rescheduling.
	 * @private
	 */
	resetReadFlagTimer: function()
	{
		if (this.readFlagTimer) {
			window.clearTimeout(this.readFlagTimer);
			delete this.readFlagTimer;
		}
	},

	/**
	 * Event handler for the {@link #readFlagTimer}, this will mark the {@link #record}
	 * as {@link Zarafa.common.Actions.markAsRead read}.
	 * @private
	 */
	markAsRead: function()
	{
		if (this.record && !Ext.isEmpty(this.record.getStore())) {
			Zarafa.common.Actions.markAsRead(this.record);
		}
		delete this.readFlagTimer;
	},

	/**
	 * Event handler for the {@link Ext.Container#destroy destroy} event.
	 * This will {@link #resetReadFlagTimer cancel} the {@link #readFlagTimer} to
	 * prevent the record from being marked as read.
	 * @private
	 */
	onDestroy: function()
	{
		this.resetReadFlagTimer();
		this.record = undefined;
	}
});

Ext.preg('zarafa.markasreadplugin', Zarafa.core.plugins.MarkAsReadPlugin);
Ext.namespace('Zarafa.core.plugins');

/**
 * @class Zarafa.core.plugins.MenuItemTooltipPlugin
 * @extends Object
 * @ptype zarafa.menuitemtooltipplugin
 *
 * This plugin is use to set the tooltip on {@link Ext.menu.Item menuitem}
 * of {@link Ext.splitButton SplitButton}.
 */
Zarafa.core.plugins.MenuItemTooltipPlugin = Ext.extend(Zarafa.core.plugins.ComponentTooltipPlugin,{

	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};
		Ext.apply(this, config);

		Zarafa.core.plugins.MenuItemTooltipPlugin.superclass.constructor.call(this, config);
	},

	/**
	 * Initializes the {@link Ext.Component Component} to which this plugin has been hooked.
	 * @param {Ext.menu.Item} field The field on which the plugin is installed.
	 */
	init: function(field)
	{
		Zarafa.core.plugins.MenuItemTooltipPlugin.superclass.init.apply(this, arguments);
		// Add event listener for the 'activate' event, if we are move the cursor on menu item then the
		// tooltip is display for particular menu item.
		this.field.on('activate', this.applyTooltip, this);
	},

	/**
	 * Used to apply the tooltip on {@link Ext.menu.Item menuitem} of {@link Ext.SplitBtton SplitBtton}
	 * @param {Ext.menu.Item} itemMenu The menu item of {@link Ext.SplitButton splitbutton}
	 */
	applyTooltip: function(itemMenu)
	{
		if(Ext.isDefined(itemMenu.tooltip)){
			Ext.QuickTips.unregister(itemMenu.getEl());
			if(Ext.isObject(itemMenu.tooltip)){
				Ext.QuickTips.register(Ext.apply({
					target: itemMenu.getEl().id
				}, itemMenu.tooltip));
			} else {
				itemMenu.getEl().dom.qtip = itemMenu.tooltip;
			}
		}
	}
});

Ext.preg('zarafa.menuitemtooltipplugin', Zarafa.core.plugins.MenuItemTooltipPlugin);Ext.namespace('Zarafa.core.plugins');

/**
 * @class Zarafa.core.plugins.RecordComponentPlugin
 * @extends Object
 * @ptype zarafa.recordcomponentplugin
 *
 * This plugin is used on a root {@link Ext.Container container} in which multiple children are
 * located which share a {@link Zarafa.core.data.MAPIRecord record} for displaying or editing.
 * This can for example occur in a {@link Ext.Window Dialog} which is used for editing a message,
 * or in the {@link Zarafa.core.ui.PreviewPanel PreviewPanel} for displaying a message.
 *
 * If the {@link #allowWrite} option is enabled, the {@link #record} will be editable, this will be done
 * by copying the {@link #record} into the {@link Zarafa.core.data.ShadowStore ShadowStore}
 * from where it can be saved to the server.
 *
 * The components located in the root {@link Ext.Container container} can use the
 * {@link Zarafa.core.ui.RecordComponentUpdaterPlugin RecordComponentUpdaterPlugin} for handling the notifications
 * from this plugin.
 *
 * When this plugin is being installed, it will set a reference to itself on the {@link #field}
 * with the name 'recordComponentPlugin'.
 */
Zarafa.core.plugins.RecordComponentPlugin = Ext.extend(Object, {
	/**
	 * The contained on which this plugin has been installed.
	 * @property
	 * @type Ext.Container
	 */
	field: undefined,

	/**
	 * @cfg {Boolean} ignoreUpdates True if the {@link #field} should not be listening to changes
	 * made to the {@link #record} from a {@link Zarafa.core.data.MAPIStore Store}. This will force
	 * the {@link #useShadowStore} option to be enabled.
	 */
	ignoreUpdates: false,

	/**
	 * @cfg {Boolean} allowWrite True if the {@link #field} supports
	 * the editing and saving of the {@link #record}.
	 */
	allowWrite: false,

	/**
	 * True when the {@link #field} has been laid out.
	 * @property
	 * @type Boolean
	 */
	isLayoutCalled: false,

	/**
	 * The record which is currently displayed in {@link #field}.
	 * @property
	 * @type Zarafa.core.data.MAPIRecord
	 */
	record: undefined,

	/**
	 * The cheapCopy argument of {@link #setRecord}, this is used when {@link #setRecord} was
	 * called when the panel has not been rendered yet and the call is deferred.
	 * @property
	 * @type Boolean
	 */
	cheapCopy: undefined,

	/**
	 * @cfg {Boolean} useShadowStore True, if the {@link #record} should be pushed into the
	 * shadowStore.
	 */
	useShadowStore: false,

	/**
	 * @cfg {Ext.data.Store} shadowStore is data store, it allows to use custom shadow store
	 * by record component plugin. This config mostly used by the external plugins like Files plugin.
	 */
	shadowStore: undefined,

	/**
	 * Indicates if the {@link #record} has been changed by the user since it has been loaded.
	 * If this is changed to true for the first time, the {@link #userupdaterecord} event will be
	 * fired on the {@link #field}.
	 * @property
	 * @type Boolean
	 * @private
	 */
	isChangedByUser: false,

	/**
	 * @cfg {Array} loadTasks An array of objects containing tasks which should be executed
	 * in order to properly load all data into the Component. The object should contain the
	 * 'fn' field which contains the function to be called, this will be called with the following
	 * arguments:
	 * - container {@link Ext.Container} The container on which this plugin is installed
	 * - record {@link Zarafa.core.data.MAPIRecord} The record which is being loaded
	 * - task {@link Object} The task object which was registered
	 * - callback {@link Function} The callback function which should be called when the task
	 *  has completed its work.
	 * The task should also contain the 'scope' property which refers to the scope in which the
	 * 'fn' should be called. If the task contains the 'defer' property, the call to 'fn' will
	 * be deferred for the given number of milliseconds.
	 */
	loadTasks: undefined,

	/**
	 * @cfg {Boolean} enableOpenLoadTask True to create a {@link #loadTask} which will
	 * {@link Zarafa.core.data.MAPIRecord#open open} the {@link #record} when it is set
	 * on the {@link #field}.
	 */
	enableOpenLoadTask: true,

	/**
	 * @cfg {Number} autoOpenLoadTaskDefer The 'defer' configuration option for the task
	 * which will be put in {@link #loadTasks} when {@link #enableOpenLoadTask} is true.
	 * This can be used to delay the opening of the record.
	 */
	autoOpenLoadTaskDefer: 0,

	/**
	 * List of {@link Ext.util.DelayedTask} instances which will be filled in when
	 * tasks in {@link #loadTasks} was configured with a 'defer' property.
	 * @property
	 * @type Array
	 * @private
	 */
	scheduledTasks: undefined,

	/**
	 * The list of Tasks from {@link #loadTasks} which have been started but are awaiting
	 * the call to the callback function.
	 * @property
	 * @type Array
	 * @private
	 */
	pendingTasks: undefined,

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

		// Apply a default value for the useShadowStore
		// based on the other settings.
		if (!Ext.isDefined(config.useShadowStore)) {
			if (config.allowWrite === true) {
				config.useShadowStore = true;
			}
			if (config.ignoreUpdates === true) {
				config.useShadowStore = true;
			}
		}

		Ext.applyIf(config, {
			shadowStore: container.getShadowStore()
		});

		Ext.apply(this, config);
	},

	/**
	 * Initializes the {@link Ext.Component Component} to which this plugin has been hooked.
	 * @param {Ext.Component} The parent field to which this component is connected
	 */
	init: function(field)
	{
		this.field = field;
		field.recordComponentPlugin = this;

		this.field.addEvents(
			/**
			 * @event beforesetrecord
			 *
			 * Fired from {@link #field}
			 *
			 * Fires when a record is about to be added to the {@link #field}
			 * This will allow Subclasses modify the {@link Zarafa.core.data.MAPIRecord record} before
			 * it has been hooked into the {@link #field}.
			 * @param {Ext.Container} panel The panel to which the record was set
			 * @param {Zarafa.core.data.MAPIRecord} record The record which is going to be set
			 * @param {Zarafa.core.data.MAPIRecord} oldrecord The oldrecord which is currently set
			 * @return {Boolean} false if the record should not be set on this panel.
			 */
			'beforesetrecord',
			/**
			 * @event setrecord
			 *
			 * Fired from {@link #field}
			 *
			 * Fires when a record has been added to the {@link #field}.
			 * No event handler may modify any properties inside the provided record (if this
			 * is needed for the Panel initialization, use the {@link #beforesetrecord} event).
			 * @param {Ext.Container} panel The panel to which the record was set
			 * @param {Zarafa.core.data.MAPIRecord} record The record which was set
			 * @param {Zarafa.core.data.MAPIRecord} oldrecord The oldrecord which was previously set
			 */
			'setrecord',
			/**
			 * @event beforeloadrecord
			 *
			 * Fired from {@link #field}
			 *
			 * Fires when a record is going to be {@link Zarafa.core.data.MAPIRecord#isOpened opened},
			 * or if there are registered {@link #loadTasks} to be executed.
			 * No event handler may modify any properties inside the provided record.
			 *
			 * @param {Ext.Container} panel The panel to which the record was set
			 * @param {Zarafa.core.data.MAPIRecord} record The record which was updated
			 * @return {Boolean} false if the record should not be loaded.
			 */
			'beforeloadrecord',
			/**
			 * @event loadrecord
			 *
			 * Fired from {@link #field}
			 *
			 * Fires when a record has been {@link Zarafa.core.data.MAPIRecord#isOpened opened},
			 * and all registered {@link #loadTasks} have been executed.
			 * No event handler may modify any properties inside the provided record.
			 *
			 * @param {Ext.Container} panel The panel to which the record was set
			 * @param {Zarafa.core.data.MAPIRecord} record The record which was updated
			 */
			'loadrecord',
			/**
			 * @event updaterecord
			 *
			 * Fired from {@link #field}
			 *
			 * Fires when the record of this {@link #field} was updated,
			 * through a {@link Zarafa.core.data.MAPIStore store}.
			 * No event handler may modify any properties inside the provided record.
			 * @param {Ext.Container} panel The panel to which the record was set
			 * @param {String} action write Action that occurred. Can be one of
			 * {@link Ext.data.Record.EDIT EDIT}, {@link Ext.data.Record.REJECT REJECT} or
			 * {@link Ext.data.Record.COMMIT COMMIT}
			 * @param {Zarafa.core.data.MAPIRecord} record The record which was updated
			 */
			'updaterecord',
			/**
			 * @event userupdaterecord
			 *
			 * Fired from {@link #field}
			 *
			 * Fires when the record of this {@link #field} was changed by the user.
			 * This allows the UI to apply special indicators to show the user that
			 * the user might need to save his changes.
			 * @param {Ext.Container} panel The panel to which the record was set
			 * @param {Zarafa.core.data.MAPIRecord} record The record which is dirty
			 */
			'userupdaterecord',
			/**
			 * @event exceptionrecord
			 *
			 * Fired from {@link #field}
			 *
			 * Fires when an exception event was fired by the {@link Ext.data.DataProxy}.
			 * No event handler may modify any properties inside the provided record.
			 * @param {String} type See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception}
			 * for description.
			 * @param {String} action See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception}
			 * for description.
			 * @param {Object} options See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception}
			 * for description.
			 * @param {Object} response See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception}
			 * for description.
			 * @param {Zarafa.core.data.MAPIRecord} record The record which was subject of the request
			 * that encountered an exception.
			 * @param {String} error (Optional) Passed when a thrown JS Exception or JS Error is
			 * available.
			 */
			'exceptionrecord',
			/**
			 * @event writerecord
			 *
			 * Fired from {@link #field}
			 *
			 * Fires when write event was fired by the {@link Zarafa.core.data.MAPIStore store}.
			 * @param {Ext.data.Store} store
			 * @param {String} action [Ext.data.Api.actions.create|update|destroy]
			 * @param {Object} result The 'data' picked-out out of the response for convenience.
			 * @param {Ext.Direct.Transaction} res
			 * @param {Zarafa.core.data.IPMRecord[]} records Store's records, the subject(s) of the write-action
			 */
			'writerecord'
		);

		// Raise an event after all components have been layout, we can then call this.setRecord
		// to force all components to be aware of the Record which is bound to this component.
		// we can't do that in the constructor because then nobody is listening to the event
		// yet. Neither can we do it after rendering, since that only indicates that this container.
		// has been rendered and not the components.
		this.field.on('afterlayout', this.onAfterFirstLayout, this, {single: true});

		// Add event listener for the 'close' event, if we are editing the record, then the
		// record must be removed from the shadowStore when closing the dialog.
		this.field.on('close', this.onClose, this);
	},

	/**
	 * Start editing on a {@link Zarafa.core.data.MAPIRecord record} by hooking
	 * it into the {@link Zarafa.core.data.ShadowStore ShadowStore} and mark the
	 * {@link Zarafa.core.data.MAPIRecord record} as being edited. This function
	 * should only be called when {@link #useShadowStore} is set to true.
	 *
	 * @param {Zarafa.core.data.MAPIRecord} record The record to edit
	 * @private
	 */
	startShadowRecordEdit: function(record)
	{
		if(this.useShadowStore) {
			this.shadowStore.add(record);
		}
	},

	/**
	 * Stop editing on a {@link Zarafa.core.data.MAPIRecord record} by unhooking it
	 * from the {@link Zarafa.core.data.ShadowStore ShadowStore}. This function
	 * should only be called when {@link #useShadowStore} is set to true.
	 *
	 * @param {Zarafa.core.data.MAPIRecord} record The record to finish editing
	 * @private
	 */
	endShadowRecordEdit: function(record)
	{
		if(this.useShadowStore) {
			this.shadowStore.remove(record, true);
		}
	},

	/**
	 * Check if the given {@link Zarafa.core.data.MAPIRecord record} matches the {@link #record}
	 * which is used inside this container. When we have {@link #allowWrite} support, we only accept updates
	 * when the record is exactly the same as the record inside the container. But when we have
	 * read-only support, we accept updates from all records with the same entryid.
	 *
	 * @param {Zarafa.core.data.MAPIRecord} record The record to compare
	 * @return {Boolean} True if the given record matches the record inside this container
	 * @private
	 */
	isRecordInContainer: function(record)
	{
		if (this.allowWrite === true && this.useShadowStore === false) {
			return (this.record && this.record === record);
		} else {
			return (this.record && this.record.equals(record));
		}
	},

	/**
	 * Set the {@link Zarafa.core.data.MAPIRecord record} which must be shown inside the {@link #field}
	 * When the field has not yet {@link #isLayoutCalled laid out}, then the {@link #record} is
	 * set, but all work is deferred to the first {@link #doLayout layout} action on this container.
	 * Otherwise this function will call {@link #beforesetrecord} to check if the record should be
	 * set on this field. Depending on the {@link Zarafa.core.data.MAPIRecord#isOpened opened} status
	 * of the record, it will call to the server to open the record (and wait with displaying the
	 * record until the server has responded) or display the record directly.
	 * @param {Zarafa.core.data.MAPIRecord} record The record to set
	 * @param {Boolean} cheapCopy true to prevent the record from being copied. This is usually the case
	 * when {@link #allowWrite} is enabled, and the given record is not a phantom.
	 */
	setRecord: function(record, cheapCopy)
	{
		var oldrecord = this.record;

		if (this.record === record) {
			return;
		}

		// Cancel any scheduled tasks which
		// might have been registered for
		// the previous record.
		if (this.scheduledTasks) {
			Ext.each(this.scheduledTasks, function(task) {
				task.cancel();
			});
			this.scheduledTasks = [];
		}

		// Clear all pending tasks for the
		// previous registered record.
		if (this.pendingTasks) {
			this.pendingTasks = [];
		}

		// Layout has not yet been called, cache the record
		// until the onAfterFirstLayout has been called, which will
		// set the record again.
		if (this.isLayoutCalled === false) {
			this.record = record;
			this.cheapCopy = cheapCopy;
			return;
		}

		if (this.field.fireEvent('beforesetrecord', this, record, oldrecord) === false) {
			return;
		}

		// Unhook the old record from the modifications tracking
		if (oldrecord) {
			oldrecord.setUpdateModificationsTracking(false);
			if (this.ignoreUpdates !== true) {
				this.field.mun(oldrecord.getStore(), 'update', this.onUpdateRecord, this);
				this.field.mun(oldrecord.getStore(), 'write', this.onWrite, this);
				this.field.mun(oldrecord.getStore(), 'exception', this.onExceptionRecord, this);
			}
			if (this.useShadowStore === true) {
				this.endShadowRecordEdit(oldrecord);
			}
		}

		// If we are clearing the record, we will take a shortcut, simply fire the
		// 'setrecord' event.
		if (!record) {
			this.record = record;
			this.field.fireEvent('setrecord', this, record, oldrecord);
			return;
		}

		// Check if the record must be move into the ShadowStore. When the record
		// is a phantom we can safely move it, otherwise we need to create a copy.
		// If the cheapCopy argument was provided, then it means we have already
		// received a copy and we can move it without problems.
		if (this.useShadowStore) {
			if (record.phantom !== true && cheapCopy !== true) {
				record = record.copy();
			}
			this.startShadowRecordEdit(record);
		}

		// All preparations have been completed, it is now time to set
		// the Record correctly into the component.
		this.record = record;

		// remove modified from modal dialog record
		if(this.record.isModalDialogRecord) {
			delete this.record.modified;
		}

		this.record.setUpdateModificationsTracking(true);
		this.field.fireEvent('setrecord', this, record, oldrecord);

		// Register the exception event handler, as we want to catch exceptions for
		// opening the record as well.
		if (this.ignoreUpdates !== true) {
			this.field.mon(record.getStore(), 'exception', this.onExceptionRecord, this);
		}

		// If the record is not yet opened, we must open the record now,
		// Add the 'openTaskHandler' to the loadTasks so we can start loading it.
		var tasks = this.loadTasks ? this.loadTasks.clone() : [];
		if (!record.isOpened() && this.enableOpenLoadTask) {
			tasks.push({
				fn: this.openTaskHandler,
				scope: this,
				defer: this.autoOpenLoadTaskDefer
			});
		}

		// Start loading the record.
		this.doLoadRecord(record, tasks);
	},

	/**
	 * Task handler for {@link #loadTasks} in case the {@link #record} needs
	 * to be opened. This will open the record, and wait for the response from
	 * the server.
	 * @param {Ext.Component} component The component which contains the record
	 * @param {Zarafa.core.data.MAPIRecord} record The record to be opened
	 * @param {Object} task The task object
	 * @param {Function} callback The function to call when the task has been
	 * completed.
	 * @private
	 */
	openTaskHandler: function(component, record, task, callback)
	{
		var fn = function(store, record) {
			if (this.isRecordInContainer(record)) {
				this.field.mun(store, 'open', fn, this);

				if (this.record !== record) {
					this.record.applyData(record);
				}
				callback();
			}
		};
		this.field.mon(record.getStore(), 'open', fn, this);
		record.open();
	},

	/**
	 * Obtain the current {@link #record} which has been sent to all
	 * listeners of the {@link #setrecord} event.
	 *
	 * @return {Zarafa.core.data.MAPIRecord} The record which is currently active
	 */
	getActiveRecord: function()
	{
		// The setrecord event is called during setRecord, but always after
		// isLayoutCalled.
		if (this.isLayoutCalled === true) {
			return this.record;
		}
	},

	/**
	 * Event handler which is triggered after the {@link #field}
	 * is first time laid out. This will reset the current {@link #record}
	 * to trigger the {@link #setrecord} event for the initial Record.
	 *
	 * @param {Ext.Component} component This component
	 * @param {ContainerLayout} layout The layout
	 * @private
	 */
	onAfterFirstLayout: function(component, layout)
	{
		this.isLayoutCalled = true;

		if (this.record) {
			// Force record reload by clearing this.record first.
			var record = this.record;
			this.record = undefined;
			this.setRecord(record, this.cheapCopy);
		}
	},

	/**
	 * This will check if any {@link #loadTasks tasks} have been registered.
	 * If there are, this will fire the {@link #beforeloadrecord} event, and
	 * starts executing the tasks. When it is done, the {@link #loadrecord} event
	 * is fired.
	 * Finally in all cases, the function {@link #afterLoadRecord} is called
	 * to complete the loading of the record.
	 * @param {Zarafa.core.data.MAPIRecord} record The record to load
	 * @param {Array} Array of Task objects as copied from {@link this.loadTasks}.
	 * @private
	 */
	doLoadRecord: function(record, tasks)
	{
		this.pendingTasks = [];
		this.scheduledTasks = [];

		// Check if we have any loading tasks, if we do, then start executing
		// them and wait for all of them to complete before continuing
		// to afterLoadRecord.
		if (!Ext.isEmpty(tasks)) {
			if (this.field.fireEvent('beforeloadrecord', this.field, record) !== false) {
				Ext.each(tasks, function(task) {
					// Add it to the pendingTasks list, so we can
					// keep track of the pending completion events.
					this.pendingTasks.push(task);

					// Check if the task should be deferred, if so
					// then use a Ext.util.DelayedTask to schedule it.
					if (task.defer && task.defer > 0) {
						var delayTask = new Ext.util.DelayedTask(this.doTask, this, [ record, task ]);

						// Add it to the scheduledTasks, so we can
						// keep track of the tasks (and cancel them
						// if needed).
						this.scheduledTasks.push(delayTask);
						delayTask.delay(task.defer);
					} else {
						this.doTask(record, task);
					}
				}, this);
			}
		} else {
			this.afterLoadRecord(record);
		}

	},

	/**
	 * Execute a task as registered in the {@link #loadTasks registered tasks}.
	 * @param {Zarafa.core.data.MAPIRecord} record The record to perform the action on
	 * @param {Object} task The task object from {@link #loadTasks}
	 * @private
	 */
	doTask: function(record, task)
	{
		if (this.isRecordInContainer(record)) {
			task.fn.call(task.scope || task, this.field, record, task, this.doLoadRecordCallback.createDelegate(this, [ task ]));
		}
	},

	/**
	 * Callback function used when executing {@link #loadTasks tasks}. Each
	 * time this function is called, the task is removed from the {@link #loadTasks}
	 * list. If this was the last task, {@link #loadrecord} event is fired, and
	 * {@link #afterLoadRecord} will be called.
	 * @param {Object} task The task which was completed
	 * @private
	 */
	doLoadRecordCallback: function(task)
	{
		var record = this.record;

		// Unregister the task when it is completed.
		this.pendingTasks.remove(task);

		// Check if this was the last task...
		if (this.pendingTasks.length === 0) {
			// If all pending Tasks were done, all
			// scheduled tasks have been completed as well
			this.scheduledTasks = [];

			this.field.fireEvent('loadrecord', this.field, record);
			this.afterLoadRecord(record);
		}
	},

	/**
	 * Reset the {@link #isChangedByUser} property to false and fire the {@link #userupdaterecord} event
	 * to signal the value change. This will tell all components that the {@link #record} contains
	 * no modifications from the user.
	 * @private
	 */
	resetUserChangeTracker: function()
	{
		this.isChangedByUser = false;
		this.field.fireEvent('userupdaterecord', this.field, this.record, this.isChangedByUser);
	},

	/**
	 * Set the {@link #isChangedByUser} property to true and fire the {@link #userupdaterecord} event
	 * when this means the property was changed. This will tell all components that the {@link #record}
	 * contains modifications from the user.
	 * @private
	 */
	registerUserChange: function()
	{
		if (this.isChangedByUser !== true) {
			this.isChangedByUser = true;
			this.field.fireEvent('userupdaterecord', this.field, this.record, this.isChangedByUser);
		}
	},

	/**
	 * Check if the given record contains user changes. This will check the contents for
	 * {@link Zarafa.core.data.MAPIRecord#updateModifications updateModifications} and
	 * {@link Zarafa.core.data.MAPIRecord#updateSubStoreModifications updateSubStoreModifications}
	 * to see if they were modified.
	 * @param {Zarafa.core.data.MAPIRecord} record The record to validate
	 * @private
	 */
	checkForUserChanges: function(record)
	{
		var updateModifications = record.updateModifications;
		var updateSubStoreModifications = record.updateSubStoreModifications;

		if ((updateModifications && Object.keys(updateModifications).length > 0) ||
		  (updateSubStoreModifications && Object.keys(updateSubStoreModifications).length > 0)) {
			this.registerUserChange();
		}
	},

	/**
	 * Called by {@link #doLoadRecord} when all {@link #loadTasks} have been executed
	 * (or when no tasks were registered}.
	 *
	 * This will register additional event handlers on the {@link Zarafa.core.data.MAPIStore store}.
	 *
	 * @param {Zarafa.core.data.MAPIRecord} record The record which has been loaded
	 * @private
	 */
	afterLoadRecord: function(record)
	{
		if (this.ignoreUpdates !== true) {
			this.resetUserChangeTracker();

			var store = this.record.getStore();
			this.field.mon(store, {
				'update': this.onUpdateRecord,
				'write': this.onWrite,
				'scope': this
			});
		}
	},

	/**
	 * Event handler will be called when the {@link Zarafa.core.data.MAPIStore Store} has
	 * updated the {@link Zarafa.core.data.MAPIRecord record}. This function will fire
	 * {@link #updaterecord} event to notify that the record is updated.
	 *
	 * @param {Zarafa.core.data.IPMStore} store The store in which the record is located.
	 * @param {Zarafa.core.data.IPMRecord} record The record which has been updated
	 * @param {String} operation The update operation being performed. Value may be one of
	 * {@link Ext.data.Record.EDIT EDIT}, {@link Ext.data.Record.REJECT REJECT} or
	 * {@link Ext.data.Record.COMMIT COMMIT}
	 * @private
	 */
	onUpdateRecord: function(store, record, operation)
	{
		if (!this.isRecordInContainer(record)) {
			return;
		}

		// Check if this exact record was updated, or if this is an external notification.
		// If this was a notification, then we only accept committed changes, before applying
		// the update to our current active record.
		if (this.record !== record) {
			if (operation !== Ext.data.Record.COMMIT) {
				return;
			}

			var modelRecord = this.record.isModalDialogRecord ? this.field.modalRecord : this.record;
			if (modelRecord !== record) {
				this.record.applyData(record);
			}
		}

		if (this.ignoreUpdates !== true) {
			if (operation === Ext.data.Record.COMMIT) {
				this.resetUserChangeTracker();
			} else {
				this.checkForUserChanges(record);
			}
		}

		this.field.fireEvent('updaterecord', this.field, operation, this.record);
	},

	/**
	 * Event handler will be called when the {@link Zarafa.core.data.MAPIStore Store} has
	 * fired an exception event.
	 * @param {Ext.data.DataProxy} proxy The proxy which fired the event.
	 * No event handler may modify any properties inside the provided record.
	 * @param {String} type See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception}
	 * for description.
	 * @param {String} action See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception}
	 * for description.
	 * @param {Object} options See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception}
	 * for description.
	 * @param {Object} response See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception}
	 * for description.
	 * @param {Zarafa.core.data.MAPIRecord} record The record which was subject of the request
	 * that encountered an exception.
	 * @param {String} error (Optional) Passed when a thrown JS Exception or JS Error is
	 * available.
	 * @private
	 */
	onExceptionRecord: function(proxy, type, action, options, response, args)
	{
		/** Extract the record from the args, and pull it out of an array if that is the case.
		 * If the array length is bigger than 1, we can discard this exception as this
		 * RecordCompontentPlugin can only handle one at a time. In that case the record variable
		 * will remain an array and will not pass the next IF-statement when it is compared against
		 * the MAPIRecord class.
		 */
		var record = args.sendRecords;
		if(Array.isArray(record) && record.length == 1){
			record = record[0];
		}
		// If it is not a record or if the record is not the record in the Container we should skip it
		if(!(record instanceof Zarafa.core.data.MAPIRecord) || !this.isRecordInContainer(record)){
			return;
		}
		var error = args.error || undefined;

		this.field.fireEvent('exceptionrecord', type, action, options, response, record, error);
	},

	/**
	 * Event handler will be called when the {@link Zarafa.core.data.MAPIStore Store} has
	 * fired a write event.
	 * Store fires it if the server returns 200 after an Ext.data.Api.actions CRUD action.
	 *
	 * @param {Ext.data.Store} store
	 * @param {String} action [Ext.data.Api.actions.create|update|destroy]
	 * @param {Object} result The 'data' picked-out out of the response for convenience.
	 * @param {Ext.Direct.Transaction} res
	 * @param {Zarafa.core.data.IPMRecord[]} records Store's records, the subject(s) of the write-action
	 * @private
	 */
	onWrite: function(store, action, result, res, records)
	{
		// If records isn't array then make it.
		records = [].concat(records);

		for (var i = 0, len = records.length; i < len; i++) {
			if (this.record === records[i]) {
				if(action == Ext.data.Api.actions.destroy) {
					// the record has been destroyed and removed from store
					// so user made changes are not useful anymore
					this.resetUserChangeTracker();
				}
				this.field.fireEvent('writerecord', store, action, result, res, records[i]);
				return;
			}
		}
	},

	/**
	 * Event handler which is called when the container is being closed.
	 * This will remove the {@link Zarafa.core.data.MAPIRecord record} from the
	 * {@link Zarafa.core.data.ShadowStore ShadowStore} (when the record was being {@link #allowWrite edited}).
	 * @param {Ext.Component} container The parent container on which this plugin is installed.
	 * @private
	 */
	onClose: function(dialog)
	{
		if (this.record) {
			this.endShadowRecordEdit(this.record);
		}
	}
});

Ext.preg('zarafa.recordcomponentplugin', Zarafa.core.plugins.RecordComponentPlugin);
Ext.namespace('Zarafa.core.plugins');

/**
 * @class Zarafa.core.plugins.RecordComponentUpdaterPlugin
 * @extends Object
 * @ptype zarafa.recordcomponentupdaterplugin
 *
 * This plugin is used on {@link Ext.Container Container} elements which
 * are placed as child (or grandchild) to a {@link Ext.Container Container} which
 * contains the {@link Zarafa.core.plugins.RecordComponentPlugin RecordComponent} plugin.
 *
 * This plugin will hook into the {@link Zarafa.core.plugins.RecordComponentPlugin#setrecord setrecord}
 * {@link Zarafa.core.plugins.RecordComponentPlugin#updaterecord updaterecord} events and call
 * the {@link Ext.Container#update update} function with the {@link Ext.data.Record Record}
 * argument.
 */
Zarafa.core.plugins.RecordComponentUpdaterPlugin = Ext.extend(Object, {
	/**
	 * @cfg {Ext.Container} rootContainer (optional) The container on which
	 * the {@link Zarafa.core.plugins.RecordComponentPlugin RecordComponent} plugin has been
	 * installed to which the container on which this plugin has been installed must
	 * listen to.
	 */
	rootContainer: undefined,
	/**
	 * The contained on which this plugin has been installed.
	 * @property
	 * @type Ext.Container
	 */
	field: undefined,
	/**
	 * True when the {@link #field} is {@link #onReadyComponent ready}.
	 * @property
	 * @type Boolean
	 */
	isReady: false,
	/**
	 * The record which is currently displayed in {@link #field}.
	 * @property
	 * @type Zarafa.core.data.IPMRecord
	 */
	record: undefined,
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		Ext.apply(this, config);
	},

	/**
	 * Initializes the {@link Ext.Component Component} to which this plugin has been hooked.
	 * @param {Ext.Component} The parent field to which this component is connected
	 */
	init: function(field)
	{
		this.field = field;
		field.recordComponentUpdaterPlugin = this;

		// Check if we already know our rootContainer.
		// If not, we have to wait for the 'added' event
		// to walk up the hierarchy tree to find a parent
		// which is the root container.
		if (this.rootContainer) {
			this.hookToRootContainer(this.rootContainer);
		} else if (field.rootContainer) {
			this.hookToRootContainer(field.rootContainer);
		} else {
			this.field.on('beforerender', this.onFirstBeforeRender, this, { single: true });
		}

		// If a layout is present on the component, then we must wait until the component
		// has been laid out. Otherwise rendering will be sufficient.
		if (Ext.isDefined(this.field.layout)) {
			this.field.on('afterlayout', this.onReadyComponent, this, { single: true });
		} else if (this.field.rendered !== true) {
			this.field.on('afterrender', this.onReadyComponent, this, { single: true });
		} else {
			this.onReadyComponent(this.field);
		}
	},

	/**
	 * This will set the {@link #rootContainer} for this plugin. The rootContainer is a
	 * {@link Ext.Container container} which has the {@link Zarafa.core.plugins.RecordComponentPlugin RecordComponent}
	 * plugin installed.
	 *
	 * @param {Ext.Container} root The root container on which we must hook our events.
	 * @private
	 */
	hookToRootContainer: function(root)
	{
		this.rootContainer = root;

		// Obtain the record from the root container,
		// this prevents issues when the root container
		// has already been rendered and we have missed the
		// initial 'setrecord' event.
		this.record = root.recordComponentPlugin.getActiveRecord();

		this.field.mon(root, {
			'activate': this.onActivateRoot,
			'setrecord': this.onSetRecord,
			'loadrecord': this.onLoadRecord,
			'updaterecord': this.onUpdateRecord,
			'exceptionrecord': this.onExceptionRecord,
			'beforesaverecord': this.onBeforeSaveRecord,
			'beforesendrecord': this.onBeforeSendRecord,
			'beforeclose': this.onBeforeClose,
			'deactivate': this.onDeactivate,
			scope: this
		});
	},

	/**
	 * Event handler which is fired when the {@link #field} is being rendered
	 * When this happened, we start walking up the hierarchy tree to find the
	 * {@link #rootContainer} which contains the {@link Zarafa.core.plugins.RecordComponentPlugin RecordComponent}
	 * from which we must listen for updates.
	 *
	 * @private
	 */
	onFirstBeforeRender: function()
	{
		var root = this.field;

		while (!(root.recordComponentPlugin instanceof Zarafa.core.plugins.RecordComponentPlugin)) {
			root = root.ownerCt;
			if (!Ext.isDefined(root)) {
				break;
			}
		}

		if (root) {
			this.hookToRootContainer(root);
		}
	},

	/**
	 * Called after the {@link #field component} has is ready for initialization by the plugin,
	 * depending on the type this can be on {@link Ext.Container#afterlayout} or {@link Ext.Component#afterrender}.
	 *
	 * This will load the current {@link Zarafa.core.data.IPMRecord Record} using
	 * the {@link Ext.Container#update} function.
	 *
	 * @param {Ext.Component} component This component
	 * @private
	 */
	onReadyComponent: function(component)
	{
		if (this.record) {
			this.field.update(this.record, true);
		}
		this.isReady = true;
	},

	/**
	 * Event handler which is fired for the {@link #rootContainer}#{@link Ext.Panel#activate activate}
	 * event. This will call the {@link Ext.Container#update} function to update the
	 * {@link #field} to which this plugin is attached. This ensures that any changes
	 * in data or UI which have occurred while the tab was deactivated are being forced
	 * into the UI again.
	 * @param {Ext.Panel} panel The panel which was activated
	 * @private
	 */
	onActivateRoot: function(panel)
	{
		if (this.record && this.isReady === true && this.field.isDestroyed !== true) {
			this.field.update(this.record, true);
		}
	},

	/**
	 * Event handler for the {@link Zarafa.core.plugins.RecordComponentPlugin#setrecord setrecord} event.
	 * This will call the {@link Ext.Container#update update} function
	 * to update the {@link #field} to which this plugin is attached.
	 *
	 * @param {Ext.Container} panel The panel to which the record was set
	 * @param {Zarafa.core.data.IPMRecord} record The record which was set
	 * @param {Zarafa.core.data.IPMRecord} oldrecord The oldrecord which was previously set
	 * @private
	 */
	onSetRecord: function(panel, record, oldrecord)
	{
		this.record = record;
		if (this.isReady === true) {
			this.field.update(record, true);
		}
	},

	/**
	 * Event handler for the {@link Zarafa.core.plugins.RecordComponentPlugin#loadrecord loadrecord} event.
	 * This will call the {@link Ext.Container#update update} function
	 * to update the {@link #field} to which this plugin is attached
	 *
	 * @param {Ext.Container} panel The panel to which the record belongs
	 * @param {Zarafa.core.data.IPMRecord} record The record which was updated
	 * @private
	 */
	onLoadRecord: function(panel, record)
	{
		if (this.isReady === true && this.field.isDestroyed !== true) {
			this.field.update(record, true);
		}
	},

	/**
	 * Event handler for the {@link Zarafa.core.plugins.RecordComponentPlugin#updaterecord updaterecord} event.
	 * This will call the {@link Ext.Container#update update} function
	 * to update the {@link #field} to which this plugin is attached
	 *
	 * @param {Ext.Container} panel The panel to which the record belongs
	 * @param {String} action write Action that occurred. Can be one of
	 * {@link Ext.data.Record.EDIT EDIT}, {@link Ext.data.Record.REJECT REJECT} or
	 * {@link Ext.data.Record.COMMIT COMMIT}
	 * @param {Zarafa.core.data.IPMRecord} record The record which was updated
	 * @private
	 */
	onUpdateRecord: function(panel, action, record)
	{
		if (this.isReady === true && this.field.isDestroyed !== true) {
			this.field.update(record, false);
		}
	},

	/**
	 * Event handler for the {@link Zarafa.core.plugins.RecordComponentPlugin#exceptionrecord exceptionrecord} event.
	 * This will call the {@link Ext.Container#doException doException} function if it is available.
	 * to update the {@link #field} to which this plugin is attached
	 * @param {String} type See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception}
	 * for description.
	 * @param {String} action See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception}
	 * for description.
	 * @param {Object} options See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception}
	 * for description.
	 * @param {Object} response See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception}
	 * for description.
	 * @param {Zarafa.core.data.MAPIRecord} record The record which was subject of the request
	 * that encountered an exception.
	 * @param {String} error (Optional) Passed when a thrown JS Exception or JS Error is
	 * available.
	 * @private
	 */
	onExceptionRecord: function(type, action, options, response, record, error)
	{
		if (this.isReady === true && this.field.isDestroyed !== true) {
			if (Ext.isFunction(this.field.doException)){
				this.field.doException(type, action, options, response, record, error);
			}
		}
	},

	/**
	 * Event handler for the {@link Zarafa.core.plugins.RecordComponentPlugin#beforesave beforesave} event.
	 * This will call the 'updateRecord' function to update the {@link #record} based on the
	 * information from the {@link #field}.
	 *
	 * @param {Ext.Container} panel The panel to which the record belongs.
	 * @param {Zarafa.core.data.IPMRecord} record The record which is being saved.
	 * @return {Boolean} false if the operation failed, and the save action must be aborted.
	 * @private
	 */
	onBeforeSaveRecord: function(panel, record)
	{
		if (this.isReady === true && this.field.isDestroyed !== true) {
			if (Ext.isFunction(this.field.updateRecord)) {
				return this.field.updateRecord(record);
			}
		}
		return true;
	},

	/**
	 * Event handler for the {@link Zarafa.core.plugins.RecordComponentPlugin#beforesendrecord beforesendrecord} event.
	 * This will call the 'updateRecord' function to update the {@link #record} based on the
	 * information from the {@link #field}.
	 *
	 * @param {Ext.Container} panel The panel to which the record belongs.
	 * @param {Zarafa.core.data.IPMRecord} record The record which is being saved.
	 * @return {Boolean} false if the operation failed, and the save action must be aborted.
	 * @private
	 */
	onBeforeSendRecord: function(panel, record)
	{
		if (this.isReady === true && this.field.isDestroyed !== true) {
			if (Ext.isFunction(this.field.updateRecord)) {
				return this.field.updateRecord(record);
			}
		}
		return true;
	},

	/**
	 * Event handler for the {@link Ext.Container#beforeclose} event.
	 * This will call the 'updateRecord' function to update the {@link #record} based on the
	 * information from the {@link #field}.
	 *
	 * @param {Ext.Container} panel The panel to which the record belongs.
	 * @private
	 */
	onBeforeClose: function(panel)
	{
		if (this.isReady === true && this.field.isDestroyed !== true) {
			if (this.record && Ext.isFunction(this.field.updateRecord)) {
				return this.field.updateRecord(this.record);
			}
		}
		return true;
	},

	/**
	 * Event handler for the {@link Ext.Panel#deactivate} event.
	 * This will call the 'updateRecord' function to update the {@link #record} based on the
	 * information from the {@link #field}.
	 *
	 * @param {Ext.Container} panel The panel to which the record belongs.
	 * @private
	 */
	onDeactivate: function(panel)
	{
		if (this.isReady === true && this.field.isDestroyed !== true) {
			if (this.record && Ext.isFunction(this.field.updateRecord)) {
				return this.field.updateRecord(this.record);
			}
		}
		return true;
	}
});

Ext.preg('zarafa.recordcomponentupdaterplugin', Zarafa.core.plugins.RecordComponentUpdaterPlugin);
Ext.namespace('Zarafa.core.plugins');

/**
 * @class Zarafa.core.plugins.TabCloseMenuPlugin
 * @extends Ext.ux.TabCloseMenu
 * @ptype zarafa.tabclosemenuplugin
 *
 * Extended so that the context menu would call the tab's close() method instead of directly destroying it
 * This class is intended to be used with {@link Zarafa.core.ui.MainContentTabPanel}
 * TODO: handle closing multiple tabs better
 */
Zarafa.core.plugins.TabCloseMenuPlugin = Ext.extend(Ext.ux.TabCloseMenu, {
	/**
	 * Overridden to add menu items from an insertion point
	 * @override
	 * @private
	 */
	createMenu: function(){
		if(!this.menu){
			var items = [{
				itemId: 'close',
				text: this.closeTabText,
				scope: this,
				handler: this.onClose
			}];
			if(this.showCloseAll){
				items.push('-');
			}
			items.push({
				itemId: 'closeothers',
				text: this.closeOtherTabsText,
				scope: this,
				handler: this.onCloseOthers
			});
			if(this.showCloseAll){
				items.push({
					itemId: 'closeall',
					text: this.closeAllTabsText,
					scope: this,
					handler: this.onCloseAll
				});
			}
			var lazyItems = container.populateInsertionPoint('main.content.tabpanel.tabclosemenu');
			this.menu = new Ext.menu.Menu({
				items: items.concat(lazyItems)
			});
		}
		return this.menu;
	},

	/**
	 * Overridden, because the original function calls remove() directly
	 * item.close() is better because it fires an event that notifies other components that the tab wants to close
	 * @override
	 * @private
	 */
	onClose: function(){
		this.active.close();
	},

	/**
	 * Overridden, because the original function calls remove() directly
	 * item.close() is better because it fires an event that notifies other components that the tab wants to close
	 * @param {Boolean} excludeActive Flag for whether to close all tabs but preserve the currently active one
	 * @override
	 * @private
	 */
	doClose: function(excludeActive){
		this.tabs.items.each(function(item){
			if(item.closable){
				if(!excludeActive || item != this.active){
					item.close();
				}
			}
		}, this);
	}
});

Ext.preg('zarafa.tabclosemenuplugin', Zarafa.core.plugins.TabCloseMenuPlugin);
Ext.namespace('Zarafa.core.ui');

/**
 * @class Zarafa.core.ui.ButtonGroup
 * @extends Ext.ButtonGroup
 * @xtype zarafa.buttongroup
 */
Zarafa.core.ui.ButtonGroup = Ext.extend(Ext.ButtonGroup, {
	/**
	 * @constructor
	 * @param config
	 */
	constructor: function(config)
	{
		Ext.apply(this, config, {
			xtype: 'zarafa.buttongroup'
		});

		Zarafa.core.ui.ButtonGroup.superclass.constructor.call(this, config);
	},

	/**
	 * Function will check whether buttongroup has any visible buttons or not.
	 *
	 * @return {Boolean} function will return true if buttonGroups has any visible items,
	 * It will return false if buttonGroup is empty or buttonGroup has all hidden buttons.
	 */
	hasVisibleButtons: function()
	{
		var buttonsArray = this.items.getRange();
		if(!Ext.isEmpty(buttonsArray)){
			for (var i = 0; i < buttonsArray.length; i++)
			{
				//isVisible function is not working properly in current version of ExtJs.
				if(buttonsArray[i].hidden === false) {
					return true;
				}
			}
		}
		return false;
	}
});

Ext.reg('zarafa.buttongroup', Zarafa.core.ui.ButtonGroup);
Ext.namespace('Zarafa.core.ui');

/**
 * @class Zarafa.core.ui.ContentPanel
 * @extends Ext.Container
 * @xtype zarafa.contentpanel
 *
 * FIXME: this documentation needs to be completely rewritten
 * The {@link Zarafa.core.ui.ContentPanel ContentPanel} class a base class for content panels and is therefore not intended to be instantiated
 * directly. Developers must extend this class to implement a dialog window that can be shown as either an ExtJS
 * window or a separate browser window.
 * <p>
 * All dialog classes must be registered using the static register method:
 * <p>
 * <code><pre>
 * Zarafa.core.ui.ContentPanel.register(MyDialog, 'mydialog');
 * </pre></code>.
 * <p>
 * The dialog can then be instantiated using the create() method:
 * <p>
 * <code><pre>
 * MyDialog.create(config, {
 *  browser: true,
 *  width: 500,
 *  height: 300,
 * });
 * </pre></code>.
 *
 */
Zarafa.core.ui.ContentPanel = Ext.extend(Ext.Container, {
	/**
	 * @cfg {String} title The title for the ContentPanel
	 */
	title: undefined,

	/**
	 * @cfg {String} The action to take when the close header tool is clicked.
	 * Only used when using {@link Zarafa.core.data.UIFactoryWindowLayer UIFactoryWindowLayer} to
	 * display this {@link Zarafa.core.ui.ContentPanel ContentPanel}.
	 */
	closeAction: 'closeWrap',

	/**
	 * @cfg {Boolean} standalone If true, the {@link Zarafa.core.ui.ContentPanel contentpanel}
	 * will not be hooked into the {@link Zarafa.core.data.ContentPanelMgr}. This will prevent
	 * listening to events coming from the {@link Zarafa.core.data.ContentPanelMgr ContentPanelMgr}.
	 * Defaults to false.
	 */
	standalone: false,

	/**
	 * @cfg {Number} width The width for the ContentPanel
	 */
	width: 800,

	/**
	 * @cfg {Number} height The height for the ContentPanel
	 */
	height: 550,

	/**
	 * @cfg {String} iconCls Icon class for the tab
	 */
	iconCls: undefined,

	/**
	 * @cfg {Boolean} useInputAutoFocusPlugin True to use the inputautofocusplugin for
	 * this panel
	 */
	useInputAutoFocusPlugin: true,

	/**
	 * @cfg {Boolean} forceFullyOpenInMainWindow True to render panel on main browser window
	 */
	forceFullyOpenInMainWindow: false,

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

		if ( config.useInputAutoFocusPlugin !== false ) {
			config.plugins = Ext.value(config.plugins, []);
			config.plugins.push({
				ptype: 'zarafa.inputautofocusplugin'
			});
		}

		Ext.applyIf(config, {
			xtype: 'zarafa.contentpanel',
			stateful: true
		});

		Zarafa.core.ui.ContentPanel.superclass.constructor.call(this, config);

		/**
		 * @event beforeclose
		 * Fires before the {@link Zarafa.core.ui.ContentPanel ContentPanel} is closed.
		 * A handler can return false to cancel the close.
		 * @param {Ext.Panel} panel The Panel being closed.
		 */
		/**
		 * @event close
		 * Fires after the Panel is closed.
		 * @param {Ext.Panel} panel The Panel that has been closed.
		 */
		this.initializeChildComponent(this);

		if (Ext.isDefined(this.title)) {
			this.setTitle(this.title);
		}

		// register with the ContentPanelMgr unless specifically instructed not to
		if (!this.standalone) {
			Zarafa.core.data.ContentPanelMgr.register(this);

		}
	},

	/**
	 * Called when the ContentPanel has been rendered.
	 * This activate the keymap on the element of this component after the normal operations of
	 * afterRender have been completed. It will activate by getting the xtype hierarchy from
	 * {@link #getXTypes} and format it into a string usable by the
	 * {@link Zarafa.core.KeyMapMgr KeyMapMgr}.
	 * @private
	 */
	afterRender: function()
	{
		Zarafa.core.ui.ContentPanel.superclass.afterRender.apply(this, arguments);
		var xtypes = this.getXTypes();

		// The first part leading up to zarafa.contentpanel will be stripped
		xtypes = xtypes.replace('component/box/container/zarafa.contentpanel','');

		// Then the "zarafa." will be stripped off from all the xtypes like "zarafa.somecontentpanel".
		xtypes = xtypes.replace(/\/zarafa\./g,'.');

		// Finally we strip string the "contentpanel" from all the xtypes. Otherwise each level will have
		// that "contentpanel" mentioned in them. Also we add contentpanel to the start as that sets
		// it apart from other components in the key mapping.
		xtypes = 'contentpanel' + xtypes.replace(/contentpanel/g, '');

		// This will activate keymaps with 'contentpanel.some.somemore' so the component
		// will have all key events register with 'contentpanel', 'some', 'somemore'
		Zarafa.core.KeyMapMgr.activate(this, xtypes);
	},

	/**
	 * This will initialize the {@link Ext.Container container} which is
	 * placed within the {@link Zarafa.core.ui.ContentPanel ContentPanel}. The
	 * {@link Ext.Container container} will receive a reference to the
	 * {@link Zarafa.core.ui.ContentPanel ContentPanel} into which it has been embedded.
	 * @param {Ext.Component} component The component which must be initialized
	 * @private
	 */
	initializeChildComponent: function(component)
	{
		// Empty objects (undefined, null, []) cannot be initialized.
		if (Ext.isEmpty(component)) {
			return;
		}

		// If it is an array, just recursively call
		// this function again for each individual item.
		var recursive = function(c) {
			this.initializeChildComponent(c);
		};

		if (Array.isArray(component)) {
			Ext.each(component, recursive, this);
			return;
		} else if (component instanceof Ext.util.MixedCollection) {
			component.each(recursive, this);
			return;
		}

		// We only initialize containers (and their subclasses).
		if (!component.isXType('container')) {
			return;
		}

		component.dialog = this;

		// Toolbars include the top- and bottomtoolbars
		if (!Ext.isEmpty(component.toolbars)) {
			this.initializeChildComponent(component.toolbars);
		}

		// Initialize all child items
		if (!Ext.isEmpty(component.items)) {
			this.initializeChildComponent(component.items);
		}
	},

	/**
	 * Closes the panel. Destroys all child components, rendering the panel unusable.
	 */
	close: function()
	{
		if (this.fireEvent('beforeclose', this) !== false) {
			this.doClose();
		}
	},

	/**
	 * Close handler for the {@link #close} function.
	 * @private
	 */
	doClose: function()
	{
		this.fireEvent('close', this);
		Zarafa.core.data.ContentPanelMgr.unregister(this);
	},

	/**
	 * Function will be called when {@link Ext.Window#closeAction} config will be used to
	 * close the window. This is hardcoded to close the dialog instead of hiding it to make sure that
	 * we fire proper events to notify dialog is closed.
	 * @protected
	 */
	closeWrap: function()
	{
		this.close();
	},

	/**
	 * Sets the title of the panel.
	 * @param {String} title the new window title.
	 */
	setTitle: function(title)
	{
		this.title = title;
		this.fireEvent('titlechange', this, title);
	},

	/**
	 * @param {String} iconCls Icon class of the panel
	 */
	setIcon: function(iconCls)
	{
		var oldIcon = this.iconCls;
		this.iconCls = iconCls;
		this.fireEvent('iconchange', this, iconCls, oldIcon);
	},

	/**
	 * @return {Boolean} true iff the content panel is a modal dialog
	 */
	isModal: function()
	{
		return this.modal;
	},

	/**
	 * Obtain the path in which the {@link #getState state} must be saved.
	 * This option is only used when the {@link Zarafa.core.data.SettingsStateProvider SettingsStateProvider} is
	 * used in the {@link Ext.state.Manager}. This returns {@link #statefulName} if provided, or else generates
	 * a custom name.
	 * @return {String} The unique name for this component by which the {@link #getState state} must be saved.
	 */
	getStateName: function()
	{
		return 'dialogs/' + Zarafa.core.ui.ContentPanel.superclass.getStateName.call(this);
	},

	/**
	 * When {@link #stateful} the State object which should be saved into the
	 * {@link Ext.state.Manager}.
	 * @return {Object} The state object
	 * @protected
	 */
	getState: function()
	{
		var state = Zarafa.core.ui.ContentPanel.superclass.getState.call(this) || {};
		return Ext.apply(state, this.getSize());
	}
});

Ext.reg('zarafa.contentpanel', Zarafa.core.ui.ContentPanel);
Ext.namespace('Zarafa.core.ui');
/**
 * @class Zarafa.core.ui.ContextNavigationPanel
 * @extends Ext.Panel
 * @xtype zarafa.contextnavigation
 *
 * ContextNavigationPanel provides custom navigation options to context through {@link Zarafa.hierarchy.ui.HierarchyTreePanel}.
 */
Zarafa.core.ui.ContextNavigationPanel = Ext.extend(Ext.Panel, {
	/**
	 * For this Context this panel will be visible in the {@link Zarafa.core.ui.NavigationPanel NavigationPanel}.
	 * @cfg {Zarafa.core.Context} Related Context
	 */
	context: null,

	/**
	 * @cfg {Boolean} true to avoid to {@link Zarafa.core.ui.NavigationPanel#getAllFoldersPanel show all folders} Panel.
	 * even if {@link Zarafa.core.ui.NavigationPanel#showFolderList showFolderList} config is true
	 */
	restrictToShowAllFolderList: false,

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

		// Config options for component itself.
		Ext.applyIf(config, {
			border: false,
			layout: 'fit',
			defaults: {
				border: false,
				autoScroll: false,
				defaults: { cls: 'zarafa-context-navigation-item-body' }
			}
		});

		Zarafa.core.ui.ContextNavigationPanel.superclass.constructor.call(this, config);
	},

	/**
	 * @return {Zarafa.core.Context}
	 */
	getContext: function() {
		return this.context || false;
	}
});

Ext.reg('zarafa.contextnavigation', Zarafa.core.ui.ContextNavigationPanel);
Ext.namespace('Zarafa.core.ui');

/**
 * @class Zarafa.core.ui.MainContentTabPanel
 * @extends Ext.TabPanel
 * @xtype zarafa.maincontenttabpanel
 * This subclass is used in the main content area, and contains the ContextContainer and dialogs that the user opens
 * Initialized in MainViewport.CreateContentContainer
 */
Zarafa.core.ui.MainContentTabPanel = Ext.extend(Ext.TabPanel, {

	/**
	 * The list of opened tab. It contains the list with the entryid and tab id.
	 * @property
	 * @type Ext.util.MixedCollection
	 * @private
	 */
	openedTabs: new Ext.util.MixedCollection(),

	/**
	 * Overridden to modify the tab depending on whether the record has been edited or not
	 * This method is called when a contained component fires the 'titlechange' event
	 * @param {Component} item
	 * @param {String} title
	 * @override
	 * @private
	 */
	onItemTitleChanged: function(item, title, oldTitle)
	{
		var el = this.getTabEl(item);
		if (el) {
			// now elide title if it exceeds 20 character length
			var tab = Ext.get(el).child('.x-tab-strip-text', true);

			// Change tooltip, and give full title in tab tool tip
			tab.qtip = title;

			tab.innerHTML = Ext.util.Format.htmlEncodeElide(title, 20, 0);
		}
	},

	/**
	 * This method is called when a contained component fires the 'iconchange' event
	 * @param {Component} item
	 * @param {String} iconCls icon class to apply
	 * @param {String} oldCls The previous icon class
	 * @private
	 */
	onItemIconChanged: function(item, iconCls, oldCls)
	{
		var tabEl = this.getTabEl(item);
		if (!Ext.isEmpty(tabEl)) {
			// removeClass only works when the CSS classes have been split
			// into an array, as it will not do it manually. For addClass,
			// we do not have this restriction.
			if (oldCls) {
				oldCls = oldCls.split(' ');
			}

			var tabText = Ext.get(tabEl).child('.x-tab-strip-text');
			if (iconCls) {
				tabText.addClass('x-tab-strip-icon');
				tabText.replaceClass(oldCls, iconCls);
			} else {
				tabText.removeClass('x-tab-strip-icon');
				tabText.removeClass(oldCls);
			}
		}
	},

	/**
	 * This method is called when contained component fires the 'userupdaterecord' event
	 * @param {Component} item The item which fired the event
	 * @param {Ext.data.Record} record Which was updated by the user
	 * @param {Boolean} changed True if the item has been changed by the user
	 * @private
	 */
	onItemUserUpdateRecord: function(item, record, changed)
	{
		var el = this.getTabEl(item);
		if (el) {
			var tab = Ext.get(el).child('.x-tab-strip-text');
			if (record.phantom || changed) {
				tab.addClass('zarafa-tab-edited');
			} else {
				tab.removeClass('zarafa-tab-edited');
			}
		}

		// If record get saved then add record id
		if (!record.phantom && !this.getOpenedTab(record)) {
			this.registeredOpenTab(record, item.getId());
		}
	},

	/**
	 * Overridden in order to listen to close event of child component
	 * @param {Component} item
	 * @param {Number} index
	 * @override
	 * @private
	 */
	initTab: function(item, index)
	{
		var title = item.title;
		if(!Ext.isEmpty(title)) {
			// provide a tooltip for tab titles
			item.tabTip = title;
			// now we can shorten the length of title if its exceeding 20 characters
			item.title = Ext.util.Format.htmlEncodeElide(title, 20, 0);
		}

		Zarafa.core.ui.MainContentTabPanel.superclass.initTab.call(this, item, index);

		item.on({
			scope: this,
			render: this.applyTooltip,
			iconchange: this.onItemIconChanged,
			userupdaterecord: this.onItemUserUpdateRecord,
			close: this.onTabClose
		});
	},

	/**
	 * This will apply tooltip on close button ('X') of {@link Ext.Panel Panel}.
	 * @param {Component} item.
	 */
	applyTooltip: function(item)
	{
		var el = item.tabEl;
		var closeTab = Ext.get(el).child('.x-tab-strip-close', true);
		if(closeTab) {
			closeTab.qtip = _('Close') + Zarafa.core.KeyMapMgr.formatShortcutHint('Ctrl + Alt + W', false);
		}
	},

	/**
	 * Handler for closing a tab when a child component has fired its close event
	 * For instance when a mail is sent, the MailCreateContentPanel needs to be closed
	 * @param {Component} tab
	 */
	onTabClose: function(tab)
	{
		// The tab's own close lifecycle already handled beforeclose/confirm logic.
		// Avoid a second beforeclose veto here, otherwise confirmed discard can be blocked.
		this.remove(tab);
		this.openedTabs = this.openedTabs.filterBy(function (item) {
			return item !== tab.getId();
		});
		this.fireEvent('close', tab);
	},

	/**
	 * handler for the '+' button in the tab strip
	 * adds a new item of type depending on current context
	 * @param {Ext.EventObjectImpl} e Event object
	 * @param {Element} t Event target
	 * @param {Object} o Configuration object
	 */
	onTabAddClick: function(e, t, o)
	{
		var model = container.getCurrentContext().getModel();

		if(model){

			if(model.createRecord === Ext.emptyFn) {
				//if unable to create record from the current context model, try to get the model based on the scope of the mainToolbar button
				var button = container.getMainPanel().mainToolbar.newButton;
				model = button.scope.model;
			}

			if (model.checkCreateRights === Ext.emptyFn) {
				var record = model.createRecord();
				if (!record) {
					//if unable to create record from the current context model, invoke the handler of the first item in the 'new' menu
					var button = container.getMainPanel().mainToolbar.newButton;
					button.handler.call(button.scope);
					return;
				} else {
					// This will always use tab layer only, no matter what layer is configured in settings
					Zarafa.core.data.UIFactory.openCreateRecord(record, {layerType: 'tab'});
				}
			} else {
				model.createRecord(function(record){
					if (!record) {
						//if unable to create record from the current context model, invoke the handler of the first item in the 'new' menu
						var button = container.getMainPanel().mainToolbar.newButton;
						button.handler.call(button.scope);
						return;
					} else {
						// This will always use tab layer only, no matter what layer is configured in settings
						Zarafa.core.data.UIFactory.openCreateRecord(record, {layerType: 'tab'});
					}
				}.createDelegate(this));
			}
		}
	},

	/**
	 * Overridden in order to add the '+' button to the edge of the tabstrip
	 * @param {Ext.Element} ct Container in which the panel is created
	 * @param {Element} position Element at which the panel is created (relative to its position)
	 * @override
	 */
	onRender: function(ct, position)
	{
		Zarafa.core.ui.MainContentTabPanel.superclass.onRender.call(this, ct, position);

		// insert add button into the edge element
		var edge = this.edge.update('<span id="zarafa-mainpanel-addtabbutton" class=\'x-tab-add\' role="button" tabindex="0" aria-label="' + _('New item') + '"></span>');
		this.mon(edge, 'click', this.onTabAddClick, this);
		this.mon(edge, 'keydown', function(e) {
			if (e.getKey() === e.ENTER || e.getKey() === e.SPACE) {
				e.stopEvent();
				this.onTabAddClick(e);
			}
		}, this);

		// set tooltip on add button
		var addBtn = edge.child('.x-tab-add', true);
		addBtn.qtip = _('New item') + Zarafa.core.KeyMapMgr.formatShortcutHint('Ctrl + Alt + N', false);
	},

	/**
	 * Overridden in order to call close() on the item, instead of removing it immediately
	 * This allows the contained panel to fire a confirmation dialog
	 * @param {Ext.EventObjectImpl} e Event
	 * @private
	 * @override
	 */
	onStripMouseDown: function(e)
	{
		if (e.button !== 0) {
			return;
		}

		var target = this.findTargets(e);
		if (target.close) {
			target.item.close();
			return;
		}
		if (target.item && target.item != this.activeTab) {
			this.setActiveTab(target.item);
		}
	},

	/**
	 * Overridden so that '+' sign for adding tabs remains visible when there are scroll buttons
	 * @private
	 * @override
	 */
	getScrollWidth: function()
	{
		return this.edge.getOffsetsTo(this.stripWrap)[0] + this.getScrollPos() + this.edge.getWidth();
	},

	/**
	 * Fires before Ext.Component is added or inserted into the Zarafa.core.ui.MainContentTabPanel
	 * It will verify if the record is already opened in tab. If yes then moved focus on that tab,
	 * Otherwise add tab id into openedTabs.
	 * @param {Component} tab
	 */
	onBeforeAdd: function (tab)
	{
		if (tab.name === "main.content" || !tab.dialog) {
			return;
		}

		var record = tab.dialog.record;
		if (!Ext.isEmpty(record) && !Ext.isEmpty(record.get("entryid"))) {
			var openedTab = this.getOpenedTab(record);
			if (openedTab) {
				container.getTabPanel().setActiveTab(openedTab);
				return false;
			} else {
				this.registeredOpenTab(record,tab.getId());
			}
		}
	},

	/**
	 * Register a newly created tab with the {@link #openedTabs}.
	 * If record is {@link Zarafa.core.data.AppointmentRecord AppointmentRecord} recurring occurrence appointment then
	 * append basedate with entryid.
	 *
	 * @param {Ext.data.Record} record
	 * @param {String} tabId id of tab component.
	 */
	registeredOpenTab: function (record, tabId)
	{
		var entryid = record.get("entryid");

		// If record is attachment record then take id instead of entryid because
		// Attachment record id contains entryid with attach_num
		if (Array.isArray(record.get('attach_num'))) {
			entryid = record.id;
		} else if (!Ext.isEmpty(record.get("basedate"))) {
			entryid += "_" + record.get('basedate').getTime();
		}
		this.openedTabs.add(entryid, tabId);
	},

	/**
	 * Function which is use to find tab id of given record.
	 * @param {Zarafa.core.data.MAPIRecord} record
	 * @returns {String | Boolean} Tab id if record is already opened, false otherwise.
	 */
	getOpenedTab: function (record)
	{
		return this.openedTabs.find(function (key, item) {
			if (!Ext.isEmpty(record.get("basedate"))) {
				if (item.indexOf(record.get("basedate").getTime()) > -1) {
					item = item.split("_")[0];
				} else {
					item = false;
				}
			}
			var entryid = record.get("entryid");

			// If record is attachment record then take id instead of entryid because
			// Attachment record id contains entryid with attach_num
			if (Array.isArray(record.get('attach_num'))) {
				entryid = record.id;
			}

			if (Zarafa.core.EntryId.compareEntryIds(item, entryid)) {
				return true;
			}
		});
	}
});

Ext.reg('zarafa.maincontenttabpanel', Zarafa.core.ui.MainContentTabPanel);
Ext.namespace('Zarafa.core.ui');

/**
 * @class Zarafa.core.ui.MainTab
 * @extends Ext.Toolbar.Item
 * @xtype zarafa.maintab
 *
 * Is used to render the tabs in the {@link Zarafa.core.ui.MainTabBar MainTabBar}. It will relate to
 * a context and based on what is the active context according to the
 * {@link Zarafa.core.Container Container} it will mark itself as active.
 */
Zarafa.core.ui.MainTab = Ext.extend( Ext.Button, {
	/**
	 * @cfg {String} context Holds the name of the {@link Zarafa.core.Context Context} that this tab is related to.
	 */
	context: null,

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

		Ext.applyIf(config, {
			// Override from Ext.Component
			xtype: 'zarafa.maintab',
			cls: 'zarafa-maintab',
			buttonActiveCls: 'zarafa-maintabbar-maintab-active',
			handler: this.selectContext
		});

		Zarafa.core.ui.MainTab.superclass.constructor.call(this, config);

		this.mon(container, 'contextswitch', this.onContextSwitch, this);
		this.on('render', this.onRenderButton, this);
	},

	/**
	 * When switching to another context the state of the tab is re-evaluated.
	 * @private
	 */
	onContextSwitch: function(parameters, oldContext, newContext)
	{
		this.setContextActivityState(newContext);
	},

	/**
	 * When the tab is rendered the correct state is set.
	 * @private
	 */
	onRenderButton: function()
	{
		this.setContextActivityState(container.getCurrentContext());
	},

	/**
	 * Set the state of the tab based on the what the currently active context is. It will add the
	 * {@link #buttonActiveCls} as CSS class for the tab that is related to the active context. It
	 * will be removed if the related context is not active.
	 * @param {Zarafa.core.Context} currentContext The current context
	 * @private
	 */
	setContextActivityState: function(currentContext)
	{
		if(this.context == currentContext.getName()){
			this.addClass(this.buttonActiveCls);
			if (this.el) {
				this.el.set({ 'aria-current': 'page' });
			}
		} else {
			this.removeClass(this.buttonActiveCls);
			if (this.el) {
				this.el.dom.removeAttribute('aria-current');
			}
		}
	},


	/**
	 * Selects the context that this tab is related to set by the {@link #context} property. It will
	 * grab the {@link Zarafa.core.ContextModel ContextModel} and retrieve the default folder if the
	 * ContextModel exists. The default folder is opened for that context. The navigation panel is
	 * also just showing the folders related to that context.
	 * @private
	 */
	selectContext: function()
	{
		var context = container.getContextByName(this.context);

		if(context) {
			if(context === container.getCurrentContext()) {
				// if we are loading same context then don't do anything
				return;
			}

			var contextModel = context.getModel();
			var contextFolder;
			if (contextModel) {
				// Try to determine the folders, if previously
				// no folders were selected, we should select
				// the default folder.
				contextFolder = contextModel.getFolders();
				if (Ext.isEmpty(contextFolder)) {
					contextFolder = contextModel.getDefaultFolder();
				}
			}

			container.switchContext(context, contextFolder);
		}
	}
});

Ext.reg('zarafa.maintab', Zarafa.core.ui.MainTab);
Ext.namespace('Zarafa.core.ui');

/**
 * @class Zarafa.core.ui.MainTabBar
 * @extends Ext.Toolbar
 * @xtype zarafa.maintabbar
 *
 * The MainTabBar shows the tabs at the top of the application. It can be filled by two insertion
 * points that populate the left and the right side of the bar. It will use instances of the
 * {@link Zarafa.core.ui.MainTab MainTab} to represent the tabs.
 */
Zarafa.core.ui.MainTabBar = Ext.extend(Ext.Toolbar, {
	// Insertion points for this class
	/**
	 * @insert main.maintabbar.left
	 * Insertion point for populating main tabbar to the left. The tabOrderIndex is used to
	 * determine the order of the tabs. The lower the number the more to the left the button is
	 * added.
	 * @param {Zarafa.core.ui.MainTabBar} toolbar This toolbar
	 */
	/**
	 * @insert main.maintabbar.right
	 * Insertion point for populating main tabbar to the right. The tabOrderIndex is used to
	 * determine the order of the tabs. The lower the number the more to the right the button is
	 * added.
	 * @param {Zarafa.core.ui.MainTabBar} toolbar This toolbar
	 */

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

		Ext.applyIf(config, {
			// Override from Ext.Component
			xtype: 'zarafa.maintabbar',
			id: 'zarafa-mainmenu',
			cls: 'zarafa-maintabbar',
			defaultType: 'zarafa.maintab',
			ariaLabel: _('Main navigation')
		});

		Zarafa.core.ui.MainTabBar.superclass.constructor.call(this, config);

		this.initBar();
	},

	/**
	 * Add items to this toolbar by using the main.maintabbar.left and main.maintabbar.right
	 * insertion points. Also the text who is logged in and the log out button is added. The buttons
	 * added through the insertion points are sorted using the tabOrderIndex set on the objects in
	 * the list returned by the insertion point.
	 * @private
	 */
	initBar: function()
	{

		var leftItems = container.populateInsertionPoint('main.maintabbar.left', this) || [];
		var rightItems = container.populateInsertionPoint('main.maintabbar.right', this) || [];

		// Make sure the items are properly sorted by priority.
		leftItems = Zarafa.core.Util.sortArray(leftItems, 'ASC', 'tabOrderIndex');
		// The right items are sorted so that the first item appears to the most right
		rightItems = Zarafa.core.Util.sortArray(rightItems, 'DESC', 'tabOrderIndex');

		this.addTooltip(leftItems, rightItems);

		var loginText = {
				xtype: 'tbtext',
				width: 'auto',
				cls: 'zarafa-maintabbar-logintext',
				text: container.getUser().getDisplayName(),
				id: 'mainmenu-logintext'
		};

		// Adding reminder button with bell icon.
		var reminder = {
			width: 30,
			id: 'mainmenu-button-reminder',
			ref: 'reminder',
			tooltip: _('Reminders'),
			handler: function() {
				var store = container.getReminderStore();
				Zarafa.common.Actions.openReminderContent(store.getRange());
			},
			listeners: {
				afterRender: function(reminderBtn) {
					var store = container.getReminderStore();
					var recordLength = store.getRange().length;
					reminderBtn.getEl().setStyle('backgroundImage', 'url(\'' + Zarafa.common.ui.IconClass.getReminderSvgIcon( recordLength ) + '\')');
					var noReminder = recordLength === 0;
					reminderBtn.setDisabled(noReminder);
					reminderBtn.setTooltip(noReminder? _('There are no reminders'): '');
				},
				scope: this
			},
			style: {
				backgroundImage: 'url(\'' + Zarafa.common.ui.IconClass.getReminderSvgIcon() + '\')',
				backgroundRepeat: 'no-repeat',
				backgroundPosition: 'center'
			},
			scope: this
		};

		var darkModeSwitcher = this.createDarkModeSwitcher();

		this.add(leftItems, {xtype: 'tbfill'}, loginText, reminder, darkModeSwitcher, rightItems);

		// Don't show the logout button when using SSO or if it's disabled in the config, but always show it in DeskApp
		if ( !(container.getServerConfig().usingSSO() || !container.getServerConfig().showLogoutButton()) || Zarafa.isDeskApp ){
			var logoutButton = {
				text: _('Logout'),
				handler: this.onLogoutButton,
				id: 'mainmenu-button-logout'
			};

			this.add(logoutButton);
		}
	},

	/**
	 * Create an SVG data URI for the dark mode switcher icon.
         * Icons are optimized, secondary pass not required
	 * @param {String} mode One of 'light', 'dark', 'system'
	 * @return {String} SVG data URI
	 * @private
	 */
	getDarkModeIcon: function(mode)
	{
		var svg;
		if (mode === 'light') {
			// Sun icon
			svg = '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24"><circle cx="12" cy="12" r="5"/><path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/></svg>';
		} else if (mode === 'dark') {
			// Moon icon
			svg = '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79"/></svg>';
		} else {
			// Auto icon: sun-moon combo
			svg = '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24"><path d="M12 3V1m0 22v-2m9-9h2M1 12h2m15.78-7.78L20.2 2.8M4.22 19.78l1.42-1.42m12.72 1.42 1.42 1.42M4.22 4.22l1.42 1.42"/><circle cx="12" cy="12" r="4"/><path fill="#fff" stroke="none" d="M12 8a4 4 0 0 1 0 8z"/></svg>';
		}
		return 'data:image/svg+xml;charset=utf8,' + encodeURIComponent(svg);
	},

	/**
	 * Get the tooltip text for the dark mode switcher.
	 * @param {String} mode One of 'light', 'dark', 'system'
	 * @return {String} tooltip text
	 * @private
	 */
	getDarkModeTooltip: function(mode)
	{
		switch (mode) {
			case 'light': return _('Appearance') + ': ' + _('Light');
			case 'dark': return _('Appearance') + ': ' + _('Dark');
			default: return _('Appearance') + ': ' + _('Automatic');
		}
	},

	/**
	 * Create the dark mode 3-way switcher button config.
	 * Cycles through light -> system -> dark -> light.
	 * @return {Object} Ext button config
	 * @private
	 */
	createDarkModeSwitcher: function()
	{
		var currentMode = Zarafa.core.DarkMode.getMode();
		return {
			width: 30,
			id: 'mainmenu-button-darkmode',
			tooltip: this.getDarkModeTooltip(currentMode),
			handler: function() {
				var mode = Zarafa.core.DarkMode.getMode();
				// Cycle: light -> system -> dark -> light
				if (mode === 'light') {
					mode = 'system';
				} else if (mode === 'system') {
					mode = 'dark';
				} else {
					mode = 'light';
				}
				Zarafa.core.DarkMode.setMode(mode);
			},
			style: {
				backgroundImage: 'url(\'' + this.getDarkModeIcon(currentMode) + '\')',
				backgroundRepeat: 'no-repeat',
				backgroundPosition: 'center'
			}
		};
	},

	/**
	 * Used to apply key shortcut and context name in tooltip.
	 * @param {Array} leftItems The leftItems contains the left context items which are properly sorted by priority.
	 * @param {Array} rightItems The rightItems contains the right context items.
	 */
	addTooltip: function(leftItems, rightItems)
	{
		var contextItems = [];
		contextItems = contextItems.concat(leftItems, rightItems);
		Ext.each(contextItems, function(context, index) {
			context.tooltip = context.text + Zarafa.core.KeyMapMgr.formatShortcutHint('Ctrl + '+index, false);
		});
	},

	/**
	 * Event handler which is called when the user presses the 'logout' button
	 * @private
	 */
	onLogoutButton: function()
	{
		container.logout();
	}
});

Ext.reg('zarafa.maintabbar', Zarafa.core.ui.MainTabBar);
Ext.namespace('Zarafa.core.ui');

/**
 * @class Zarafa.core.ui.MainViewSidebar
 * @extends Ext.Panel
 * @xtype mainviewsidebar
 *
 * The sidebars for the {@link Zarafa.core.ui.MainViewport Main Viewport}
 * which support {@link #collapse collapsing} and {@link #stateful}.
 */
Zarafa.core.ui.MainViewSidebar = Ext.extend(Ext.Panel, {

	/**
	 * {@link Ext.QuickTips tooltip} message for {@link Zarafa.core.ui.MainViewSidebar panel}
	 * collapse {@link Ext.Button button}
	 * @property
	 * @type String
	 */
	collapseQuickTip: undefined,

	/**
	 * {@link Ext.QuickTips tooltip} message for {@link Zarafa.core.ui.MainViewSidebar panel}
	 * expand {@link Ext.Button button}
	 * @property
	 * @type String
	 */
	expandQuickTip: undefined,

	/**

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

		config.plugins = Ext.value(config.plugins, []);

		Ext.applyIf(config, {
			cls: 'zarafa-context-mainpanel',
			border: false,
			collapsible: true,
			collapsed: false,
			split: true,
			width: 300,
			minSize: 150,
			// Persist user-driven splitbar width and collapse changes.
			stateEvents: ['resize', 'collapse', 'expand']
		});

		Zarafa.core.ui.MainViewSidebar.superclass.constructor.call(this, config);
	},

	/**
	 * Called during rendering of the panel, this will initialize all events.
	 * @private
	 */
	initEvents: function ()
	{
		this.on('afterlayout', this.onAfterLayoutPanel, this, {single: true});
		this.on('collapse', this.onBeforeCollapsePanel, this, {single: true});
	},

	/**
	 * Event handler which is called after {@link Zarafa.core.ui.MainViewSidebar panel} get layout
	 * This will set {@link Ext.QuickTips} on {@link Ext.Button collapse} button
	 * @private
	 */
	onAfterLayoutPanel: function ()
	{
		if (this.collapseQuickTip) {
			Ext.QuickTips.register({
				target: this.tools.toggle,
				text: this.collapseQuickTip
			});
		}
	},

	/**
	 * Event handler which is called before {@link Zarafa.core.ui.MainViewSidebar panel} collapse
	 * This will set {@link Ext.QuickTips} on {@link Ext.Button expand} button
	 * @private
	 */
	onBeforeCollapsePanel: function ()
	{
		if (this.expandQuickTip) {
			Ext.QuickTips.register({
				target: Ext.get(this.id + '-xcollapsed').first(),
				text: this.expandQuickTip
			});
		}
	},

	/**
	 * Obtain the path in which the {@link #getState state} must be saved.
	 * This option is only used when the {@link Zarafa.core.data.SettingsStateProvider SettingsStateProvider} is
	 * used in the {@link Ext.state.Manager}. This returns {@link #statefulName} if provided, or else generates
	 * a custom name.
	 * @return {String} The unique name for this component by which the {@link #getState state} must be saved.
	 */
	getStateName: function()
	{
		return 'sidebars/' + Zarafa.core.ui.MainViewSidebar.superclass.getStateName.call(this);
	}
});

Ext.reg('mainviewsidebar', Zarafa.core.ui.MainViewSidebar);
Ext.namespace('Zarafa.core.ui');

/**
 * @class Zarafa.core.ui.MainViewport
 * @extends Ext.Viewport
 * The main viewport for the application. It defines the basic switching context
 * containers (toolbar, main content panel)
 */
Zarafa.core.ui.MainViewport = Ext.extend(Ext.Viewport, {
	// Insertion points for this class
	/**
	 * @insert main.content
	 * Insertion point for the main panel content area.
	 */

	/**
	 * The {@link Zarafa.core.ui.NavigationPanel NavigationPanel}.
	 * @property
	 * @type Zarafa.core.ui.NavigationPanel
	 */
	navigationPanel: undefined,

	/**
	 * The {@link Zarafa.core.ui.widget.WidgetPanel WidgetPanel}
	 * @property
	 * @type Zarafa.core.ui.widget.WidgetPanel
	 */
	widgetPanel: undefined,

	/**
	 * The {@link Zarafa.core.ui.ContextContainer ContextContainer}
	 * @property
	 * @type Zarafa.core.ui.ContextContainer
	 */
	contentPanel: undefined,

	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};
		config = Ext.applyIf(config, {
			layout: 'fit',
			items: [{
				xtype: 'container',
				id: 'zarafa-mainview',
				layout: 'border',
				cls: 'zarafa-panel-body',
				border: false,
				items: [
					this.createTopbarContainer(),
					this.createNavigationPanel(),
					this.createTodayPanel(),
					this.createContentContainer()				]
			}]
		});

		// initialise the viewport with some pre-defined settings
		Zarafa.core.ui.MainViewport.superclass.constructor.call(this, config);

		// Add skip-to-content link for keyboard accessibility (WCAG 2.4.1)
		this.on('afterrender', function() {
			// Set landmark roles on rendered elements
			var mainView = Ext.getCmp('zarafa-mainview');
			if (mainView && mainView.getEl()) {
				mainView.getEl().set({ 'role': 'application', 'aria-label': _('grommunio Web') });
			}

			var skipLink = Ext.DomHelper.insertFirst(document.body, {
				tag: 'a',
				href: '#',
				cls: 'sr-only',
				html: _('Skip to main content'),
				style: 'z-index:99999;'
			}, true);
			skipLink.on('focus', function() {
				this.setStyle({
					position: 'fixed', top: '5px', left: '5px',
					width: 'auto', height: 'auto', clip: 'auto',
					'white-space': 'normal', overflow: 'visible',
					padding: '8px 16px', background: '#fff', color: '#1d2939',
					'font-size': '14px', 'font-weight': '600',
					'border-radius': '4px', 'box-shadow': '0 2px 8px rgba(0,0,0,0.2)',
					margin: '0'
				});
			});
			skipLink.on('blur', function() {
				this.setStyle({
					position: 'absolute', width: '1px', height: '1px',
					clip: 'rect(0,0,0,0)', 'white-space': 'nowrap',
					overflow: 'hidden', padding: '0', margin: '-1px'
				});
			});
		}, this, { single: true });

		// Activate global key events.
		Zarafa.core.KeyMapMgr.activate(null, 'global', Ext.getBody());
		// Don't allow propagation of all other key events which are registered in KeyMapMgr.
		Zarafa.core.KeyMapMgr.activate(null, 'globaldisable', Ext.getBody());
	},

	/**
	 * Create the {@link Zarafa.core.ui.NavigationPanel NavigationPanel} for
	 * the west region of the client in which the treePanel can be shown.
	 * @return {Zarafa.core.ui.NavigationPanel} NavigationPanel.
	 * @private
	 */
	createNavigationPanel: function()
	{
		this.navigationPanel = new Zarafa.core.ui.NavigationPanel({
			region: 'west',
			stateful: true,
			statefulName: 'hierarchybar',
			statefulRelativeDimensions: true
		});
		return this.navigationPanel;
	},

	/**
	 * Returns the {@link Zarafa.core.ui.NavigationPanel NavigationPanel} for
	 * the west region of the client in which the treePanel can be shown.
	 * @return {Zarafa.core.ui.NavigationPanel} NavigationPanel.
	 */
	getNavigationPanel: function()
	{
		return this.navigationPanel;
	},

	/**
	 * Create the {@link Zarafa.core.ui.widget.WidgetPanel WidgetPanel} for
	 * the east region of the client in which the Today view can be shown.
	 * @return {Object} Configuration object for the WidgetPanel.
	 * @private
	 */
	createTodayPanel: function()
	{
		var hide = container.getServerConfig().isWidgetEnabled() ? container.getSettingsModel().get('zarafa/v1/widgets/sidebar/hide_widgetpanel', true, false) : true;
		this.widgetPanel = new Zarafa.core.ui.widget.WidgetPanel({
			region: 'east',
			title: _('Widgets'),
			numColumns: 1,
			stateful: true,
			statefulName: 'todaybar',
			statefulRelativeDimensions: true,
			settingsPath: 'zarafa/v1/contexts/today/sidebar',
			hidden: hide,
			collapsed: true
		});
		return this.widgetPanel;
	},

	/**
	 * Returns the {@link Zarafa.core.ui.widget.WidgetPanel WidgetPanel} for
	 * the east region of the client in which the widgets can be shown.
	 * @return {Zarafa.core.ui.widget.WidgetPanel} widgetPanel
	 */
	getWidgetPanel: function()
	{
		return this.widgetPanel;
	},

	/**
	 * Create the {@link Zarafa.core.ui.ContextContainer ContextContainer} for
	 * the center region of the client in which the main contents can be shown.
	 * @return {Object} Configuration object for the ContextContainer
	 * @private
	 */
	createContentContainer: function()
	{
		var cc = new Zarafa.core.ui.ContextContainer({
			name: 'main.content',
			id: 'zarafa-mainpanel-content'
		});
		//get items from insertion point
		//TODO: create separate insertion points for front and back of tab strip?
		var lazyItems = container.populateInsertionPoint('main.content.tabpanel', this);

		this.contentPanel = new Zarafa.core.ui.MainContentTabPanel({
			id: 'zarafa-mainpanel',
			activeTab: 0,
			region: 'center',
			enableTabScroll: true,
			layoutOnTabChange: true,
			items: [ cc ].concat(lazyItems),
			plugins: [ 'zarafa.tabclosemenuplugin' ],
			cls: 'zarafa-body-tabbar',
			listeners: {
				"afterrender": function() {
					container.fireEvent('afterrendercontentpanel', this);
				}
			}
		});
		return this.contentPanel;
	},

	/**
	 * Returns the {@link Zarafa.core.ui.ContextContainer ContentPanel} for
	 * the center region of the client in which the context will display its contents
	 * @return {Zarafa.core.ui.ContextContainer} contentPanel
	 */
	getContentPanel: function()
	{
		return this.contentPanel;
	},

	/**
	 * Create a {@link Ext.Container Container} that contains the
	 * {@link Zarafa.core.ui.MainTabBar MainTabBar} and the {@link Zarafa.core.ui.MainTab MainTab}
	 * at the top of the client.
	 * @return {Ext.Container} Container holding the MainTabBar and MainTab
	 * @private
	 */
	createTopbarContainer: function()
	{
		return new Ext.Container({
			name: 'main.topbar',
			region: 'north',
			layout: 'border',
			height: 36+54,
			items: [
				new Zarafa.core.ui.MainTabBar({
					name: 'main.maintabbar',
					region: 'center',
					height: 36,
					boxMinHeight: 36,
					boxMaxHeight: 36,
					ref: '../../mainTabBar'
				}),
				new Zarafa.core.ui.MainToolbar({
					name: 'main.toolbar',
					region: 'south',
					height: 54,
					boxMinHeight: 54,
					boxMaxHeight: 54,
					ref: '../../mainToolbar'
				})
			]
		});
	}
});
Ext.namespace('Zarafa.core.ui');

/**
 * @class Zarafa.core.ui.PreviewPanel
 * @extends Ext.Panel
 * @xtype zarafa.previewpanel
 *
 * Panel that shows a preview of a {@link Zarafa.core.data.IPMRecord message}.
 */
Zarafa.core.ui.PreviewPanel = Ext.extend(Ext.Panel, {
	// Insertion points for this class
	/**
	 * @insert previewpanel.toolbar.left
	 * Insertions point for inserting buttons in previewpane's toolbar on left side.
	 * @param {Zarafa.core.ui.PreviewPanel} panel This panel
	 * @param {Zarafa.core.ContextModel} model The contextmodel ({@link #model}).
	 */

	/**
	 * @insert previewpanel.toolbar.right
	 * Insertions point for inserting buttons in previewpane's toolbar on right side.
	 * @param {Zarafa.core.ui.PreviewPanel} panel This panel
	 * @param {Zarafa.core.ContextModel} model The contextmodel ({@link #model}).
	 */

	/**
	 * @cfg {Zarafa.core.ContextModel} model The model for which this
	 * PreviewPanel is being loaded.
	 */
	model: undefined,

	/**
	 * Reference to the {@link Zarafa.core.plugins.RecordComponentPlugin RecordComponent} plugin
	 * which is used to send update events to all child {@link Ext.Container containers}
	 * in this container. This field is initialized by the
	 * {@link Zarafa.core.plugins.RecordComponentPlugin RecordComponent} itself.
	 *
	 * @property
	 * @type Zarafa.core.plugins.RecordComponentPlugin
	 */
	recordComponentPlugin: undefined,

	/**
	 * @cfg {Object} Configuration object which will be used
	 * to instantiate the {@link Zarafa.core.plugins.RecordComponentPlugin RecordComponent}.
	 * See the plugin for the available configuration options.
	 */
	recordComponentPluginConfig: undefined,

	/**
	 * @cfg {Zarafa.core.data.IPMRecord} record (See {@link Zarafa.core.plugins.RecordComponentPlugin#record}).
	 */
	record: undefined,

	/**
	 * @cfg {Boolean} isLoadMaskShown true if load mask should be shown else false.
	 */
	isLoadMaskShown: false,

	/**
	 * @cfg {Number} loadMaskDelay Delay in milliseconds before showing the loading mask.
	 * Set to 0 to show immediately.
	 */
	loadMaskDelay: 0,

	/**
	 * The LoadMask object which will be shown when the {@link #record} is being opened, and
	 * the dialog is waiting for the server to respond with the desired data. This will only
	 * be set if {@link #showLoadMask} is true.
	 * @property
	 * @type Zarafa.common.ui.LoadMask
	 */
	loadMask: undefined,

	/**
	 * Deferred task which can be used to delay showing of the load mask.
	 * @property
	 * @type Ext.util.DelayedTask
	 */
	loadMaskTask: undefined,

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

		config = Ext.applyIf(config, {
			layout: 'fit',
			stateful: true,
			stateEvents: ['resize', 'collapse', 'expand'],
			minWidth: 200,
			minHeight: 200,
			xtype: 'zarafa.previewpanel'
		});

		config.tbar = Ext.applyIf(config.tbar || {}, {
			cls: 'zarafa-previewpanel-toolbar',
			xtype: 'zarafa.toolbar',
			hidden: true,
			items: []
		});

		var tbarItems = [
			container.populateInsertionPoint('previewpanel.toolbar.left', this, config.model),
			config.tbar.items, // Default items in toolbar should be left aligned.
			{xtype: 'tbfill'},
			container.populateInsertionPoint('previewpanel.toolbar.right.first', {scope: this, model: config.model}),
			container.populateInsertionPoint('previewpanel.toolbar.right', {scope: this, model: config.model})
		];

		config.tbar.items = Ext.flatten(tbarItems);
		config.plugins = Ext.value(config.plugins, []);
		config.plugins.push(Ext.applyIf(config.recordComponentPluginConfig || {}, {
			ptype: 'zarafa.recordcomponentplugin',
			useShadowStore: true,
			allowWrite: false
		}));

		config.plugins.push({
			ptype: 'zarafa.recordcomponentupdaterplugin'
		}, {
			ptype: 'zarafa.enablefocusplugin'
		});

		if (container.getSettingsModel().get('zarafa/v1/contexts/mail/readflag_time_enable') === true) {
			config.plugins.push({
				ptype: 'zarafa.markasreadplugin'
			});
		}

		Zarafa.core.ui.PreviewPanel.superclass.constructor.call(this, config);

		if (this.model) {
			this.mon(this.model, 'previewrecordchange', this.onPreviewRecordChange, this);
		}

		this.on({
			'beforeloadrecord': this.onBeforeLoadRecord,
			'loadrecord': this.onLoadRecord,
			'exceptionrecord': this.onExceptionRecord,
			'show': this.onPreviewPanelShow,
			'expand': this.onPreviewPanelShow,
			'scope': this
		});
	},

	/**
	 * Called when the ContentPanel has been rendered.
	 * This activate the keymap on the element of this component after the normal operations of
	 * afterRender have been completed. It will activate by getting the xtype hierarchy from
	 * {@link #getXTypes} and format it into a string usable by the
	 * {@link Zarafa.core.KeyMapMgr KeyMapMgr}.
	 * @private
	 */
	afterRender: function()
	{
		Zarafa.core.ui.PreviewPanel.superclass.afterRender.apply(this, arguments);
		var xtypes = this.getXTypes();

		// The first part leading up to zarafa.previewpanel will be stripped
		xtypes = xtypes.replace('component/box/container/panel/zarafa.previewpanel','');

		// Then the "zarafa." will be stripped off from start, the xtype like "zarafa.previewpanel".
		xtypes = xtypes.replace(/\/zarafa\./g,'.');

		// Finally we string the "previewpanel" from all the xtypes. Otherwise each level will have
		// that "previewpanel" mentioned in them. Also we add previewpanel to the start as that sets
		// it apart from other components in the key mapping.
		xtypes = 'previewpanel' + xtypes.replace(/previewpanel/g, '');

		// This will activate keymaps with 'previewpanel.mail' so the component
		// will have all key events register with 'previewpanel', 'mail'
		Zarafa.core.KeyMapMgr.activate(this, xtypes);
	},

	/**
	 * See {@link Zarafa.core.plugins.RecordComponentPlugin#setRecord}.
	 *
	 * @param {Zarafa.core.data.MAPIRecord} record The record to set
	 */
	setRecord: function(record)
	{
		for (var i = 0; i < this.toolbars.length; i++) {
			this.toolbars[i].setVisible(!!record);
		}

		if (this.recordComponentPlugin) {
			this.recordComponentPlugin.setRecord(record);
		}

		// Remove the update handler of the record in the preview panel
		// before we set a new one
		if ( this.record && this.record.store ){
			this.record.store.un('update', this.updatePreviewPanel, this);
		}
		if ( record && record.store ){
			record.store.on('update', this.updatePreviewPanel, this);
		}
	},

	/**
	 * Updates the categories in the preview panel when the data of the record changes.
	 *
	 * @param {Zarafa.core.data.IPMStore} store The store to which the record belongs
	 * that is shown in the preview panel
	 * @param {Zarafa.core.data.IPMRecord} record The Record that was updated
	 */
	updatePreviewPanel: function(store, record)
	{
		// Only update when we are showing a record in the preview panel
		if ( this.record && record.equals(this.record) ){
			this.record.set('categories', record.get('categories'));
			this.doLayout();
		}
	},

	/**
	 * Update the components with the given record.
	 *
	 * @param {Zarafa.core.data.MAPIRecord} record The record to update in this component
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 */
	update: function(record, contentReset)
	{
		this.record = record;
	},

	/**
	 * Function for 'previewrecordchange' and 'show' events before setting record into component
	 * @param {Zarafa.core.data.MAPIRecord} record
	 * @private
	 */
	showRecordInPanel: function(record)
	{
		var panelConstructor;

		// Record is already loaded...
		if (this.record === record) {
			return;
		}

		// Check if same record is loaded again
		if (this.record && record && this.record.equals(record)) {
			return;
		}

		if (Ext.isDefined(record)) {
			// TODO: Find a proper way to suppress exception popup
			record.suppressException();
			panelConstructor = container.getSharedComponent(Zarafa.core.data.SharedComponentType['common.preview'], record);
			if (panelConstructor && this.get(0) instanceof panelConstructor) {
				if(this.isLoadMaskShown){
					this.hideLoadMask();
				}
				this.setRecord(record);
				return;
			}
		}

		// Do a complete refresh, clearing all UI components
		// which might be active inside the panel.
		this.removeAll();

		if (panelConstructor) {
			this.add(new panelConstructor());
		}

		this.setRecord(record);
		this.doLayout();
		this.hideLoadMask();
	},

	/**
	 * Removes all components from this container. Additionally this function will also remove all the items from
	 * {@link Ext.Toolbar toolbar} also.
	 * @param {Boolean} autoDestroy (optional) True to automatically invoke the removed Component's {@link Ext.Component#destroy} function.
	 * Defaults to the value of this Container's {@link #autoDestroy} config.
	 * @return {Array} Array of the destroyed components
	 */
	removeAll: function(autoDestroy)
	{
		// remove toolbar items first
		var destroyedItems = [];
		if(this.getTopToolbar()) {
			destroyedItems.concat(this.getTopToolbar().removeAll.apply(this, arguments));
		}

		if(this.getBottomToolbar()) {
			destroyedItems.concat(this.getBottomToolbar().removeAll.apply(this, arguments));
		}

		destroyedItems.concat(Zarafa.core.ui.PreviewPanel.superclass.removeAll.apply(this, arguments));

		return destroyedItems;
	},

	/**
	 * Event handler which is trigggerd when the {@link Zarafa.core.ContextModel ContextModel} receives a new
	 * record for previewing (it fired the {@link Zarafa.core.ContextModel#previewrecordchange previewrecordchange} event).
	 * This updates the {@link Ext.Container previewpanel} with the selected record to be previewed.
	 *
	 * @param {Zarafa.core.ContextModel} model The context model.
	 * @param {Ext.data.Record} record The record which is selected for preview.
	 * @private
	 */
	onPreviewRecordChange: function(contextModel, record)
	{
		// Load record in preview panel
		if(this.isVisible()) {
			this.showRecordInPanel(record);
		}
	},

	/**
	 * If {@link #showLoadMask} is enabled, this function will display
	 * the {@link #loadMask}.
	 * @param {Boolean} errorMask True to show an error mask instead of the loading mask.
	 * @protected
	 */
	showLoadMask: function(errorMask)
	{
		this.cancelLoadMaskTask();

		if (this.isLoadMaskShown && !errorMask) {
			return;
		}
		if (!this.loadMask) {
			this.loadMask = new Zarafa.common.ui.LoadMask(this.el);
		}

		if (errorMask) {
			this.loadMask.showError();
		} else {
			if (this.loadMaskDelay > 0) {
				this.loadMaskTask = this.loadMaskTask || new Ext.util.DelayedTask(function() {
					if (this.isDestroyed || this.isLoadMaskShown) {
						return;
					}

					this.loadMask.show();
					this.isLoadMaskShown = true;
				}, this);
				this.loadMaskTask.delay(this.loadMaskDelay);
			} else {
				this.loadMask.show();
				this.isLoadMaskShown = true;
			}
		}
	},

	/**
	 * If {@link #showLoadMask} is enabled, and {@link #showLoadMask} has been
	 * called to display the {@link #loadMask} this function will disable the
	 * loadMask.
	 * @protected
	 */
	hideLoadMask: function()
	{
		this.cancelLoadMaskTask();

		if (this.isLoadMaskShown === false) {
			return;
		}

		if (this.loadMask) {
			this.loadMask.hide();
			this.isLoadMaskShown = false;
		}
	},

	/**
	 * Cancel any pending delayed show of the loading mask.
	 * @protected
	 */
	cancelLoadMaskTask: function()
	{
		if (this.loadMaskTask) {
			this.loadMaskTask.cancel();
		}
	},

	/**
	 * Event handler for the {@link Zarafa.core.plugins.RecordComponentPlugin#beforeloadrecord beforeloadrecord} event.
	 * This will call the {@link #showLoadMask} function to show the loadmask.
	 *
	 * @param {Ext.Container} panel The panel to which the record belongs
	 * @param {Zarafa.core.data.IPMRecord} record The record which was updated
	 * @private
	 */
	onBeforeLoadRecord: function(panel, record)
	{
		this.showLoadMask();
	},

	/**
	 * Event handler for the {@link Zarafa.core.plugins.RecordComponentPlugin#loadrecord loadrecord} event.
	 * This will call the {@link #hideLoadMask} function to hide the loadmask.
	 *
	 * @param {Ext.Container} panel The panel to which the record belongs
	 * @param {Zarafa.core.data.IPMRecord} record The record which was updated
	 * @private
	 */
	onLoadRecord: function(panel, record)
	{
		this.hideLoadMask();
	},

	/**
	 * Event handler for the {@link Zarafa.core.plugins.RecordComponentPlugin#exceptionrecord} event.
	 * This will call {@link #showLoadMask} to update it with a new error message.
	 *
	 * @param {String} type See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception}
	 * for description.
	 * @param {String} action See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception}
	 * for description.
	 * @param {Object} options See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception}
	 * for description.
	 * @param {Object} response See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception}
	 * for description.
	 * @param {Zarafa.core.data.MAPIRecord} record The record which was subject of the request
	 * that encountered an exception.
	 * @param {String} error (Optional) Passed when a thrown JS Exception or JS Error is
	 */
	onExceptionRecord: function(type, action, options, response, record)
	{
		this.showLoadMask(true);
	},

	/**
	 * Event handler which is fired when this panel has been set to visible. Fetches previewRecord from
	 * the {@link Zarafa.core.ContextModel ContextModel} and displays into panel.
	 */
	onPreviewPanelShow: function()
	{
		if (Ext.isDefined(this.model)) {
			var record = this.model.getPreviewRecord();
			if (Ext.isDefined(record)) {
				this.showRecordInPanel(record);
			}
		}
	},

	beforeDestroy: function()
	{
		this.cancelLoadMaskTask();
		Zarafa.core.ui.PreviewPanel.superclass.beforeDestroy.apply(this, arguments);
	},

	/**
	 * Obtain the path in which the {@link #getState state} must be saved.
	 * This option is only used when the {@link Zarafa.core.data.SettingsStateProvider SettingsStateProvider} is
	 * used in the {@link Ext.state.Manager}. This returns {@link #statefulName} if provided, or else generates
	 * a custom name.
	 * @return {String} The unique name for this component by which the {@link #getState state} must be saved.
	 */
	getStateName: function()
	{
		return 'preview/' + Zarafa.core.ui.PreviewPanel.superclass.getStateName.call(this);
	}
});
Ext.reg('zarafa.previewpanel', Zarafa.core.ui.PreviewPanel);
Ext.namespace('Zarafa.core.ui');

/**
 * @class Zarafa.core.ui.SwitchViewContentContainer
 * @extends Ext.Container
 * @xtype zarafa.switchviewcontentcontainer
 *
 * This class represents an {@link Ext.Panel panel} which contains multiple views which
 * can be enabled/disabled at any time by the user. Using lazy loading each view will only
 * be allocated when the user switches to the given view for the first time.
 */
Zarafa.core.ui.SwitchViewContentContainer = Ext.extend(Ext.Container, {
	/**
	 * @cfg {Object[]} lazyItems List of {@link Ext.Containers containers} which
	 * act as view for this {@link Ext.Container container}. This array consists of configuration
	 * objects which will be allocated when the user switches to the given view for the
	 * first time.
	 * All items added to this list must always contain the 'id' property,
	 * used in switchView. Without this property it is not possible to switch
	 * to this view.
	 */
	lazyItems: undefined,

	/**
	 * @cfg {Boolean} autoClean If true this container will automatically remove
	 * and delete the previously selected {@link Ext.Container container} when switching
	 * to a new active {@link Ext.Container container}.
	 */
	autoClean: true,

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

		// Always ensure that at least 1 item is non-lazy
		if (Ext.isEmpty(config.items) && !Ext.isEmpty(config.lazyItems)) {
			config.items = [ config.lazyItems[0] ];
		}
		// Ensure that the non-lazy item is marked as active
		if (Ext.isEmpty(config.activeItem)) {
			config.activeItem = config.items[0].id;
		}

		Ext.applyIf(config, {
			autoDestroy: true
		});

		this.addEvents(
			/**
			 * @event switchview
			 * Fires when the active view is being changed
			 * @param {Ext.Container} this The {@link Zarafa.core.ui.SwitchViewContentContainer switchcontainer}.
			 * @param {Ext.Container} newView The new {@link Ext.Container view} which is shown
			 * @param {Ext.Container} oldView The old {@link Ext.Container view} which was shown
			 */
			'switchview'
		);

		Zarafa.core.ui.SwitchViewContentContainer.superclass.constructor.call(this, config);
	},

	/**
	 * Called by Extjs when the container is being {@link #doLayout laid out}. This will obtain
	 * the {@link Ext.layout.CardLayout#activeItem} and {@link Ext.Panel#doLayout update the layout}
	 * on that component as well.
	 * @private
	 */
	onLayout: function()
	{
		Zarafa.core.ui.SwitchViewContentContainer.superclass.onLayout.apply(this, arguments);

		// If the activeItem contains a layout, it should be laid out as well
		var item = this.getActiveItem();
		if (Ext.isFunction(item.doLayout)) {
			item.doLayout();
		}
	},

	/**
	 * This function will be used to switch between different views.
	 * It will attempt to find the view within this panel, if found,
	 * then the layout manager will be updated with the new view.
	 * @param {String} viewId id of the view that should be shown
	 */
	switchView: function(viewId)
	{
		var oldView = this.getActiveItem();
		var newView = this.findById(viewId);

		if (!Ext.isDefined(newView) || oldView == newView) {
			return;
		}

		// Check if the layout has been created yet, if not
		// then we store the activeItem inside the current
		// panel so it can be applied to the layout when it
		// is being created.
		var layout = this.getLayout();
		if (!Ext.isFunction(layout.setActiveItem)) {
			this.activeItem = viewId;
		} else {
			layout.setActiveItem(viewId);
		}

		this.fireEvent('switchview', this, newView, oldView);

		// TODO: We should enable some timeout mechanism which
		// removes and deletes the oldView after a particular timeout.
		// This should increase performance when switching between 2
		// views often.
		if (this.autoClean === true && oldView && oldView != this.getActiveItem()) {
			this.remove(oldView);
			delete oldView;
		}
	},

	/**
	 * This function returns the currently active item
	 * @return {Ext.Component} The active item
	 */
	getActiveItem: function()
	{
		var layout = this.getLayout();
		if (!Ext.isFunction(layout.setActiveItem)) {
			return this.activeItem;
		} else {
			return layout.activeItem;
		}
	},

	/**
	 * Find a component under this container at any level by id.
	 * This extension will read all lazy items as well, if the required id, is
	 * one of the lazy items, then the item will be created and added to the panel.
	 * @param {String} id
	 * @return Ext.Component
	 */
	findById: function(id)
	{
		var retval = Zarafa.core.ui.SwitchViewContentContainer.superclass.findById.call(this, id);
		if (!retval) {
			retval = this.findBy(function(item) { return item.id === id; });
			if (!Ext.isEmpty(retval)) {
				retval = retval[0];
			}
		}

		return retval;
	},

	/**
	 * Find a component under this container at any level by a custom function.
	 * If the passed function returns true, the component will be included in the results.
	 * The passed function is called with the arguments (component, this container).
	 *
	 * This function will not only search through {@link #items} but also through {@link #lazyItems}.
	 *
	 * @param {Function} fn The function to call
	 * @param {Object} scope (optional)
	 * @return {Array} Array of Ext.Components
	 */
	findBy: function(fn, scope)
	{
		var retval = Zarafa.core.ui.SwitchViewContentContainer.superclass.findBy.apply(this, arguments);

		if (Ext.isDefined(this.lazyItems)) {
			for (var i = 0; i < this.lazyItems.length; i++) {
				var item = this.lazyItems[i];

				if (fn.call(scope || item, item, this) === true) {
					/*
					 * Make an exact clone of the lazyItems entry. When we pass the config
					 * object to the Component constructor, we must consider the fact that
					 * this would be a reference. What we pass here, might be modified by
					 * the constructor. However we do not want those changes to be saved
					 * back into our lazyItems object, since that might cause problems
					 * when we instantiate the lazyItem for the second time.
					 */
					item = Ext.create(Ext.apply({}, item));
					this.add(item);
					retval.push(item);
				}
			}
		}

		return retval;
	}
});

Ext.reg('zarafa.switchviewcontentcontainer', Zarafa.core.ui.SwitchViewContentContainer);
Ext.namespace('Zarafa.core.ui');

/**
 * @class Zarafa.core.ui.Toolbar
 * @extends Ext.Toolbar
 * @xtype zarafa.toolbar
 */
Zarafa.core.ui.Toolbar = Ext.extend(Ext.Toolbar, {
	// Private
	// Used to keep track of whether the toolbar buttongroups have been corrected in size yet.
	initialSizeCorrectionDone: false,

	/**
	 * @constructor
	 * @param config
	 */
	constructor: function(config)
	{
		Ext.apply(this, config, {
			// Override from Ext.Component
			xtype: 'zarafa.toolbar',
			// Override from Ext.Toolbar
			enableOverflow: true
		});

		Zarafa.core.ui.Toolbar.superclass.constructor.call(this, config);

		this.on('afterlayout', this.onAfterLayout, this);
	},

	/**
	 * Add a new {@link Ext.ButtonGroup} to the {@link Ext.Toolbar} using an insertionpoint to support
	 * the addition of extra {@link Ext.Button} elements to the {@link Ext.ButtonGroup}. If the insertion
	 * point contains a {@link Ext.ButtonGroup} it will be added outside the given {@link Ext.ButtonGroup}
	 * to prevent nesting.
	 *
	 * @param {Object/String} group The {@link Ext.ButtonGroup} to which individual {@link Ext.Button} elements must be added.
	 * If this argument is a {@link String} it will be used as title for the {@link Ext.ButtonGroup}.
	 * @param {String} insertion (optional) The name of the insertion point used to add additional {@link Ext.Button} and
	 * {@link Ext.ButtonGroup} elements.
	 * @protected
	 */
	addItems: function(buttons, insertion)
	{
		var items = [];

		if (Ext.isDefined(buttons)) {
			if (!Ext.isEmpty(buttons)) {
				items = items.concat(buttons);
			}
		}

		if (Ext.isString(insertion)) {
			var insert = container.populateInsertionPoint(insertion);
			if (!Ext.isEmpty(insert)) {
				items = items.concat(insert);
			}
		}

		// Make sure we get a single array containing all buttons
		items = Ext.flatten(items);

		this.add(items);
	},

	/**
	 * When the toolbar is laid out the sizes for the different buttongroups may differ. When the
	 * afterlayout event is fired the heights are corrected to have all the buttongroups match in
	 * height. This is only done the first time the toolbar is shown.
	 * @private
	 */
	onAfterLayout: function()
	{
		// Only do it the first time the toolbar is shown.
		if(!this.initialSizeCorrectionDone && this.el.getHeight() > 0){

			var elements = [];
			var maxHeight = 0;
			this.items.each(function(item){
				if (item.isXType('buttongroup')) {
					var elem = item.body;
					maxHeight = Math.max(maxHeight, elem.getHeight());
					elements.push(elem);
				}
			});

			if(maxHeight > 0){
				Ext.each(elements, function(elem){
					elem.setHeight(maxHeight);
				}, this);
				this.initialSizeCorrectionDone = true;
			}
		}
	},

	onAddItem: function(toolbar, item, index)
	{
		if ( item.isXType('menuitem') && Ext.isDefined(item.recordComponentUpdaterPlugin) ){
			// The plugin has called the update function of the item, but (for overflown toolbars)
			// when buttons have been changed to menuitems by Ext they do not have the defined
			// update function anymore, and use the update function of Ext.Component. This function
			// will change the text and we don't want this, so we set it back
			var html = item.itemTpl.apply({
				id: item.id,
				cls: item.cls,
				href: '#',
				hrefTarget: '',
				iconCls: item.iconCls,
				text: item.overflowText
			});
			var tmp = new Ext.Element(document.createElement('div'));
			tmp.dom.outerHTML = html;
			html = tmp.dom.innerHTML;
			item.getEl().dom.innerHtml = html;
		}
	}
});

Ext.reg('zarafa.toolbar', Zarafa.core.ui.Toolbar);
Ext.namespace('Zarafa.core.ui');

/**
 * @class Zarafa.core.ui.ToolbarButton
 * @extends Ext.Button
 * @xtype zarafa.toolbarbutton
 *
 * Special implementation for the {@link Ext.Button button}
 * which connects to the Mail {@link Ext.Panel panel} for the
 * currently active {@link Zarafa.core.Context context}.
 * Using this link, the {@link Ext.Button button} can be disabled
 * or enabled depending on the number of currently selected
 * {@link Zarafa.core.data.IPMRecord records}.
 *
 * Also this class makes it easier for the {@link Ext.Button button}
 * to request the currently selected {@link Zarafa.core.data.IPMRecord records}
 * on which it can perform an action.
 *
 * FIXME: This should somehow be merged with Zarafa.core.ui.menu.ConditionalItem.
 */
Zarafa.core.ui.ToolbarButton = Ext.extend(Ext.Button, {
	/**
	 * @cfg {Boolean} emptySelectOnly This button must only be enabled
	 * if no record is selected
	 */
	emptySelectOnly: false,
	/**
	 * @cfg {Boolean} nonEmptySelectOnly This button must only be enabled
	 * if one or more records are selected.
	 */
	nonEmptySelectOnly: false,
	/**
	 * @cfg {Boolean} singleSelectOnly This button must only be enabled
	 * if a single record is selected
	 */
	singleSelectOnly: false,
	/**
	 * @cfg {Boolean} multiSelectOnly This button must only be enabled
	 * if multiple records are selected.
	 */
	multiSelectOnly: false,
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			xtype: 'zarafa.toolbarbutton'
		});

		Ext.apply(this, config);

		Zarafa.core.ui.ToolbarButton.superclass.constructor.call(this, config);

		var model = this.model || container.getCurrentContext().getModel();
		if (Ext.isDefined(model)) {
			this.onRecordSelectionChange(model, model.getSelectedRecords());
			this.mon(model, 'recordselectionchange', this.onRecordSelectionChange, this);
		}
	},

	/**
	 * Event handler which is triggered when the current {@link Zarafa.core.data.IPMRecord record}
	 * selection changes. This will then evaluate if the {@link Ext.Button button} must be
	 * enabled or disabled.
	 *
	 * @param {Zarafa.core.ContextModel} model this model.
	 * @param {Zarafa.core.data.Record[]} records The selected records
	 */
	onRecordSelectionChange: function(model, records)
	{
		if (this.emptySelectOnly) {
			if (Ext.isDefined(records) && (!Array.isArray(records) || records.length > 0)) {
				this.setDisabled(true);
			} else {
				this.setDisabled(false);
			}
		} else if (this.nonEmptySelectOnly) {
			if (!Ext.isDefined(records) || (Array.isArray(records) && records.length === 0)) {
				this.setDisabled(true);
			} else {
				this.setDisabled(false);
			}
		} else if (this.singleSelectOnly) {
			if (!Ext.isDefined(records) || (Array.isArray(records) && records.length !== 1)) {
				this.setDisabled(true);
			} else {
				this.setDisabled(false);
			}
		} else if (this.multiSelectOnly) {
			if (!Ext.isDefined(records) || (!Array.isArray(records) || records.length === 1)) {
				this.setDisabled(true);
			} else {
				this.setDisabled(false);
			}
		}
	}
});

Ext.reg('zarafa.toolbarbutton', Zarafa.core.ui.ToolbarButton);
Ext.namespace("Zarafa.core.ui");

/**
 * @class Zarafa.core.ui.View
 * @extends Ext.util.Observable
 * The View class is a component for building views. Views abstract away the details
 * of manipulating HTML and the DOM from underlying controls such as grid panels
 * and {@link Zarafa.calendar.ui.CalendarPanel CalendarPanel}.
 *
 * The View class supports convenience methods for creating elements, managing
 * several child views, etc.
 * <p>
 * The createDiv() method can be used to create div elements. These elements are then
 * automatically destroyed by the destroy() method.
 * Child views can be added using addChildView(). These are then also destroyed
 * automatically by the default destroy() implementation.
 */
Zarafa.core.ui.View = Ext.extend(Ext.util.Observable, {
	/**
	 * @cfg {String} baseCls The base CSS class to apply to this panel's element.
	 */
	baseCls: undefined,

	/**
	 * @cfg {String} itemCls The item CSS class to apply to this panel's element.
	 */
	itemCls: undefined,

	/**
	 * @cfg {String} themeCls The CSS theme which must be applied to this view. The name will
	 * be added to the various CSS classes which are generated using {@link #getClassName}.
	 * will be used.
	 */
	themeCls: undefined,

	/**
	 * The parent element for this view
	 * @property
	 * @type Object
	 */
	parentView: undefined,

	/**
	 * The {@link Ext.Element} in which this view has been rendered.
	 * This is passed as argument to {@link #render}.
	 * @property
	 * @type Ext.Element
	 */
	container: undefined,

	/**
	 * The list of child objects which were created for this view. When
	 * this object is destroyed all registered children will be automatically
	 * cleaned up as well.
	 * @property
	 * @type Array
	 */
	children: undefined,

	/**
	 * The list of child elements which were created by this view using
	 * the {@link #create} and {@link #createDiv} functions. When this
	 * object is destroyed all registered elements will be automatically
	 * cleaned up as well.
	 * @property
	 * @type Array
	 */
	elements: undefined,

	/**
	 * Indicates if this view has been {@link #render rendered}.
	 * @property
	 * @type Boolean
	 */
	rendered: false,

	/**
	 * True if the view has been destroyed already. Read only
	 * @property isDestroyed
	 * @type Boolean
	 */
	isDestroyed: false,

	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		Ext.apply(this, config);

		this.addEvents(
			/**
			 * @event beforedestroy
			 * Fires before the component is {@link #destroy}ed. Return false from an event handler to stop the {@link #destroy}.
			 * @param {Ext.Component} this
			 */
			'beforedestroy',
			/**
			 * @event destroy
			 * Fires after the component is {@link #destroy}ed.
			 * @param {Ext.Component} this
			 */
			'destroy'
		);

		Zarafa.core.ui.View.superclass.constructor.call(this, config);

		this.init();
	},

	/**
	 * Initialises the view. When a {@link #parentView} is defined,
	 * this will automatically register this view to the given parentView.
	 * @protected
	 */
	init: function()
	{
		this.children = [];
		this.elements = [];

		if (Ext.isDefined(this.parentView)) {
			this.parentView.addChildView(this);
		}
	},

	/**
	 * Returns the base className which must be applied to all {@link Ext.Element elements} within this
	 * view. This will also be the prefix of any additional CSS classes which will be applied to those elements.
	 * The exact base classname depends on the {@link #baseCls} and the availability of the {@link #itemCls}.
	 * @return {String} The base class.
	 * @private
	 */
	getBaseClassName: function()
	{
		return this.itemCls ? (this.baseCls + '-' + this.itemCls) : this.baseCls;
	},

	/**
	 * Helper function for generating a string with CSS class names. Each {@link Ext.Element Element} must
	 * at least contain the {@link #baseCls} CSS class and CSS classes containing the {@link #itemCls} and
	 * {@link #themeCls} but additional names will be generated for specific elements. Example:
	 * <pre>
	 *  baseCls = 'zarafa-calendar';
	 *  getClassName('body', 'image');
	 * </pre>
	 * The outcome will be:
	 * <pre>
	 *  'zarafa-calendar zarafa-calendar-body zarafa-calendar-body-image zarafa-calendar-blue zarafa-calendar-blue-body zarafa-calendar-blue-body-image'
	 * </pre>
	 *
	 * @param {String} name The main name for the CSS element, this will be appended to the {@link #baseCls}.
	 * @param {String/Array} postfix (optional) If provided extra CSS classNames will be generated with the name and postfix. If the postfix
	 * is an array, all elements from the array will be used as postfix.
	 * @return {String} the CSS class names
	 * @param {String/Array} extraCls (optional) If provided extra CSS classNames will be generated with this extraCls. If the postfix
	 * is an array, all elements from the array will be used as extraCls.
	 * @private
	 */
	getClassName: function(name, postfix, extraCls)
	{
		var baseCls = this.getBaseClassName();
		var className = this.baseCls;

		if (!Ext.isEmpty(this.themeCls)) {
			className += ' ' + this.baseCls + '-' + this.themeCls;
		}

		className += ' ' + baseCls + '-' + name;

		if (Ext.isDefined(postfix) && !Array.isArray(postfix)) {
			postfix = [ postfix ];
		}

		if (!Ext.isEmpty(postfix)) {
			for (var i = 0, len = postfix.length; i < len; i++) {
				className += ' ' + baseCls + '-' + name + '-' + postfix[i];
			}
		}

		if (Ext.isDefined(extraCls) && !Array.isArray(extraCls)) {
			extraCls = [ extraCls ];
		}

		if (!Ext.isEmpty(extraCls)) {
			for (var i = 0, len = extraCls.length; i < len; i++) {
				className += ' ' + baseCls + '-' + extraCls[i];
			}
		}

		return className;
	},

	/**
	 * A convenience method for creating DIV elements. The new DIV element will be
	 * wrapped in an {@link Ext.Element} and added to the view as a property called 'name'.
	 * Equivalent to <code>create('div', parent, name, className)</code>.
	 * @param {Mixed} parent element
	 * @param {String} name the name of the property to create
	 * @param {String} className (optional) class name for the new DIV
	 * @return {Ext.Element} The created div element
	 * @private
	 */
	createDiv: function(parent, name, className)
	{
		return this.create('div', parent, name, className);
	},

	/**
	 * A convenience method for creating DOM elements. The new element will be
	 * wrapped in an {@link Ext.Element} and added to the view as a property called 'name'.
	 * Calling <pre>this.create('div', 'test')</pre> for instance will create a new
	 * property <pre>this.test</pre>, and the DIV can be manipulated with code such
	 * as <pre>this.test.setSize(100, 100);</pre>. If a property already exists
	 * with the given name, the property will be converted into an array and
	 * the new element will the appended to that array.
	 * @param {String|Object} tagName type of HTML tag to generate (i.e. 'div', 'img', etc.)
	 * @param {Mixed} parent element
	 * @param {String} name the name of the property to create
	 * @param {String} className (optional) class name for the new DIV
	 * @return {Ext.Element} The created element
	 * @private
	 */
	create: function(tagName, parent, name, className)
	{
		var config = Ext.isObject(tagName) ? tagName : { tag: tagName };

		Ext.applyIf(config, { cls: className || this.baseCls });

		// create a new HTML element and add it to the parent element
		var element = Ext.get(parent).createChild(config);

		// add the element as a property to this
		if (!this[name]) {
			this[name] = element;
		} else if (Array.isArray(this[name])) {
			this[name].push(element);
		} else {
			this[name] = [ this[name], element ];
		}

		// add the element to the elements list so that we may destroy it easily later on
		this.elements.push(element);

		return element;
	},

	/**
	 * Removes a child element from the view and destroys it.
	 * @param {Ext.Element} element DOM element
	 */
	remove: function(element)
	{
		element = Ext.get(element);
		this.elements.remove(element);
		element.remove();
	},

	/**
	 * @return {Ext.Element} The main element of the view. This is the element the
	 * view is rendered <i>into</i>. The container of a view may be shared with other
	 * views, so manipulating the container of a given view might affect other views
	 * as well.
	 */
	getContainer: function()
	{
		return this.container;
	},

	/**
	 * Calls 'render' on each of the child views
	 * @param {Ext.Element} container The container into which the children must be rendered. Defaults to {@link #el}.
	 * @private
	 */
	renderChildren: function(container)
	{
		for (var i = 0, len = this.children.length; i < len; i++) {
			this.children[i].render(container || this.container);
		}
	},

	/**
	 * Renders the view.
	 * Rendering a view is done once after construction, and creates all the DOM elements needed for the visual
	 * representation of the view.
	 * @param {Ext.Element} container The Ext.Element into which the view must be rendered.
	 */
	render: function(container)
	{
		this.container = container;
		// TODO Can't we call renderChildren here?
		this.rendered = true;
	},

	/**
	 * Calls 'layout' on each of the child views.
	 * @private
	 */
	layoutChildren: function()
	{
		for (var i = 0, len = this.children.length; i < len; i++) {
			this.children[i].layout();
		}
	},

	/**
	 * Called just before this View will be {@link #layout laid out}.
	 * @protected
	 */
	onBeforeLayout: Ext.emptyFn,

	/**
	 * Called when this view is being {@link #layout laid out}.
	 * @protected
	 */
	onLayout: Ext.emptyFn,

	/**
	 * Called just after this View has been {@link #layout laid out}.
	 * @protected
	 */
	onAfterLayout: Ext.emptyFn,

	/**
	 * Lays out the view, setting the position and size of the individual DOM elements.
	 */
	layout: function()
	{
		this.onBeforeLayout();
		this.onLayout();
		this.onAfterLayout();
	},

	/**
	 * Calls 'destroy' on each of the child views.
	 * @private
	 */
	destroyChildren: function()
	{
		for (var i = 0, len = this.children.length; i < len; i++) {
			this.children[i].destroy();
		}
	},

	/**
	 * Destroys the view, removing all DOM elements generated by render() from the DOM tree and unhooks
	 * any events.
	 */
	destroy: function()
	{
		if (!this.isDestroyed) {
			if (this.fireEvent('beforedestroy', this) !== false) {
				// remove all elements generated with createDiv() from the DOM
				for (var i = 0, len = this.elements.length; i < len; i++) {
					this.elements[i].remove();
				}

				// destroy child views
				this.destroyChildren();

				this.onDestroy();

				this.fireEvent('destroy', this);
				this.purgeListeners();
			}

			this.isDestroyed = true;
		}
	},

	/**
	 * Set the {@link #parentView} field. This will be called by {@link #addChildView}
	 * to update the parent to this view.
	 * @param {Zarafa.core.ui.View} parentView The parent view
	 * @private
	 */
	setParentView: function(parentView)
	{
		this.parentView = parentView;
	},

	/**
	 * Adds a child view to this view.
	 * @param {Zarafa.core.ui.View} child child view to be added
	 */
	addChildView: function(child)
	{
		child.setParentView(this);
		this.children.push(child);
		return child;
	},

	/**
	 * Removes a child view from this view.
	 * @param {Zarafa.core.ui.View} child child view to be added
	 * @param {Boolean} destroy if true, destroy the child view
	 */
	removeChildView: function(child, destroy)
	{
		this.children.remove(child);
		child.setParentView(undefined);
		if (destroy) {
			child.destroy();
		}
	},

	/**
	 * Purge all event listeners.
	 * @private
	 */
	purgeListeners: Ext.Container.prototype.purgeListeners,

	/**
	 * Clear all monitored event listeners. This will call
	 * {@link Ext.util.Observable.un un()} on each previously
	 * registered event handler which was registered with
	 * {@link #mon}.
	 * @private
	 */
	clearMons: Ext.Container.prototype.clearMons,

	/**
	 * Create the tracking object in which we store
	 * each event handler which was registered with {@link #mon}.
	 * @private
	 */
	createMons: Ext.Container.prototype.createMons,

	/**
	 * Adds listeners to any Observable object (or Elements) which are automatically removed when this View
	 * is destroyed.
	 * @param {Observable|Element} item The item to which to add a listener/listeners.
	 * @param {Object|String} ename The event name, or an object containing event name properties.
	 * @param {Function} fn Optional. If the <code>ename</code> parameter was an event name, this
	 * is the handler function.
	 * @param {Object} scope Optional. If the <code>ename</code> parameter was an event name, this
	 * is the scope (<code>this</code> reference) in which the handler function is executed.
	 * @param {Object} opt Optional. If the <code>ename</code> parameter was an event name, this
	 * is the {@link Ext.util.Observable#addListener addListener} options.
	 */
	mon: Ext.Container.prototype.mon,

	/**
	 * Removes listeners that were added by the {@link #mon} method.
	 * @param {Observable|Element} item The item from which to remove a listener/listeners.
	 * @param {Object|String} ename The event name, or an object containing event name properties.
	 * @param {Function} fn Optional. If the <code>ename</code> parameter was an event name, this
	 * is the handler function.
	 * @param {Object} scope Optional. If the <code>ename</code> parameter was an event name, this
	 * is the scope (<code>this</code> reference) in which the handler function is executed.
	 */
	mun: Ext.Container.prototype.mun,

	/**
	 * Called when the View is being destroyed.
	 * @protected
	 */
	onDestroy: Ext.emptyFn
});
Ext.namespace('Zarafa.core.ui');

/**
 * @class Zarafa.core.ui.WelcomeViewport
 * @extends Ext.Viewport
 * The viewport to be used as welcome page for first time users, this will show
 * a welcome message, and allow the user to configure to initial settings
 * before continuing to the {@link Zarafa.core.ui.MainViewport Main viewport}.
 */
Zarafa.core.ui.WelcomeViewport = Ext.extend(Ext.Viewport, {

	/**
	 * The reference as returned by {@link Zarafa.core.ui.notifier.Notifier#notify} to reference the
	 * message in order to remove the message as soon as the save was completed.
	 * @property
	 * @type Ext.Element
	 * @private
	 */
	savingEl: undefined,

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

		config = Ext.applyIf(config, {
			layout: {
				cls: 'grommunio-welcome-viewport',
				type: 'vbox',
				align: 'center',
				pack: 'center'
			},
			items: [{
				xtype: 'panel',
				cls: 'zarafa-welcome-body',
				border: false,
				items: [{
					xtype: 'container',
					cls: 'k-welcome-header',
					layout: 'hbox',
					items: [{
						xtype: 'container',
						cls: 'k-welcome-avatar',
						html: '<img src="' + Ext.util.Format.htmlEncode(container.getUser().getUserImage()) + '" alt="" />'
					},{
						xtype: 'container',
						flex: 1,
						cls: 'k-welcome-header-text',
						items: [{
							xtype: 'displayfield',
							cls: 'zarafa-welcome-title',
							value: _('Welcome to grommunio Web')
						},{
							xtype: 'displayfield',
							cls: 'zarafa-welcome-message',
							value: _('Set up your preferences to get started.')
						}]
					}]
				},{
					xtype: 'zarafa.settingswelcomecategory',
					ref: '../settingsCategory'
				}],
				buttonAlign: 'right',
				buttons: [{
					cls: 'zarafa-action',
					text: _('Get Started'),
					handler: this.onContinueButton,
					scope: this
				}]
			}]
		});

		Zarafa.core.ui.WelcomeViewport.superclass.constructor.call(this, config);

		this.settingsCategory.update(container.getSettingsModel());

		// Disable autoSave, we want to call the save function manually,
		// so we can supply a callback function.
		container.getSettingsModel().autoSave = false;
	},

	/**
	 * Event handler which is fired when the user clicks the 'Continue' button
	 * This will save all settings, and reload the page to continue to the
	 * {@link Zarafa.core.ui.MainViewport}.
	 * @private
	 */
	onContinueButton: function()
	{
		var model = container.getSettingsModel();

		model.beginEdit();

		// Load settings from UI
		this.settingsCategory.updateSettings(model);

		// Disable the welcome message for next logon
		model.set('zarafa/v1/main/show_welcome', false);

		// set the default keyboard controls to 'basic'
		// We must do this explicitly because there is some logic
		// for backward compatibility that will assume 'disabled'
		// when no keycontrols are set.
		model.set('zarafa/v1/main/keycontrols', 'basic');

		model.endEdit();

		// Register event listener, so we can redirect the user
		// once the save has completed.
		this.mon(model, 'save', this.onSettingsSave, this, { single: true });
		this.mon(model, 'exception', this.onSettingsException, this, { single: true });

		// Show an information box indicating that the settings are being saved.
		this.savingEl = container.getNotifier().notify('info.saving', '', _('Saving') + '...', {
			container: this.getEl(),
			persistent: true
		});

		// Save settings
		model.save();
	},

	/**
	 * Called when the {@link Zarafa.settings.SettingsModel} fires the {@link Zarafa.settings.SettingsModel#save save}
	 * event to indicate the settings were successfully saved.
	 * @param {Zarafa.settings.SettingsModel} model The model which fired the event.
	 * @param {Object} parameters The key-value object containing the action and the corresponding
	 * settings which were saved to the server.
	 * @private
	 */
	onSettingsSave: function(model, parameters)
	{
		container.getNotifier().notify('info.saving', null, null, {
			container: this.getEl(),
			destroy: true,
			reference: this.savingEl
		});

		Zarafa.core.Util.disableLeaveRequester();
		window.location.reload();
	},

	/**
	 * Called when the {@link Zarafa.settings.SettingsModel} fires the {@link Zarafa.settings.SettingsModel#exception exception}
	 * event to indicate the settings were not successfully saved.
	 * @private
	 */
	onSettingsException: function()
	{
		container.getNotifier().notify('info.saving', null, null, {
			container: this.getEl(),
			destroy: true,
			reference: this.savingEl
		});
	}
});
Ext.namespace('Zarafa.core.ui.menu');

/**
 * @class Zarafa.core.ui.menu.ConditionalItem
 * @extends Ext.menu.Item
 * @xtype zarafa.conditionalitem
 *
 * Extends the {@link Ext.menu.Item} class and will check if the
 * item should be enabled or disabled each time before the
 * context menu is being shown.
 */
Zarafa.core.ui.menu.ConditionalItem = Ext.extend(Ext.menu.Item, {
	/**
	 * @cfg {Boolean} hideOnDisabled This item must be hidden rather
	 * then be marked disabled.
	 */
	hideOnDisabled: true,
	/**
	 * @cfg {Boolean} emptySelectOnly This item must only be enabled
	 * if no record is selected
	 */
	emptySelectOnly: false,
	/**
	 * @cfg {Boolean} nonEmptySelectOnly This item must only be enabled
	 * if one or more records are selected
	 */
	nonEmptySelectOnly: false,
	/**
	 * @cfg {Boolean} singleSelectOnly This item must only be enabled
	 * if a single record is selected
	 */
	singleSelectOnly: false,
	/**
	 * @cfg {Boolean} multiSelectOnly This item must only be enabled
	 * if multiple records are selected.
	 */
	multiSelectOnly: false,
	/**
	 * Override of {@link Ext.menu.Item#itemTpl} to add the possibility of
	 * styling the icon.
	 * @property
	 * @Type Ext.XTemplate
	 */
  itemTpl: new Ext.XTemplate(
    '<a id="{id}" class="{cls} x-unselectable" hidefocus="true" unselectable="on" href="{href}"',
      '<tpl if="hrefTarget">',
        ' target="{hrefTarget}"',
      '</tpl>',
     '>',
       '<img alt="{altText}" src="{icon}" class="x-menu-item-icon {iconCls}" {iconStyle}/>',
       '<span class="x-menu-item-text">{text}</span>',
     '</a>'
  ),
  /**
   * Override of {@link Ext.menu.Item#getTemplateArgs} to add the possibility of
   * styling the icon.
   * @return {Object}
   */
  getTemplateArgs: function() {
  	// Get the original template arguments from the original function
  	var templateArgs = Zarafa.core.ui.menu.ConditionalItem.superclass.getTemplateArgs.call(this);
  	// Add the argument for the icon style
  	templateArgs.iconStyle = this.iconBG ? 'style="background-color:'+this.iconBG+';"' : '';
		return templateArgs;
	},
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		Ext.applyIf(config, {
			xtype: 'zarafa.conditionalitem'
		});

		Ext.applyIf(this, config);

		Zarafa.core.ui.menu.ConditionalItem.superclass.constructor.call(this, config);
	},

	/**
	 * Initialize the component
	 * @private
	 */
	initComponent: function()
	{
		Zarafa.core.ui.menu.ConditionalItem.superclass.initComponent.apply(this, arguments);

		// Reset the enable/disable functions to their
		// show/hide counterparts if we should not display the item as disabled.
		if (this.hideOnDisabled) {
			this.enable = this.show;
			this.disable = this.hide;
		}
	},

	/**
	 * Apply the {@link #emptySelectOnly}, {@link #nonEmptySelectOnly}, {@link #singleSelectOnly}
	 * and {@link #multiSelectOnly} filters to determine if the item must be {@link #setDisabled disabled}
	 * or not.
	 * @private
	 */
	applySelectionFilter: function()
	{
		var records = this.getRecords();

		if (this.emptySelectOnly) {
			if (Ext.isDefined(records) && (!Array.isArray(records) || records.length > 0)) {
				this.setDisabled(true);
			} else {
				this.setDisabled(false);
			}
		}

		if (this.nonEmptySelectOnly) {
			if (Ext.isDefined(records) && Array.isArray(records) && records.length === 0) {
				this.setDisabled(true);
			} else {
				this.setDisabled(false);
			}
		}

		if (this.singleSelectOnly) {
			if (!Ext.isDefined(records) || (Array.isArray(records) && records.length !== 1)) {
				this.setDisabled(true);
			} else {
				this.setDisabled(false);
			}
		}

		if (this.multiSelectOnly) {
			if (!Ext.isDefined(records) || !Array.isArray(records) || records.length === 1) {
				this.setDisabled(true);
			} else {
				this.setDisabled(false);
			}
		}
	},

	/**
	 * Called by the {@link #parentMenu} when it is about to be shown, this can be
	 * overridden by subclasses to add extra filters on the visibility of this item.
	 * @param {Zarafa.core.ui.menu.ConditionalItem} item The item which is about to be shown.
	 * @param {Zarafa.core.data.MAPIRecord} record The record which is being shown for this menu
	 */
	beforeShow: function(item, record)
	{
		item.applySelectionFilter();
	},

	/**
	 * Obtain the record/records which are attached to the {@link Zarafa.core.ui.menu.ConditionalMenu menu}
	 * to which this item belongs.
	 * @return {Zarafa.core.data.MAPIRecord[]} records
	 */
	getRecords: function()
	{
		return this.getRootMenu().records;
	},

	/**
	 * Obtain the reference to the root menu.
	 * This will go through all parents of the {@link Zarafa.core.ui.ConditionalItem item}
	 * until no {@link Ext.menu.menu} is found as parent. The last parent is considered the
	 * root and will be returned to the caller.
	 *
	 * @return {Ext.menu.menu} The Root menu object
	 */
	getRootMenu: function()
	{
		var menu = this.parentMenu;
		if (!menu && this.ownerCt instanceof Ext.menu.Menu) {
			menu = this.ownerCt;
		}

		while (menu && (Ext.isDefined(menu.parentMenu) || menu.ownerCt instanceof Ext.menu.Menu)) {
			menu = menu.parentMenu || menu.ownerCt;
		}

		return menu;
	}
});

Ext.reg('zarafa.conditionalitem', Zarafa.core.ui.menu.ConditionalItem);
Ext.namespace('Zarafa.core.ui.menu');

/**
 * @class Zarafa.core.ui.menu.ConditionalMenu
 * @extends Ext.menu.Menu
 * @xtype zarafa.conditionalmenu
 *
 * Extends the {@link Ext.menu.Menu} class and allows menu options to determine whether to display themselves.
 */
Zarafa.core.ui.menu.ConditionalMenu = Ext.extend(Ext.menu.Menu, {
	/**
	 * @cfg {Zarafa.core.data.IPMRecord[]} records The records on which this contextmenu was requested
	 */
	records: undefined,

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

		Ext.applyIf(config, {
			xtype: 'zarafa.conditionalmenu',
			shadow: false
		});

		Zarafa.core.ui.menu.ConditionalMenu.superclass.constructor.call(this, config);

		this.on('beforeshow', this.onMenuBeforeShow, this);
	},

	/**
	 * Event handler for the {@link #beforeshow} event. This will go through all
	 * {@link Zarafa.core.ui.menu.ConditionalItem items} in the menu and call the
	 * {@link Zarafa.core.ui.menu.ConditionalItem#beforeShow} function.
	 *
	 * Optionally all duplicate menuseparators will be removed. This can happen when an
	 * item which was surrounded by separators has been hidden.
	 *
	 * @param {Zarafa.core.ui.menu.ConditionalMenu} menu The menu which is being opened.
	 * @private
	 */
	onMenuBeforeShow: function(menu)
	{
		var records = this.records;
		var allowSeparator = false;
		var lastItemIndex = -1;

		// move over the items list and call 'beforeOpen' on each item if that function exists
		menu.items.each(function(item, index) {
			if (item.isXType('zarafa.conditionalitem')) {
				if (Ext.isFunction(item.beforeShow)) {
					item.beforeShow.call(item.scope || item, item, records);
				}

				if (Ext.isDefined(item.menu)) {
					this.onMenuBeforeShow(item.menu);
				}
			}

			// A separator is being added, check if we actually want to display it.
			if (item.isXType('menuseparator')) {
				item.setVisible(allowSeparator === true);
				allowSeparator = false;
			} else {
				// If the non-separator item is visible,
				// we are allowed to display a separator when requested.
				if (item.hidden === false) {
					allowSeparator = true;
					lastItemIndex = index;
				}
			}
		}, this);

		// The menu is empty, or we have hidden everything.
		if (lastItemIndex === -1) {
			return false;
		}

		// Remove all separators which are visible as last items in the menu.
		for (var i = lastItemIndex, len = menu.items.getCount(); i < len; i++) {
			var item = menu.items.items[i];
			if (item.isXType('menuseparator')) {
				item.setVisible(false);
			}
		}

		// But what if we just removed everything?
		if (lastItemIndex === -1) {
			return false;
		}
	}
});

Ext.reg('zarafa.conditionalmenu', Zarafa.core.ui.menu.ConditionalMenu);
Ext.namespace('Zarafa.core.ui.notifier');

/**
 * @class Zarafa.core.ui.notifier.Notifier
 * @extends Object
 */
Zarafa.core.ui.notifier.Notifier = Ext.extend(Object, {
	/**
	 * The key-value list of all available plugins which
	 * might be used to send notify messages to.
	 * @property
	 * @type Object
	 */
	availablePlugins: undefined,

	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		Ext.apply(this, config);

		this.availablePlugins = {};
	},

	/**
	 * Register the {@link Zarafa.core.ui.notifier.NotifyPlugin NotifyPlugin} to the
	 * Notifier instance. This will add the plugin for the given name on the
	 * {@link #availablePlugins} array. It will also make the name available
	 * to be set in the {@link Zarafa.core.SettingsManager Settings}.
	 *
	 * @param {String} name The name how the plugin must be registered.
	 * @param {Zarafa.core.ui.notifier.NotifyPlugin} plugin The plugin which must be registered.
	 */
	registerPlugin: function(name, plugin)
	{
		if (!Ext.isDefined(this.availablePlugins[name])) {
			this.availablePlugins[name] = plugin;
		}
	},

	/**
	 * Obtain the {@link Zarafa.core.ui.notifier.NotifyPlugin plugin} which should be used
	 * for Notifications for the given category.
	 *
	 * The category should be either "error", "warning", "info" or "debug", or a subtype thereof (e.g. "info.newmail"). When
	 * searching for a valid plugin, the {@link Zarafa.core.SettingsManager Settings} is consulted to find the
	 * correct pluginname. If no pluginname is found for the given category, a supertype will be tried. For example,
	 * if no pluginname is found for "info.alfresco", then it will try to find the pluginname for "info" instead.
	 * If no pluginname can be found, then 'default' will be used as categoryname. Finally, if that also has no
	 * plugin registered, then this function will return 'undefined'.
	 *
	 * @param {String} category The category for which the plugin is searched for.
	 * @return {Zarafa.core.ui.notifier.NotifyPlugin} The plugin.
	 */
	getPlugin: function(category)
	{
		var setting = 'zarafa/v1/main/notifier/' + category.replace(/\./g, '/') + (category != 'default' ? '/value' : '');
		var pluginName = container.getSettingsModel().get(setting);
		var plugin = this.availablePlugins[pluginName];

		if (!plugin && category != 'default') {
			var index = category.lastIndexOf('.');
			if (index > 0) {
				category = category.substr(0, index);
			} else {
				category = 'default';
			}

			plugin = this.getPlugin(category);
		}

		return plugin;
	},

	/**
	 * Send a notification to the user. This {@link #getPlugin requests} the plugin for the provided category.
	 * On the plugin the {@link Zarafa.core.ui.notifier.NotifyPlugin#notify notify} function is called to display
	 * the message to the user. If no plugin could be found for
	 * the given category, then no message will be shown to the user.
	 *
	 * @param {String} category The category which applies to the notification.
	 * @param {String} title The title which must be shown in the message.
	 * @param {String} message The message which should be displayed.
	 * @param {Object} config Configuration object which can be applied to the notifier
	 * This object can contain keys like:
	 * - container: Which is the container to which the notifier should be restricted
	 * - persistent: True to make the message persistent and don't disappear automatically
	 * - destroy: Don't create new message, but destroy previous one
	 * - update: Don't create a new message, but update previous one
	 * - reference: The original message which must be updated by this action
	 * - listeners: Event handlers which must be registered on the element
	 * @return {Mixed} A reference to the message which was created, this can be used
	 * as value for 'reference' in the config argument.
	 */
	notify: function(category, title, message, config)
	{
		var categoryName = category.toLowerCase();
		var plugin = this.getPlugin(categoryName);
		if (plugin) {
			return plugin.notify(categoryName, title, message, config);
		}
	}
});
Ext.namespace('Zarafa.core.ui.notifier');

/**
 * @class Zarafa.core.ui.notifier.NotifyPlugin
 * @extends Object
 *
 * Base class for any plugins which can be registered to the
 * {@link Zarafa.core.ui.notifier.Notifier Notifier}.
 */
Zarafa.core.ui.notifier.NotifyPlugin = Ext.extend(Object, {
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		Ext.apply(this, config);
	},

	/**
	 * Notify the user with a message.
	 *
	 * The category can be either "error", "warning", "info" or "debug", or a subtype thereof (e.g. "info.newmail").
	 *
	 * @param {String} category The category which applies to the notification.
	 * @param {String} title The title which must be shown in the message.
	 * @param {String} message The message which should be displayed.
	 * @param {Object} config Configuration object which can be applied to the notifier
	 * This object can contain keys like:
	 * - container: Which is the container to which the notifier should be restricted
	 * - destroy: Don't create new message, but destroy previous one
	 * - reference: The original message which must be updated by this action
	 * @return {Mixed} A reference to the message which was created, this can be used
	 * as value for 'reference' in the config argument.
	 */
	notify: Ext.emptyFn
});
Ext.namespace('Zarafa.core.ui.notifier');

/**
 * @class Zarafa.core.ui.notifier.SliderContainer
 * @extends Object
 *
 * Special container to be used by the {@link Zarafa.core.ui.notifier.SliderNotifyPlugin}.
 * This supports holding multiple notification messages within a single DIV element,
 * and slide new ones into the container, and old ones out. The code is structured so only
 * one animation occurs at a time, and the container can be positioned anywhere on the screen.
 */
Zarafa.core.ui.notifier.SliderContainer = Ext.extend(Object, {
	/**
	 * @cfg {Ext.Element} parentContainer The parent container in which the
	 * slider will be positioned.
	 */
	parentContainer: undefined,

	/**
	 * @cfg {String} containerCls The CSS class to be applied on the {@link #container}.
	 */
	containerCls: 'zarafa-notifier-container',

	/**
	 * @cfg {String} itemCls The CSS class to be applied to the DIV wrapper around
	 * the messages added in {@link #createMessage}.
	 */
	itemCls: 'zarafa-notifier-container-item',

	/**
	 * @cfg {String} containerPosition The position of the container
	 * in which the notifier will be shown. This can be any of the following values:
	 * - 'tl': The top left corner
	 * - 't': The center of the top edge (default)
	 * - 'tr': The top right corner
	 * - 'l': The center of the left edge
	 * - 'c': In the center of the element
	 * - 'r': The center of the right edge
	 * - 'bl': The bottom left corner
	 * - 'b': The center of the bottom edge
	 * - 'br': The bottom right corner
	 */
	containerPosition: 't',

	/**
	 * @cfg {Char} slideinDirection the animation slidein direction for notification
	 */
	slideinDirection: 't',

	/**
	 * @cfg {Char} slideoutDirection the animation slideout direction for notification
	 */
	slideoutDirection: 't',

	/**
	 * @cfg {Boolean} animatedContainerResize True to animate the resizing of the container
	 * when a new notification is added into the container. This defaults to 'false' if
	 * {@link #slideinDirection} is 't', 'true' otherwise.
	 */
	animatedContainerResize: false,

	/**
	 * The container in which the sliders will be placed.
	 * @property
	 * @type Ext.Element
	 * @private
	 */
	container: undefined,

	/**
	 * The array of {@link Ext.Element Elements} which are pending to be displayed in the {@link #container}.
	 * Because we cannot animate two messages simultaneously, we keep the list of pending messages here, which
	 * serves as FIFO queue. Whenever a message has been animated, this list will be checked to see if a message
	 * is in the queue to be animated.
	 * @property
	 * @type Array
	 * @private
	 */
	pendingMessages: undefined,

	/**
	 * The array of {@link Ext.Element Elements} which are currently displayed in the {@link #container}.
	 * @property
	 * @type Array
	 * @private
	 */
	stackedMessages: undefined,

	/**
	 * Used by {@link #showNextMessage} to check if there currently is a Message which is being animated.
	 * When this field is set to 'true', then no other message is allowed to start animating.
	 * @property
	 * @type Array
	 * @private
	 */
	animating: false,

	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		Ext.apply(this, config);

		this.container = this.getContainer();
		this.pendingMessages = [];
		this.stackedMessages = [];

		// update animatedContainerResize depending on configuration
		if (!Ext.isDefined(config.animatedContainerResize)) {
			this.animatedContainerResize = this.slideinDirection !== 't';
		}
	},

	/**
	 * Add a new slider to the container. This will animate the slider into the {@link #container}.
	 * @param {String} html The HTML string for the slider to create
	 * @param {Number} timeout (optional) If provided, the message will be removed automatically after the timeout
	 * @return {Ext.Element} The slider element which was created
	 */
	createMessage: function(html, timeout)
	{
		// Construct the Ext.Element object
		var element = Ext.DomHelper.append(this.container, { html: html, cls: this.itemCls, style: 'visibility: hidden;' }, true);

		// Push it to pending messages
		this.pendingMessages.push(element);
		element.timeout = timeout;
		element.slider = this;

		// Check if there are no pending messages, and we can direct display this new one.
		this.showNextMessage();

		return element;
	},

	/**
	 * Update a message which was previously created using {@link #createMessage}.
	 * @param {Ext.Element} The element to update
	 * @param {String} html The HTML string for the slider to update
	 * @return {Ext.Element} The slider element which was updated
	 * @private
	 */
	updateMessage: function(element, html)
	{
		element.dom.innerHTML = html;
		return element;
	},

	/**
	 * Remove a message which was previously created using {@link #createMessage}.
	 * @param {Ext.Element} element The element to remove
	 */
	removeMessage: function(element)
	{
		this.animating = true;

		if (this.stackedMessages.indexOf(element) >= 0) {
			element.ghost(this.slideoutDirection, {
				remove: true,
				stopFx: true,
				callback: this.onRemoveComplete.createDelegate(this, [ element ]),
				scope: this
			});
		}
	},

	/**
	 * Called whenever a message has been added to the {@link #pendingMessages} queue, or when
	 * a message has been removed from the {@link #stackedMessages}. When we still have pending
	 * messages in {@link #pendingMessages}, we first check if we are currently busy
	 * {@link #animating animating} a message, or if there is no room for a new message to appear.
	 * When neither problem occurs, then we start animating the new message into the screen.
	 *
	 * @private
	 */
	showNextMessage: function()
	{
		var element = this.pendingMessages[0];
		var msgHeight = element.getHeight();
		var ctHeight = this.container.getHeight();
		var maxHeight = this.parentContainer.getHeight();

		// The <body> element might sometimes have height 0, compensate this
		// by asking for the document height in that case.
		if (maxHeight === 0 && this.parentContainer.dom.tagName === 'BODY') {
			maxHeight = document.height;
		}

		if (this.animating || (ctHeight + msgHeight) > maxHeight) {
			return;
		}
		this.animating = true;

		// Update the dimensions of the container to make room for the new element.
		// All existing messages will be nicely shifted the require height higher.
		var newHeight = ctHeight + msgHeight;
		this.updateContainer(newHeight, this.animatedContainerResize);

		// Store the original height of the message for later,
		// we need it for onMessageSlideOutComplete() when the real height has been reset,
		// and we need to resize the container.
		element.height = msgHeight;

		// Move the element to the next queue
		this.pendingMessages.remove(element);
		this.stackedMessages.push(element);

		this.animMessage(element);
	},

	/**
	 * Start the animation which will {@link Ext.Fx#slideIn slidein} the given element.
	 * If the slider was configured with a timeout, it will {@link Ext.Fx#pause show} the
	 * element for the given time and then {@link Ext.Fx#ghost remove} it again.
	 * @param {Ext.Element} element The element to show
	 * @private
	 */
	animMessage: function(element)
	{
		element = element.slideIn(this.slideinDirection, {
			callback: this.onShowComplete,
			scope: this
		});

		// Check if this element must automatically be removed
		// after a certain timeout.
		if (Ext.isNumber(element.timeout)) {
			element.pause(element.timeout, {
				callback: this.onPauseComplete,
				scope: this
			}).ghost(this.slideoutDirection, {
				remove: true,
				callback: this.onRemoveComplete.createDelegate(this, [ element ]),
				scope: this
			});
		}
	},

	/**
	 * Event handler which is called when the {@link Ext.Fx#slideIn slideIn} animation has been
	 * completed. This will reset the {@link #animating} flag, and call {@link #showNextMessage}
	 * to start animating any pending messages.
	 *
	 * @private
	 */
	onShowComplete: function()
	{
		this.animating = false;

		// Show the next message if there are pending messages,
		// and the parent container still exists (it might have been
		// deleted while the element was animating).
		if (!Ext.isEmpty(this.pendingMessages) && this.parentContainer.dom) {
			this.showNextMessage();
		}
	},

	/**
	 * Event handler which is called when the {@link Ext.Fx#pause pause} has been completed.
	 * This will set the {@link #animating} field again to prepare for the {@link Ext.Fx#ghost ghost}
	 * animation.
	 *
	 * @private
	 */
	onPauseComplete: function()
	{
		this.animating = true;
	},

	/**
	 * Event handler which is called when the {@link Ext.Fx#ghost ghost} animation has been
	 * completed. This will resize the {@link #container} to make sure it will only
	 * contain enough room for the visible {@link #stackedMessages messages}.
	 * @param {Ext.Element} element The element which was removed
	 * @private
	 */
	onRemoveComplete: function(element)
	{
		var newHeight = this.container.getHeight() - element.height;
		var index = this.stackedMessages.indexOf(element);

		// Technically shouldn't happen...
		if (index < 0) {
			return;
		}

		// Remove the element from stack
		this.stackedMessages.splice(index, 1);

		this.animating = false;

		if (newHeight < 0) {
			newHeight = 0;
		}

		// After cleaning up, we need to check if the parent container
		// still exists, as it might have been deleted while the element
		// was animating.
		if (!this.parentContainer.dom) {
			return;
		}

		// Don't animate the repositioning, otherwise all visible
		// messages will be bumped above and then animated down again.
		// By not animating, the messages will remain exactly where they
		// are.
		this.updateContainer(newHeight, false);

		if (!Ext.isEmpty(this.pendingMessages)) {
			this.showNextMessage();
		}
	},

	/**
	 * Obtain the {@link Ext.Element container} in which this slider will operate.
	 * This uses the {@link #parentContainer} to find if any slider container already
	 * exists, if not, it will create one at the {@link #containerPosition}.
	 * @return {Ext.Element} The slider container
	 * @private
	 */
	getContainer: function()
	{
		var parentId = this.parentContainer.id;
		var sliderCls = this.containerCls + ' ' + this.containerCls + '-' + this.containerPosition;
		var sliderId = parentId + '-' + this.containerCls + '-' + this.containerPosition;

		var ct = Ext.DomQuery.select('#' + sliderId).shift();
		if (!ct) {
			// Insert the notifier message into the given container
			ct = Ext.DomHelper.insertFirst(this.parentContainer, { id: sliderId, cls: sliderCls }, true);

			// Set initial height to 0
			ct.setHeight(0);

			// Position the container to the correct location
			ct.alignTo(this.parentContainer, this.containerPosition);
		} else {
			ct = Ext.get(ct);
		}

		return ct;
	},

	/**
	 * Update the {@link #container} with a new height. This will recalculate the
	 * position of the {@link #container} to make sure the container remains fixed
	 * to the {@link #containerPosition.
	 *
	 * @param {Number} height The new height which must be applied to the container
	 * @param {Boolean} animate True when the resize/repositioning of the container
	 * should be animated.
	 * @private
	 */
	updateContainer: function(height, animate)
	{
		// The position of the element should be: br-br, t-t, etc, to ensure
		// that the bottom-right corner of the container is in the bottom-right
		// corner of the parentContainer.
		var position = this.containerPosition + '-' + this.containerPosition;

		// Update positioning of the container
		this.container.setHeight(height);
		this.container.alignTo(this.parentContainer, position, undefined, animate);
	}
});
Ext.namespace('Zarafa.core.ui.widget');

/**
 * @class Zarafa.core.ui.widget.Widget
 * @extends Ext.ux.Portlet
 * @xtype zarafa.widget
 *
 * A 'widget' is a plug-in that users can instantiate and put on their today view or tool bar.
 * Examples of widgets are the clock, the weather widget, public notes, etc.
 *
 * Users can add new instances of a widget freely, and create multiple instances if desired. Each
 * widget instance has a unique GUID, and settings for that instance are stored in the widgets/[GUID]
 * settings folder. When a widget is destroyed, that folder is deleted.
 */
Zarafa.core.ui.widget.Widget = Ext.extend(Ext.ux.Portlet, {
	/**
	 * @cfg {Zarafa.core.WidgetInfo} info The Widget Meta Data object
	 * which was used to {@link Zarafa.core.Container#registerWidget register}
	 * this Widget to the {@link Zarafa.core.Container container}.
	 */
	info: undefined,

	/**
	 * @cfg {String} guid The unique identifier used for reference to this particular
	 * widget in side a {@link Zarafa.core.ui.widget.WidgetPanel}. This is used for
	 * {@link #set setting} and {@link #get getting} {@link Zarafa.settings.SettingsModel settings}.
	 */
	guid: undefined,

	/**
	 * @cfg {Boolean} hasConfig True if the {@link #config} function
	 * has been implemented and a 'gear' icon should be shown besides
	 * the close button.
	 */
	hasConfig: false,

	/**
	 * @cfg {String} about The about text. If provided, {@link #registerAboutText}
	 * will be automatically called during {@link #initWidget initialization}.
	 */
	about: undefined,

	/**
	 * The widget panel on which this widget is located
	 * @property
	 * @type Zarafa.core.ui.widget.WidgetPanel
	 */
	widgetPanel: undefined,

	/**
	 * {@link Ext.QuickTips tooltip} message for the widget's collapse {@link Ext.Button button}
	 * @property
	 * @type String
	 */
	collapseQuickTip: undefined,

	/**
	 * {@link Ext.QuickTips tooltip} message for the widget's expand {@link Ext.Button button}
	 * @property
	 * @type String
	 */
	expandQuickTip: undefined,

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

		var tools = config.tools || [];

		// Check if the configuration function has been
		// implemented. If it is the case, create the config tool.
		if (config.hasConfig === true) {
			tools.push({
				id: 'gear',
				qtip: _('Configure widget'),
				handler: this.config,
				scope: this
			});
		}

		// Always add the close tool.
		tools.push({
			id: 'close',
			qtip: _('Remove widget'),
			scope: this,
			handler: this.close
		});

		Ext.applyIf(config, {
			title: config.info.getDisplayName(),
			anchor: '100%',
			frame: true,
			collapsible: true,
			collapseQuickTip: _('Collapse widget'),
			expandQuickTip: _('Expand widget'),
			draggable: {
				ddGroup: 'dd.widget'
			},
			tools: tools
		});

		Zarafa.core.ui.widget.Widget.superclass.constructor.call(this, config);

		this.initWidget();
	},

	/**
	 * Called during rendering of the panel, this will initialize all events.
	 * @private
	 */
	initEvents: function ()
	{
		Zarafa.core.ui.widget.Widget.superclass.initEvents.call(this);
		this.on('afterlayout', this.setCollapseQuickTip, this, {single: true});
		this.on('expand', this.setCollapseQuickTip, this);
		this.on('collapse', this.setExpandQuickTip, this);
	},

	/**
	 * Event handler which is called after {@link Zarafa.core.ui.MainViewSidebar panel} get layout
	 * This will set {@link Ext.QuickTips} on {@link Ext.Button collapse} button
	 * @private
	 */
	setCollapseQuickTip: function ()
	{
		this.tools['toggle'].dom.qtip = this.collapseQuickTip;
	},

	/**
	 * Event handler which is called before {@link Zarafa.core.ui.MainViewSidebar panel} collapse
	 * This will set {@link Ext.QuickTips} on {@link Ext.Button expand} button
	 * @private
	 */
	setExpandQuickTip: function ()
	{
		this.tools['toggle'].dom.qtip = this.expandQuickTip;
	},

	/**
	 * Function to be implemented by Widget subclasses to initialize the widget.
	 * @protected
	 */
	initWidget: function()
	{
		// If the about text is provided, automatically register it
		if (!Ext.isEmpty(this.about)) {
			this.registerAboutText(this.title, this.about);
		}
	},

	/**
	 * Add a About/Copyright notice to the Widget.
	 * @param {String} title The title for the About widget
	 * @param {String} text The text which should be shown in the About text (may contain HTML)
	 * @protected
	 */
	registerAboutText: function(title, text)
	{
		this.tools.splice(this.tools.length - 1, 0, {
			id: 'help',
			qtip: _('About this widget'),
			handler: this.showAbout,
			scope: this,
			title: title,
			text: text
		});
	},

	/**
	 * Checks if this widget is currently visible. This returns true when
	 * the widget itself is {@link #isVisible visible} and the {@link #widgetPanel}
	 * is currently not {@link Ext.Panel#collapsed}.
	 */
	isWidgetVisible: function()
	{
		return this.isVisible() && !this.widgetPanel.collapsed;
	},

	/**
	 * Called when the widget has been rendered.
	 * This will initialize the {@link #widgetPanel}.
	 */
	onRender: function()
	{
		Zarafa.core.ui.widget.Widget.superclass.onRender.apply(this, arguments);
		this.widgetPanel = this.findParentByType('zarafa.widgetpanel');
	},

	/**
	 * Get a settings property..
	 * @param {String} key settings path. This is path relative to where the widget's settings are stored.
	 * @return {String} value.
	 */
	get: function(key)
	{
		return container.getSettingsModel().get(Zarafa.core.ui.widget.Widget.settingsPath(this.guid, key));
	},

	/**
	 * Set a settings property.
	 * @param {String} key settings path. This is path relative to where the widget's settings are stored.
	 * @param {String} value value.
	 */
	set: function(key, value)
	{
		container.getSettingsModel().set(Zarafa.core.ui.widget.Widget.settingsPath(this.guid, key), value);
	},

	/**
	 * Closes and destroys the widget, removing its settings from the settings tree.
	 */
	close: function(e, target, panel)
	{
		this.widgetPanel.destroyWidget(this);
	},

	/**
	 * Called when a user clicks the config button on the widget panel.
	 * Should be overridden by child classes.
	 * @protected
	 */
	config: Ext.emptyFn,

	/**
	 * Called when a user clicks the about button on the widget panel.
	 * This will show a {@link Ext.Window} containing the {@link #about} text.
	 * @protected
	 */
	showAbout: function(event, toolEl, panel, tc)
	{
		var win = new Ext.Window({
			title: tc.title,
			width: 320,
			height: 200,
			padding: 5,
			autoScroll: true,
			items: [{
				xtype: 'panel',
				layout: 'form',
				border: false,
				items: [{
					xtype: 'displayfield',
					value: tc.text,
					hideLabel: true,
					htmlEncode: false
				}]
			}]
		});

		win.show(this);
	}
});

Ext.reg('zarafa.widget', Zarafa.core.ui.widget.Widget);

/**
 * @param {String} guid The unique identifier for a {@link Zarafa.core.ui.widget.Widget widget}.
 * @param {String} key The setting key which should be updated
 * @return {String} The settings path for the guid & key combination.
 * @static
 */
Zarafa.core.ui.widget.Widget.settingsPath = function(guid, key)
{
	return ('zarafa/v1/widgets/' + guid + '/' + key).replace(/(\/)+/,'/');
};
Ext.namespace('Zarafa.core.ui.widget');

/**
 * @class Zarafa.core.ui.widget.WidgetMetaData
 * @extends Object
 *
 * The Meta Data object containing the registration details
 * of a {@link Zarafa.core.ui.widget.Widget}. An instance of this object
 * must be passed to {@link Zarafa.core.Container#registerWidget}.
 */
Zarafa.core.ui.widget.WidgetMetaData = Ext.extend(Object, {
	/**
	 * @cfg {String} name (required) The unique name for this widget.
	 * For a user-friendly name for UI components, see {@link #displayName}
	 */
	name: '',

	/**
	 * @cfg {String} displayName The display name for this widget. This
	 * will be used in places where the widget is referenced in UI components.
	 * If not provided, {@link #name} will be used.
	 */
	displayName: '',

	/**
	 * @cfg {String} iconCls The icon to be used in places where the widget is referenced
	 * in UI components.
	 */
	iconCls: '',

	/**
	 * @cfg {String} iconPath (deprecated) The path to the icon to be used in places where the widget is referenced
	 * in UI components. This configuration option is deprecated, it is preferred to use {@link #iconCls} instead.
	 */
	iconPath: '',

	/**
	 * @cfg {String} about The about text. If provided, {@link Zarafa.core.ui.widget.Widget#registerAboutText}
	 * will be automatically called during {@link Zarafa.core.ui.widget.Widget#initWidget initialization}.
	 */
	about: undefined,

	/**
	 * @cfg {Constructor} WidgetConstructor (required) The constructor of the
	 * {@link Zarafa.core.ui.widgetWidget} which is described by this WidgetMetaData instance.
	 */
	widgetConstructor: undefined,

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

		Ext.apply(this, config);

		Zarafa.core.ui.widget.WidgetMetaData.superclass.constructor.call(this, config);

		// Initialize displayName if not initialized from config
		if (Ext.isEmpty(this.displayName)) {
			this.displayName = this.name;
		}
	},

	/**
	 * Obtain the unique name for this widget
	 * @return {String} The unique name for this widget
	 */
	getName: function()
	{
		return this.name;
	},

	/**
	 * Obtain the display name for this widget
	 * @return {String} The display name for this widget
	 */
	getDisplayName: function()
	{
		return this.displayName;
	},

	/**
	 * Obtain the CSS classname for this widget
	 * @return {String} The CSS classname for this widget
	 */
	getIconCls: function()
	{
		return this.iconCls;
	},

	/**
	 * Obtain the path to the image for this widget
	 * @return {String} The path to the icon for this widget
	 */
	getIconPath: function()
	{
		return this.iconPath;
	},

	/**
	 * Obtain the About text containing the copyright and other disclaimers.
	 * @return {String} The about text for this widget
	 */
	getAbout: function()
	{
		return this.about;
	},

	/**
	 * Obtain the instance of the {@link Zarafa.core.ui.widget.Widget} which is instantiated
	 * using the {@link #widgetConstructor}.
	 * @param {Object} config The configuration object to apply
	 * @return {Zarafa.core.ui.widget.Widget} The Widget instance
	 */
	getInstance: function(config)
	{
		return new this.widgetConstructor(Ext.apply({ info: this }, config));
	}
});
Ext.namespace('Zarafa');

/**
 * @class Zarafa.ABOUT
 * @extends String
 *
 * The copyright string holding the copyright notice for grommunio Web.
 */

Zarafa.ABOUT = ""
	+ "<style>div.atxt h1{font-weight:inherit;font-style:italic;}</style><div class=\"atxt\">"
	+ "<p>Copyright (C) 2020 - 2026 grommunio GmbH</p>"

	+ "<p>This program is free software: you can redistribute it and/or modify "
	+ "it under the terms of the GNU Affero General Public License as "
	+ "published by the Free Software Foundation, either version 3 of the "
	+ "License, or (at your option) any later version.</p>"

	+ "<p>This program is distributed in the hope that it will be useful, "
	+ "but WITHOUT ANY WARRANTY; without even the implied warranty of "
	+ "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the "
	+ "GNU Affero General Public License for more details.</p>"

	+ "<p>You should have received a copy of the GNU Affero General Public License "
	+ "along with this program. If not, see <a href=\"https://www.gnu.org/licenses/\" target=\"_blank\">https://www.gnu.org/licenses/</a>.</p>"

	+ "<hr />"

	+ "<p>grommunio Web contains the following components:</p>"

	+ "<h1>grommunio Web</h1>"

	+ "<p>Copyright (C) 2020 - 2026 grommunio GmbH<br>"
	+ "Copyright (C) 2016 Kopano and its licensors<br>"
	+ "Copyright (C) 2005 - 2016 Zarafa B.V. and its licensors</p>"

	+ "<h1>grommunio Web File Previewer Plugin</h1>"

	+ "<p>Copyright (C) 2020 - 2026 grommunio GmbH<br>"
	+ "Copyright (C) 2016 Kopano and its licensors<br>"
	+ "Copyright (C) 2016 Zarafa B.V. and its licensors</p>"

	+ "<h1>grommunio Web Desktop Notifications Plugin</h1>"

	+ "<p>Copyright (C) 2020 - 2026 grommunio GmbH<br>"
	+ "Copyright (C) 2016 Kopano and its licensors<br>"
	+ "Copyright (C) 2013 Saket Patel</p>"

	+ "<h1>grommunio Web Meet Plugin</h1>"

	+ "<p>Copyright (C) 2020 - 2026 grommunio GmbH<br>"
	+ "Copyright (C) 2020 Siedl Networks<br>"
	+ "Copyright (C) 2017 Kopano and its licensors</p>"

	+ "<h1>grommunio Web Password Plugin</h1>"

	+ "<p>Copyright (C) 2020 - 2026 grommunio GmbH<br>"
	+ "Copyright (C) 2017 Kopano and its licensors<br>"
	+ "Copyright (C) 2013 - 2017 Saket Patel</p>"

	+ "<h1>grommunio Web Device Management Plugin</h1>"

	+ "<p>Copyright (C) 2020 - 2026 grommunio GmbH<br>"
	+ "Copyright (C) 2016 Kopano and its licensors<br>"
	+ "Copyright (C) 2005 - 2016 Zarafa B.V. and its licensors</p>"

	+ "<h1>grommunio Web Chat Plugin</h1>"

	+ "<p>Copyright (C) 2020 - 2026 grommunio GmbH<br>"
	+ "Copyright (C) 2017 Kopano and its licensors</p>"

	+ "<h1>grommunio Web Maps Plugin</h1>"

	+ "<p>Copyright (C) 2020 - 2026 grommunio GmbH<br>"
	+ "Copyright (C) 2016 Kopano and its licensors<br>"
	+ "Copyright (C) 2005 - 2016 Zarafa B.V. and its licensors</p>"

	+ "<h1>grommunio Web Files & Backend Plugin</h1>"

	+ "<p>Copyright (C) 2020 - 2026 grommunio GmbH<br>"
	+ "Copyright (C) 2016 Kopano and its licensors<br>"
	+ "Copyright (C) 2016 Zarafa B.V. and its licensors</p>"

	+ "<h1>grommunio Web Intranet Plugin</h1>"

	+ "<p>Copyright (C) 2020 - 2026 grommunio GmbH<br>"
	+ "Copyright (C) 2017 Kopano and its licensors</p>"

	+ "<h1>grommunio Web Kendox Plugin</h1>"

	+ "<p>Copyright (C) 2020 - 2026 grommunio GmbH<br>"
	+ "Copyright (C) 2021 Kendox and its licensors</br>"
        + "Copyright (C) 2021 NDG IT-Systeme GmbH</p>"

	+ "<p>This program is free software: you can redistribute it and/or modify "
	+ "it under the terms of the GNU Affero General Public License as "
	+ "published by the Free Software Foundation, either version 3 of the "
	+ "License, or (at your option) any later version.</p>"

	+ "<p>This program is distributed in the hope that it will be useful, "
	+ "but WITHOUT ANY WARRANTY; without even the implied warranty of "
	+ "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the "
	+ "GNU Affero General Public License for more details.</p>"

	+ "<p>You should have received a copy of the GNU Affero General Public License "
	+ "along with this program. "
	+ "If not, see <a href=\"https://www.gnu.org/licenses/\" target=\"_blank\">https://www.gnu.org/licenses/</a>.</p>"


	+ "<h1>Ext JS</h1>"

	+ "<p>Copyright (C) 2011-2013 Sencha Inc</p>"

	+ "<p>Ext JS is free software: you can redistribute it and/or modify "
	+ "it under the terms of the GNU General Public License version 3.0 as "
	+ "published by the Free Software Foundation."
	+ "<a href=\"https://www.gnu.org/copyleft/gpl.html\" target=\"_blank\">https://www.gnu.org/copyleft/gpl.html</a></p>"


	+ "<h1>Ext.ux.form.Metaform</h1>"

	+ "<p>Copyright (C) 2008 Ing. Jozef Sakáloša.</p>"

	+ "<p>Ext.ux.form.MetaForm is licensed under the terms of the Open Source LGPL 3.0 "
	+ "license. Commercial use is permitted to the extent that the code/component(s)"
	+ "do NOT become part of another Open Source or Commercially licensed development"
	+ "library or toolkit without explicit permission."
	+ "<a href=\"https://www.gnu.org/copyleft/lgpl.html\" target=\"_blank\">https://www.gnu.org/copyleft/lgpl.html</a></p>"


	+ "<h1>Phpfastcache</h1>"

	+ "<p>Copyright (c) 2023 Phpfastcache & its Extensions</p>"

	+ "<p>Permission is hereby granted, free of charge, to any person obtaining a copy of "
	+ "this software and associated documentation files (the Software), to deal in the "
	+ "Software without restriction, including without limitation the rights to use, "
	+ "copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the" 
	+ "Software, and to permit persons to whom the Software is furnished to do so, "
	+ "subject to the following conditions:</p>"

	+ "<p>The above copyright notice and this permission notice shall be included in all "
	+ "copies or substantial portions of the Software.</p>"

	+ "<p>THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR "
	+ "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, "
	+ "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE "
	+ "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER "
	+ "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, "
	+ "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE "
	+ "SOFTWARE.</p>"


	+ "<h1>Sabre-dav</h1>"

	+ "<p>Copyright (C) 2007-2016 fruux GmbH (https://fruux.com/)</p>"
	+ "<p>Sabre-dav is licensed under the BSD3 license.</p>"

	+ "<p>Permission is hereby granted, free of charge, to any person obtaining a copy "
	+ "of this software and associated documentation files (the \"Software\"), to deal "
	+ "in the Software without restriction, including without limitation the rights "
	+ "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell "
	+ "copies of the Software, and to permit persons to whom the Software is "
	+ "furnished to do so, subject to the following conditions:</p>"

	+ "<p>The above copyright notice and this permission notice shall be included in "
	+ "all copies or substantial portions of the Software.</p>"

	+ "<p>THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR "
	+ "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, "
	+ "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE "
	+ "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER "
	+ "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, "
	+ "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN "
	+ "THE SOFTWARE.</p>"

	+ "<p>Redistribution and use in source and binary forms, with or without "
	+ "modification, are permitted provided that the following conditions are met:</p>"

	+ "<p><ul><li>Redistributions of source code must retain the above copyright notice, this "
	+ "list of conditions and the following disclaimer.</li>"
	+ "<li>Redistributions in binary form must reproduce the above copyright notice, "
	+ "this list of conditions and the following disclaimer in the documentation "
	+ "and/or other materials provided with the distribution.</li>"
	+ "<li>Neither the name of SabreDAV nor the names of its contributors may be used "
	+ "to endorse or promote products derived from this software without specific "
	+ "prior written permission.</li></p>"

	+ "<p>THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND "
	+ "ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED "
	+ "WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE "
	+ "DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR "
	+ "ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES "
	+ "(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; "
	+ "LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON "
	+ "ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT "
	+ "(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS "
	+ "SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.</p>"


	+ "<h1>Printer rendering</h1>"

	+ "<p>Copyright (C) 2010 Ed Spencer</p>"

	+ "<p>Permission is hereby granted, free of charge, to any person obtaining a copy "
	+ "of this software and associated documentation files (the \"Software\"), to deal "
	+ "in the Software without restriction, including without limitation the rights "
	+ "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell "
	+ "copies of the Software, and to permit persons to whom the Software is "
	+ "furnished to do so, subject to the following conditions:</p>"

	+ "<p>The above copyright notice and this permission notice shall be included in "
	+ "all copies or substantial portions of the Software.</p>"

	+ "<p>THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR "
	+ "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, "
	+ "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE "
	+ "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER "
	+ "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, "
	+ "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN "
	+ "THE SOFTWARE.</p>"


	+ "<h1>TinyMCE</h1>"

	+ "<p>Copyright (c) 2025, Ephox Corporation DBA Tiny Technologies, Inc.</p>"

	+ "<p>TinyMCE is free software: you can redistribute it and/or modify "
	+ "under the terms of the GNU General Public License "
	+ "as published by the Free Software Foundation; either version 2 "
	+ "of the License, or (at your option) any later version.</p>"

	+ "<p>TinyMCE is distributed in the hope that it will be useful, "
	+ "but WITHOUT ANY WARRANTY; without even the implied warranty of "
	+ "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU "
	+ "General Public License for more details.</p>"

	+ "<p>You should have received a copy of the GNU General Public "
	+ "license along with program; "
	+ "if not, see <a href=\"https://www.gnu.org/licenses/\" target=\"_blank\">https://www.gnu.org/licenses/</a>.</p>"


	+ "<h1>Tokenizr</h1>"

	+ "<p>Copyright (c) 2015-2025 Dr. Ralf S. Engelschall <a href=\"mailto:rse@engelschall.com\">rse@engelschall.com</a></p>"

	+ "<p>Permission is hereby granted, free of charge, to any person obtaining"
	+ "a copy of this software and associated documentation files (the"
	+ "\"Software\"), to deal in the Software without restriction, including"
	+ "without limitation the rights to use, copy, modify, merge, publish,"
	+ "distribute, sublicense, and/or sell copies of the Software, and to"
	+ "permit persons to whom the Software is furnished to do so, subject to"
	+ "the following conditions:</p>"

	+ "<p>The above copyright notice and this permission notice shall be included"
	+ "in all copies or substantial portions of the Software.</p>"

	+ "<p>THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,"
	+ "EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF"
	+ "MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT."
	+ "IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY"
	+ "CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,"
	+ "TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE"
	+ "SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.</p>"


	+ "<h1>DOMPurify</h1>"

	+ "<p>Copyright 2025 Dr.-Ing. Mario Heiderich, Cure53</p><p>Licensed under the Apache License, version 2.0.</p>"


	+ "<h1>ViewerJS</h1>"

	+ "<p>Copyright (C) 2015 viewerjs.org</p>"


	+ "<h1>Leaflet</h1>"

	+ "<p>Copyright (C) 2010-2025 Vladimir Agafonkin</p>"
	+ "<p>Copyright (C) 2010-2011 CloudMade</p>"

	+ "<p>Redistribution and use in source and binary forms, with or without"
	+ "modification, are permitted provided that the following conditions are met:</p>"

	+ "<p>1. Redistributions of source code must retain the above copyright notice, this"
	+ "list of conditions and the following disclaimer.</p>"

	+ "<p>2. Redistributions in binary form must reproduce the above copyright notice,"
	+ "this list of conditions and the following disclaimer in the documentation"
	+ "and/or other materials provided with the distribution.</p>"

	+ "<p>THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\""
	+ "AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE"
	+ "IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE"
	+ "DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE"
	+ "FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL"
	+ "DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR"
	+ "SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER"
	+ "CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,"
	+ "OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE"
	+ "OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.</p>"


	+ "<h1>Leaflet.GeoSearch</h1>"

	+ "<p>Copyright (c) 2010-2016 Stephan Meijer</p>"

	+ "<p>Permission is hereby granted, free of charge, to any person obtaining"
	+ "a copy of this software and associated documentation files (the"
	+ "\"Software\"), to deal in the Software without restriction, including"
	+ "without limitation the rights to use, copy, modify, merge, publish,"
	+ "distribute, sublicense, and/or sell copies of the Software, and to"
	+ "permit persons to whom the Software is furnished to do so, subject to"
	+ "the following conditions:</p>"

	+ "<p>The above copyright notice and this permission notice shall be included"
	+ "in all copies or substantial portions of the Software.</p>"

	+ "<p>THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,"
	+ "EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF"
	+ "MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT."
	+ "IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY"
	+ "CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,"
	+ "TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE"
	+ "SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.</p>"


	+ "<h1>PDF.js</h1>"

	+ "<p>Copyright (C) Mozilla and individual contributors</p><p>Licensed under the Apache License, version 2.0.</p>"


	+ "<h1>docx-preview</h1>"

	+ "<p>Copyright (C) Volodymyr Baydalka</p><p>Licensed under the Apache License, version 2.0.</p>"


	+ "<h1>JSZip</h1>"

	+ "<p>Copyright (C) 2009-2016 Stuart Knightley and contributors</p><p>Licensed under the MIT license.</p>"


	+ "<h1>SheetJS</h1>"

	+ "<p>Copyright (C) 2012-present SheetJS LLC</p><p>Licensed under the Apache License, version 2.0.</p>"

	+ "</div>";
/**
 * @class CanvasRenderingContext2D
 * @extends Object
 * 
 * Some convenience methods for the default canvas 2D context. These allow of drawing
 * circles, lines, text with auto-wrapping, etc. 
 */
if (!!document.createElement('canvas').getContext)
{
	/**
	 * Creates a circular path on the canvas.
	 * @param {Number} x center horizontal position.
	 * @param {Number} y center vertical position.
	 * @param {Number} radius circle radius.
	 */
	CanvasRenderingContext2D.prototype.circle = function(x, y, radius)
	{
		this.beginPath();
		this.arc(x, y, radius, 0, Math.PI*2, true); 
		this.closePath();
	};
	
	/**
	 * Strokes a straight line between two points.
	 * @param {Number} x1 horizontal component of the start point. 
	 * @param {Number} y1 vertical component of the start point.
	 * @param {Number} x2 horizontal component of the end point.
	 * @param {Number} y2 vertical component of the end point.
	 */
	CanvasRenderingContext2D.prototype.strokeLine = function(x1, y1, x2, y2)
	{
		this.beginPath();
		this.moveTo(x1, y1);
		this.lineTo(x2, y2);
		this.stroke();
	};

	/**
	 * Gets the font size from CSS font specifier string. font property of canvas object contains font specification.
	 * @return {Number} current font size of the text
	 */
	CanvasRenderingContext2D.prototype.getFontSize = function()
	{
		var fontSizeRegExp = new RegExp('([0-9]*)p[tx]');
		return parseInt(fontSizeRegExp.exec(this.font)[1], 10);
	};

	/**
	 * Draws a string of text.
	 * @param {String} text text to draw.
	 * @param {Number} x horizontal position.
	 * @param {Number} y vertical position.
	 * @param {Number} maxWidth (optional) maximum width in pixels. 
	 */
	CanvasRenderingContext2D.prototype.drawText = function(text, x, y, maxWidth)
	{
		if (maxWidth) {
			this.fillText(text, x, y, maxWidth);
		} else {
			this.fillText(text, x, y);
		}
	};

	/**
	 * Draws a string of wrapped text. 
	 * @param {String} text text to draw.
	 * @param {Number} x horizontal position.
	 * @param {Number} y vertical position.
	 * @param {Number} width width.
	 * @param {Number} lineHeight number of pixels to advance down after each line break.
	 * @param {Number} maxHeight (optional) maximum height in pixels
	 * @param {Number} clipX (optional) The number of pixels horizontally that should be clipped (from the left-top corner).
	 * @param {Number} clipY (optional) The number of pixels vertically that should be clipped (from the left-top corner).
	 * @return {Number} Total height of the drawn text
	 */
	CanvasRenderingContext2D.prototype.drawWrappedText = function(text, x, y, width, lineHeight, maxHeight, clipX, clipY)
	{
		// If the text contains '\n' characters, we have to split the text up
		// into individual lines which we can then draw on the canvas.
		while (text[text.length - 1] === '\n') {
			text = text.substr(0, text.length - 1);
		}
		var lines = text.split('\n');
		if (lines.length > 1) {
			var totalHeight = 0;

			for (var i = 0; i < lines.length; i++) {
				var offset = this.drawWrappedText(lines[i], x, y, width, lineHeight, maxHeight, clipX, clipY);
				y += offset;
				totalHeight += offset;
				maxHeight -= offset;
			}

			return totalHeight;
		}

		// split the text into words
		var words = text.split(' ');
		
		// number of pixels to advance horizontally after drawing a single word
		var spaceWidth = this.textWidth(' ');

		// Check the clipping options
		clipX = clipX || 0;
		clipY = clipY || 0;

		// render each word individually
		var tx = x + clipX;
		var ty = y;
		for (var i = 0, len = words.length; i < len; i++) {
			var word = words[i];
			var wordWidth = this.textWidth(word);

			// Test if the word will fit on this line
			if (((tx - x) + wordWidth) >= width && tx !== x) {
				var moveLine = true;

				// It doesn't fit, check if it will
				// fit if we move to the next line
				if (wordWidth >= width) {
					// It won't, lets see if at least half
					// the word will fit the current line
					if (((tx - x) + (wordWidth / 2)) < width) {
						// It will, just print it
						moveLine = false;
					}
				}

				if (moveLine) {
					ty += lineHeight;
					if (ty > clipY) {
						clipX=0;
					}
					tx = x + clipX;
					// See if we are going to write the next line
					// across the limit
					if ((ty - y) > maxHeight) {
						break;
					}
				}
			}

			this.drawText(word, tx, ty);
			tx += wordWidth + spaceWidth;
		}

		// Calculate the written height, this means that the current
		// offset must be subtracted by the start offset. And finally
		// we need to add a lineHeight, as we have written at least
		// 1 line.
		return ty - y + lineHeight;
	};

	/**
	 * Measures the height of a text when wrapped to a certain width. 
	 * @param {String} text text to draw.
	 * @param {Number} width width.
	 * @param {Number} lineHeight number of pixels to advance down after each line break.
	 * @return {Number} height of the text
	 */
	CanvasRenderingContext2D.prototype.textHeight = function(text, width, lineHeight)
	{
		// If the text contains '\n' characters, we have to split the text up
		// into individual lines which we can then draw on the canvas.
		while(text[text.length - 1] === '\n') {
			text = text.substr(0, text.length - 1);
		}
		var lines = text.split('\n');
		if (lines.length > 1) {
			var totalHeight = 0;

			for (var i = 0; i < lines.length; i++) {
				var offset = this.textHeight(lines[i], width, lineHeight);
				totalHeight += offset;
			}

			return totalHeight;
		}

		// split the text into words
		var words = text.split(' ');
		
		// number of pixels to advance horizontally after drawing a single word
		var spaceWidth = this.textWidth(' ');
		
		// render each word individually
		var tx = 0;
		var ty = 0;
		for (var i = 0, len = words.length; i < len; i++) {
			var word = words[i];
			var wordWidth = this.textWidth(word);

			// Test if the word will fit on this line
			if ((tx + wordWidth) >= width && tx !== 0) {
				var moveLine = true;

				// It doesn't fit, check if it will
				// fit if we move to the next line
				if (wordWidth >= width) {
					// It won't, lets see if at least half
					// the word will fit the current line
					if ((tx + (wordWidth / 2)) < width) {
						// It will, just print it
						moveLine = false;
					}
				}

				if (moveLine) {
					tx = 0;
					ty += lineHeight;
				}
			}

			tx += wordWidth + spaceWidth;
		}
		ty += lineHeight;
		return ty;
	};

	/**
	 * Measures a piece of text.
	 * @param {String} text text to measure.
	 * @return {Number} the width in pixels of the given text string if rendered with the current font. 
	 */
	CanvasRenderingContext2D.prototype.textWidth = function(text)
	{
		return this.measureText(text).width;
	};
	
	/**
	 * Converts the hexadecimal RGB notation (#FFAA00) to a decimal notation using the rgba 
	 * function. It will output a string that can be used in defining colors and opacity in Canvas. 
	 * By default it will return rgba(0,0,0,0) if no or incorrect values are passed. This is a black
	 * and completely transparent rgba() value.
	 * @param {String} str Hexadecimal RGB notation like #FFAA00. The '#' is optional, the rest of 
	 * the strings needs to contain 6 characters.
	 * @param {Number} opacity (Optional) Will set the opacity in the rgba function. Defaults to 1.
	 * @return {String} The rgba() notation. 
	 */
	CanvasRenderingContext2D.prototype.convertHexRgbToDecRgba = function(str, opacity)
	{
		// The regex ensures that the input has at least 6 RGB characters and the preceding # is optional
		if(Ext.isString(str) && str.search(/#?[A-F0-9]{6}/i) === 0){
			if(!Ext.isNumber(opacity)){
				opacity = 1;
			}

			var decRGB = [];
			str = str.replace('#','');
			decRGB[0] = parseInt(str.substr(0, 2), 16);
			decRGB[1] = parseInt(str.substr(2, 2), 16);
			decRGB[2] = parseInt(str.substr(4, 2), 16);
			return 'rgba('+decRGB[0]+','+decRGB[1]+','+decRGB[2]+','+opacity+')';
		}

		// Will return a black, completely transparent rgba() value
		return 'rgba(0,0,0,0)';
	};
}
Ext.namespace('Zarafa.addressbook');

/**
 * @class Zarafa.addressbook.Actions
 * Common actions which can be used within {@link Ext.Button buttons}
 * or other {@link Ext.Component components} with action handlers.
 * @singleton
 */
Zarafa.addressbook.Actions = {
	/**
	 * Open the address book
	 * @param {Object} config (optional) Configuration object used to create the ContentPanel
	 */
	openAddressBook: function(config)
	{
		config = Ext.applyIf(config || {}, {
			modal: true
		});

		var componentType = Zarafa.core.data.SharedComponentType['addressbook.dialog.addressbook'];
		Zarafa.core.data.UIFactory.openLayerComponent(componentType, undefined, config);
	},

	/**
	 * Open the details panel for the selected records
	 * @param {Zarafa.core.data.IPMRecord} records The records for which
	 * the details {@link Zarafa.core.ui.ContentPanel contentpanel} must be shown
	 * @param {Object} config (optional) Configuration object used to create the ContentPanel
	 */
	openDetailsContent: function(records, config)
	{
		if (Array.isArray(records) && !Ext.isEmpty(records)) {
			records = records[0];
		}
		if (records.isPersonalDistList()) {
			// A personal distlist needs to be converted to a distlist so the correct panel can be shown.
			// This must be checked before isSharedContact() because shared distlists also
			// have is_shared set and would incorrectly be opened as contacts.
			records = records.convertToDistListRecord();
			// FIXME: We put the abRecord into the ShadowStore to be able
			// to open it, and obtain all details. However, we also need to
			// find a point where we can remove it again.
			container.getShadowStore().add(records);
		} else if (records.isPersonalContact() || records.isSharedContact()) {
			// A personal contact needs to be converted to a contact so the correct panel can be shown.
			records = records.convertToContactRecord();
			// FIXME: We put the abRecord into the ShadowStore to be able
			// to open it, and obtain all details. However, we also need to
			// find a point where we can remove it again.
			container.getShadowStore().add(records);
		}

		config = Ext.applyIf(config || {}, {
			modal: true
		});

		Zarafa.core.data.UIFactory.openViewRecord(records, config);
	}
};
Ext.namespace('Zarafa.addressbook.dialogs');

/**
 * @class Zarafa.addressbook.dialogs.ABEmailAddressTab
 * @extends Ext.form.FormPanel
 * @xtype zarafa.abemailaddresstab
 *
 * This class is used to create layout of email address tab panel.
 */
Zarafa.addressbook.dialogs.ABEmailAddressTab = Ext.extend(Ext.form.FormPanel, {
	/**
	 * @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, {
			xtype: 'zarafa.abemailaddresstab',
			title: _('Email Addresses'),
			bodyStyle: 'padding: 5px;',
			layout: {
				type: 'vbox',
				pack: 'start',
				align: 'stretch'
			},
			items: [{
				xtype: 'fieldset',
				title: _('Email addresses'),
				border: true,
				cls: 'zarafa-fieldset',
				flex: 1,
				layout: 'fit',
				items: [{
					xtype: 'grid',
					ref: '../emailList',
					store: new Ext.data.JsonStore({
						fields: ['address'],
						root: 'item'
					}),
					border: true,
					hideHeaders: false,
					columns: [{
						header: _('Email Address'),
						dataIndex: 'address',
						sortable: true,
						renderer: function(value) {
							if (value && value.indexOf(':') !== -1) {
								return Ext.util.Format.htmlEncode(value.substring(value.indexOf(':') + 1));
							}
							return Ext.util.Format.htmlEncode(value || '');
						}
					}],
					viewConfig: {
						forceFit: true
					}
				}]
			}]
		});

		Zarafa.addressbook.dialogs.ABEmailAddressTab.superclass.constructor.call(this, config);
	},

	/**
	 * Function is used to update values of form fields when ever
	 * an updated {@link Zarafa.core.data.MAPIRecord record} is received
	 * @param {Zarafa.core.data.MAPIRecord} record The record update the panel with.
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 * @private
	 */
	update: function(record, contentReset)
	{
		this.getForm().loadRecord(record);

		var proxyAddressSubStore = record.getSubStore('ems_ab_proxy_addresses');
		if (proxyAddressSubStore && this.emailList.getStore() !== proxyAddressSubStore) {
			this.emailList.reconfigure(proxyAddressSubStore, this.emailList.getColumnModel());
		}
	}
});

Ext.reg('zarafa.abemailaddresstab', Zarafa.addressbook.dialogs.ABEmailAddressTab);
Ext.namespace('Zarafa.addressbook.dialogs');

/**
 * @class Zarafa.addressbook.dialogs.ABGroupDetailPanel
 * @extends Ext.TabPanel
 * @xtype zarafa.abgroupdetailpanel
 */
Zarafa.addressbook.dialogs.ABGroupDetailPanel = Ext.extend(Ext.TabPanel, {
	// Insertion points for this class
	/**
	 * @insert context.addressbook.abgroupdetailcontentpanel.tabs
	 * can be used to add extra tabs to addressbook group details dialog by 3rd party plugins
	 * @param {Zarafa.addressbook.dialogs.ABGroupDetailPanel} panel This contactpanel
	 */

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

		Ext.applyIf(config, {
			xtype: 'zarafa.abgroupdetailpanel',
			border: false,
			activeTab: 0,
			items: [{
				xtype: 'zarafa.abgroupgeneraltab'
			}, {
				xtype: 'zarafa.abmemberoftab'
			},{
				xtype: 'zarafa.abemailaddresstab'
			},
			// Add insertion point
			container.populateInsertionPoint('context.addressbook.abgroupdetailcontentpanel.tabs', this)
			]
		});

		Zarafa.addressbook.dialogs.ABGroupDetailPanel.superclass.constructor.call(this, config);
	}
});

Ext.reg('zarafa.abgroupdetailpanel', Zarafa.addressbook.dialogs.ABGroupDetailPanel);
Ext.namespace('Zarafa.addressbook.dialogs');

/**
 * @class Zarafa.addressbook.dialogs.ABGroupGeneralTab
 * @extends Ext.form.FormPanel
 * @xtype zarafa.abgroupgeneraltab
 *
 * This class is used to create layout of general tab in tab panel.
 */
Zarafa.addressbook.dialogs.ABGroupGeneralTab = Ext.extend(Ext.form.FormPanel, {

	/**
	 * @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, {
			xtype: 'zarafa.abgroupgeneraltab',
			title: _('General'),
			layout: 'column',
			autoScroll: true,
			bodyStyle: 'padding: 5px;',
			defaults: {
				border: false,
				style: 'padding: 2px 5px; margin: 0;'
			},
			items: [
				this.createNameFieldset(),
				this.createMembersFieldset()
			]
		});

		Zarafa.addressbook.dialogs.ABGroupGeneralTab.superclass.constructor.call(this, config);
	},

	/**
	 * Creates fieldset for general tab of tab panel.
	 * @return {Object} config object for creating {@link Ext.form.FieldSet FieldSet}.
	 * @private
	 */
	createNameFieldset: function()
	{
		return {
			xtype: 'panel',
			columnWidth: 0.5,
			layout: 'form',
			items: [{
				xtype: 'textfield',
				fieldLabel: _('Display Name'),
				name: 'display_name',
				anchor: '100%',
				readOnly: true
			},{
				xtype: 'textfield',
				fieldLabel: _('Alias'),
				name: 'account',
				anchor: '100%',
				readOnly: true
			},{
				xtype: 'displayfield',
				value: _('Notes') + ':',
				hideLabel: true
			},{
				xtype: 'textarea',
				hideLabel: true,
				fieldLabel: _('Notes'),
				name: 'comment',
				anchor: '100%',
				height: 120,
				readOnly: true
			}]
		};
	},

	/**
	 * Creates fieldset for general tab of tab panel.
	 * @return {Object} config object for creating {@link Ext.form.FieldSet FieldSet}.
	 * @private
	 */
	createMembersFieldset: function()
	{
		return {
			xtype: 'panel',
			columnWidth: 0.5,
			layout: 'fit',
			title: _('Members'),
			items: [{
				xtype: 'zarafa.abitemgrid',
				height: 250,
				ref: '../groupMembersList'
			}]
		};
	},

	/**
	 * Function is used to update values of form fields when ever
	 * an updated {@link Zarafa.core.data.MAPIRecord record} is received
	 * @param {Zarafa.core.data.MAPIRecord} record The record update the panel with.
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 * @private
	 */
	update: function(record, contentReset)
	{
		this.getForm().loadRecord(record);

		var membersSubStore = record.getSubStore('members');
		if (membersSubStore && this.groupMembersList.getStore() !== membersSubStore) {
			this.groupMembersList.reconfigure(membersSubStore, this.groupMembersList.getColumnModel());
		}
	}
});

Ext.reg('zarafa.abgroupgeneraltab', Zarafa.addressbook.dialogs.ABGroupGeneralTab);
Ext.namespace('Zarafa.addressbook.dialogs');

/**
 * @class Zarafa.addressbook.dialogs.ABItemGrid
 * @extends Ext.grid.GridPanel
 * @xtype zarafa.abitemgrid
 *
 * A gridPanel which is used in the Addressbook detail dialogs
 * for showing one or more addressbook items.
 */
Zarafa.addressbook.dialogs.ABItemGrid = Ext.extend(Ext.grid.GridPanel, {

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

		Ext.applyIf(config, {
			viewConfig: {
				forceFit: true
			},
			store: new Zarafa.addressbook.AddressBookSubStore(),
			colModel: new Ext.grid.ColumnModel({
				columns: [{
					dataIndex: 'display_type',
					header: '',
					headerCls: 'zarafa-icon-column icon',
					width: 25,
					fixed: true,
					renderer: Zarafa.common.ui.grid.Renderers.icon
				},{
					header: _('Display Name'),
					dataIndex: 'display_name',
					renderer: Ext.util.Format.htmlEncode,
					headerCls: 'k-unsortable',
					sortable: false
				},{
					header: _('Email Address'),
					dataIndex: 'smtp_address',
					renderer: Ext.util.Format.htmlEncode,
					headerCls: 'k-unsortable',
					sortable: false
				},{
					header: _('Account'),
					dataIndex: 'account',
					renderer: Ext.util.Format.htmlEncode,
					headerCls: 'k-unsortable',
					sortable: false
				}]
			})
		});

		Zarafa.addressbook.dialogs.ABItemGrid.superclass.constructor.call(this, config);

		this.on('rowdblclick', this.onRowDblClick, this);
	},

	/**
	 * Event handler which is fired when a row in the grid has been double-clicked.
	 * This will open the selected addressbook item in a new dialog.
	 * @param {Ext.grid.GridPanel} grid The grid which fired the event
	 * @param {Number} rowIndex The index of the row which was double clicked
	 * @param {Ext.EventObject} event The event
	 */
	onRowDblClick: function(grid, rowIndex, event)
	{
		var item = this.getStore().getAt(rowIndex);
		if (item) {
			Zarafa.core.data.UIFactory.openViewRecord(item);
		}
	}
});

Ext.reg('zarafa.abitemgrid', Zarafa.addressbook.dialogs.ABItemGrid);
Ext.namespace('Zarafa.addressbook.dialogs');

/**
 * @class Zarafa.addressbook.dialogs.ABMemberOfTab
 * @extends Ext.form.FormPanel
 * @xtype zarafa.abmemberoftab
 *
 * This class is used to create layout of MemberOf tab panel.
 */
Zarafa.addressbook.dialogs.ABMemberOfTab = Ext.extend(Ext.form.FormPanel, {
	/**
	 * @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, {
			xtype: 'zarafa.abmemberoftab',
			title: _('Member Of'),
			bodyStyle: 'padding: 5px;',
			layout: {
				type: 'vbox',
				align: 'stretch'
			},
			items: [{
				xtype: 'fieldset',
				title: _('Group Membership'),
				border: true,
				cls: 'zarafa-fieldset',
				flex: 1,
				layout: 'fit',
				items: [{
					xtype: 'zarafa.abitemgrid',
					ref: '../memberOfList'
				}]
			}]
		});

		Zarafa.addressbook.dialogs.ABMemberOfTab.superclass.constructor.call(this, config);
	},

	/**
	 * Function is used to update values of form fields when ever
	 * an updated {@link Zarafa.core.data.MAPIRecord record} is received
	 * @param {Zarafa.core.data.MAPIRecord} record The record update the panel with.
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 * @private
	 */
	update: function(record, contentReset)
	{
		this.getForm().loadRecord(record);
		var memberOfSubStore = record.getSubStore('ems_ab_is_member_of_dl');
		if (memberOfSubStore && this.memberOfList.getStore() !== memberOfSubStore) {
			this.memberOfList.reconfigure(memberOfSubStore, this.memberOfList.getColumnModel());
		}
	}
});

Ext.reg('zarafa.abmemberoftab', Zarafa.addressbook.dialogs.ABMemberOfTab);
Ext.namespace('Zarafa.addressbook.dialogs');

/**
 * @class Zarafa.addressbook.dialogs.ABUserDetailPanel
 * @extends Ext.TabPanel
 * This class is used as wrapper class for all tabs, individual tab will have its own class
 * @xtype zarafa.abuserdetailpanel
 */
Zarafa.addressbook.dialogs.ABUserDetailPanel = Ext.extend(Ext.TabPanel,{
	// Insertion points for this class
	/**
	 * @insert context.addressbook.abuserdetailcontentpanel.tabs
	 * can be used to add extra tabs to addressbook user details dialog by 3rd party plugins
	 * @param {Zarafa.addressbook.dialogs.ABUserDetailPanel} panel This contactpanel
	 */

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

		Ext.applyIf(config, {
			xtype: 'zarafa.abuserdetailpanel',
			border: false,
			activeTab: 0,
			items: [{
				xtype: 'zarafa.abusergeneraltab'
			}, {
				xtype: 'zarafa.abuserorganizationtab'
			},{
				xtype: 'zarafa.abuserphonetab'
			}, {
				xtype: 'zarafa.abmemberoftab'
			},{
				xtype: 'zarafa.abemailaddresstab'
			},
			// Add insertion point
			container.populateInsertionPoint('context.addressbook.abuserdetailcontentpanel.tabs', this)
			]
		});

		Zarafa.addressbook.dialogs.ABUserDetailPanel.superclass.constructor.call(this, config);
	}
});

Ext.reg('zarafa.abuserdetailpanel', Zarafa.addressbook.dialogs.ABUserDetailPanel);
Ext.namespace('Zarafa.addressbook.dialogs');

/**
 * @class Zarafa.addressbook.dialogs.ABUserGeneralTab
 * @extends Ext.form.FormPanel
 * @xtype zarafa.abusergeneraltab
 *
 * This class is used to create layout of general tab in tab panel.
 */
Zarafa.addressbook.dialogs.ABUserGeneralTab = Ext.extend(Ext.form.FormPanel, {

	/**
	 * @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, {
			xtype: 'zarafa.abusergeneraltab',
			title: _('General'),
			autoScroll: true,
			border: false,
			cls: 'k-ab-general-tab',
			items: [{
				xtype: 'container',
				cls: 'k-ab-identity',
				items: [{
					xtype: 'panel',
					border: false,
					layout: 'form',
					cls: 'k-ab-name-fields',
					labelSeparator: '',
					defaults: {
						anchor: '100%',
						readOnly: true
					},
					items: [{
						xtype: 'textfield',
						fieldLabel: _('First name'),
						name: 'given_name'
					},{
						xtype: 'textfield',
						fieldLabel: _('Last name'),
						name: 'surname'
					},{
						xtype: 'textfield',
						fieldLabel: _('Initials'),
						name: 'initials'
					},{
						xtype: 'textfield',
						fieldLabel: _('Display'),
						name: 'display_name'
					},{
						xtype: 'textfield',
						fieldLabel: _('Alias'),
						name: 'account'
					}]
				},{
					xtype: 'container',
					cls: 'k-ab-photo-ct',
					items: [{
						xtype: 'box',
						cls: 'contact_photo_box default_contact_photo k-ab-photo',
						autoEl: {
							tag: 'img',
							src: Ext.BLANK_IMAGE_URL
						},
						ref: '../../contactPhotoBox'
					}]
				}]
			},{
				xtype: 'container',
				layout: 'column',
				cls: 'k-ab-details',
				items: [{
					xtype: 'panel',
					columnWidth: 0.5,
					layout: 'form',
					border: false,
					cls: 'k-ab-detail-col',
					title: _('Address'),
					labelSeparator: '',
					defaults: {
						anchor: '100%',
						readOnly: true
					},
					items: [{
						xtype: 'textarea',
						fieldLabel: _('Street'),
						name: 'street_address',
						height: 62
					},{
						xtype: 'textfield',
						fieldLabel: _('City'),
						name: 'locality'
					},{
						xtype: 'textfield',
						fieldLabel: _('State'),
						name: 'state_or_province'
					},{
						xtype: 'textfield',
						fieldLabel: _('Zip Code'),
						name: 'postal_code'
					},{
						xtype: 'textfield',
						fieldLabel: _('Country'),
						name: 'country'
					}]
				},{
					xtype: 'panel',
					columnWidth: 0.5,
					layout: 'form',
					border: false,
					cls: 'k-ab-detail-col',
					title: _('Professional'),
					labelSeparator: '',
					defaults: {
						anchor: '100%',
						readOnly: true
					},
					items: [{
						xtype: 'textfield',
						fieldLabel: _('Title'),
						name: 'title'
					},{
						xtype: 'textfield',
						fieldLabel: _('Company'),
						name: 'company_name'
					},{
						xtype: 'textfield',
						fieldLabel: _('Department'),
						name: 'department_name'
					},{
						xtype: 'textfield',
						fieldLabel: _('Office'),
						name: 'office_location'
					},{
						xtype: 'textfield',
						fieldLabel: _('Assistant'),
						name: 'assistant'
					},{
						xtype: 'textfield',
						fieldLabel: _('Phone'),
						name: 'business_telephone_number'
					}]
				}]
			}]
		});

		Zarafa.addressbook.dialogs.ABUserGeneralTab.superclass.constructor.call(this, config);
	},

	/**
	 * Function is used to update values of form fields when ever
	 * an updated {@link Zarafa.core.data.MAPIRecord record} is received
	 * @param {Zarafa.core.data.MAPIRecord} record The record update the panel with.
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 * @private
	 */
	update: function(record, contentReset)
	{
		if(Ext.isEmpty(record)) {
			return;
		}

		this.getForm().loadRecord(record);

		if (record.isOpened() && contentReset) {
			var abThumbnail = record.get('ems_ab_thumbnail_photo');
			if (!Ext.isEmpty(abThumbnail)) {
				var imageField = this.contactPhotoBox.getEl();
				imageField.dom.setAttribute('src', abThumbnail);
				imageField.removeClass('default_contact_photo');
			}
		}
	}
});

Ext.reg('zarafa.abusergeneraltab', Zarafa.addressbook.dialogs.ABUserGeneralTab);
Ext.namespace('Zarafa.addressbook.dialogs');

/**
 * @class Zarafa.addressbook.dialogs.ABUserOrganizationTab
 * @extends Ext.form.FormPanel
 * @xtype zarafa.abuserorganizationtab
 *
 * This class is used to create layout of Organization tab of tab panel.
 */
Zarafa.addressbook.dialogs.ABUserOrganizationTab = Ext.extend(Ext.form.FormPanel, {
	/**
	 * @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, {
			xtype: 'zarafa.abuserorganizationtab',
			title: _('Organization'),
			selModel: this.initSelectionModel(),
			bodyStyle: 'padding: 5px;',
			layout: {
				type: 'vbox',
				align: 'stretch'
			},
			autoScroll: true,
			items: [{
				xtype: 'fieldset',
				title: _('Manager'),
				border: true,
				cls: 'zarafa-fieldset',
				flex: 1,
				layout: 'fit',
				items: [{
					xtype: 'zarafa.abitemgrid',
					ref: '../managerList'
				}]
			},{
				xtype: 'fieldset',
				title: _('Direct reports'),
				border: true,
				cls: 'zarafa-fieldset',
				flex: 1,
				layout: 'fit',
				items: [{
					xtype: 'zarafa.abitemgrid',
					ref: '../reportList'
				}]
			}]
		});

		Zarafa.addressbook.dialogs.ABUserOrganizationTab.superclass.constructor.call(this, config);
	},

	/**
	 * creates and returns a selection model object, used in {@link Ext.grid.GridPanel.selModel selModel} config
	 * @return {Ext.grid.RowSelectionModel} selection model object
	 * @private
	 */
	initSelectionModel: function()
	{
		return new Ext.grid.RowSelectionModel({
			singleSelect: false
		});
	},

	/**
	 * Function is used to update values of form fields when ever
	 * an updated {@link Zarafa.core.data.MAPIRecord record} is received
	 * @param {Zarafa.core.data.MAPIRecord} record The record update the panel with.
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 * @private
	 */
	update: function(record, contentReset)
	{
		this.getForm().loadRecord(record);
		var managerSubStore = record.getSubStore('ems_ab_manager');
		if (managerSubStore && this.managerList.getStore() !== managerSubStore) {
			this.managerList.reconfigure(managerSubStore, this.managerList.getColumnModel());
		}

		var reportsSubStore = record.getSubStore('ems_ab_reports');
		if (reportsSubStore && this.reportList.getStore() !== reportsSubStore) {
			this.reportList.reconfigure(reportsSubStore, this.reportList.getColumnModel());
		}
	}
});

Ext.reg('zarafa.abuserorganizationtab', Zarafa.addressbook.dialogs.ABUserOrganizationTab);
Ext.namespace('Zarafa.addressbook.dialogs');

/**
 * @class Zarafa.addressbook.dialogs.ABUserPhoneTab
 * @extends Ext.form.FormPanel
 * @xtype zarafa.abuserphonetab
 *
 * This class is used to create layout of phone tab of tab panel.
 */
Zarafa.addressbook.dialogs.ABUserPhoneTab = Ext.extend(Ext.form.FormPanel, {

	/**
	 * @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, {
			xtype: 'zarafa.abuserphonetab',
			title: _('Phone'),
			layout: {
				type: 'vbox',
				align: 'stretch'
			},
			autoScroll: true,
			bodyStyle: 'padding: 5px;',
			items: [
				this.createPhonenumberFieldset(),
				this.createNoteFieldset()
			]
		});

		Zarafa.addressbook.dialogs.ABUserPhoneTab.superclass.constructor.call(this, config);
	},

	/**
	 * Creates the Phonenumber fieldset for phone tab
	 * @return {Object} config object for creating {@link Ext.form.FieldSet FieldSet}.
	 * @private
	 */
	createPhonenumberFieldset: function()
	{
		return {
			xtype: 'fieldset',
			title: _('Phone numbers'),
			border: true,
			cls: 'zarafa-fieldset',
			layout: 'column',
			columnWidth: 1,
			autoHeight: true,
			anchor: '100%',
			items: [{
				xtype: 'container',
				layout: 'form',
				labelSeparator: '',
				columnWidth	: 0.5,
				border: false,
				defaults: {
					anchor: '100%',
					editable: false,
					readOnly: true
				},
				items: [{
					xtype: 'textfield',
					fieldLabel:_('Business'),
					name: 'business_telephone_number'
				},{
					xtype: 'combo',
					mode: 'local',
					fieldLabel:_('Business 2'),
					store: new Zarafa.addressbook.AddressBookTelephoneNumberSubStore(),
					autoSelect: true,
					forceSelection: true,
					lazyInit: false,
					editable: false,
					readOnly: false,
					triggerAction	: 'all',
					displayField: 'number',
					valueField: 'number',
					ref: '../../business2PhoneCombo'
				},{
					xtype: 'textfield',
					fieldLabel:_('Fax'),
					name: 'primary_fax_number'
				},{
					xtype: 'textfield',
					fieldLabel: _('Assistant'),
					name: 'assistant'
				}]
			},{
				xtype: 'container',
				layout: 'form',
				labelSeparator: '',
				columnWidth	: 0.5,
				border: false,
				defaults: {
					anchor: '100%',
					editable: false,
					readOnly: true
				},
				items: [{
					xtype: 'textfield',
					fieldLabel: _('Home'),
					name: 'home_telephone_number'
				},{
					xtype: 'combo',
					mode: 'local',
					fieldLabel:_('Home 2'),
					store: new Zarafa.addressbook.AddressBookTelephoneNumberSubStore(),
					forceSelection: true,
					lazyInit: false,
					editable: false,
					readOnly: false,
					triggerAction	: 'all',
					displayField: 'number',
					valueField: 'number',
					ref: '../../home2PhoneCombo'
				},{
					xtype: 'textfield',
					fieldLabel: _('Mobile'),
					name: 'mobile_telephone_number'
				},{
					xtype: 'textfield',
					fieldLabel: _('Pager'),
					name: 'pager_telephone_number'
				}]
			}]
		};
	},

	/**
	 * Creates the note fieldset for phone tab
	 * @return {Object} config object for creating {@link Ext.form.FieldSet FieldSet}.
	 * @private
	 */
	createNoteFieldset: function()
	{
		return [{
			xtype: 'fieldset',
			title: _('Notes'),
			border: true,
			cls: 'zarafa-fieldset',
			layout: 'column',
			columnWidth: 1,
			autoHeight: true,
			anchor: '100%',
			items: [{
				xtype: 'textarea',
				hideLabel: true,
				fieldLabel: _('Notes'),
				name: 'comment',
				readOnly: true,
				columnWidth: 1,
				flex: 1
			}]
		}];
	},

	/**
	 * Function is used to update values of form fields when ever
	 * an updated {@link Zarafa.core.data.MAPIRecord record} is received
	 * @param {Zarafa.core.data.MAPIRecord} record The record update the panel with.
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 * @private
	 */
	update: function(record, contentReset)
	{
		this.getForm().loadRecord(record);

		var businessPhoneSubStore = record.getSubStore('business2_telephone_numbers');
		if (businessPhoneSubStore && this.business2PhoneCombo.getStore() !== businessPhoneSubStore) {
			this.business2PhoneCombo.bindStore(businessPhoneSubStore);
			var select = businessPhoneSubStore.getAt(0);
			if (select) {
				this.business2PhoneCombo.setValue(select.get(this.business2PhoneCombo.valueField));
			}
		}

		var homePhoneSubStore = record.getSubStore('home2_telephone_numbers');
		if (homePhoneSubStore && this.home2PhoneCombo.getStore() !== homePhoneSubStore) {
			this.home2PhoneCombo.bindStore(homePhoneSubStore);
			var select = homePhoneSubStore.getAt(0);
			if (select) {
				this.home2PhoneCombo.setValue(select.get(this.home2PhoneCombo.valueField));
			}
		}
	}
});

Ext.reg('zarafa.abuserphonetab', Zarafa.addressbook.dialogs.ABUserPhoneTab);
Ext.namespace('Zarafa.addressbook.ui');

/**
 * @class Zarafa.addressbook.ui.ABMultiUserSelectionPanel
 * @extends Ext.Panel
 * @xtype zarafa.abmultiuserselectionpanel
 */
Zarafa.addressbook.ui.ABMultiUserSelectionPanel = Ext.extend(Ext.Panel, {
	/**
	 * @cfg {Function} callback the callback function to return to after selecting user from AdressBook.
	 */
	callback: Ext.emptyFn,

	/**
	 * @cfg {Function} convert the convert function which converts an
	 * {@link Zarafa.addressbook.AddressBookRecord user} to the correct type
	 * which can be placed in the {@link #store}.
	 * This function receives the selected AddressBookRecord as first argument,
	 * and optionally passes the {@link Ext.Component} which was generated from the
	 * {@link #selectionCfg} which was used to select the recipient as second argument.
	 */
	convert: Ext.emptyFn,

	/**
	 * @cfg {Function} scope the scope in which the {@link #callback} will be called
	 */
	scope: undefined,

	/**
	 * @cfg {Ext.data.Store} store The store in which all records should be placed.
	 */
	store: undefined,

	/**
	 * @cfg {Array} selectionCfg Array of {@link Zarafa.common.ui.BoxField} configuration
	 * objects which are created below the User list. These will show which users
	 * the user has selected.
	 */
	selectionCfg: undefined,

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

		var items = [{
			xtype: 'zarafa.addressbookmainpanel',
			ref: 'abPanel',
			flex: 1,
			hideContactsFolders: config.hideContactsFolders,
			listRestriction: config.listRestriction
		}];

		Ext.each(config.selectionCfg, function(cfg) {
			var btn = {
				xtype: 'button',
				text: cfg.fieldLabel,
				//width: 100,
				autoHeight: true,
				handler: this.onSelectionBtnClick,
				scope: this
			};

			items.push({
				xtype: 'zarafa.resizablecompositefield',
				cls: 'zarafa-addressbook-dialog-compositefield',
				hideLabel: true,
				anchor: '100%',
				autoHeight: false,
				items: [ btn, cfg ]
			});
		}, this);

		Ext.applyIf(config, {
			xtype: 'zarafa.abmultiuserselectionpanel',
			cls: 'k-abmultiuserselectionpanel',
			layout: {
				type: 'vbox',
				align: 'stretch'
			},
			items: items,
			buttons: [{
				text: _('Ok'),
				handler: this.onSubmit,
				scope: this
			},{
				text: _('Cancel'),
				handler: this.onCancel,
				scope: this
			}]
		});

		Zarafa.addressbook.ui.ABMultiUserSelectionPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Calls callback function
	 */
	doCallBack: function() {
		var grid = this.abPanel.getGridPanel();
		var sm = grid.getSelectionModel();
		var records = sm.getSelections();

		if (!Ext.isEmpty(records)) {
			Ext.each(records, function(r) {
				this.store.add(this.convert(r));
			}, this);
		}
	},

	/**
	 * Initialize events
	 * @private
	 */
	initEvents: function()
	{
		Zarafa.addressbook.ui.ABMultiUserSelectionPanel.superclass.initEvents.apply(this, arguments);

		this.mon(this.abPanel.getGridPanel(), 'rowdblclick', this.onAddressBookRowDblClick, this);
		this.mon(this.abPanel.getGridPanel(), 'rowcontextmenu', this.onAddressBookRowContextMenu, this);
	},

	/**
	 * Event handler which is triggered when the user doubleclicks on a
	 * row within the {@link Ext.grid.GridPanel gridpanel}. This will add
	 * the selected user or group to the {@link Zarafa.core.ui.RecipientField recipientfield}
	 * @private
	 */
	onAddressBookRowDblClick: function()
	{
		this.onSelectionBtnClick();
	},

	/**
	 * Event handler which is triggered when the user rightclicks
	 * on a row in the {@link Ext.grid.GridPanel gridpanel}. This will
	 * open a {@link Zarafa.core.ui.menu.ConditionalMenu contextmenu}
	 * for the selected row.
	 * @param {Ext.grid.GridPanel} grid The grid on which the user clicked
	 * @param {Number} rowIndex the row on which was doubleclicked
	 * @param {Ext.EventObject} event The event information
	 * @private
	 */
	onAddressBookRowContextMenu: function(grid, rowIndex, event)
	{
		var sm = grid.getSelectionModel();

		if (sm.hasSelection()) {
			// Some records were selected...
			if (!sm.isSelected(rowIndex)) {
				// But none of them was the record on which the
				// context menu was invoked. Reset selection.
				sm.clearSelections();
				sm.selectRow(rowIndex);
			}
		} else {
			// No records were selected,
			// select row on which context menu was invoked
			sm.selectRow(rowIndex);
		}

		Zarafa.core.data.UIFactory.openDefaultContextMenu(sm.getSelections(), {
			position: event.getXY(),
			dialog: this.dialog
		});
	},

	/**
	 * Event handler which is fired when one of the Selection Buttons generated
	 * from the {@link #selectionCfg} was called. This will call {@link #convert}
	 * on the selected record, and then add it to the {@link #store}.
	 * @param {Ext.Button} btn The button which was clicked
	 * @private
	 */
	onSelectionBtnClick: function(btn)
	{
		var field = btn ? btn.ownerCt.get(1) : undefined;
		var grid = this.abPanel.getGridPanel();
		var sm = grid.getSelectionModel();
		var records = sm.getSelections();

		if (Ext.isFunction(this.convert) && !Ext.isEmpty(records)) {
			Ext.each(records, function(r) {
				r = this.convert.call(this.scope || this, r, field);
				if (r) {
					this.store.add(r);
				}
			}, this);
		}
	},

	/**
	 * Event handler which is called when the user presses the "Ok" button.
	 * Function will store changed data in record and close the dialog
	 * @private
	 */
	onSubmit: function()
	{
		if (Ext.isFunction(this.callback)) {
			if (this.callback.call(this.scope || this) !== false) {
				this.dialog.close();
			} else {
				Ext.MessageBox.show({
					title: _('No user selected'),
					msg: _('You must select one or more users.'),
					buttons: Ext.MessageBox.OK
				});
			}
		}
	},

	/**
	 * Closes {@link Zarafa.core.ui.CreateFolderDialog CreateFolder} dialog
	 * @private
	 */
	onCancel: function()
	{
		this.dialog.close();
	}
});

Ext.reg('zarafa.abmultiuserselectionpanel', Zarafa.addressbook.ui.ABMultiUserSelectionPanel);
Ext.namespace('Zarafa.addressbook.ui');

/**
 * @class Zarafa.addressbook.ui.AddressBookMainPanel
 * @extends Ext.Panel
 * @xtype zarafa.addressbookmainpanel
 */
Zarafa.addressbook.ui.AddressBookMainPanel = Ext.extend(Ext.Panel, {
	/**
	 * @cfg {Zarafa.addressbook.AddressBookStore} addressBookStore
	 * The store which should be used for displaying the contents of the
	 * addressbook inside the grid. If not provided, a new store will
	 * allocated.
	 */
	addressBookStore: undefined,

	/**
	 * @cfg (Boolean) Set to true to hide contacts folders in the address book
	 * hierarchy dropdown.
	 */
	hideContactsFolders: false,

	/**
	 * @cfg {Object} listRestriction The default restriction which
	 * must be send to the server side when obtaining a fresh list
	 * from the server. This can be used to restrict the visibility
	 * of users, groups, companies etc.
	 */
	listRestriction: undefined,

	/**
	 * @cfg {Boolean} singleSelect true to allow selection of only one row at a time (defaults to false allowing multiple selections)
	 */
	singleSelect: false,

	/**
	 * The text that will be shown in the grid when the user has to do a search before
	 * results are shown. (for the GAB when the admin has set ENABLE_FULL_GAB to false)
	 * @property
	 * @type {String}
	 */
	emptyGridText: _('Use the search bar to get results'),

	/**
	 * The text that will be shown in the grid when a search gave no results.
	 * @property
	 * @type {String}
	 */
	noResultsGridText: _('There are no items to show in this list'),

	/**
 	 * @constructor
	 * @param {Object} config Configuration structure
	 */
	constructor: function(config)
	{
		// Let's remove the state settings of the address book panel once so
		// everyone will start again with the new default settings (januari 2017)
		var sm = container.getSettingsModel();
		var stateUpdated = sm.get('zarafa/v1/contexts/addressbook/stateUpdated');
		if ( Ext.isEmpty(stateUpdated) ) {
			sm.remove('zarafa/v1/state/dialogs/addressbookcontentpanel');
			sm.remove('zarafa/v1/state/dialogs/abuserselectioncontentpanel');
			sm.remove('zarafa/v1/state/gab/contacts');
			sm.remove('zarafa/v1/state/gab/globaladdressbook');
			sm.set('zarafa/v1/contexts/addressbook/stateUpdated', 1);
		}

		config = config || {};

		if (!Ext.isDefined(config.addressBookStore)) {
			config.addressBookStore = new Zarafa.addressbook.AddressBookStore();
		}

		var items = config.items || [];
		// whatever items passed in config should be added at last place
		config.items = [
			this.createHeaderPanel(config.hideContactsFolders),
			this.createViewPanel(config.addressBookStore, { singleSelect: Ext.isDefined(config.singleSelect) ? config.singleSelect : this.singleSelect})
		].concat(items);

		Ext.applyIf(config, {
			xtype: 'zarafa.addressbookmainpanel',
			border: false,
			cls: 'k-addressbookmainpanel',
			layout: {
				type:'vbox',
				align:'stretch'
			}
		});

		// Call parent constructor
		Zarafa.addressbook.ui.AddressBookMainPanel.superclass.constructor.call(this, config);

		// Load the address book
		this.initDialog();

		// When a contact or distlist is saved from a sub-dialog opened via
		// the AB, reload the grid to reflect the changes.
		this.mon(container.getShadowStore(), 'write', this.onShadowStoreWrite, this);

		// When shared stores are added/removed in the hierarchy, keep the
		// AB hierarchy dropdown in sync.
		this.hierarchyStore = container.getHierarchyStore();
		this.hierarchyStore.on('remove', this.onHierarchyStoreRemove, this);
		this.hierarchyStore.on('addFolder', this.onHierarchyFolderChange, this);
		this.hierarchyStore.on('removeFolder', this.onHierarchyFolderChange, this);

		// When the MainPanel is destroyed, also destroy the store,
		// this ensures that any pending requests will be cancelled.
		this.on('destroy', this.onMainPanelDestroy, this);
	},

	/**
	 * Called when a shared store is removed from the hierarchy (e.g. user
	 * closes a shared store). Directly removes matching entries from the
	 * AB hierarchy store to avoid a race condition with the server-side
	 * closesharedfolder action.
	 *
	 * @param {Zarafa.hierarchy.data.HierarchyStore} store The hierarchy store
	 * @param {Zarafa.hierarchy.data.MAPIStoreRecord} record The removed store record
	 * @param {Number} index The index from where the record was removed
	 * @private
	 */
	onHierarchyStoreRemove: function(store, record)
	{
		var storeEntryId = record.get('store_entryid');
		if (!storeEntryId) {
			return;
		}

		// Find all AB hierarchy entries that belong to the removed store
		var toRemove = [];
		Zarafa.addressbook.AddressBookHierarchyStore.each(function(r) {
			if (r.get('store_entryid') === storeEntryId) {
				toRemove.push(r);
			}
		});

		if (toRemove.length > 0) {
			Zarafa.addressbook.AddressBookHierarchyStore.remove(toRemove);

			// Clean up group headers that no longer have any children
			var emptyHeaders = [];
			var count = Zarafa.addressbook.AddressBookHierarchyStore.getCount();
			Zarafa.addressbook.AddressBookHierarchyStore.each(function(r, index) {
				if (index > 0 && r.get('depth') === 0) {
					if (index === count - 1 ||
						Zarafa.addressbook.AddressBookHierarchyStore.getAt(index + 1).get('depth') === 0) {
						emptyHeaders.push(r);
					}
				}
			});

			if (emptyHeaders.length > 0) {
				Zarafa.addressbook.AddressBookHierarchyStore.remove(emptyHeaders);
			}
		}
	},

	/**
	 * Called when a folder is added to or removed from the hierarchy.
	 * Reloads the AB hierarchy from the server if the folder is a
	 * contact folder (IPF.Contact).
	 *
	 * @param {Zarafa.hierarchy.data.HierarchyStore} hierarchyStore The hierarchy store
	 * @param {Zarafa.hierarchy.data.MAPIStoreRecord} storeRecord The store containing the folder
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord/Array} records The added/removed folder(s)
	 * @private
	 */
	onHierarchyFolderChange: function(hierarchyStore, storeRecord, records)
	{
		if (!records) {
			return;
		}
		if (!Array.isArray(records)) {
			records = [records];
		}

		for (var i = 0; i < records.length; i++) {
			var containerClass = records[i].get ? records[i].get('container_class') : '';
			if (containerClass && containerClass.indexOf('IPF.Contact') === 0) {
				Zarafa.addressbook.AddressBookHierarchyStore.loadAddressBookHierarchy();
				return;
			}
		}
	},

	/**
	 * Called when a record is saved via the ShadowStore (e.g. the user edited
	 * a contact or distribution list opened from this dialog). Reloads the
	 * grid to reflect the changes.
	 * @private
	 */
	onShadowStoreWrite: function()
	{
		this.onSearchButtonClick();
	},

	/**
	 * Cleanup handler when the panel is destroyed. Removes hierarchy store
	 * listeners and destroys the address book store.
	 * @private
	 */
	onMainPanelDestroy: function()
	{
		this.hierarchyStore.un('remove', this.onHierarchyStoreRemove, this);
		this.hierarchyStore.un('addFolder', this.onHierarchyFolderChange, this);
		this.hierarchyStore.un('removeFolder', this.onHierarchyFolderChange, this);
		this.addressBookStore.destroy();
	},

	/**
	 * Initialize the header panel in which the search bar, and the
	 * container selection components are placed which can filter the
	 * contents of the {@link Ext.grid.GridPanel gridpanel}.
	 * @return {Object} Configuration object for the header panel
	 * @private
	 */
	createHeaderPanel: function(hideContactsFolders)
	{
		var hierarchyTpl = new Ext.XTemplate(
			'<tpl for=".">',
				'<div class="x-combo-list-item<tpl if="group_header"> k-combo-list-item-header</tpl>">',
					'{depth:indent}{display_name:htmlEncode}',
				'</div>',
			'</tpl>',
			{
				compiled: true
			}
		);

		return {
			xtype: 'panel',
			cls: 'k-addressbookmainpanel-header',
			border: false,
			layout: 'hbox',
			items: [{
				xtype: 'trigger',
				ref: '../searchText',
				flex: 1,
				hideFieldLabel: true,
				enableKeyEvents: true,
				triggerClass: 'icon_magnifier',
				triggerScope: this,
				onTriggerClick: this.onSearchButtonClick.createDelegate(this),
				wrapFocusClass: '',
				listeners:{
					scope: this,
					render: this.onRenderSearchField,
					keyup: this.onSearchTextFiledKeyUp
				}
			},{
				xtype: 'spacer',
				width: 30,
				height: 10
			},{
				xtype: 'container',
				width: 355,
				items: [{
					xtype: 'combo',
					width: 200,
					plugins: [ 'zarafa.fieldlabeler', 'zarafa.comboautowidth' ],
					fieldLabel: _('Show Names from the'),
					labelWidth: 150,
					listClass: 'k-addressbook-combo-list',
					editable: false,
					mode: 'local',
					triggerAction: 'all',
					store: Zarafa.addressbook.AddressBookHierarchyStore,
					displayField: 'display_name',
					valueField: 'entryid',
					ref: '../../addressBookSelectionCB',
					tpl: hierarchyTpl,
					autoSelect: true,
					minListWidth: 150,
					listeners:{
						beforeselect: this.onBeforeSelectAddressBook,
						select: this.onAddressBookChange,
						scope: this
					},
					onLoad: this.onAddressBookComboLoad.createDelegate(this, [hideContactsFolders])
				}]
			}]
		};
	},

	/**
	 * Initialize View Panel which shows the grid of the address book items
	 * in the selected container.
	 * @param {Zarafa.addressbook.AddressBookStore} addressBookStore The store which must
	 * be displayed in the GridPanel.
	 * @param {Object} viewConfig config options for view panel
	 * be displayed in the GridPanel.
	 * @return {Object} The Configuration object for the {@link Ext.grid.GridPanel gridpanel}
	 * @private
	 */
	createViewPanel: function(addressBookStore, viewConfig)
	{
		return Ext.apply(viewConfig, {
			xtype: 'zarafa.addressbookgrid',
			hideLabel: true,
			name: 'viewpanel',
			cls: 'k-addressbookmainpanel-grid',
			viewConfig: {
				emptyText: this.emptyGridText,
				deferEmptyText: false
			},
			store: addressBookStore,
			border: false,
			ref: 'viewPanel',
			flex: 1
		});
	},

	/**
	 * Obtain the {@link Ext.grid.GridPanel gridpanel} which is used within this
	 * {@link Ext.Panel panel}.
	 * @return {Zarafa.addressbook.ui.AddressBookGrid} The GridPanel for this panel
	 */
	getGridPanel: function()
	{
		return this.viewPanel;
	},

	/**
	 * Event handler for the render event of the searchfield. Will add a placeholder
	 * attribute to the input.
	 */
	onRenderSearchField: function(triggerField)
	{
		triggerField.getEl().set({'placeholder': _('Search…')});
	},

	/**
	 * Event handler for the onbeforeselect event of the Address Book combo. Will
	 * make sure group headers cannot be selected.
	 *
	 * @param {Ext.form.ComboBox} combo The Address Book combobox
	 * @param {Zarafa.core.data.IPMRecord IPMRecord} record The selected Address Book record
	 * @param {Number} index The index of the selected record in the combo
	 */
	onBeforeSelectAddressBook: function(combo, record, index)
	{
		return !record.get('group_header');
	},

	/**
	 * Event handler which is triggered when the addressbook
	 * combobox has been used to change the selected addressbook
	 *
	 * @param {Ext.form.ComboBox} field The combobox which was selected
	 * @param {Zarafa.core.data.IPMRecord} record The selected Record
	 * @param {Number} The index number of the selected record.
	 * @private
	 */
	onAddressBookChange: function(field, record, index)
	{
		// Trigger a search
		this.onSearchButtonClick();
	},

	/**
	 * Overriding the onLoad function of the combobox to be able to use the filter
	 * on the store of the combobox. The doQuery function of {@Ext.form.ComboBox}
	 * will clear the filter before calling onLoad, so we set it in this
	 * override.
	 */
	onAddressBookComboLoad: function(hideContactsFolders) {
		if ( hideContactsFolders === true ) {
			Zarafa.addressbook.AddressBookHierarchyStore.filter('type', 'gab');
		} else {
			Zarafa.addressbook.AddressBookHierarchyStore.clearFilter();
		}

		Ext.form.ComboBox.prototype.onLoad.call(this.addressBookSelectionCB);
	},

	/**
	 * Initializes the dialog by reloading the AB hierarchy (to pick up any
	 * shared store changes), selecting the default address book in the
	 * dropdown, and triggering a search to fill the grid.
	 * @private
	 */
	initDialog: function()
	{
		// Check that addressBookSelectionCB is created
		if (!Ext.isDefined(this.addressBookSelectionCB)) {
			return;
		}

		// Always reload the AB hierarchy to pick up changes (shared stores
		// opened/closed, contact folders created/deleted since last load).
		Zarafa.addressbook.AddressBookHierarchyStore.on('load', this.selectDefaultAddressBook, this, {single: true});
		Zarafa.addressbook.AddressBookHierarchyStore.loadAddressBookHierarchy();
	},

	/**
	 * Selects the default address book in the combo box and triggers the
	 * initial search. Called after the AB hierarchy store has been reloaded.
	 * @private
	 */
	selectDefaultAddressBook: function()
	{
		// Check that we have at least obtained one item
		if (Zarafa.addressbook.AddressBookHierarchyStore.getCount() === 0) {
			return;
		}

		var record;

		// Get the entryId of default address book configured in setting
		var folderEntryId = container.getSettingsModel().get('zarafa/v1/main/default_addressbook');

		// If there is no configuration for default address book into setting,
		// than we by default displays the 'Global Address Book'
		if (!Ext.isEmpty(folderEntryId)) {
			record = Zarafa.addressbook.AddressBookHierarchyStore.getById(folderEntryId);
		}

		if (Ext.isEmpty(record)) {
			record = Zarafa.addressbook.AddressBookHierarchyStore.getAt(0);
		}

		var entryid = record.get('entryid');
		if (!Ext.isDefined(entryid)) {
			return;
		}

		this.addressBookSelectionCB.setValue(entryid);

		// Trigger a search when the grid has been rendered. We do this
		// because Ext 'forgets' to add the scrollOfset to an empty grid
		// when the store fires the load event, but it does do this when
		// rendering an empty grid on start. Since we don't want the
		// emptyText to jump because of this we will make sure the store
		// fires the load event when the grid is rendered and not before
		// it is rendered.
		if (this.viewPanel.rendered) {
			this.onSearchButtonClick();
		} else {
			this.viewPanel.on('render', this.onSearchButtonClick, this);
		}
	},

	/**
	 * Event handler which is triggered when the searchButton is
	 * clicked, The handler will send request to server to search
	 * in addressbook
	 * @private
	 */
	onSearchButtonClick: function()
	{
		var selectedFolder = this.getSelectedFolderRecord();
		if ( !Ext.isDefined(selectedFolder) ) {
			return;
		}

		var folderType = selectedFolder.get('type');
		var fullGabDisabled = container.getServerConfig().isFullGabDisabled();
		var searchText = (this.searchText.getValue() || '').trim();

		// Do not load when we are doing a GAB load without a search text
		// and the admin has configured to not load the GAB in that case
		if ( folderType === 'gab' && fullGabDisabled && Ext.isEmpty(searchText) ) {
			this.viewPanel.getView().emptyText = '<div class="emptytext">' + this.emptyGridText + '</div>';
			this.addressBookStore.removeAll();
			return;
		}

		this.viewPanel.getView().emptyText = '<div class="emptytext">' + this.noResultsGridText + '</div>';

		var params = {
			subActionType: Zarafa.core.Actions['globaladdressbook'],
			entryid: selectedFolder.get('entryid'),
			store_entryid: selectedFolder.get('store_entryid'),
			restriction: Ext.applyIf({searchstring: searchText}, this.listRestriction),
			folderType: folderType
		};

		if (folderType === 'contacts' || folderType === 'sharedcontacts') {
			var folderId = selectedFolder.get("entryid");
			var lookupEntryId;

			// Only unwrap entry IDs that are wrapped by the AB Contact Provider.
			// Direct store entry IDs (raw) should be used as-is.
			if (folderType === 'contacts' && Zarafa.core.EntryId.hasContactProviderGUID(folderId)) {
				lookupEntryId = Zarafa.core.EntryId.unwrapContactProviderEntryId(folderId);
			} else {
				lookupEntryId = folderId;
			}

			var folder = container.getHierarchyStore().getFolder(lookupEntryId);
			if (folder) {
				var isSharedStore = folder.getMAPIStore().isSharedStore() || folder.getMAPIStore().isPublicStore();
				if (isSharedStore) {
					params["isSharedFolder"] = isSharedStore;
					params["sharedFolder"] = {
						store_entryid: folder.get("store_entryid")
					};
				}
			} else if (folderType === 'sharedcontacts' && selectedFolder.get('store_entryid')) {
				// Fallback: use store_entryid from the addressbook hierarchy record
				params["isSharedFolder"] = true;
				params["sharedFolder"] = {
					store_entryid: selectedFolder.get("store_entryid")
				};
			}
		}

		this.addressBookStore.load({
			actionType: Zarafa.core.Actions['list'],
			params: params
		});
	},

	/**
	 * Returns the currently selected folder in the hierarchy combobox
	 *
	 *@return {Zarafa.core.data.IPMRecord IPMRecord} The selected record from the
	 * {#hierarchyStore}
	 */
	getSelectedFolderRecord: function()
	{
		var entryid = this.addressBookSelectionCB.getValue();
		var index = Zarafa.addressbook.AddressBookHierarchyStore.find('entryid', entryid);
		return Zarafa.addressbook.AddressBookHierarchyStore.getAt(index);
	},

	/**
	 * Event handler which is triggered when
	 * a key is pressed in the searchTextField
	 *
	 * @param {Ext.form.TextField} field
	 * @param {Ext.EventObject} e
	 * @private
	 */
	onSearchTextFiledKeyUp: function(field, e)
	{
		if (e.getKey() === e.ENTER) {
			this.onSearchButtonClick();
		}
	}
});

Ext.reg('zarafa.addressbookmainpanel', Zarafa.addressbook.ui.AddressBookMainPanel);
Ext.namespace('Zarafa.advancesearch');

/**
 * @class Zarafa.advancesearch.Actions
 *
 * Advance search actions which can be used within {@link Ext.Button buttons}
 * or other {@link Ext.Component components} with action handlers.
 * @singleton
 */
Zarafa.advancesearch.Actions = {
	/**
	 * Open a {@link Zarafa.advancesearch.dialogs.SelectFolderContentPanel SelectFolderContentPanel} for
	 * select the {@link Zarafa.core.data.IPFRecord folder} on which search gets performs.
	 *
	 * @param {Object} config (optional) Configuration object to create the ContentPanel
	 */
	openSelectSearchFolderDialog: function(config)
	{
		config = Ext.applyIf(config || {}, {
			modal: true
		});
		var componentType = Zarafa.core.data.SharedComponentType['search.dialog.selectfolder'];
		Zarafa.core.data.UIFactory.openLayerComponent(componentType, [], config);
	},

	/**
	 * Open a dialog in which a new {@link Zarafa.common.favorites.data.FavoritesFolderRecord search} folder record can be
	 * further edited.
	 *
	 * @param {Zarafa.advancesearch.AdvanceSearchContextModel} model Context Model object that will be used
	 * to {@link Zarafa.advancesearch.AdvanceSearchContextModel#createSearchFolderRecord create} the search folder.
	 * @param {Object} config configuration options for {@link Zarafa.advancesearch.dialogs.CreateSearchFolderContentPanel CreateSearchFolderContentPanel}.
	 */
	openCreateSearchFolderContentPanel: function(model, config)
	{
		config = Ext.apply(config||{}, {
			modal: true,
			manager: Ext.WindowMgr,
			iconCls: 'icon_favorites',
			closable: false,
			resizable: false,
			showModalWithoutParent: true
		});

		var record = model.createSearchFolderRecord(config.searchText);

		Zarafa.core.data.UIFactory.openCreateRecord(record, config);
	},

	/**
	 * Open a {@link Zarafa.advancesearch.dialogs.SearchCategoriesContentPanel dialog}
	 * @param {Object} config configuration options.
	 */
	openSearchCategoryContentPanel: function (config)
	{
		var componentType = Zarafa.core.data.SharedComponentType['search.dialog.searchcategory'];
		Zarafa.core.data.UIFactory.openLayerComponent(componentType, [], config);
	}
};
Ext.namespace('Zarafa.advancesearch');

/**
 * @class Zarafa.advancesearch.KQLParser
 * @singleton
 * @extends Object
 */
Zarafa.advancesearch.KQLParser = Ext.extend(Object, {
	lexer: null,

	/**
	 * Creates a tokenizer with rules to split strings into KQL tokens.
	 * The tokenizer is an instance of Tokenizr.
	 * See {@link https://github.com/rse/tokenizr}
	 * @return {Tokenizr} The tokenizer
	 */
	getLexer: function() {
		if ( this.lexer ) {
			return this.lexer;
		}

		// eslint-disable-next-line no-undef
		this.lexer = new Tokenizr();

		var keys = [
			'body',
			'attachment',
			'subject',
			'sender',
			'from',
			'to',
			'cc',
			'bcc',
			'category'
		];
		var ops = [':', '=', '<>'];
		var boolOps = ['AND', 'OR', 'NOT', '+', '-'];

		var keyword = false;
		var operator = false;
		this.lexer.before(function(ctx, match, rule) {
			switch (rule.name) {
				case 'phrase':
				case 'word':
					if ( keyword === false ) {
						var op = match[1].toUpperCase();
					if ( boolOps.indexOf(op) > -1 ) {
						ctx.accept('operator', {op: op});
					} else {
						ctx.accept(rule.name, match[1]);
					}
					} else {
						// We found an expression
						ctx.accept('expression', {key: keyword, op: operator, val: match[1]});
						keyword = false;
						operator = false;
					}
					break;
			default:
				if ( keyword ) {
					ctx.accept('word', keyword);
					keyword = false;
					operator = false;
				}
			}
		});

		this.lexer.rule(/\(/, function(ctx, match) {
			ctx.accept('parenthesis', 'open');
			ctx.push('parens');
		}, 'parens');
		this.lexer.rule('parens', /\)/, function(ctx, match) {
			ctx.accept('parenthesis', 'close');
			ctx.pop();
		}, 'parensclose');
		this.lexer.rule(new RegExp('(' + keys.join('|') + ')(' + ops.join('|') + ')', 'i'), function(ctx, match) {
			ctx.ignore();
			keyword = match[1].toLowerCase();
			operator = match[2];
		}, 'keyword');
		this.lexer.rule(/"(.*?)"/, function(ctx, match) {
			ctx.ignore();
		}, 'phrase');
		this.lexer.rule(this.getWordRuleRegExp(), function(ctx, match) {
			ctx.ignore();
		}, 'word');
		this.lexer.rule(/[ \t\r\n]+/, function(ctx, match) {
			ctx.ignore();
		}, 'whitespace');
		// Ignore anything else
		this.lexer.rule(/./, function(ctx, match) {
			ctx.ignore();
		});

		return this.lexer;
	},

	/**
	 * Builds the Regular Expression that will be used by the lexer to find 'words'.
	 * It supports unicode characters, like letters with umlauts.
	 * @suppress {checkRegExp|suspiciousCode}
	 *
	 * @return {RegExp} A regular expression object
	 */
	getWordRuleRegExp: function() {
		// Test if we can use unicode regexps. (IE11 does not support it)
		// Otherwise we will match anything up till white space
		if ( typeof (/bla/).unicode === 'boolean' ) {
			var flags = 'iu';
			// Not all browsers that support unicode also support the \p{L} syntax,
			// so we will test for that too.
			try {
				// The following lines might look strange, but they written like this to
				// trick the closure compiler, because we can't seem to suppress the
				// checkRegExp and suspiousCode warnings.
				var f = 'ui';
				f = f.substr(0,1);
				var re = new RegExp('\\p{L}', f);
				re = '((?:\\p{L}|[a-z0-9_\\/@.!#$%&\'*+-=?^`{|}~])+)';
			} catch (e) {
				// The browser supports unicode, but not the shorthand syntax, so we must
				// explicitly tell which characters to match.
				// See https://stackoverflow.com/a/37668315 for more information on the following regexp
				re = '([a-z0-9\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0-\u08B4\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D5F-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16F1-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FD5\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B-\uA7AD\uA7B0-\uA7B7\uA7F7-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA8FD\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB65\uAB70-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC_\\/@.!#$%&\'*+-=?^`{|}~]+)';
			}
		} else {
			re = '([^\t\r\n\s]+)';
			flags = 'i';
		}

		return new RegExp(re, flags);
	},

	/**
	 * Helper function to tokenize a string without explicitly
	 * creating a {@link getLexer lexer}
	 * @param {String} query
	 * @return {Array|Boolean} An array of token objects or false when no KQL tokens were found
	 */
	tokenize: function(query) {
		var lexer = this.getLexer();

		lexer.input(query);

		// Turn off debugging of the tokenizr library
		// Turn it on when needed during development
		lexer.debug(false);

		var tokens = lexer.tokens();

		if ( !this.isKqlQuery(tokens) ) {
			return false;
		}

		return tokens;
	},

	/**
	 * Scans the tokens to see if the tokenized query can be considered a KQL query.
	 * It is regarded as one when an expression or operator is found.
	 *
	 * @param {String|Array} tokens The search query, either tokenized or just a plain string
	 * @return {Boolean} True if the query is a KQL query, false otherwise
	 */
	isKqlQuery: function(tokens) {
		if ( typeof tokens === 'string' ) {
			return Boolean(this.tokenize(tokens));
		}

		if ( !Array.isArray(tokens) ) {
			return false;
		}


		tokens = this.normalize(tokens);

		var expressionOrOperatorFound = tokens.some(function(t) {
			return typeof t === 'object' && (t.type === 'expression' || t.type === 'subquery' || t.type === 'operator');
		});

		return expressionOrOperatorFound;
	},

	usesExplicitSyntax: function(tokens) {
		if ( typeof tokens === 'string' ) {
			tokens = this.tokenize(tokens);
		}

		if ( !Array.isArray(tokens) ) {
			return false;
		}

		return tokens.some(function(token) {
			if (!token || typeof token !== 'object') {
				return false;
			}

			if (token.type === 'operator') {
				var op = token.value && token.value.op;
				return op === 'AND' || op === 'OR' || op === 'NOT' || op === '+' || op === '-';
			}

			if (token.type === 'expression') {
				return true;
			}

			if (token.type === 'word' && typeof token.value === 'string') {
				return /^[+-]/.test(token.value);
			}

			return false;
		});
	},

	/**
	 * Normalize the tokens if necessary
	 * We want something like <expression> <operator> <expression> <operator> etc
	 * but users are allowed to omit the AND between expressions and NOT is a special
	 * operator that needs to be preceded by another operator.
	 * Query parts within parenthesis will be moved into a so-called 'subquery' token
	 *
	 * @param {Object[]} Array of token objects
	 */
	normalize: function(tokens) {
		// make sure we don't change the original tokens
		tokens = JSON.parse(JSON.stringify(tokens));

		// Convert plain words or quoted phrases into expressions that
		// search across all indexed fields. This allows queries like
		// "foo AND bar" without specifying a field name.
		tokens = tokens.map(function(token) {
			if ( token.type === 'word' || token.type === 'phrase' ) {
				return {
					type: 'expression',
					value: {key: 'any', op: ':', val: token.value}
				};
			}
			return token;
		});

		var t = [];
		var lastToken = null;
		while (tokens.length) {
			var token = tokens.shift();

			// First rewrite + to AND and - to NOT
			if ( token.type === 'operator') {
				if ( token.value.op === '+' ) {
					token.value.op = 'AND';
				} else if ( token.value.op === '-' ) {
					token.value.op = 'NOT';
				}
			}

			if ( token.type === 'operator' && token.value.op !== 'NOT' ) {
				if ( lastToken && (lastToken.type === 'expression' || lastToken.type === 'subquery') ) {
					// This is fine, add the token
					t.push(token);
				} else {
					// This is wrong. We'll should probably generate an error
					// Another option is to just drop this token. Let's do this
					// for now.
				}
			} else if ( token.type === 'operator' ) { // This is a NOT
				if ( lastToken && (lastToken.type === 'expression' || lastToken.type === 'subquery' || lastToken.type === 'parenthesis') ) {
					// An expression followed by a negated expression is regarded as joined
					// by an 'AND', so first push that
					t.push({type:'operator', value: {op: 'AND'}});
				}
				// We have a NOT, so we need an expression next and no NOT before this.
				if ( tokens.length && (tokens[0].type === 'operator' && tokens[0].value.op === 'NOT' ) ) {
					// Drop this NOT and the next one
					t.pop();
					tokens.shift();
				} else if ( tokens.length && (tokens[0].type === 'expression' || tokens[0].type === 'subquery' || (tokens[0].type === 'parenthesis' && tokens[0].value === 'open')) ) {
					tokens[0].negate = true;
				}
			} else if ( (token.type === 'expression' || token.type === 'subquery') ) {
				if ( lastToken && (lastToken.type === 'expression' || lastToken.type === 'subquery') ) {
					// An expression or subquery followed by an expression or subquery is
					// regarded as joined by an 'AND', so first push that
					t.push({type:'operator', value: {op: 'AND'}});
				}
				t.push(token);
			} else if ( token.type ==='parenthesis' && token.value === 'open' ) {
				// Find all tokens up till the closing parenthesis
				var subqueryTokens = [];
				var parensOpenCount = 1;
				while ( tokens.length && !(tokens[0].type === 'parenthesis' && tokens[0].value === 'close' && parensOpenCount === 1) ) {
					var tmpToken = tokens.shift();
					if ( tmpToken.type === 'parenthesis' && tmpToken.value === 'open' ) {
						parensOpenCount++;
					}
					if ( tmpToken.type === 'parenthesis' && tmpToken.value === 'close' ) {
						parensOpenCount--;
					}
					subqueryTokens.push(tmpToken);
				}
				if ( subqueryTokens.length === 0 || subqueryTokens[subqueryTokens.length -1].type === 'EOF') {
					// Something is wrong with the query! There is nothing in the parentheses or
					// we couldn't find the closing parenthesis. Let's just drop the subquery for now.
					// TODO: Show a message

				} else {
					// Create a subquery, but only if our subquery tokens contain more than one token.
					// Otherwise we can just drop the parenthesis
					subqueryTokens = this.normalize(subqueryTokens);
					if ( subqueryTokens.length > 1 ) {
						t.push({
							type: 'subquery',
							value: subqueryTokens,
							negate: token.negate
						});
					} else if (subqueryTokens.length === 1 ) {
						if ( token.negate ) {
							subqueryTokens[0].negate = !subqueryTokens[0].negate;
						}
						t.push(subqueryTokens[0]);
					}
				}
			}

			lastToken = t.length ? t[t.length - 1] : null;
		}

		// We can't end with an operator
		while ( t.length && t[t.length-1].type === 'operator' ) {
			t.pop();
		}

		return t;
	},

	/**
	 * Will process the tokens and move AND parts into subqueries.
	 * We now have tokens like <expression> OR <subquery> AND <expression>
	 * Since the AND takes presendence over OR we will move the AND with
	 * its expressions into a subquery so we will only have something like
	 * <expression> OR <subquery> OR <subquery>
	 * 					or
	 * <expression> AND <subquery> AND <subquery>
	 * (so only with the same operator)
	 * @param {Object[]} tokens Array of token objects
	 * @return {Object[]} Array of token objects
	 */
	flatten: function(tokens) {
		// Recursively flatten any existing subqueries first
		tokens.forEach(function(token) {
			if ( token.type === 'subquery' ) {
				token.value = this.flatten(token.value);
			}
		}, this);

		// Only need to flatten when both AND and OR are present
		var hasAnd = false;
		var hasOr = false;
		tokens.forEach(function(token) {
			if ( token.type === 'operator' ) {
				if ( token.value.op === 'AND' ) { hasAnd = true; }
				if ( token.value.op === 'OR' ) { hasOr = true; }
			}
		});
		if ( !hasAnd || !hasOr ) {
			return tokens;
		}

		// AND has higher precedence than OR, so split at OR boundaries
		// and wrap AND groups as subqueries
		var result = [];
		var currentGroup = [];

		tokens.forEach(function(token) {
			if ( token.type === 'operator' && token.value.op === 'OR' ) {
				if ( currentGroup.length > 1 ) {
					result.push({type: 'subquery', value: currentGroup});
				} else if ( currentGroup.length === 1 ) {
					result.push(currentGroup[0]);
				}
				result.push(token);
				currentGroup = [];
			} else {
				currentGroup.push(token);
			}
		});

		// Don't forget the last group
		if ( currentGroup.length > 1 ) {
			result.push({type: 'subquery', value: currentGroup});
		} else if ( currentGroup.length === 1 ) {
			result.push(currentGroup[0]);
		}

		return result;
	},

	/**
	 * Creates a restriction from a set of tokens
	 *
	 * @param {Array} tokens An array of token objects
	 * @return {Array} A Restriction array
	 */
	createTokenRestriction: function(tokens, anyFieldPreference) {
		tokens = this.flatten(this.normalize(tokens));

		var propMap = {
			subject: ['subject'],
			body: ['body'],
			sender: ['sender_name', 'sender_email_address', 'sent_representing_name', 'sent_representing_email_address'],
			from: ['sender_name', 'sender_email_address', 'sent_representing_name', 'sent_representing_email_address'],
			to: [{type:'recipient', recipientType: Zarafa.core.mapi.RecipientType.MAPI_TO}],
			cc: [{type:'recipient', recipientType: Zarafa.core.mapi.RecipientType.MAPI_CC}],
			bcc: [{type:'recipient', recipientType: Zarafa.core.mapi.RecipientType.MAPI_BCC}],
			category: ['categories'],
			attachment: [{type: 'attachment'}],
			// default search across multiple fields
			any: [
				'subject',
				'body',
				'sender_name',
				'sender_email_address',
				'sent_representing_name',
				'sent_representing_email_address',
				{type:'recipient', recipientType: Zarafa.core.mapi.RecipientType.MAPI_TO},
			{type:'recipient', recipientType: Zarafa.core.mapi.RecipientType.MAPI_CC},
			{type:'recipient', recipientType: Zarafa.core.mapi.RecipientType.MAPI_BCC},
			]
		};
		if (Ext.isArray(anyFieldPreference) && anyFieldPreference.length) {
			var anyFields = [];
			var seen = {};
			Ext.each(anyFieldPreference, function(field) {
				if (Ext.isEmpty(field)) {
					return;
				}
				var entry;
				switch (field) {
					case 'display_to':
						entry = {type:'recipient', recipientType: Zarafa.core.mapi.RecipientType.MAPI_TO};
						break;
					case 'display_cc':
						entry = {type:'recipient', recipientType: Zarafa.core.mapi.RecipientType.MAPI_CC};
						break;
					case 'display_bcc':
						entry = {type:'recipient', recipientType: Zarafa.core.mapi.RecipientType.MAPI_BCC};
						break;
					default:
						entry = field;
				}
				var key = Ext.isObject(entry) ? entry.type + ':' + entry.recipientType : entry;
				if (!seen[key]) {
					anyFields.push(entry);
					seen[key] = true;
				}
			});
			if (anyFields.length) {
				propMap.any = anyFields;
			}
		}
		var restrictions = [];

		// find the operator used in the restriction
		var op = tokens.filter(function(t) {
			return t.type === 'operator';
		});
		if ( op.length ) {
			op = op[0].value.op;
		} else {
			op = false;
		}

		tokens.forEach(function(token) {
			var curRes = [];
			if ( token.type === 'expression' ) {
				if ( !propMap[token.value.key] ) {
					return;
				}
				propMap[token.value.key].forEach(function(prop) {
					var r;
					if ( typeof prop === 'string' ) {
						r = this.createStringRestriction(prop, token.value.val);
					} else if ( typeof prop === 'object' && prop.type === 'recipient' ) {
						r = this.createRecipientRestriction(prop.recipientType, token.value.val);
					} else if ( typeof prop === 'object' && prop.type === 'attachment' ) {
						r = this.createAttachmentRestriction(token.value.val);
					}

					curRes.push(r);
				}, this);
			} else if ( token.type === 'subquery' ) {
				curRes.push(this.createTokenRestriction(token.value, anyFieldPreference));
			}

			if ( curRes.length === 0 ) {
				return;
			}

			if ( curRes.length > 1 ) {
				curRes = Zarafa.core.data.RestrictionFactory.createResOr(curRes);
			} else {
				curRes = curRes[0];
			}

			if ( token.negate ) {
				curRes = Zarafa.core.data.RestrictionFactory.createResNot(curRes);
			}

			restrictions.push(curRes);
		}, this);

		if ( restrictions.length === 0 ) {
			return false;
		}

		if ( op === 'OR' ) {
			restrictions = Zarafa.core.data.RestrictionFactory.createResOr(restrictions);
		} else {
			restrictions = Zarafa.core.data.RestrictionFactory.createResAnd(restrictions);
		}

		return restrictions;
	},

	/**
	 * Creates a text restriction on the given property
	 *
	 * @param {String} prop The property to set the restriction on
	 * @param {String} value The value the restriction will search for
	 * @return {Array} A Restriction array
	 */
	createStringRestriction: function(prop, value) {
		return Zarafa.core.data.RestrictionFactory.dataResContent(
			prop,
			Zarafa.core.mapi.Restrictions.FL_SUBSTRING | Zarafa.core.mapi.Restrictions.FL_IGNORECASE,
			value
		);
	},

	/**
	 * Creates a restriction on a recipient type
	 *
	 * @param {Number} recipientType The {@link Zarafa.core.mapi.RecipientType RecipientType}
	 * to put the restriction on
	 * @param {String} value The value the restriction will search for
	 * @return {Array} A Restriction array
	 */
	createRecipientRestriction: function(recipientType, value) {
		return Zarafa.core.data.RestrictionFactory.createResSubRestriction('PR_MESSAGE_RECIPIENTS',
			Zarafa.core.data.RestrictionFactory.createResAnd([
				Zarafa.core.data.RestrictionFactory.dataResProperty('PR_RECIPIENT_TYPE',
					Zarafa.core.mapi.Restrictions.RELOP_EQ,
					recipientType
				),
				Zarafa.core.data.RestrictionFactory.createResOr([
					this.createStringRestriction('PR_DISPLAY_NAME', value),
					this.createStringRestriction('PR_EMAIL_ADDRESS', value),
					this.createStringRestriction('PR_SMTP_ADDRESS', value)
				])
			])
		);
	},

	/**
	 * Creates a restriction on a attachment name
	 *
	 * @param {String} value The value the restriction will search for
	 * @return {Array} A Restriction array
	 */
	createAttachmentRestriction: function(value) {
		return Zarafa.core.data.RestrictionFactory.createResSubRestriction('PR_MESSAGE_ATTACHMENTS',
			this.createStringRestriction('PR_ATTACH_LONG_FILENAME', value)
		);
	}

});

Zarafa.advancesearch.KQLParser = new Zarafa.advancesearch.KQLParser();
Ext.namespace('Zarafa.advancesearch.data');

/**
 * @class Zarafa.advancesearch.data.DateRangeFields
 * Search date range fields for search toolBox
 * @singleton
 */
Zarafa.advancesearch.data.DateRangeFields = [{
    'name': _('Any date'),
    'value': 'all_dates'
}, {
    'name': _('Past week'),
    'value': 'past_week'
}, {
    'name': _('Past 2 weeks'),
    'value': 'past_two_weeks'
}, {
    'name': _('Past month'),
    'value': 'past_month'
}, {
    'name': _('Past 6 month'),
    'value': 'past_six_month'
}, {
    'name': _('Past year'),
    'value': 'past_year'
}, {
    'name': _('Custom date'),
    'value': 'custom_date'
}];
Ext.namespace('Zarafa.advancesearch.data');
/**
 * @class Zarafa.advancesearch.data.SearchCategoriesStore
 * @extends Ext.data.ArrayStore
 * @xtype zarafa.searchcategoriesstore
 *
 * Store which will bind with category filter
 * and manage categories which is added in category filter.
 */
Zarafa.advancesearch.data.SearchCategoriesStore = Ext.extend(Ext.data.ArrayStore, {

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

		Ext.applyIf(config, {
			fields: ['name', 'backgroundColor', 'colorClass'],
			autoDestroy: true
		});

		Ext.apply(this, config);

		Zarafa.advancesearch.data.SearchCategoriesStore.superclass.constructor.call(this, config);
	},

	/**
	 * Helper function which is use to add categories in to this store.
	 * @param {Array} categories list of categories which will add in category filter.
	 */
	addCategories: function (categories)
	{
		categories.forEach(function (category) {
			if (this.findExactCaseInsensitive('name', category) === -1) {
				// Add the category to the store
				var backgroundColor = Zarafa.common.categories.Util.getCategoryColor(category);
				this.add(new this.recordType({
					name: category,
					backgroundColor: Zarafa.common.categories.Util.getCategoryColor(category),
					colorClass: Zarafa.core.ColorSchemes.getLuma(backgroundColor) < 200 ? 'zarafa-dark' : ''
				}));
			}
		}, this);
	},

	/**
	 * Helper function which is use to remove categories from this store.
	 * @param {Array} categories list of categories which will remove from category filter.
	 */
	removeCategories: function (categories)
	{
		categories.forEach(function (category) {
			this.removeAt(this.findExactCaseInsensitive('name', category));
		}, this);
	},

	/**
	 * Finds the index of the first matching Record in this store by a specific field value.
	 * Matches case-insensitive.
	 * @param {String} fieldName The field that should be used to match the record
	 * @param {String} value The value that should be used to match the records
	 *
	 * @return {Number} The index of the first matching record or -1 if no match was found
	 */
	findExactCaseInsensitive: function (fieldName, value)
	{
		return this.findBy(function (category, index) {
			return category.get(fieldName).toLowerCase() === value.toLowerCase();
		}, this);
	},

	/**
	 * Helper function which is use to get list of categories.
	 * @returns {Array} list of categories which is added in category filter.
	 */
	getCategories: function ()
	{
		var categories = [];
		this.each(function (record) {
			categories.push(record.get('name'));
		});
		return categories;
	}
});

Ext.reg('zarafa.searchcategoriesstore', Zarafa.advancesearch.data.SearchCategoriesStore);
Ext.namespace('Zarafa.advancesearch.dialogs');

/**
 * @class Zarafa.advancesearch.dialogs.CreateSearchFolderPanel
 * @extends Ext.form.FormPanel
 * @xtype zarafa.createsearchfolderpanel
 *
 * Panel that is used to create new search folder under the favorites folder list.
 */
Zarafa.advancesearch.dialogs.CreateSearchFolderPanel = Ext.extend(Ext.form.FormPanel, {

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

		Ext.applyIf(config, {
			cls: 'k-create-search-folder-panel',
			border: false,
			items: [{
				xtype: 'displayfield',
				value: _('Add a folder to favorites based on search query'),
				hideLabel: true,
				margins: '0 0 9 0',
				anchor: '100%'
			}, {
				xtype: 'container',
				layout: 'form',
				labelAlign: 'top',
				items:[{
					xtype: 'textfield',
					ref: '../../searchFolderTextField',
					emptyText: _('Folder name'),
					labelSeparator: '',
					anchor: '100%'
				}]
			}]
		});

		Zarafa.advancesearch.dialogs.CreateSearchFolderPanel.superclass.constructor.call(this, config);
	}
});

Ext.reg('zarafa.createsearchfolderpanel', Zarafa.advancesearch.dialogs.CreateSearchFolderPanel);
Ext.namespace('Zarafa.advancesearch.dialogs');

/**
 * @class Zarafa.advancesearch.dialogs.SearchCenterPanel
 * @extends Ext.Panel
 * @xtype zarafa.searchcenterpanel
 *
 */
Zarafa.advancesearch.dialogs.SearchCenterPanel = Ext.extend(Ext.Panel, {

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

		if (!Ext.isDefined(config.model) && Ext.isDefined(config.searchContext)) {
			config.model = config.searchContext.getModel();
		}

		Ext.applyIf(config, {
			xtype: 'zarafa.searchcenterpanel',
			cls: 'k-searchcenterpanel',
			region: 'center',
			layout: 'fit',
			ref: 'centerRegion',
			unstyled: true,
			items: [{
				xtype: 'panel',
				layout: 'zarafa.switchborder',
				ref: 'switchBorder',
				border: false,
				unstyled: true,
				items: [{
					layout: 'fit',
					cls: 'zarafa-context-mainpanel',
					collapsible: false,
					region: 'center',
					minHeight: 200,
					minWidth: 300,
					items: [{
						xtype: 'zarafa.switchviewcontentcontainer',
						ref: '../viewPanel',
						layout: 'card',
						activeItem: 0,
						items: [{
							xtype: 'zarafa.searchgrid',
							flex: 1,
							id: 'search-grid' + (++Ext.Component.AUTO_ID),
							searchTabId: config.searchTabId,
							anchor: '100%',
							searchContext: config.searchContext,
							ref: '../../searchGrid',
							searchCenterPanel: this
						}]
					}]
				},{
					region: 'south',
					xtype: 'zarafa.searchresultpreviewpanel',
					ref: '../searchResultPreviewPanel',
					split: true,
					width: 400,
					height: 400,
					minWidth: 400,
					minHeight: 200,
					searchContext: config.searchContext
				}]
			}]
		});

		Zarafa.advancesearch.dialogs.SearchCenterPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Function called by Extjs when the panel has been {@link #render rendered}.
	 * At this time all events can be registered.
	 * @private
	 */
	initEvents: function ()
	{
		if (Ext.isDefined(this.searchContext)) {
			this.switchBorder.mon(this.searchContext,{
				viewchange: this.onViewChange,
				viewmodechange: this.onViewModeChange,
				scope: this
			});

			this.switchBorder.on('afterlayout', this.onAfterLayout, this, {single: true});
		}
	},

	/**
	 * Function is used to get the {@link Zarafa.advancesearch.ui.SearchResultPreviewPanel searchResultPreviewPanel}
	 * @return {Object } return {@link Zarafa.advancesearch.ui.SearchResultPreviewPanel searchResultPreviewPanel}
	 */
	getSearchResultPreviewPanel: function ()
	{
		return this.searchResultPreviewPanel;
	},

	/**
	 * Event handler triggered when {@link Zarafa.core.ui.SwitchViewContentContainer Switch view content container}
	 * layout has been initialized
	 */
	onAfterLayout: function ()
	{
		this.onViewModeChange(this.searchContext, this.searchContext.getCurrentViewMode());
	},

	/**
	 * Event handler which is fired when the currently active view inside the {@link #context}
	 * has been updated. This will update the call
	 * {@link #viewPanel}#{@link Zarafa.core.ui.SwitchViewContentContainer#switchView}
	 * to make the requested view active.
	 *
	 * @param {Zarafa.core.Context} context The context which fired the event.
	 * @param {Zarafa.common.data.Views} newView The ID of the selected view.
	 * @param {Zarafa.common.data.Views} oldView The ID of the previously selected view.
	 */
	onViewChange: function (context, newView, oldView)
	{
		if(newView === Zarafa.common.data.Views.LIST) {
			var searchGridId = this.switchBorder.searchGrid.getId();
			this.switchBorder.viewPanel.switchView(searchGridId);
		}
	},

	/**
	 * Event handler which is fired when the {@link Zarafa.core.Context} fires the
	 * {@link Zarafa.core.Context#viewmodechange viewmodechange} event. This will
	 * convert the configured {@link Zarafa.common.data.ViewModes mode} to a
	 * {@link Zarafa.common.ui.layout.SwitchBorderLayout.Orientation orientation}
	 * to be {@link Zarafa.common.ui.layout.SwitchBorderLayout.setOrientation applied}
	 * to the {@link #layout}.
	 * @param {Zarafa.core.Context} context The context which fired the event
	 * @param {Zarafa.common.data.ViewModes} newViewMode The new active mode
	 * @param {Zarafa.common.data.ViewModes} oldViewMode The previous mode
	 * @private
	 */
	onViewModeChange: function (context, newViewMode, oldViewMode)
	{
		var orientation;

		switch (newViewMode) {
			case Zarafa.common.data.ViewModes.NO_PREVIEW:
				orientation = Zarafa.common.ui.layout.SwitchBorderLayout.Orientation.OFF;
				break;
			case Zarafa.common.data.ViewModes.RIGHT_PREVIEW:
				orientation = Zarafa.common.ui.layout.SwitchBorderLayout.Orientation.HORIZONTAL;
				// hide the toolbar when right preview panel is enabled.
				this.getSearchResultPreviewPanel().getTopToolbar().onHide();
				break;
			case Zarafa.common.data.ViewModes.BOTTOM_PREVIEW:
				orientation = Zarafa.common.ui.layout.SwitchBorderLayout.Orientation.VERTICAL;
				// show the toolbar when bottom preview panel is enabled and at lest one record
				// is selected in search grid.
				var record = this.model.getPreviewRecord();
				if (Ext.isDefined(record)) {
					this.getSearchResultPreviewPanel().getTopToolbar().onShow();
				}
				break;
			case Zarafa.common.data.ViewModes.SEARCH:
			case Zarafa.common.data.ViewModes.LIVESCROLL:
				return;
		}

		// This function could be called when the layout has not yet
		// been instantiated. In that case we update the layoutConfig
		// so it will be automatically picked up by the layout when
		// it needs it.
		var layout = this.switchBorder.getLayout();
		if (!Ext.isFunction(layout.setOrientation)) {
			if (Ext.isString(layout)) {
				this.layoutConfig = Ext.apply(this.layoutConfig || {}, {orientation: orientation});
			} else {
				this.layout.orientation = orientation;
			}
		} else {
			layout.setOrientation(orientation);
		}
	}
});

Ext.reg('zarafa.searchcenterpanel', Zarafa.advancesearch.dialogs.SearchCenterPanel);

Ext.namespace('Zarafa.advancesearch.dialogs');

/**
 * @class Zarafa.advancesearch.dialogs.SearchPanel
 * @extends Ext.Panel
 * @xtype zarafa.searchpanel
 *
 */
Zarafa.advancesearch.dialogs.SearchPanel = Ext.extend(Ext.Panel, {

	/**
	 * @cfg{Object} searchContentPanel which contains {@link Zarafa.advancesearch.dialogs.SearchPanel searchPanel}
	 */
	searchContentPanel: undefined,

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

		var searchContext = container.getContextByName('advancesearch');
		if (!Ext.isDefined(config.model) && Ext.isDefined(searchContext)) {
			config.model = searchContext.getModel();
			if(Ext.isDefined(config.searchFolder)) {
				config.model.store.searchFolder[config.searchTabId] = config.searchFolder;
			}
			var parentModel = container.getCurrentContext().getModel();
			searchContext.enable(parentModel.getDefaultFolder(), true);
		}

		Ext.applyIf(config, {
			xtype: 'zarafa.searchpanel',
			layout: 'border',
			border: false,
			items: [{
				xtype: 'zarafa.searchtoolboxpanel',
				searchContext: searchContext,
				searchTabId: config.searchTabId,
				region:'west',
				scope: this
			},{
				xtype: 'zarafa.searchcenterpanel',
				searchContext: searchContext,
				searchTabId: config.searchTabId,
				region: 'center',
				searchPanel: this,
				scope: this
			},{
				xtype: 'zarafa.searchtoolbarpanel',
				searchContext: searchContext,
				searchText: config.searchText,
				region: 'north',
				scope: this
			}]
		});

		Zarafa.advancesearch.dialogs.SearchPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Function called by Extjs when the panel has been {@link #render rendered}.
	 * At this time all events can be registered.
	 * @private
	 */
	initEvents: function()
	{
		this.mon(this.centerRegion.getSearchResultPreviewPanel(),{
			afterupdatesearchpreviewpanel: this.onAfterUpdateSearchPreviewPanel,
			scope: this
		});

		this.mon(this.searchContentPanel,{
			beforeclose: this.onBeforeCloseContentPanel,
			scope: this
		});

		// Events registered on Search grid.
		this.mon(this.centerRegion.switchBorder.searchGrid,{
			resize: this.onSearchGridResize,
			scope: this
		});

		this.mon(this.searchToolBox,{
			afterupdaterestriction: this.onAfterUpdateRestriction,
			scope: this
		});

		this.mon(this.searchToolBox, 'afterlayout',this.onAfterRenderSearchToolBox, this, {single: true});

		this.mon(this.model, {
			searchfinished: this.onModelSearchFinished,
			searchexception: this.onModelSearchException,
			scope: this
		});

		this.mon(this.searchToolBox.includeSubFolderFieldSet, {
			beforerender: this.onBeforeRenderSubFolderFieldSet,
			scope: this
		});

		this.mon(this.searchToolBox.includeSubFolder, {
			check: this.onCheckIncludeSubFolder,
			render: this.onRenderSubFolderCheckbox,
			scope: this
		});

		// Events registered on Advance Search field.
		this.searchToolbar.mon(this.searchToolbar.getAdvanceSearchField(),{
			render: this.onRenderSearchTextField,
			valid: this.onValidSearchTextField,
			change: this.onChangeSearchTextField,
			start: this.onSearchStart,
			stop: this.onSearchStop,
			scope: this
		});

		this.searchToolbar.mon(this.searchToolbar.getSearchFolderCombo(),{
			render: this.onRenderSearchFolderCombo,
			select: this.onSelectSearchFolderComboValue,
			beforeselect: this.onBeforeSelectSearchFolder,
			scope: this
		});

		this.mon(container,{
			 'aftercontextswitch': this.onAfterContextSwitch,
			 scope: this
		});

		this.mon(container.getHierarchyStore(), 'addFolder', this.onHierarchyAddFolder, this);
		this.mon(container.getHierarchyStore(), 'removeFolder', this.onHierarchyRemoveFolder, this);
	},

	/**
	 * Event handler triggers when folder was added in hierarchy. function was
	 * responsible to save the search criteria in settings.
	 *
	 * @param {Zarafa.hierarchy.data.HierarchyStore} store The store which fired the event
	 * @param {Zarafa.hierarchy.data.MAPIStoreRecord} mapiStore mapi store in which new folders are added.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord/Zarafa.hierarchy.data.MAPIFolderRecord[]} record folder record(s) which are added in hierarchy.
	 * @private
	 */
	onHierarchyAddFolder: function(store, mapiStore, record)
	{
		if (Ext.isArray(record)) {
			record.forEach(function (record) {
				this.onHierarchyAddFolder(store, mapiStore, record);
			},this);
			return;
		}
		var searchStore = this.model.getActiveStore();
		var searchStoreUniqueId = searchStore.searchStoreUniqueId;
		var searchToolBox = this.searchToolBox;
		if (record.isSearchFolder() && searchStoreUniqueId === searchToolBox.searchTabId) {
			searchStore.searchFolder[searchStore.searchStoreUniqueId] = record;

			this.suspendEvents();
			var obj = {};
			var searchInCheckBoxGroup = searchToolBox.searchInCheckboxGroup.getValue();
			var messageTypeCheckBoxGroup = searchToolBox.messageTypeCheckboxGroup.getValue();
			var filterCheckBoxGroup = searchToolBox.filterCheckBoxGroup.getValue();
			var dateRangeDropdown = searchToolBox.dateRangeCombo;

			obj['searchInCheckBoxGroup'] = {};
			obj['messageTypeCheckBoxGroup'] = {};
			obj['filterCheckBoxGroup'] = {};
			obj["search_text"] = this.searchText;
			obj["search_folder_combo"] = {};
			obj['categories'] = searchToolBox.searchCategoriesStore.getCategories();

			searchInCheckBoxGroup.forEach(function(item){
				obj['searchInCheckBoxGroup'][item.itemId] = true;
			}, this);

			messageTypeCheckBoxGroup.forEach(function(item){
				obj['messageTypeCheckBoxGroup'][item.name] = true;
			}, this);

			filterCheckBoxGroup.forEach(function(item){
				obj['filterCheckBoxGroup'][item.name] = true;
			}, this);

			if (dateRangeDropdown.getValue() === 'custom_date') {
				obj["date_range"] = {};
				Ext.apply(obj["date_range"], {
					start: searchToolBox.dateField.getValue().getStartDate().getTime(),
					due: searchToolBox.dateField.getValue().getDueDate().getTime()
				});
			} else {
				obj["date_range"] = dateRangeDropdown.getValue();
			}

			var searchFolderCombo = this.searchToolbar.getSearchFolderCombo();
			var folder = searchFolderCombo.getStore().getAt(searchFolderCombo.getStore().find('value',searchFolderCombo.getValue()));
			if(folder.get('flag') === Zarafa.advancesearch.data.SearchComboBoxFieldsFlags.ALL_FOLDERS) {
				obj["search_folder_combo"]["folder_type"] = folder.get('flag');
			} else {
				obj["search_folder_combo"]["folder_entryid"] = folder.get('value');
				obj["search_folder_combo"]["folder_name"] = folder.get('name');
				obj["search_folder_combo"]["folder_type"] = folder.get('flag');
				obj["search_folder_combo"]["include_subfolder"] = folder.get('include_subfolder');
			}
			container.getSettingsModel().set('zarafa/v1/contexts/search/search_criteria/'+record.get('entryid'), obj);
			this.resumeEvents();
		}
	},

	/**
	 * Event handler triggers when folder was remove in hierarchy. function was
	 * responsible to verify that search tab is still exist of folder which was removed.
	 * If search tab is still open then set 'keepSearchFolder' flags true.
	 *
	 * @param {Zarafa.hierarchy.data.HierarchyStore} store The store which fired the event
	 * @param {Zarafa.hierarchy.data.MAPIStoreRecord} mapiStore mapi store in which new folders was removed.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord/Zarafa.hierarchy.data.MAPIFolderRecord[]} record folder record(s) which was removed in hierarchy.
	 * @private
	 */
	onHierarchyRemoveFolder: function (store, mapiStore, record)
	{
		var searchStore = this.model.getActiveStore();
		var searchStoreRecord = searchStore.searchFolder[this.searchToolBox.searchTabId];
		if (searchStoreRecord && Zarafa.core.EntryId.compareEntryIds(searchStoreRecord.get('entryid'), record.get('entryid'))) {
			record.addMessageAction('keepSearchFolder', true);
		}
	},

	/**
	 * Event handler triggers when context was switch successfully.
	 * It was set the parent search field because after the switching context
	 * search field of parent context gets destroy so we need to update the parent
	 * search field object and also required to {#reinitializeEvents}
	 *
	 * @param {Object} folders contains folder details
	 * @param {Context} oldParentContext previously selected context
	 * @param {Context} newParentContext selected context
	 */
	onAfterContextSwitch: function(folders, oldParentContext, newParentContext)
	{
		var activeItem = container.getTabPanel().getActiveTab().getActiveItem();
		var topToolbar = activeItem.getTopToolbar();
		var parentSearchField;

		if (Ext.isDefined(topToolbar) && Ext.isDefined(parentSearchField = topToolbar.searchTextfield)) {
			this.searchContentPanel.setParentSearchField(parentSearchField);
		} else {
			this.searchContentPanel.setParentSearchField(undefined);
		}
	},

	/**
	 * Event handler triggered before {@link Zarafa.advancesearch.dialogs.SearchContentPanel searchcontentpanel}
	 * gets close. it will call the {#onSearchStop} which stop the search.
	 * @param {Zarafa.advancesearch.dialogs.SearchContentPanel} contentPanel which gets the close
	 */
	onBeforeCloseContentPanel: function(contentPanel)
	{
		this.resetParentSearchField();
		var parentSearchField = this.searchContentPanel.getParentSearchField();
		if (parentSearchField) {
			parentSearchField.searchPanelRendered = false;
		}

		/**
		 * when user tries to close the search tab which is not currently active tab,
		 * it will find the closing search tab store and set it to active store in model, so when stopSearch
		 * function of model is call to remove the search folder on server side it will send correct search folder
		 * entry id
		 */
		var currentSearchStore = false;
		var model = this.model;
		if (model.getActiveStore().getSearchStoreUniqueId() !== contentPanel.name){
			currentSearchStore = this.model.store;
			var store = model.stores[contentPanel.name];
			model.setActiveStore(store);
		}

		this.onSearchStop();

		/**
		 * Model has hold the selected record in Zarafa.core.ContextModel.selectedRecords config
		 * so when close the search panel we have to clear the selection manually.
		 */
		if (Ext.isDefined(model.getSelectedRecords())) {
			model.setSelectedRecords(undefined);
		}

		// search tab is going to close so remove the search store from stores object
		model.discardStore(contentPanel.name);

		/**
		 * After removed search folder on server side and close the search tab we need
		 * to again set the active search tab store in model.
 		 */
		if(currentSearchStore !== false) {
			model.setActiveStore(currentSearchStore);
		}
	},

	/**
	 * Event handler which will be called when the {@link #model} fires the
	 * {@link Zarafa.core.ContextModel#searchfinished} event. This will update
	 * the UI to indicate that the search is no longer running and enable the
	 * components that a new search might be created.
	 *
	 * @param {Zarafa.core.ContextModel} model The model which fired the event
	 * @private
	 */
	onModelSearchFinished: function(model)
	{
		this.resetParentSearchField();
		this.searchToolbar.getAdvanceSearchField().hideMask();
	},

	/**
	 * Function is reset the {@link Zarafa.common.searchfield.ui.SearchTextField SearchTextField}
	 * if parent context has.
	 */
	resetParentSearchField: function()
	{
		var parentSearchField = this.searchContentPanel.getParentSearchField();
		if (Ext.isDefined(parentSearchField)) {
			parentSearchField.hideMask();
		}
	},

	/**
	 * Event handler which will be called when the {@link #model} fires the
	 * {@link Zarafa.core.ContextModel#searchexception} event. This will
	 * show an error message indicating that the search has failed.
	 *
	 * @param {Zarafa.core.ContextModel} model The model which fired the event
	 * @param {Zarafa.core.data.IPMProxy} proxy object that received the error
	 * and which fired exception event.
	 * @param {String} type 'request' if an invalid response from server received,
	 * '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
	 * @private
	 */
	onModelSearchException: function(model, proxy, type, action, options, response, args)
	{
		var searchTextfield = this.searchToolbar.getAdvanceSearchField();
		searchTextfield.hideMask();
		searchTextfield.focus();
	},

	/**
	 * Function will be used to start actual search on {@link Zarafa.core.data.ListModuleStore ListModuleStore},
	 * and also it will register event on {@link Zarafa.core.data.ListModuleStore ListModuleStore} to get
	 * updated status of search.
	 * @param {Zarafa.common.searchfield.ui.SearchTextField} advanceSearchField the advance search field which
	 * performs the search.
	 * @private
	 */
	onSearchStart: function(advanceSearchField)
	{
		var searchText = advanceSearchField.getValue();
		this.searchContentPanel.searchText = searchText;
		var searchField = this.searchToolbar.getAdvanceSearchField();

		// Strip KQL field prefixes from the title for readability
		var displayTitle = searchText
			.replace(/\b(?:subject|from|to|cc|bcc|body|sender|attachment|category):["']?/gi, '')
			.replace(/["']/g, '')
			.replace(/\b(AND|OR|NOT)\b/gi, '')
			.replace(/\s+/g, ' ')
			.trim() || searchText;
		this.searchContentPanel.setTitle(displayTitle);

		if(searchField.getValue() !== searchText) {
			searchField.setValue(searchText);
		}

		var restriction = this.searchToolBox.createRestriction(searchText);
		var searchFolderCombo = this.searchToolbar.getSearchFolderCombo();
		var folder = container.getHierarchyStore().getFolder(searchFolderCombo.getValue());

		var record = searchFolderCombo.findRecord('value', searchFolderCombo.getValue());
		var includeSubFolders = record.get('include_subfolder');

		this.model.startSearch(restriction , includeSubFolders, {'folder': folder});

		// if search is performed from the parent search field then, it will set the search tab
		// to active tab.
		var tabPanel = container.getTabPanel();
		tabPanel.setActiveTab(this.searchContentPanel);
	},

	/**
	 * Function will be used to execute stop search request on {@link Zarafa.core.data.ListModuleStore ListModuleStore},
	 * it will also unregister event on store to for getting updates of search status.
	 * @private
	 */
	onSearchStop: function()
	{
		var store = this.model.getActiveStore();
		if (!Ext.isDefined(store.searchFolder[store.searchStoreUniqueId])) {
			this.model.stopSearch();
		} else {
			// If the search folder does not exists in the hierarchy,
			// Then execute the stop search request.
			var hierarchyStore = container.getHierarchyStore();
			var defaultStore = hierarchyStore.getDefaultStore();
			if (defaultStore.getFavoritesStore().findExact('entryid', store.searchFolderEntryId) === -1) {
				this.model.stopSearch();
			}
			delete store.searchFolder[store.searchStoreUniqueId];
		}

		// Always clear results when search is stopped, even if the
		// search had already finished (isSearching() is false).
		if (store && store.getCount() > 0) {
			store.removeAll();
		}

		if(Ext.isDefined(this.searchToolbar)) {
			this.searchToolbar.getAdvanceSearchField().focus();
		}
	},

	/**
	 * Event handler triggers when {@link Zarafa.advancesearch.dialogs.SearchToolBoxPanel search tool box}
	 * layout was rendered also it will start the search.
	 * @param {Zarafa.advancesearch.dialogs.SearchToolBoxPanel} searchToolBox The search tool box panel.
	 */
	onAfterRenderSearchToolBox: function()
	{
		var searchField = this.searchToolbar.getAdvanceSearchField();
		searchField.searchPanelRendered = this.rendered;

		var store = this.model.getActiveStore();
		if (Ext.isDefined(store.searchFolder[store.searchStoreUniqueId])) {
			var folder = store.searchFolder[store.searchStoreUniqueId];

			// Load a new set of folders from the store.
			var data = Ext.applyIf({}, {
				'folder': folder
			});

			store.load(data);
			store.hasSearchResults = true;
		} else {
			// Trigger search operation.
			searchField.onTriggerClick();
		}
	},

	/**
	 * Event handler triggers when {@link Zarafa.advancesearch.dialogs.SearchToolBoxPanel#searchCriteria}
	 * gets update. it will trigger the advance search.
	 */
	onAfterUpdateRestriction: function()
	{
		this.searchToolbar.getAdvanceSearchField().onTriggerClick();
	},

	/**
	 * Event handler triggers when {@link Zarafa.common.searchfield.ui.SearchTextField SearchTextField}
	 * gets render and also it will set the search text in search field.
	 *
	 * @param {Zarafa.common.searchfield.ui.SearchTextField} searchTextField the search text field which
	 * contains the search string.
	 */
	onRenderSearchTextField: function(searchTextField)
	{
		var searchFolderSettingObj = this.searchToolBox.getSearchFolderSettings();
		if (searchFolderSettingObj) {
			searchTextField.setValue(searchFolderSettingObj.search_text);
		} else {
			searchTextField.setValue(this.searchText);
			// Apply virtual tokens (type, date, unread) from main toolbar
			var vt = this.virtualTokens;
			if (vt && vt.length > 0 && searchTextField.applyVirtualTokens) {
				// Defer to afterrender so tokenWrapEl exists
				searchTextField.on('afterrender', function() {
					searchTextField.applyVirtualTokens(vt);
				}, this, {single: true});
			}
		}
	},

	/**
	 * Event handler triggered when value of {@link Zarafa.common.searchfield.ui.SearchTextField Search text field}
	 * was validated.
	 *
	 * @param {Zarafa.common.searchfield.ui.SearchTextField} searchTextField search text field which contains
	 * search text.
	 */
	onValidSearchTextField: function(searchTextField)
	{
		this.searchText = searchTextField.getValue();
	},

	/**
	 * Event handler triggered when value of search text field has been changed.
	 *
	 * @param {Zarafa.common.searchfield.ui.SearchTextField} searchTextField search text field which contains
	 * @param {String} newValue new value of search text field.
	 * @param {String} oldValue old value of search text field.
	 */
	onChangeSearchTextField: function(searchTextField, newValue, oldValue)
	{
		this.searchText = newValue;
	},

	/**
	 * Event handler triggers when {@link Zarafa.common.searchfield.ui.SearchFolderCombo SearchFolderCombo}
	 * is render. it will update the {@link Zarafa.common.searchfield.ui.SearchFolderCombo SearchFolderCombo}
	 * store with parent search folder
	 *
	 * @param {Zarafa.common.searchfield.ui.SearchTextField} searchTextField the search text field which
	 * contains the search string.
	 */
	onRenderSearchFolderCombo: function(searchFolderCombo)
	{
		var searchFolderSettingObj = this.searchToolBox.getSearchFolderSettings();
		if (searchFolderSettingObj) {
			var searchComboSettingObj = searchFolderSettingObj['search_folder_combo'];
			var store = searchFolderCombo.getStore();
			var value;

			if (searchComboSettingObj['folder_type'] === Zarafa.advancesearch.data.SearchComboBoxFieldsFlags.ALL_FOLDERS) {
				var folder = store.getAt(store.find('flag', searchComboSettingObj['folder_type']));
				value = folder.get('value');
			} else {
				var index = 0;
				if (searchComboSettingObj['folder_type'] === Zarafa.advancesearch.data.SearchComboBoxFieldsFlags.CURRENT_SELECTED_FOLDER) {
					index = 1;
					store.removeAt(index);
				} else if (store.getAt(0).get('flag') === Zarafa.advancesearch.data.SearchComboBoxFieldsFlags.IMPORTED_FOLDER) {
					store.removeAt(index);
				}
				var folder = new Ext.data.Record({
					'name': searchComboSettingObj['folder_name'],
					'value': searchComboSettingObj['folder_entryid'],
					'flag': searchComboSettingObj['folder_type'],
					'include_subfolder': searchComboSettingObj['include_subfolder']
				});
				store.insert(index, folder);
				value = searchComboSettingObj['folder_entryid'];
			}
			searchFolderCombo.setValue(value);
		} else {
			searchFolderCombo.store.clearData();
			var parentSearchFolderCombo = this.searchContentPanel.getParentSearchFolderCombo();
			parentSearchFolderCombo.store.getRange().forEach(function (record) {
				searchFolderCombo.store.add(record.copy());
			});
			var value = parentSearchFolderCombo.getValue();
			searchFolderCombo.setValue(value);
		}

	},

	/**
	 * Event handler triggered when selection was performed on
	 * {@link Zarafa.common.searchfield.ui.SearchFolderCombo SearchFolderCombo}.
	 * function was used to check/un-check "Include sub folder" checkbox which belongs to
	 * {@link Zarafa.advancesearch.dialogs.SearchToolBoxPanel SearchToolBox}.
	 *
	 * @param {Zarafa.common.searchfield.ui.SearchFolderCombo} combo The combo box which fired the event.
	 * @param {Ext.data.Record} record The data record returned from the underlying store
	 * @param {number} index The index of the selected item in the drop down list
	 */
	onSelectSearchFolderComboValue: function(combo, record, index)
	{
		var subFolderCheckBox = this.searchToolBox.includeSubFolder;
		subFolderCheckBox.setValue(record.get('include_subfolder'));
		this.onRenderSubFolderCheckbox(subFolderCheckBox);
	},

	/**
	 * Event handler which is raised just before the {@link Ext.form.FieldSet IncludeSubFolderFieldSet}
	 * is being rendered. it will hide the {@link Ext.form.FieldSet IncludeSubFolderFieldSet} if
	 * selected folder is does not support the search folder.
	 *
	 * @param {Ext.form.FieldSet} fieldSet field set which fire this event.
	 */
	onBeforeRenderSubFolderFieldSet: function (fieldSet)
	{
		var searchFolderCombo = this.searchToolbar.getSearchFolderCombo();
		var folder = container.getHierarchyStore().getFolder(searchFolderCombo.getValue());
		fieldSet.hidden = !this.model.supportsSearchFolder(folder);
	},

	/**
	 * Event handler triggered when "Include sub folder" checkbox which belongs to
	 * {@link Zarafa.advancesearch.dialogs.SearchToolBoxPanel SearchToolBox}
	 * was checked/un-checked. it will also update the "include_subfolder" property of
	 * {@link Ext.data.Record searchFolder} record of search folder combo box.
	 *
	 * @param {Ext.form.Checkbox} checkbox The checkbox which fired the event
	 * @param {Boolean} check True if the checkbox is currently checked
	 * @private
	 */
	onCheckIncludeSubFolder: function (checkBox, checked)
	{
		var searchFolderCombo = this.searchToolbar.getSearchFolderCombo();
		var record = searchFolderCombo.findRecord('value', searchFolderCombo.getValue());

		if (checked !== record.get('include_subfolder')) {
			record.set('include_subfolder', checked);
			record.commit();
			this.searchToolBox.afterUpdateRestriction();
		}
	},

	/**
	 * Event handle triggered when "Include sub folder" checkbox which belongs to
	 * {@link Zarafa.advancesearch.dialogs.SearchToolBoxPanel SearchToolBox} was rendered
	 * also it will call when search folder change in {@link Zarafa.common.searchfield.ui.SearchFolderCombo SearchFolderCombo}.
	 * Also function is responsible to disable "Include sub folder" check box
	 * and apply the tooltip when "All folders" option in {@link Zarafa.common.searchfield.ui.SearchFolderCombo SearchFolderCombo}
	 * is selected else enable checkbox and remove the tooltip from "Include Sub folder" checkbox.
	 *
	 * @param {Ext.form.Checkbox} checkBox The checkbox which was rendered.
	 */
	onRenderSubFolderCheckbox: function (checkBox)
	{
		var searchFolderCombo = this.searchToolbar.getSearchFolderCombo();
		var record = searchFolderCombo.findRecord('value', searchFolderCombo.getValue());
		var isAllFolderSelected = record.get('flag') === Zarafa.advancesearch.data.SearchComboBoxFieldsFlags.ALL_FOLDERS;
		checkBox.setDisabled(isAllFolderSelected);
		checkBox.setValue(record.get('include_subfolder'));

		if(checkBox.rendered) {
			// Add tooltip on "include sub folder" check box when "All folders"
			// was selected in search folder combo box else remove tooltip from
			// "include sub folder" check box
			if (isAllFolderSelected) {
				checkBox.wrap.dom.qtip = _("All folders are selected");
			} else {
				delete(checkBox.wrap.dom.qtip);
			}
		}
	},

	/**
	 * Event handler triggered before selection performs in {@link Zarafa.common.searchfield.ui.SearchFolderCombo SearchFolderCombo}
	 * Will open {@link Zarafa.advancesearch.dialogs.SelectFolderContentPanel SelectFolderContentPanel}, if
	 * "Other…" option was selected. also it will check select folder from {@link Zarafa.common.searchfield.ui.SearchFolderCombo SearchFolderCombo}
	 * is supports search folder if not we hide the {@link Ext.form.FieldSet IncludeSubFolderFieldSet}.
	 *
	 * @param {Zarafa.common.searchfield.ui.SearchFolderCombo} combo The combo which fired the event.
	 * @param {Ext.data.Record} record The data record returned from the underlying store
	 * @param {number} index The index of the selected item in the dropdown list
	 * @return {boolean} true if selected record is not 'Other…' else false.
	 */
	onBeforeSelectSearchFolder: function (combo, record, index)
	{
		if(record.get('value') === 'other') {
			combo.collapse();
			Zarafa.advancesearch.Actions.openSelectSearchFolderDialog({
				searchFolderCombo: combo,
				model: this.model
			});
			return false;
		} else {
			var folder = container.getHierarchyStore().getFolder(record.get('value'));
			this.searchToolBox.includeSubFolderFieldSet.setVisible(this.model.supportsSearchFolder(folder));
			this.doLayout();
		}
		return true;
	},

	/**
	 * Event handler triggered when Search result grid is gets resize.
	 * also it will set the width of the left and right toolbar of the
	 * {@link Zarafa.advancesearch.dialogs.SearchToolbarPanel search toolbar panel}
	 *
	 * @param {Ext.grid.GridPanel} grid which holds the search result.
	 * @param {Number} adjWidth The box-adjusted width that was set
	 * @param {Number} adjHeight The box-adjusted height that was set
	 * @param {Number} rawWidth The width that was originally specified
	 * @param {Number} rawHeight The height that was originally specified
	 */
	onSearchGridResize: function(grid, adjWidth, adjHeight, rawWidth, rawHeight )
	{
		var leftToolbar = this.searchToolbar.contextMainPanelToolbar;
		var rightToolbar = this.searchToolbar.getRightSearchToolbar();

		var searchToolBox = this.getLayout().west;
		var searchToolBoxMargins = searchToolBox.getMargins();

		var margins = searchToolBoxMargins.left + searchToolBoxMargins.right;

		leftToolbar.setWidth(searchToolBox.getSize().width + margins + adjWidth);

		rightToolbar.setWidth(this.centerRegion.switchBorder.getLayout().south.getSize().width);
	},

	/**
	 * Event handler which triggered when {@link Zarafa.advancesearch.ui.SearchResultPreviewPanel SearchResultPreviewPanel}
	 * was updated.
	 *
	 * @param {Zarafa.advancesearch.ui.SearchResultPreviewPanel} SearchResultPreviewPanel The SearchResultPreviewPanel which fired the event
	 * @param {Zarafa.core.data.MAPIRecord} record The record to update in this component
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 */
	onAfterUpdateSearchPreviewPanel: function(SearchResultPreviewPanel,record, contentReset)
	{
		var rightToolbar = this.searchToolbar.getRightSearchToolbar();
		rightToolbar.setVisible(!!record);

		if (record) {
			var isFaultyMessage = record.isFaultyMessage();
			var isMessageReplyable = Zarafa.core.MessageClass.isClass(record.get('message_class'), ['IPM.NOTE', 'REPORT.IPM', 'IPM.SCHEDULE', 'IPM.APPOINTMENT']);

			// Additional check when the message is IPM.Appointment and not a meeting request
			// but a simple appointment which can not be replied as there is no reply-to recipients available.
			if (isMessageReplyable && Zarafa.core.MessageClass.isClass(record.get('message_class'), ['IPM.APPOINTMENT'])) {
				if(!record.isMeeting()){
					isMessageReplyable = false;
				}
			}

			rightToolbar.replyBtn.setVisible(!isFaultyMessage && isMessageReplyable);
			rightToolbar.replyAllBtn.setVisible(!isFaultyMessage && isMessageReplyable);
			rightToolbar.forwardBtn.setVisible(!isFaultyMessage && isMessageReplyable);

			// Currently pop-out functionality is not available for
			// contact, sticky note, distribution list, appointment and task
			// So disable showing popout button in search results preview panel for those context item
			// TODO Remove when we support popout for all context
			if (Zarafa.supportsPopOut()) {
				var isSupportPopout = Zarafa.core.MessageClass.isClass(record.get('message_class'), ['IPM.NOTE', 'REPORT.IPM.Note', 'IPM.Schedule.Meeting'], true);
				rightToolbar.popoutBtn.setVisible(isSupportPopout);
			}

			// Only show the "Edit as New" button in the toolbar when the item is in the Sent folder
			var defaultFolder = SearchResultPreviewPanel.model.getDefaultFolder();
			rightToolbar.editAsNewBtn.setVisible(defaultFolder.getDefaultFolderKey() === 'sent' && !isFaultyMessage && isMessageReplyable);

			this.searchToolbar.recordComponentPlugin.setRecord(record);
		}
	}
});

Ext.reg('zarafa.searchpanel', Zarafa.advancesearch.dialogs.SearchPanel);
Ext.namespace('Zarafa.advancesearch.dialogs');

/**
 * @class Zarafa.advancesearch.dialogs.SearchToolBoxPanel
 * @extends Ext.Panel
 * @xtype zarafa.searchtoolboxpanel
 *
 */
Zarafa.advancesearch.dialogs.SearchToolBoxPanel = Ext.extend(Ext.Panel, {

	/**
	 * The {@link Zarafa.advancesearch.AdvanceSearchContextModel} which is obtained from
	 * the {@link #context}.
	 *
	 * @property
	 * @type Zarafa.advancesearch.AdvanceSearchContextModel
	 */
	model: undefined,

	/**
	 * The template of the category blocks
	 * @property
	 * @type {Ext.Template/String}
	 */
	categoriesHtmlTemplate:
		'<tpl for=".">' +
			'<span class="k-category-block {colorClass}" '+
				'<tpl if="!Ext.isEmpty(backgroundColor)">style="background-color:{backgroundColor};"</tpl>'+
				'<tpl if="!Ext.isEmpty(hoverString)">ext:qtip = "{hoverString}" ext:qwidth="100%"</tpl>'+
				'>' +
				'{categoryName}' + '<span class="k-category-close"></span>'+
			'</span>' +
		'</tpl>',

	/**
	 * @cfg {Zarafa.advancesearch.data.SearchCategoriesStore} store The store which contain categories,
	 * That added in category filter.
	 */
	searchCategoriesStore: undefined,

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

		if (!Ext.isDefined(config.model) && Ext.isDefined(config.searchContext)) {
			config.model = config.searchContext.getModel();
		}

		this.searchCategoriesStore = new Zarafa.advancesearch.data.SearchCategoriesStore();
		var messageType = {};
		var filterSetting = {};
		var searchInCheckBoxSetting = {};
		var searchCriteria = this.getSearchFolderSettings(config.model, config.searchTabId);
		if (searchCriteria) {
			messageType = searchCriteria.messageTypeCheckBoxGroup || {};
			filterSetting = searchCriteria.filterCheckBoxGroup || {};
			searchInCheckBoxSetting = searchCriteria.searchInCheckBoxGroup || {};
			this.searchCategoriesStore.addCategories(searchCriteria.categories);
		}

		/**
		 * messageClasses contains all message classes which used in advance search.
		 */
		config.messageClasses = {};

		/**
		 * folder on which search gets performed.
		 */
		config.folder = undefined;

		/**
		 * search fields which used to match with search string.
		 */
		config.searchFields = {};

		/**
		 * searchCriteria which contains the search criteria based on this
		 * search criteria search restriction will be form.
		 */
		config.searchCriteria = {};
		config.searchCriteria['date_range'] = {};
		config.searchCriteria['date_range']['start'] = 0;
		config.searchCriteria['date_range']['end'] = 0;
		config.searchCriteria['negated_extra_fields'] = [];

		var dateRangeStore = {
			xtype: 'jsonstore',
			autoDestroy: true,
			fields: ['name', 'value'],
			autoLoad: true,
			data: Zarafa.advancesearch.data.DateRangeFields
		};

		Ext.applyIf(config, {
			xtype: 'zarafa.searchtoolboxpanel',
			header: false,
			width: 190,
			minWidth: 190,
			maxWidth: 190,
			cls: 'zarafa-search-toolbox',
			plugins: [{
				ptype: 'zarafa.recordcomponentplugin'
			},{
				ptype: 'zarafa.recordcomponentupdaterplugin'
			}],
			collapsible: false,
			layout: 'fit',
			unstyled: true,
			ref: 'searchToolBox',
			items: [{
				xtype: 'container',
				style: 'overflow-y: auto; overflow-x: hidden;',
				items: [
					this.createFoldersFieldset(),
					this.createMessageTypeFieldset(messageType),
					this.createFilterFieldset(filterSetting),
					this.createDateRangeFieldset(dateRangeStore),
					this.createSearchInFieldset(searchInCheckBoxSetting),
					this.createCategoryFilterFieldset(dateRangeStore),
					this.createFavoritesContainer(config)
				]
			}],
			listeners: {
				afterrender: this.onAfterRender,
				scope: this
			}
		});

		this.addEvents(
			/**
			 * @event afterupdaterestriction fired after the {@link #searchCriteria} gets updated by the
			 * {@link Zarafa.advancesearch.dialogs.SearchToolBoxPanel searchToolBox}.
			 * @param {Zarafa.advancesearch.dialogs.SearchToolBoxPanel} searchToolBox which used to triggers/update the search restriction.
			 */
			'afterupdaterestriction'
		);

		Zarafa.advancesearch.dialogs.SearchToolBoxPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Event handler for the render event of the SearchToolBoxPanel. Will add an event listener to the
	 * input element of the {@link Zarafa.common.searchfield.ui.SearchTextField}
	 */
	onAfterRender: function()
	{
		var searchTextField = this.ownerCt.searchToolbar.contextMainPanelToolbar.searchFieldContainer.searchTextField;

		// Because the input event is not relayed by the Ext.form.TextField (like e.g. keyup) we must listen
		// to the input element itself. We can only add the listener once it has been rendered, hence the
		// double mon()
		this.mon(searchTextField, 'render', function() {
			this.mon(searchTextField.getEl(), 'input', this.onSearchTextFieldChange, this);
		}, this, {single: true});

		// Listen for chip changes to sync search bar chips → toolbox checkboxes
		this.mon(searchTextField, 'chipchange', this.onChipChange, this);

		// Sync chips and fieldset state once the search field has parsed its
		// tokens. If already rendered, sync now; otherwise wait for afterrender
		// so tokens are available.
		var syncInitial = function() {
			var chips = searchTextField.getFilterChips ? searchTextField.getFilterChips() : [];
			if (chips.length > 0) {
				this.onChipChange(searchTextField, chips);
			}
			this.onSearchTextFieldChange();
		};

		if (searchTextField.rendered) {
			syncInitial.call(this);
		} else {
			this.mon(searchTextField, 'afterrender', function() {
				// Defer to allow the CheckboxGroup to finish rendering
				// its items into a MixedCollection before we try to
				// iterate with .each().
				syncInitial.defer(1, this);
			}, this, {single: true});
		}
	},

	/**
	 * Event handler for the input event of the input of the {@link Zarafa.common.searchfield.ui.SearchTextField}
	 * Will disable the search field panels when the search query is a KQL query, or enable them otherwise
	 * @param {Zarafa.common.searchfield.ui.SearchTextField} searchTextField The text field where the
	 * search queries are entered.
	 */
	onSearchTextFieldChange: function()
	{
		var searchTextField = this.ownerCt.searchToolbar.contextMainPanelToolbar.searchFieldContainer.searchTextField;
		var query = searchTextField.getValue();
		var tokens = Zarafa.advancesearch.KQLParser.tokenize(query);
		var usesAdvancedSyntax = Zarafa.advancesearch.KQLParser.usesExplicitSyntax(tokens);

		// When the user has active filter chips, keep the checkboxes enabled
		// so they stay in sync with the chips. Only disable when the user
		// typed raw KQL syntax without using the chip UI.
		var hasChips = searchTextField.tokens && searchTextField.tokens.length > 0;

		if (usesAdvancedSyntax && !hasChips) {
			this.searchInFieldset.disable();
			this.categoryFilterFieldSet.disable();
		} else {
			this.searchInFieldset.enable();
			this.categoryFilterFieldSet.enable();
		}
	},

	/**
	 * Mapping from KQL filter keys to SearchToolBoxPanel checkbox itemIds.
	 * Multiple KQL keys can map to the same checkbox.
	 * @property
	 * @type Object
	 */
	/**
	 * Mapping from KQL chip keys to "Search in" checkbox itemIds.
	 * @property
	 * @type Object
	 */
	chipToCheckboxMap: {
		'subject': 'subject',
		'from': 'sender',
		'sender': 'sender',
		'to': 'recipients',
		'cc': 'recipients',
		'bcc': 'recipients',
		'body': 'body'
	},

	/**
	 * Reverse mapping from "Search in" checkbox itemIds to KQL keys.
	 * @property
	 * @type Object
	 */
	checkboxToChipMap: {
		'sender': ['from', 'sender'],
		'recipients': ['to', 'cc', 'bcc'],
		'subject': ['subject'],
		'body': ['body']
	},

	/**
	 * Mapping from chip keys to "Filter" checkbox names.
	 * @property
	 * @type Object
	 */
	chipToFilterMap: {
		'attachment': 'hasattach',
		'unread': 'message_flags'
	},

	/**
	 * Reverse mapping from "Filter" checkbox names to chip keys.
	 * @property
	 * @type Object
	 */
	filterToChipMap: {
		'hasattach': 'attachment',
		'message_flags': 'unread'
	},

	/**
	 * Mapping from type: chip values to message type checkbox names.
	 * @property
	 * @type Object
	 */
	typeValueToCheckboxMap: {
		'mail': 'mail',
		'appointment': 'calendar',
		'contact': 'contact',
		'task': 'task',
		'note': 'note'
	},

	/**
	 * Reverse mapping from message type checkbox names to type: chip values.
	 * @property
	 * @type Object
	 */
	checkboxToTypeValueMap: {
		'mail': 'mail',
		'calendar': 'appointment',
		'contact': 'contact',
		'task': 'task',
		'note': 'note'
	},

	/**
	 * Flag to prevent recursive sync between chips and checkboxes.
	 * @property
	 * @type Boolean
	 */
	syncing: false,

	/**
	 * Event handler for the chipchange event from SearchTextField.
	 * Syncs chip state → all toolbox panel controls.
	 * @param {Zarafa.common.searchfield.ui.SearchTextField} field
	 * @param {Array} chips Array of chip objects
	 */
	onChipChange: function(field, chips)
	{
		if (this.syncing) {
			return;
		}

		// Guard: checkbox groups may not be MixedCollections yet
		if (!this.searchInCheckboxGroup ||
			!this.searchInCheckboxGroup.items ||
			typeof this.searchInCheckboxGroup.items.each !== 'function') {
			if (chips && chips.length > 0) {
				this.onChipChange.defer(1, this, [field, chips]);
			}
			return;
		}

		this.syncing = true;

		// Collect all chip keys and values, tracking negation
		var filterKeys = [];
		var negatedKeys = [];
		var typeValues = [];
		var categoryValues = [];
		var dateValue = null;
		for (var i = 0; i < chips.length; i++) {
			var chip = chips[i];
			if (chip.type !== 'filter') {
				continue;
			}
			filterKeys.push(chip.key);
			if (chip.negated) {
				negatedKeys.push(chip.key);
			}
			if (chip.key === 'type' && chip.value) {
				typeValues.push(chip.value.toLowerCase());
			}
			if (chip.key === 'category' && chip.value) {
				categoryValues.push(chip.value);
			}
			if (chip.key === 'date' && chip.value) {
				dateValue = chip.value.toLowerCase().replace(/\s+/g, '_');
			}
		}
		this.searchCriteria['negated_extra_fields'] = negatedKeys;

		// Sync "Search in" checkboxes
		this.searchInCheckboxGroup.items.each(function(checkbox) {
			var chipKeys = this.checkboxToChipMap[checkbox.itemId];
			if (!chipKeys) {
				return;
			}
			var shouldCheck = false;
			for (var j = 0; j < chipKeys.length; j++) {
				if (filterKeys.indexOf(chipKeys[j]) !== -1) {
					shouldCheck = true;
					break;
				}
			}
			if (checkbox.getValue() !== shouldCheck) {
				checkbox.setValue(shouldCheck);
			}
		}, this);

		// Sync "Filter" checkboxes (attachment, unread)
		// Negated filters should not check the checkbox
		if (this.filterCheckBoxGroup && this.filterCheckBoxGroup.items &&
			typeof this.filterCheckBoxGroup.items.each === 'function') {
			this.filterCheckBoxGroup.items.each(function(checkbox) {
				var chipKey = this.filterToChipMap[checkbox.name];
				if (!chipKey) {
					return;
				}
				var shouldCheck = filterKeys.indexOf(chipKey) !== -1 &&
					negatedKeys.indexOf(chipKey) === -1;
				if (checkbox.getValue() !== shouldCheck) {
					checkbox.setValue(shouldCheck);
				}
			}, this);
		}

		// Sync "Show" (message type) checkboxes
		if (this.messageTypeCheckboxGroup && this.messageTypeCheckboxGroup.items &&
			typeof this.messageTypeCheckboxGroup.items.each === 'function') {
			this.messageTypeCheckboxGroup.items.each(function(checkbox) {
				var shouldCheck = typeValues.indexOf(this.checkboxToTypeValueMap[checkbox.name] || '') !== -1;
				if (checkbox.getValue() !== shouldCheck) {
					checkbox.setValue(shouldCheck);
				}
			}, this);
		}

		// Sync date range combo
		if (this.dateRangeCombo && dateValue) {
			var store = this.dateRangeCombo.getStore();
			if (store.find('value', dateValue) !== -1) {
				this.dateRangeCombo.setValue(dateValue);
				this.dateRangeCombo.fireEvent('select', this.dateRangeCombo,
					store.getAt(store.find('value', dateValue)),
					store.find('value', dateValue));
			}
		} else if (this.dateRangeCombo && !dateValue && filterKeys.indexOf('date') === -1) {
			// No date chip → reset to "Any date"
			var currentDateValue = this.dateRangeCombo.getValue();
			if (currentDateValue !== 'all_dates') {
				this.dateRangeCombo.setValue('all_dates');
				var dateStore = this.dateRangeCombo.getStore();
				this.dateRangeCombo.fireEvent('select', this.dateRangeCombo,
					dateStore.getAt(dateStore.find('value', 'all_dates')),
					dateStore.find('value', 'all_dates'));
			}
		}

		// Sync categories: add missing ones and remove extras
		if (this.searchCategoriesStore) {
			var existingCategories = this.searchCategoriesStore.getCategories();
			// Add categories from chips that aren't in the store
			for (var c = 0; c < categoryValues.length; c++) {
				if (this.searchCategoriesStore.findExactCaseInsensitive('name', categoryValues[c]) === -1) {
					this.searchCategoriesStore.addCategories([categoryValues[c]]);
				}
			}
			// Remove categories from store that aren't in chips
			for (var r = existingCategories.length - 1; r >= 0; r--) {
				var found = false;
				for (var cv = 0; cv < categoryValues.length; cv++) {
					if (categoryValues[cv].toLowerCase() === existingCategories[r].toLowerCase()) {
						found = true;
						break;
					}
				}
				if (!found) {
					var idx = this.searchCategoriesStore.findExactCaseInsensitive('name', existingCategories[r]);
					if (idx !== -1) {
						this.searchCategoriesStore.removeAt(idx);
					}
				}
			}
			this.setCategoriesRestriction(this.searchCategoriesStore.getCategories());
			if (this.categoryFilterLabel) {
				this.categoryFilterLabel.setVisible(!this.searchCategoriesStore.getCount());
			}
		}

		this.syncing = false;

		// Re-evaluate fieldset enable/disable state
		this.onSearchTextFieldChange();
	},

	/**
	 * Creates the folders fieldset for search tool box of form panel.
	 * @return {Object} config object for creating {@link Ext.form.FieldSet FieldSet}.
	 * @private
	 */
	createFoldersFieldset: function()
	{
		return {
			layout: 'form',
			xtype:'fieldset',
			border: false,
			title: _('Folders'),
			ref: '../includeSubFolderFieldSet',
			items: [{
				xtype: "checkbox",
				hideLabel: true,
				ref: '../../includeSubFolder',
				boxLabel: _('Include subfolders')
			}]
		};
	},

	/**
	 * Creates the message type fieldset for search tool box of form panel.
	 *
	 * @param {Object} messageType setting object which used to pre-select the check box when
	 * user trying to open saved search folder.
	 *
	 * @return {Object} config object for creating {@link Ext.form.FieldSet FieldSet}.
	 * @private
	 */
	createMessageTypeFieldset: function(messageType)
	{
		return {
			layout: 'form',
			xtype:'fieldset',
			border: false,
			title: _('Show…'),
			items: [{
				xtype: 'checkboxgroup',
				ref: '../../messageTypeCheckboxGroup',
				columns: 1,
				name: 'messageTypeCheckboxGroup',
				hideLabel: true,
				listeners: {
					change: this.onMessageTypeCheckboxChange,
					scope: this
				},
				items: [{
					name: 'mail',
					boxLabel: _('Mails'),
					checked: Ext.isDefined(messageType['mail'])
				},{
					name: 'calendar',
					boxLabel: _('Appointments'),
					checked: Ext.isDefined(messageType['calendar'])
				},{
					name: 'contact',
					boxLabel: _('Contacts'),
					checked: Ext.isDefined(messageType['contact'])
				},{
					name: 'task',
					boxLabel: _('Tasks'),
					checked: Ext.isDefined(messageType['task'])
				},{
					name: 'note',
					boxLabel: _('Notes'),
					checked: Ext.isDefined(messageType['note'])
				}]
			}]
		};
	},

	/**
	 * Creates the filter fieldset for search tool box of form panel.
	 *
	 * @param {Object} filterSetting setting object which used to pre-select the check box when
	 * user trying to open saved search folder.
	 *
	 * @return {Object} config object for creating {@link Ext.form.FieldSet FieldSet}.
	 * @private
	 */
	createFilterFieldset: function(filterSetting)
	{
		return {
			layout: 'form',
			xtype:'fieldset',
			border: false,
			title: _('Filter…'),
			items: [{
				xtype: 'checkboxgroup',
				columns: 1,
				ref: '../../filterCheckBoxGroup',
				hideLabel: true,
				name: 'filterCheckBoxGroup',
				listeners: {
					change: this.onFilterCheckBoxGroup,
					render: this.onRenderCheckboxGroup,
					scope: this
				},
				items: [{
					name: 'message_flags',
					boxLabel: _('Unread'),
					checked: Ext.isDefined(filterSetting['message_flags'])
				},{
					name: 'hasattach',
					boxLabel: _('Attachments'),
					checked: Ext.isDefined(filterSetting['hasattach'])
				}]
			}]
		};
	},

	/**
	 * Creates the date range fieldset for search tool box of form panel.
	 *
	 * @param {Ext.data.JsonStore} dateRangeStore store which contains different date range for
	 * date range combo box.
	 *
	 * @return {Object} config object for creating {@link Ext.form.FieldSet FieldSet}.
	 * @private
	 */
	createDateRangeFieldset: function(dateRangeStore)
	{
		return {
			layout: 'form',
			xtype:'fieldset',
			border: false,
			title: _('Date'),
			items: [{
				xtype: 'combo',
				displayField: 'name',
				editable: false,
				hideLabel: true,
				ref: '../../dateRangeCombo',
				store: dateRangeStore,
				valueField: 'value',
				value: dateRangeStore.data[0].value,
				mode: 'local',
				triggerAction: 'all',
				width: 150,
				listeners: {
					select: this.onSelectCombo,
					beforerender: this.onBeforeRenderDateRangeCombo,
					scope: this
				}
			},{
				xtype: 'zarafa.dateperiodfield',
				ref: '../../dateField',
				hidden: true,
				allowBlank: false,
				onStartChange: this.onStartChange,
				onEndChange: this.onEndChange,
				defaultValue: new Zarafa.core.DateRange({
					allowBlank: false ,
					startDate: new Date().add(Date.MONTH, -1),
					dueDate: new Date()
				}),
				startFieldConfig: {
					labelSeparator: "",
					fieldLabel: pgettext('search.date', 'From'),
					labelStyle: 'width: 35px',
					itemCls: 'zarafa-dateperiodfield-itemsCls',
					labelWidth: 50,
					width: 110
				},
				endFieldConfig: {
					labelSeparator: "",
					fieldLabel: pgettext('search.date', 'To'),
					labelStyle: 'width: 35px',
					itemCls: 'zarafa-dateperiodfield-itemsCls',
					labelWidth: 50,
					width: 110
				}
			}]
		};
	},

	/**
	 * Creates the category filter fieldset for search tool box of form panel.
	 *
	 * @return {Object} config object for creating {@link Ext.form.FieldSet FieldSet}.
	 * @private
	 */
	createCategoryFilterFieldset: function ()
	{
		return {
			layout: 'form',
			xtype: 'fieldset',
			border: false,
			cls: 'k-category-filter',
			title: _('Filter category…'),
			autoHeight: true,
			ref: '../categoryFilterFieldSet',
			items: [{
				xtype: 'button',
				iconCls: 'icon_category_add',
				cls: 'k-category-add-button',
				tooltip: _('Add Category'),
				handler: this.onSelectCategory,
				scope: this
			}, {
				xtype: 'button',
				text: _('Select Category'),
				cls: 'k-category-filter-label',
				width: 'auto',
				hidden: this.searchCategoriesStore.getCount(),
				ref: '../../categoryFilterLabel',
				handler: this.onSelectCategory,
				scope: this
			}, {
				xtype: 'dataview',
				anchor: '100% 100%',
				autoHeight: true,
				tpl: this.categoriesHtmlTemplate,
				prepareData: function (data) {
					Ext.apply(data, {
						hoverString: Ext.util.Format.htmlEncode(data.name.length > 20 ? data.name : '').replace(/\s/g, '&nbsp;'),
						categoryName: Ext.util.Format.ellipsis(data.name, 20)
					});
					return data;
				},
				store: this.searchCategoriesStore,
				itemSelector: 'span.k-category-block',
				listeners: {
					click: this.onCategoryRemove,
					scope: this
				}
			}]
		};
	},

	/**
	 * Create the "Search in" {@link Ext.form.CheckboxGroup checkboxgroup} which specifies
	 * which fields search has to look in.
	 *
	 * @param {Object} searchInCheckBoxSetting setting object which used to pre-select the check box when
	 * user trying to open saved search folder.
	 *
	 * @return {Object} config object for creating {@link Ext.form.FieldSet FieldSet}.
	 * @private
	 */
	createSearchInFieldset: function(searchInCheckBoxSetting)
	{
		return {
			layout: 'form',
			xtype:'fieldset',
			border: false,
			title: _('Search…'),
			ref: '../searchInFieldset',
			items: [{
				xtype: 'checkboxgroup',
				columns: 1,
				ref: '../../searchInCheckboxGroup',
				name: 'searchInCheckboxGroup',
				hideLabel: true,
				listeners: {
					change: this.onSearchInCheckboxChange,
					render: this.onRenderCheckboxGroup,
					scope: this
				},
				items: [{
					name: ['sender_name', 'sender_email_address', 'sent_representing_name', 'sent_representing_email_address'],
					itemId: 'sender',
					boxLabel: _('Sender'),
					checked: Ext.isDefined(searchInCheckBoxSetting['sender'])
				},{
					name: ['display_to', 'display_cc', 'display_bcc'],
					itemId: 'recipients',
					boxLabel: _('Recipients'),
					checked: Ext.isDefined(searchInCheckBoxSetting['recipients'])
				},{
					name: 'subject',
					itemId: 'subject',
					boxLabel: _('Subject'),
					checked: Ext.isDefined(searchInCheckBoxSetting['subject'])
				},{
					name: 'body',
					itemId: 'body',
					boxLabel: _('Body & Attachments'),
					checked: Ext.isDefined(searchInCheckBoxSetting['body'])
				}]
			}]
		};
	},

	/**
	 * Create the "Search in" {@link Ext.form.CheckboxGroup checkboxgroup} which specifies
	 * which fields search has to look in.
	 * @return {Object} config object for creating {@link Ext.form.FieldSet FieldSet}.
	 * @private
	 */
	createFavoritesContainer: function ()
	{
		return {
			xtype: 'container',
			cls: 'zarafa-search-toolbox-favoritesbutton-container',
			layout:'fit',
			hidden: container.getSettingsModel().get('zarafa/v1/contexts/hierarchy/hide_favorites', true, false),
			items: [{
				cls: 'search-toolbox-favorites-button',
				iconCls: 'icon_favorites',
				xtype:'button',
				text: '<span>' + _('Favorites') + '</span>',
				handler: this.onClickFavorites,
				tooltip: {
					text: _('Add a folder to favorites based on search query'),
					width: 350
				},
				scope: this
			}]
		};
	},

	/**
	 * Initialize events
	 * @private
	 */
	initEvents: function()
	{
		this.mon(this.messageTypeCheckboxGroup, {
			render: this.onRenderCheckboxGroup,
			scope: this
		});

		this.mon(this.dateRangeCombo, {
			enable: this.onEnableCombo,
			scope: this
		});

		this.mon(this.dateField,{
			change: this.onChangeDateField,
			scope: this
		});

		this.dateField.mon(this.dateField.startField, 'specialkey', this.onSpecialKey, this);
		this.dateField.mon(this.dateField.endField, 'specialkey', this.onSpecialKey, this);
		this.mon(this.searchCategoriesStore, 'add', this.onSearchCategoryUpdate, this);
		this.mon(this.searchCategoriesStore, 'remove', this.onSearchCategoryUpdate, this);
	},

	/**
	 * Overridden event handler which is called when the start date has been changed.
	 *
	 * @param {Ext.form.Field} field The field which has changed
	 * @param {Mixed} newValue The new value for the field
	 * @param {Mixed} oldValue The old value for the field
	 */
	onStartChange: function(field, newValue, oldValue)
	{
		var range = this.defaultValue;
		var oldRange = this.defaultValue.clone();

		if (range.getStartDate() !== newValue) {
			if (Ext.isEmpty(newValue)) {
				range.setStartDate(null);
			} else {
				var dueTime = range.getDueTime();
				// If the start date is after the due date, then set due date same as the start date
				if (newValue.getTime() > dueTime) {
					range.set(newValue, newValue.clone());
				} else {
					range.setStartDate(newValue);
				}
			}
		}

		this.fireEvent('change', this, range.clone(), oldRange);
	},

	/**
	 * Overridden handler which is called when the due date has been changed.
	 *
	 * @param {Ext.form.Field} field The field which has changed
	 * @param {Mixed} newValue The new value for the field
	 * @param {Mixed} oldValue The old value for the field
	 */
	onEndChange: function(field, newValue, oldValue)
	{
		var range = this.defaultValue;
		var oldRange = this.defaultValue.clone();

		if (range.getDueDate() !== newValue) {
			if (Ext.isEmpty(newValue)) {
				range.set(null, null);
			} else {
				var startTime = range.getStartTime();
				if (newValue.getTime() < startTime) {
					// If the due date is before the start date, then set start date same as the due date
					range.set(newValue, newValue.clone());
				} else {
					range.setDueDate(newValue);
				}
			}
		}

		this.fireEvent('change', this, range.clone(), oldRange);
	},

	/**
	 * Event handler which is raised just before the {@link Ext.form.ComboBox ComboBox}
	 * is being rendered. it will call {@link #getSearchFolderSettings} which
	 * provide saved search folder related setting objects which used to pre-select
	 * date range combo box.
	 *
	 * @param {Ext.form.ComboBox} combo the combo box component.
	 */
	onBeforeRenderDateRangeCombo: function(combo)
	{
		var searchCriteria = this.getSearchFolderSettings();
		if (searchCriteria) {
			var dateRange = searchCriteria.date_range;
			var record = combo.findRecord(combo.valueField, dateRange);
			this.setDateRangeRestriction(combo, record);
			if (!Ext.isObject(dateRange)) {
				combo.setValue(dateRange);
			} else {
				combo.setValue('custom_date');
				this.dateField.hidden = false;
				var startDate = new Date(dateRange.start);
				var dueDate = new Date(dateRange.due);

				this.dateField.startField.setValue(startDate);
				this.dateField.endField.setValue(dueDate);
			}
		}
	},

	/**
	 * Function which used to retrieve the saved search folder related settings object
	 * if we are trying to open it.
	 *
	 * @param {Zarafa.advancesearch.AdvanceSearchContextModel} contextModel (optional) the advance search context model
	 * @param {String} searchStoreUniqueId (optional) searchStoreUniqueId is represent the unique id of
	 * {@link Zarafa.advancesearch.AdvanceSearchStore AdvanceSearchStore}.
	 *
	 * @returns {Object|Boolean} return settings object of saved search folder else false.
	 */
	getSearchFolderSettings: function(contextModel, searchStoreUniqueId)
	{
		var model = Ext.isDefined(contextModel) ? contextModel : this.model;
		var store = model.store;
		searchStoreUniqueId = Ext.isDefined(searchStoreUniqueId) ? searchStoreUniqueId : store.searchStoreUniqueId;
		if (Ext.isDefined(store.searchFolder[searchStoreUniqueId])) {
			var folder = store.searchFolder[searchStoreUniqueId];
			return container.getSettingsModel().getSettingsObject('zarafa/v1/contexts/search/search_criteria/'+folder.get('entryid'));
		}
		return false;
	},

	/**
	 * Event handler is fired for each special key, but it only handles the {@link Ext.EventObjectImp#ENTER} key.
	 * it was call the triggerBlur function of updated date field. which internally fire the
	 * blur event and blur event fire the change event, which handled by
	 * {@link Zarafa.common.ui.DatePeriodField#onStartChange} or {@link Zarafa.common.ui.DatePeriodField#onEndChange}
	 * which fire the {@link Zarafa.common.ui.DateRangeField#change} event of {@link Zarafa.common.ui.DateRangeField date rage field}
	 * and it was handled by the {@link #onChangeDateField}.
	 *
	 * @param {Ext.form.Field} field The field which fired the event
	 * @param {Ext.EventObject} eventObj The event object for this event
	 */
	onSpecialKey: function(field, eventObj)
	{
		if (eventObj.getKey() === eventObj.ENTER) {
			field.triggerBlur();
		}
	},

	/**
	 * Function which is used to set the date range related restriction
	 * in {@link #searchCriteria} object.
	 *
	 * @param {Ext.form.ComboBox} combo The field which fired the event
	 * @param {Ext.data.Record} record The selected record
	 * @private
	 */
	setDateRangeRestriction: function(combo, record)
	{
		var value = record.get('value');
		var today = new Date();
		// Add a day to implement until
		this.searchCriteria['date_range']['end'] = today.add(Date.DAY, 1).getTime() / 1000;

		if (value !== 'custom_date' && this.dateField.isVisible()) {
			this.dateField.hide();
			this.doLayout();
		}

		switch(value) {
			case 'past_week':
				this.searchCriteria['date_range']['start'] = today.add(Date.DAY, -7).getTime() / 1000;
			break;
			case 'past_two_weeks':
				this.searchCriteria['date_range']['start'] = today.add(Date.DAY, -14).getTime() / 1000;
			break;
			case 'past_month':
				this.searchCriteria['date_range']['start'] = today.add(Date.MONTH, -1).getTime() / 1000;
				break;
			case 'past_six_month':
				this.searchCriteria['date_range']['start'] = today.add(Date.MONTH, -6).getTime() / 1000;
				break;
			case 'past_year':
				this.searchCriteria['date_range']['start'] = today.add(Date.YEAR, -1).getTime() / 1000;
				break;
			case 'custom_date':
				this.dateField.show();
				this.doLayout();
				this.searchCriteria['date_range']['start'] = this.dateField.startField.getValue().getTime() / 1000;
				// Add a day to implement until
				this.searchCriteria['date_range']['end'] = this.dateField.endField.getValue().add(Date.DAY, 1).getTime() / 1000;
				break;
			default:
				this.searchCriteria['date_range']['start'] = 0;
				this.searchCriteria['date_range']['end'] = 0;
		}
	},

	/**
	 * Event handler which is called when a selection has been made in the
	 * {@link Ext.form.ComboBox combobox}.
	 * @param {Ext.form.ComboBox} combo The field which fired the event
	 * @param {Ext.data.Record} record The selected record
	 * @private
	 */
	onSelectCombo: function(combo, record)
	{
		this.setDateRangeRestriction(combo, record);
		if (!this.syncing) {
			this.afterUpdateRestriction();
		}

		// Sync date range combo → search bar date: chip
		if (this.syncing) {
			return;
		}
		this.syncing = true;

		var searchTextField = this.ownerCt.searchToolbar.contextMainPanelToolbar.searchFieldContainer.searchTextField;
		var dateValue = record.get('value');

		// Remove existing date: chips
		if (searchTextField.tokens) {
			for (var i = searchTextField.tokens.length - 1; i >= 0; i--) {
				if (searchTextField.tokens[i].type === 'filter' && searchTextField.tokens[i].key === 'date') {
					searchTextField.removeTokenAt(i, true);
				}
			}
		}

		// Add new date: chip if not "all_dates"
		if (dateValue && dateValue !== 'all_dates') {
			searchTextField.insertFilter('date', dateValue, true);
		}

		this.syncing = false;
	},

	/**
	 * Event handler for the {@link Ext.form.CheckboxGroup#change change} event, this will
	 * update the {@link #searchCriteria}, which used in advance search request.
	 *
	 * @param {Ext.form.CheckboxGroup} group the checkboxgroup
	 * @param {Array} checked an array of {Ext.form.Checkbox} items which are selected
	 */
	onFilterCheckBoxGroup: function(group, checked)
	{
		this.setFilterRestriction(group, checked);
		if (!this.syncing) {
			this.afterUpdateRestriction();
		}

		// Sync filter checkboxes → search bar chips
		if (this.syncing) {
			return;
		}
		this.syncing = true;

		var searchTextField = this.ownerCt.searchToolbar.contextMainPanelToolbar.searchFieldContainer.searchTextField;
		var checkedNames = {};
		if (!Ext.isEmpty(checked)) {
			Ext.each(checked, function(cb) {
				checkedNames[cb.name] = true;
			});
		}

		var activeKeys = searchTextField.getActiveFilterKeys ? searchTextField.getActiveFilterKeys() : [];
		group.items.each(function(checkbox) {
			var chipKey = this.filterToChipMap[checkbox.name];
			if (!chipKey) {
				return;
			}
			if (checkedNames[checkbox.name]) {
				if (activeKeys.indexOf(chipKey) === -1) {
					searchTextField.insertFilter(chipKey, chipKey === 'unread' ? 'true' : '', true);
				} else if (searchTextField.isFilterNegated && searchTextField.isFilterNegated(chipKey)) {
					// Filter exists but is negated (NOT Unread) — remove the negation
					searchTextField.removeFilterNegation(chipKey, true);
				}
			} else {
				searchTextField.removeFilterChip(chipKey, true);
			}
		}, this);

		this.syncing = false;
	},

	/**
	 * Event handler for the {@link Ext.form.CheckboxGroup#change change} event, this will
	 * update the {@link #searchCriteria}, which used in advance search request.
	 *
	 * @param {Ext.form.CheckboxGroup} group the checkboxgroup
	 * @param {Array} checked an array of {Ext.form.Checkbox} items which are selected
	 */
	onMessageTypeCheckboxChange: function(group, checked)
	{
		this.setMessageClassRestriction(group, checked);
		if (!this.syncing) {
			this.afterUpdateRestriction();
		}

		// Sync message type checkboxes → search bar type: chips
		if (this.syncing) {
			return;
		}
		this.syncing = true;

		var searchTextField = this.ownerCt.searchToolbar.contextMainPanelToolbar.searchFieldContainer.searchTextField;
		var checkedNames = {};
		if (!Ext.isEmpty(checked)) {
			Ext.each(checked, function(cb) {
				checkedNames[cb.name] = true;
			});
		}

		// Collect existing type: chip values
		var existingTypeValues = [];
		if (searchTextField.tokens) {
			for (var i = 0; i < searchTextField.tokens.length; i++) {
				var tok = searchTextField.tokens[i];
				if (tok.type === 'filter' && tok.key === 'type') {
					existingTypeValues.push(tok.value.toLowerCase());
				}
			}
		}

		group.items.each(function(checkbox) {
			var typeValue = this.checkboxToTypeValueMap[checkbox.name];
			if (!typeValue) {
				return;
			}
			if (checkedNames[checkbox.name]) {
				if (existingTypeValues.indexOf(typeValue) === -1) {
					searchTextField.insertFilter('type', typeValue, true);
				}
			} else {
				// Remove the type: chip with this value
				if (searchTextField.tokens) {
					for (var j = searchTextField.tokens.length - 1; j >= 0; j--) {
						var t = searchTextField.tokens[j];
						if (t.type === 'filter' && t.key === 'type' &&
							t.value.toLowerCase() === typeValue) {
							searchTextField.removeTokenAt(j, true);
							break;
						}
					}
				}
			}
		}, this);

		this.syncing = false;
	},

	/**
	 * Event handler for the {@link Ext.form.CheckboxGroup#change change} event, this will
	 * update the {@link #searchCriteria} and restricts the "search_fields" so that it only
	 * contains fields which are selected.
	 *
	 * @param {Ext.form.CheckboxGroup} group the checkboxgroup
	 * @param {Array} checked an array of {Ext.form.Checkbox} items which are selected
	 */
	onSearchInCheckboxChange: function(group, checked)
	{
		this.setSearchInRestriction(group, checked);
		if (!this.syncing) {
			this.afterUpdateRestriction();
		}

		// Sync checkboxes → search bar chips (reverse direction)
		if (this.syncing) {
			return;
		}
		this.syncing = true;

		var searchTextField = this.ownerCt.searchToolbar.contextMainPanelToolbar.searchFieldContainer.searchTextField;
		var checkedIds = {};
		if (!Ext.isEmpty(checked)) {
			Ext.each(checked, function(cb) {
				checkedIds[cb.itemId] = true;
			});
		}

		// Sync checkbox state to chips: add chips for checked, remove for unchecked
		var activeKeys = searchTextField.getActiveFilterKeys ? searchTextField.getActiveFilterKeys() : [];
		group.items.each(function(checkbox) {
			var checkboxId = checkbox.itemId;
			var chipKeys = this.checkboxToChipMap[checkboxId];
			if (!chipKeys) {
				return;
			}
			if (checkedIds[checkboxId]) {
				// Add the first matching chip if none exists yet
				var hasAny = false;
				for (var j = 0; j < chipKeys.length; j++) {
					if (activeKeys.indexOf(chipKeys[j]) !== -1) {
						hasAny = true;
						break;
					}
				}
				if (!hasAny) {
					searchTextField.insertFilter(chipKeys[0]);
				}
			} else {
				// Remove all matching chips
				for (var i = 0; i < chipKeys.length; i++) {
					searchTextField.removeFilterChip(chipKeys[i], true);
				}
			}
		}, this);

		this.syncing = false;
	},

	/**
	 * Sets the search restriction for Search filtering fields.
	 *
	 * @param {Ext.form.CheckboxGroup} group the checkboxgroup
	 * @param {Array} checked an array of {Ext.form.Checkbox} items which are selected
	 */
	setSearchInRestriction: function (group, checked)
	{
		if (Ext.isEmpty(checked)) {
			// Set the search_fields restriction based the current selected messageClasses.
			this.setMessageClassRestriction(this.messageTypeCheckboxGroup, this.messageTypeCheckboxGroup.getValue());
		} else {
			var searchFields = [];
			checked.forEach(function(checkBox) {
				searchFields = searchFields.concat(checkBox.name);
			});
			this.searchCriteria['search_fields'] = searchFields;
		}
	},

	/**
	 * Function call after the {@link #searchCriteria} gets updated
	 * by {@link Zarafa.advancesearch.dialogs.SearchToolBoxPanel search tool box}. This
	 * will fire the {@link #afterupdaterestriction} which triggers the advance search.
	 */
	afterUpdateRestriction: function()
	{
		this.fireEvent('afterupdaterestriction' , this);
	},

	/**
	 * Event handler was fire when message type/ filter/ search check box group gets rendered.
	 * @param {Ext.form.CheckboxGroup} group the group is {@link Ext.form.CheckboxGroup checkbox}
	 * @private
	 */
	onRenderCheckboxGroup: function(group)
	{
		switch(group.name) {
			case 'filterCheckBoxGroup':
				this.setFilterRestriction(group, group.getValue());
				break;
			case 'searchInCheckboxGroup':
				this.setSearchInRestriction(group, group.getValue());
				break;
			default:
				this.setMessageClassRestriction(group, group.getValue());
		}
	},

	/**
	 * Sets the search restriction for extra filtering fields.
	 *
	 * @param {Ext.form.CheckboxGroup} group the group is {@link Ext.form.CheckboxGroup checkbox}
	 * @param {Array} checked an array of {Ext.form.Checkbox} items which are selected
	 */
	setFilterRestriction: function(group, checked)
	{
		this.searchCriteria['extra_fields'] = checked.map(function(checkbox) { return checkbox.name; });
	},

	/**
	 * Sets the search restriction for message classes based on the checkboxes which are available,
	 * if no checkboxes are selected we want to search through all available message and searchfields.
	 *
	 * @param {Ext.form.CheckboxGroup} group the {@link Ext.form.CheckboxGroup checkbox} group
	 * @param {Array} checked a list of checkboxes which are enabled
	 */
	setMessageClassRestriction: function(group, checked)
	{
		// Helper to filter out duplicates
		const onlyUnique = function(value, index, arr) {
			return arr.indexOf(value) === index;
		};

		var messageClasses = [];
		var searchFields = [];

		if (Ext.isEmpty(checked)) {
			checked = group.items.items;
		}

		var searchInCheckBox = this.searchInCheckboxGroup.getValue();
		var searchInCheckBoxFields = [];
		if (!Ext.isEmpty(searchInCheckBox)) {
			searchInCheckBox.forEach(function (checkBox) {
				searchInCheckBoxFields = searchInCheckBoxFields.concat(checkBox.name);
			}, this);
		}

		checked.forEach(function(checkBox) {
			messageClasses = messageClasses.concat(this.getMessageClass(checkBox.name));
			// searchInCheckBox has high priority, If any of the checkBox selected from that
			// then don't add/contact default search fields in searchFields array.
			if (Ext.isEmpty(searchInCheckBoxFields)) {
				searchFields = searchFields.concat(Zarafa[checkBox.name].data.SearchFields[0].value.split(' '));
			}
		}, this);

		if (!Ext.isEmpty(searchInCheckBoxFields)) {
			searchFields = searchInCheckBoxFields;
		}

		this.searchCriteria['message_class'] = messageClasses.filter(onlyUnique);
		this.searchCriteria['search_fields'] = searchFields.filter(onlyUnique);
	},

	/**
	 * Function is use to retive the message class based on the selected
	 * {@link #createMessageTypeFieldset}.
	 * @param {String} checkBoxName The checkBoxName of the selected check box from check box list
	 * @return {Array} return and array of message classes.
	 */
	getMessageClass: function(checkBoxName)
	{
		switch(checkBoxName) {
			case 'mail':
				return ['IPM.Note', 'REPORT.IPM.Note'];
			case 'calendar':
				return ['IPM.Appointment', 'IPM.Schedule'];
			case 'contact':
				return ['IPM.Contact', 'IPM.DistList'];
			case 'task':
				return ['IPM.Task'];
			case 'note':
				return ['IPM.StickyNote'];
		}
	},

	/**
	 * Event handler which is fired when the {@link Zarafa.common.ui.DateRangeField} has been changed.
	 * This will update the start and due date inside the {@link #searchCriteria} accordingly.
	 *
	 * @param {Ext.form.Field} field The field which has changed
	 * @param {Mixed} newRange The new date range
	 * @param {Mixed} oldRange The old date range
	 * @private
	 */
	onChangeDateField: function(field, newRange, oldRange)
	{
		var newStartDate = newRange.startDate.getTime()/1000;
		// Add a day to implement until
		var newDueDate = newRange.dueDate.add(Date.DAY, 1).getTime()/1000;

		this.searchCriteria['date_range']['start'] = newStartDate;
		this.searchCriteria['date_range']['end'] = newDueDate;

		if(newRange.compare(oldRange) !== 0) {
			this.afterUpdateRestriction();
		}
	},

	/**
	 * Event handler triggers after the date range combo box gets enabled.
	 * also it will update the {@link #searchCriteria} based on the selected
	 * value of the combo box.
	 *
	 * @param {Ext.form.ComboBox} combo which gets enabled.
	 * @private
	 */
	onEnableCombo: function(combo)
	{
		var index = combo.getStore().find('value', combo.getValue());
		var record = combo.getStore().getAt(index);
		if (record) {
			this.onSelectCombo(combo, record);
		}
	},

	/**
	 * Event handler triggered when "Favorites" button was pressed. it will open
	 * {@link Zarafa.advancesearch.dialogs.CreateSearchFolderPanel CreateSearchFolderPanel}.
	 */
	onClickFavorites: function ()
	{
		var config = {
			searchText: this.dialog.searchText,
			searchStoreEntryId: this.model.getStore().searchStoreEntryId,
			includeSubFolder: this.includeSubFolder.getValue(),
			searchFolderEntryId: this.model.store.searchFolderEntryId
		};
		Zarafa.advancesearch.Actions.openCreateSearchFolderContentPanel(this.model, config);
	},


	/**
	 * Function will be used to create search restriction based on value entered in
	 * search textfield and {@link Zarafa.common.search.dialogs.SearchToolBoxPanel SearchToolBox}.
	 *
	 * In words: all terms must occur at least once, but it doesn't matter in which of the fields they occur.
	 *
	 * @param {String} textFieldValue value of search text field.
	 * @return {Object} Object that will be passed as restriction to server.
	 * @private
	 */
	createRestriction: function(textFieldValue)
	{
		if (Ext.isEmpty(textFieldValue)) {
			return [];
		}

		var searchFieldPreference = Ext.isArray(this.searchCriteria['search_fields']) ? this.searchCriteria['search_fields'].slice(0) : [];
		var tokens = Zarafa.advancesearch.KQLParser.tokenize(textFieldValue);
		var usesAdvancedSyntax = Zarafa.advancesearch.KQLParser.usesExplicitSyntax(tokens);
		var andRes = [];
		if ( tokens ) {
			var tokenRes = Zarafa.advancesearch.KQLParser.createTokenRestriction(tokens, usesAdvancedSyntax ? null : searchFieldPreference);
			if ( tokenRes ) {
				andRes = [tokenRes];
			} else {
				// treat malformed KQL as plain search text
				tokens = false;
			}
		}

		var finalRes = [];
		var orResDate = [];
		var orResSearchField = [];
		var orResMessageClass = [];
		var andResCategory = [];
		var orFilters = [];

		Ext.iterate(this.searchCriteria, function(key, values) {
			if ( !tokens ) {
				// search field restriction
				if(key === 'search_fields') {
					Ext.each(values, function(value){
						orResSearchField.push(
							Zarafa.core.data.RestrictionFactory.dataResContent(
								value,
								Zarafa.core.mapi.Restrictions.FL_SUBSTRING | Zarafa.core.mapi.Restrictions.FL_IGNORECASE,
								textFieldValue
							)
						);
					}, this);
				}
			}

			if (key === 'extra_fields') {
				Ext.each(values, function(value) {
					if (value === 'hasattach') {
						orFilters.push(
							Zarafa.core.data.RestrictionFactory.createResAnd([
								Zarafa.core.data.RestrictionFactory.dataResProperty(
									'hide_attachments',
									Zarafa.core.mapi.Restrictions.RELOP_NE,
									true
								),
								Zarafa.core.data.RestrictionFactory.createResSubRestriction(
									'PR_MESSAGE_ATTACHMENTS',
									Zarafa.core.data.RestrictionFactory.dataResProperty(
										'PR_ATTACHMENT_HIDDEN',
										Zarafa.core.mapi.Restrictions.RELOP_NE,
										true
									)
								)
							])
						);

					}
					if (value === 'message_flags') {
						orFilters.push(
							Zarafa.core.data.RestrictionFactory.dataResBitmask(
								value,
								Zarafa.core.mapi.Restrictions.BMR_EQZ,
								Zarafa.core.mapi.MessageFlags.MSGFLAG_READ
							)
						);
					}
				});
			}

			// Negated unread filter (NOT Unread = show read messages)
			// This is separate from extra_fields since the checkbox is unchecked
			if (key === 'negated_extra_fields' && Ext.isArray(values) &&
				values.indexOf('unread') !== -1) {
				orFilters.push(
					Zarafa.core.data.RestrictionFactory.dataResBitmask(
						'message_flags',
						Zarafa.core.mapi.Restrictions.BMR_NEZ,
						Zarafa.core.mapi.MessageFlags.MSGFLAG_READ
					)
				);
			}

			// Date Range restriction
			if(key === 'date_range') {
				if(values.start !== 0 && values.end !== 0) {
					// Modification date
					orResDate = Zarafa.core.data.RestrictionFactory.createResOr([
						Zarafa.core.data.RestrictionFactory.createResAnd([
							Zarafa.core.data.RestrictionFactory.createResNot(
								Zarafa.core.data.RestrictionFactory.dataResExist('PR_MESSAGE_DELIVERY_TIME')
							),
							Zarafa.core.data.RestrictionFactory.dataResProperty(
								'last_modification_time',
								Zarafa.core.mapi.Restrictions.RELOP_GE,
								values.start
							),
							Zarafa.core.data.RestrictionFactory.dataResProperty(
								'last_modification_time',
								Zarafa.core.mapi.Restrictions.RELOP_LT,
								values.end
							)
						]),
						Zarafa.core.data.RestrictionFactory.createResAnd([
							Zarafa.core.data.RestrictionFactory.dataResExist('PR_MESSAGE_DELIVERY_TIME'),
							Zarafa.core.data.RestrictionFactory.dataResProperty(
								'message_delivery_time',
								Zarafa.core.mapi.Restrictions.RELOP_GE,
								values.start
							),
							Zarafa.core.data.RestrictionFactory.dataResProperty(
								'message_delivery_time',
								Zarafa.core.mapi.Restrictions.RELOP_LT,
								values.end
							)
						])
					]);
				}
			}

			// message class restriction
			if(key === 'message_class' && !Ext.isEmpty(values)) {
				Ext.each(values, function(value){
					orResMessageClass.push(
						Zarafa.core.data.RestrictionFactory.dataResContent(
							key,
							Zarafa.core.mapi.Restrictions.FL_PREFIX | Zarafa.core.mapi.Restrictions.FL_IGNORECASE,
							value
						)
					);
				}, this);
			}

			// category restriction
			if (key === 'categories' && !Ext.isEmpty(values)) {
				Ext.each(values, function (value) {
					andResCategory.push(
						Zarafa.core.data.RestrictionFactory.dataResContent(
							key,
							Zarafa.core.mapi.Restrictions.FL_FULLSTRING | Zarafa.core.mapi.Restrictions.FL_IGNORECASE,
							value
						)
					);
				}, this);
			}
		}, this);

		/**
		 * If date-information is present in search criteria then create search restriction
		 * something like this.
		 * AND
		 * 		AND
		 * 			OR
		 * 				AND
		 * 					Not PR_MESSAGE_DELIVERY_TIME exists
		 * 					start date (last_modification_time)
		 * 					end date (last_modification_time)
		 * 				AND
		 * 					PR_MESSAGE_DELIVERY_TIME exists
		 * 					start date (message_delivery_time)
		 * 					end date (message_delivery_time)
		 * 			OR
		 * 				searchFields
		 * 		OR
		 * 			message class
		 * 		OR
		 * 			search filters
		 */
		if(!Ext.isEmpty(orResDate)) {
			var andResDateSearchField = [];
			andResDateSearchField.push(orResDate);
			if ( orResSearchField.length ) {
				andResDateSearchField.push(Zarafa.core.data.RestrictionFactory.createResOr(orResSearchField));
			}
			andRes.push(Zarafa.core.data.RestrictionFactory.createResAnd(andResDateSearchField));
		} else if ( orResSearchField.length ) {
			/**
			 * If date information is not present in search criteria then create search restriction
			 * something like this.
			 * AND
			 * 		OR
			 * 			searchFields
			 * 		OR
			 * 			message class
			 * 		OR
			 * 			search filters
			 */
			andRes.push(Zarafa.core.data.RestrictionFactory.createResOr(orResSearchField));
		}

		// Message class restriction which indicates which type of message you want to search.
		andRes.push(Zarafa.core.data.RestrictionFactory.createResOr(orResMessageClass));

		if (!Ext.isEmpty(andResCategory)) {
			andRes.push(Zarafa.core.data.RestrictionFactory.createResAnd(andResCategory));
		}

		if (!Ext.isEmpty(orFilters)) {
			andRes.push(Zarafa.core.data.RestrictionFactory.createResAnd(orFilters));
		}

		if(!Ext.isEmpty(andRes)) {
			finalRes = Zarafa.core.data.RestrictionFactory.createResAnd(andRes);
		}


		return finalRes;
	},

	/**
	 * Function which is handle click event of select category.
	 * It will show {@link Zarafa.advancesearch.dialogs.SearchCategoriesContentPanel search category panel}.
	 */
	onSelectCategory: function ()
	{
		Zarafa.advancesearch.Actions.openSearchCategoryContentPanel({
			modal: true,
			searchCategoryStore: this.searchCategoriesStore,
			scope: this
		});
	},

	/**
	 * Function which is handle click event of category box.
	 * It will identify that is user click on close button, If yes then
	 * Remove that category from {@link Zarafa.advancesearch.data.SearchCategoriesStore}
	 *
	 * @param {Ext.DataView} item Categories data view.
	 * @param {Number} index The index of the target node in {@link Zarafa.advancesearch.data.SearchCategoriesStore}.
	 * @param {HTMLElement} node html element.
	 * @param {Ext.EventObject} e event object.
	 */
	onCategoryRemove: function (item, index, node, e)
	{
		var element = e.getTarget();
		if (element.className === "k-category-close") {
			this.searchCategoriesStore.removeAt(index);
		}
	},

	/**
	 * Sets the search restriction for categories
	 * based on categories available in {@link #searchCategoriesStore},
	 *
	 * @param {Array} categories a list of categories.
	 */
	setCategoriesRestriction: function(categories)
	{
		this.searchCriteria['categories'] = categories;
	},

	/**
	 * Handler which is call while category will be add or remove.
	 * It will update the search restriction with categories available in {@link #searchCategoriesStore}
	 */
	onSearchCategoryUpdate: function ()
	{
		var categories = this.searchCategoriesStore.getCategories();
		this.setCategoriesRestriction(categories);
		if (!this.syncing) {
			this.afterUpdateRestriction();
		}
		this.categoryFilterLabel.setVisible(!this.searchCategoriesStore.getCount());

		// Sync categories → search bar chips
		if (this.syncing) {
			return;
		}
		this.syncing = true;

		var searchTextField = this.ownerCt.searchToolbar.contextMainPanelToolbar.searchFieldContainer.searchTextField;
		if (searchTextField && searchTextField.tokens) {
			// Remove existing category chips
			for (var i = searchTextField.tokens.length - 1; i >= 0; i--) {
				if (searchTextField.tokens[i].type === 'filter' && searchTextField.tokens[i].key === 'category') {
					searchTextField.tokens.splice(i, 1);
				}
			}
			// Add category chips for each store entry
			for (var j = 0; j < categories.length; j++) {
				var last = searchTextField.tokens.length > 0 ? searchTextField.tokens[searchTextField.tokens.length - 1] : null;
				if (last && last.type !== 'operator') {
					searchTextField.tokens.push({ type: 'operator', key: 'AND' });
				}
				searchTextField.tokens.push({ type: 'filter', key: 'category', value: categories[j] });
			}
			searchTextField.cleanupOperators();
			searchTextField.renderTokens();
			searchTextField.syncHiddenInput();
			searchTextField.fireEvent('chipchange', searchTextField, searchTextField.getFilterChips());
		}

		this.syncing = false;
	}
});

Ext.reg('zarafa.searchtoolboxpanel', Zarafa.advancesearch.dialogs.SearchToolBoxPanel);

Ext.namespace('Zarafa.advancesearch.dialogs');

/**
 * @class Zarafa.advancesearch.dialogs.SearchToolbarPanel
 * @extends Ext.Panel
 * @xtype zarafa.searchtoolbarpanel
 *
 */
Zarafa.advancesearch.dialogs.SearchToolbarPanel = Ext.extend(Ext.Panel, {

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

		if (!Ext.isDefined(config.model) && Ext.isDefined(config.searchContext)) {
			config.model = config.searchContext.getModel();
		}

		Ext.applyIf(config, {
			xtype: 'zarafa.searchtoolbarpanel',
			layout: 'auto',
			ref: 'searchToolbar',
			cls: 'k-search-toolbar-panel',
			border: false,
			plugins: [{
				ptype: 'zarafa.recordcomponentplugin',
				enableOpenLoadTask: false
			},{
				ptype: 'zarafa.recordcomponentupdaterplugin'
			}],
			autoHeight: true,
			items: [{
				xtype: 'zarafa.contextmainpaneltoolbar',
				style: 'border-style: none',
				searchText: config.searchText,
				context: config.searchContext
			},{
				xtype: 'zarafa.toolbar',
				style: 'border-style: none; margin-left:5px;',
				cls: 'zarafa-previewpanel-toolbar zarafa-search-previewpanel-toolbar zarafa-context-mainpanel', // change the css class name
				ref: 'rightSearchToolbar',
				hidden: true,
				items: [container.populateInsertionPoint('previewpanel.toolbar.left', {scope: this, model: config.model}),
				{
					xtype: 'button',
					tooltip: _('Reply') + Zarafa.core.KeyMapMgr.formatShortcutHint('Ctrl + R', false),
					overflowText: _('Reply'),
					text: _('Reply'),
					iconCls: 'icon_reply',
					ref: 'replyBtn',
					responseMode: Zarafa.mail.data.ActionTypes.REPLY,
					handler: this.onResponse,
					scope: this
				},{
					xtype: 'button',
					tooltip: _('Reply All') + Zarafa.core.KeyMapMgr.formatShortcutHint('Ctrl + Alt + R', false),
					overflowText: _('Reply All'),
					text: _('Reply All'),
					iconCls: 'icon_reply_all',
					ref: 'replyAllBtn',
					responseMode: Zarafa.mail.data.ActionTypes.REPLYALL,
					handler: this.onResponse,
					scope: this
				},{
					xtype: 'button',
					tooltip: _('Forward') + Zarafa.core.KeyMapMgr.formatShortcutHint('Ctrl + F', false),
					overflowText: _('Forward'),
					text: _('Forward'),
					iconCls: 'icon_forward',
					ref: 'forwardBtn',
					responseMode: Zarafa.mail.data.ActionTypes.FORWARD,
					handler: this.onResponse,
					scope: this
				},{
					xtype: 'tbfill'
				},
				container.populateInsertionPoint('previewpanel.toolbar.right.first', {scope: this, model: config.model}),
				{
					xtype: 'button',
					tooltip: _('Edit as New') + Zarafa.core.KeyMapMgr.formatShortcutHint('Ctrl + E', false),
					overflowText: _('Edit as New'),
					iconCls: 'icon_edit_as_new_mail',
					ref: 'editAsNewBtn',
					responseMode: Zarafa.mail.data.ActionTypes.EDIT_AS_NEW,
					handler: this.onResponse,
					scope: this
				},container.populateInsertionPoint('previewpanel.toolbar.right', {scope: this, model: config.model})]
			}]
		});

		Zarafa.advancesearch.dialogs.SearchToolbarPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Function is used to retrieve {@link Zarafa.common.searchfield.ui.SearchFieldContainer SearchFieldContainer}
	 *
	 * @return {Zarafa.common.searchfield.ui.SearchFieldContainer} Search field container.
	 */
	getSearchFieldContainer: function()
	{
		return this.contextMainPanelToolbar.searchFieldContainer;
	},

	/**
	 * Function is used to retrieve the {@link Zarafa.common.searchfield.ui.SearchTextField SearchTextField}.
	 * @return {Zarafa.common.searchfield.ui.SearchTextField} Search text field.
	 */
	getAdvanceSearchField: function()
	{
		return this.getSearchFieldContainer().searchTextField;
	},

	/**
	 * Function is used to retrieve the {@link Zarafa.common.searchfield.ui.SearchFolderCombo SearchFolderCombo}.
	 * @return {Zarafa.common.searchfield.ui.SearchFolderCombo} Search folder combo
	 */
	getSearchFolderCombo: function()
	{
		return this.getSearchFieldContainer().searchFolderCombo;
	},

	/**
	 * Update the components with the given record.
	 *
	 * @param {Zarafa.core.data.MAPIRecord} record The record to update in this component
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 * @private
	 */
	update: function(record , contentReset)
	{
		this.record = record;
	},

	/**
	 * Function was used to get the right search toolbar.
	 * @returns {Object} return right search tool bar
	 */
	getRightSearchToolbar: function()
	{
		return this.rightSearchToolbar;
	},

	/**
	 * Called when one of the "Reply"/"Reply All"/"Forward"/"Edit as New" menuitems are clicked from
	 * right toolbar of search tool bar.
	 * @param {Ext.Button} button The button which was clicked
	 * @private
	 */
	onResponse: function(button)
	{
		var mailContextModel = container.getContextByName('mail').getModel();
		Zarafa.mail.Actions.openCreateMailResponseContent(this.record, mailContextModel, button.responseMode);
	}
});

Ext.reg('zarafa.searchtoolbarpanel', Zarafa.advancesearch.dialogs.SearchToolbarPanel);

Ext.namespace('Zarafa.advancesearch.dialogs');

/**
 * @class Zarafa.advancesearch.dialogs.SelectFolderPanel
 * @extends Ext.Panel
 * @xtype zarafa.selectfolderpanel
 *
 * Panel for users to select the {@link Zarafa.core.data.IPFRecord folder}
 * on which search can perform.
 */
Zarafa.advancesearch.dialogs.SelectFolderPanel = Ext.extend(Ext.Panel, {
	/**
	 * {@link Zarafa.common.searchfield.ui.SearchFolderCombo SearchFolderCombo} contains the search folders
	 * which used in search operation.
	 * @type Object
	 * @property searchFolderCombo
	 */
	searchFolderCombo: undefined,

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

		Ext.applyIf(config, {
			xtype: 'zarafa.selectfolderpanel',
			layout: {
				type: 'fit',
				align: 'stretch'
			},
			border: false,
			searchFolderCombo: config.searchFolderCombo,
			header: false,
			items: [
				this.createTreePanel()
			],
			buttonAlign: 'right',
			buttons: [{
				text: _('Ok'),
				handler: this.onOk,
				disabled: true,
				ref: '../okButton',
				cls: 'zarafa-action',
				scope: this
			},{
				text: _('Cancel'),
				disabled: true,
				ref: '../cancelButton',
				handler: this.onCancel,
				scope: this
			}]
		});

		Zarafa.advancesearch.dialogs.SelectFolderPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Initialize events
	 * @private
	 */
	initEvents: function ()
	{
		Zarafa.advancesearch.dialogs.SelectFolderPanel.superclass.initEvents.apply(this, arguments);
		this.mon(this.hierarchyTree, 'load', this.onTreeNodeLoad, this);
		this.mon(this.hierarchyTree.getSelectionModel(), 'selectionchange', this.onSelectionChange, this);
	},

	/**
	 * Creates a {@link Zarafa.hierarchy.ui.Tree treepanel}
	 * which contains all the {@link Zarafa.hierarchy.data.MAPIFolderRecord folders}
	 * on which search get perform.
	 * @return {Object} Configuration object for the tree panel.
	 * @private
	 */
	createTreePanel: function()
	{
		return {
			xtype: 'panel',
			layout: {
				type: 'vbox',
				align: 'stretch'
			},
			defaults: {
				margins: "0 0 5 0"
			},
			border: false,
			flex: 1,
			bodyStyle: 'background-color: inherit;',
			items: [{
				xtype: 'zarafa.hierarchytree',
				flex: 1,
				border: true,
				treeSorter: true,
				hideFavorites: true,
				enableDD: false,
				anchor: '100% 90%',
				ref: '../hierarchyTree'
			},{
				xtype: "checkbox",
				hideLabel: true,
				ref: '../includeSubFolder',
				boxLabel: _('Include subfolders')
			}]
		};
	},

	/**
	 * Event handler which is triggered when the user select a {@link Zarafa.hierarchy.data.MAPIFolderRecord folder}
	 * from the {@link Zarafa.hierarchy.ui.Tree tree}. This will determine if a valid
	 * {@link Zarafa.hierarchy.data.MAPIFolderRecord folder} is selected on which search gets performed.
	 *
	 * @param {DefaultSelectionModel} selectionModel The selectionModel for the treepanel
	 * @param {TreeNode} node The selected tree node
	 * @private
	 */
	onSelectionChange: function(selectionModel, node)
	{
		if (!Ext.isDefined(node) || (node.getFolder().isIPMSubTree() && this.objectType == Zarafa.core.mapi.ObjectType.MAPI_MESSAGE)) {
			this.okButton.disable();
			this.cancelButton.disable();
		} else {
			this.okButton.enable();
			this.cancelButton.enable();
			this.updateIncludeSubFolderCheckBox(node);
		}
	},

	/**
	 * Function is used to disable "Include sub folder" checkbox and apply tooltip when "All folders" option is
	 * selected from {@link Zarafa.common.searchfield.ui.SearchFolderCombo SearchFolderCombo} or IPM_SUBTREE folder
	 * selected from hierarchy and enable as well as remove tooltip from "Include Sub folder" checkbox if other then
	 * "All folders" option is selected in {@link Zarafa.common.searchfield.ui.SearchFolderCombo SearchFolderCombo} or
	 * hierarchy
	 *
	 * @param {Ext.tree.TreeNode} node The selected tree node
	 */
	updateIncludeSubFolderCheckBox: function(node)
	{
		var supportSearchFolder = this.model.supportsSearchFolder(node.getFolder());
		this.includeSubFolder.setVisible(supportSearchFolder);
		if (supportSearchFolder) {
			var record = this.searchFolderCombo.findRecord('value', node.getFolder().get('entryid'));
			var isChecked = false;
			var isDisabled = false;
			if(Ext.isDefined(record)) {
				isChecked = record.get('include_subfolder');
				isDisabled = record.get('flag') === Zarafa.advancesearch.data.SearchComboBoxFieldsFlags.ALL_FOLDERS;
			}
			var subFolderCheckBox = this.includeSubFolder;
			subFolderCheckBox.setValue(isChecked);
			subFolderCheckBox.setDisabled(isDisabled);

			if(subFolderCheckBox.rendered) {
				// Add tooltip on "include subfolder" check box when "All folders"
				// was selected in search folder combo box else remove tooltip from
				// "include subfolder" check box
				if (isDisabled) {
					subFolderCheckBox.wrap.dom.qtip = _("All folders are selected");
				} else {
					delete(subFolderCheckBox.wrap.dom.qtip);
				}
			}
		}
	},

	/**
	 * Fired when the {@link Zarafa.hierarchy.ui.Tree Tree} fires the {@link Zarafa.hierarchy.ui.Tree#load load}
	 * event. This function will try to select the {@link Ext.tree.TreeNode TreeNode} in
	 * {@link Zarafa.hierarchy.ui.Tree Tree} initially. When the given node is not loaded yet, it will try again
	 * later when the event is fired again.
	 *
	 * @private
	 */
	onTreeNodeLoad: function()
	{
		// Select folder in hierarchy tree.
		var folder = container.getHierarchyStore().getFolder(this.searchFolderCombo.getValue());

		// If the folder could be selected, then unregister the event handler.
		if (this.hierarchyTree.selectFolderInTree(folder)) {
			this.mun(this.hierarchyTree, 'load', this.onTreeNodeLoad, this);
		}
	},

	/**
	 * Event handler which is triggered when the user presses the ok
	 * {@link Ext.Button button}. This will add selected {@link Zarafa.core.data.IPFRecord folder}
	 * into {@link Zarafa.common.searchfield.ui.SearchFolderCombo SearchFolderCombo}, if folder
	 * is not already exists in {@link Zarafa.common.searchfield.ui.SearchFolderCombo SearchFolderCombo}.
	 * Also it will use to check/un-check "Include sub folder" checkbox which belongs to
	 * {@link Zarafa.advancesearch.dialogs.SearchToolBoxPanel SearchToolBox}.
	 *
	 * @private
	 */
	onOk: function ()
	{
		var folder = this.hierarchyTree.getSelectionModel().getSelectedNode().getFolder();

		if (!Ext.isDefined(folder)) {
			return;
		}
		var includeSubFolder = this.includeSubFolder.checked;
		var store = this.searchFolderCombo.getStore();
		var record = store.getAt(store.findExact("value", folder.get('entryid')));
		if (!Ext.isDefined(record)) {
			var importedFolderFlag = Zarafa.advancesearch.data.SearchComboBoxFieldsFlags.IMPORTED_FOLDER;
			if (store.getAt(0).get('flag') === importedFolderFlag) {
				store.removeAt(0);
			}
			record = new Ext.data.Record({
				'name': folder.get('display_name'),
				'value': folder.get('entryid'),
				'flag': importedFolderFlag,
				'include_subfolder': includeSubFolder
			});
			store.insert(0, record);
		} else {
			record.set('include_subfolder', includeSubFolder);
		}
		this.searchFolderCombo.setValue(record.get('value'));
		this.searchFolderCombo.onSelect(record, 0);
		this.dialog.close();
	},

	/**
	 * Event handler which is triggered when the user presses the cancel
	 * {@link Ext.Button button}. This will close the {@link Zarafa.advancesearch.dialogs.SelectFolderPanel dialog}
	 * without adding any {@link Ext.data.Record records} in search combo box.
	 * @private
	 */
	onCancel: function()
	{
		this.dialog.close();
	}
});

Ext.reg('zarafa.selectfolderpanel', Zarafa.advancesearch.dialogs.SelectFolderPanel);
Ext.namespace('Zarafa.advancesearch.ui');

/**
 * @class Zarafa.advancesearch.ui.AdvanceSearchRowSelectionModel
 * @extends Ext.grid.RowSelectionModel
 *
 * The {@link Zarafa.advancesearch.ui.MailRowSelectionModel MailRowSelectionModel}
 * is the {@link Ext.grid.RowSelectionModel RowSelectionModel} used in the
 * {@link Zarafa.advancesearch.ui.SearchGrid SearchGrid}.
 * It checks columns that have the preventRowSelection property and triggers actions depending on it.
 * See {@link #handleMouseDown}
 */
Zarafa.advancesearch.ui.AdvanceSearchRowSelectionModel = Ext.extend(Ext.grid.RowSelectionModel, {
	/**
	 * If one of the columns of interest is clicked, then the row should not be selected.
	 * Otherwise call original handler.
	 * 'cellmousedown' does not fire when drag&drop is installed on a component,
	 * otherwise it may have been possible to cancel selection from there by returning false.
	 *
	 * @param {Zarafa.advancesearch.ui.SearchGrid} grid The search grid from which the event came
	 * @param {Number} rowIndex Index of the row that was clicked
	 * @param {Ext.EventObject} event The mouse event
	 *
	 * @override
	 * @private
	 */
	handleMouseDown: function(grid, rowIndex, event)
	{
		// boolean to determine what we are going to do
		var preventRowSelection = false;
		if(event.target.className.indexOf('icon') !== -1){
			 preventRowSelection = true;
		}

		if (preventRowSelection !== true) {
			Zarafa.advancesearch.ui.AdvanceSearchRowSelectionModel.superclass.handleMouseDown.call(this, grid, rowIndex, event);
		}
	}
});

Ext.reg('zarafa.advancesearchrowselectionmodel', Zarafa.advancesearch.ui.AdvanceSearchRowSelectionModel);
Ext.namespace('Zarafa.advancesearch.ui');

/**
 * @class Zarafa.advancesearch.ui.SearchGridRenderers
 * Methods of this object can be used as renderers for search grid panels, to render
 * cells in custom format according to data type
 * @singleton
 */
Zarafa.advancesearch.ui.SearchGridRenderers = {

	/**
	 * Render the subject and body information of record
	 *
	 * @param {Object} value The data value for the cell.
	 * @param {Object} p An object with metadata
	 * @param {Ext.data.record} record The {Ext.data.Record} from which the data was extracted.
	 * @return {String} The formatted string
	 */
	subjectWithBodyColumn: function (value, p, record)
	{
		p.css = 'search-data';

		if (Ext.isEmpty(value)) {
			// if value is empty then add extra css class for empty cell
			p.css += ' zarafa-grid-empty-cell';
		}

		var messageClass = record.get('message_class');

		var subject = '';
		var body = '';

		switch (messageClass) {
			case 'IPM.Contact':
				subject = Zarafa.advancesearch.ui.SearchGridRenderers.phoneNumber(record.get('home_telephone_number'), {}, record);
				body = Ext.util.Format.htmlEncode(record.get('email_address_1'));
				break;
			case 'IPM.Task':
				body = Ext.util.Format.htmlEncode(record.get('body'));
				break;
			case 'IPM.StickyNote':
				subject = Ext.util.Format.htmlEncode(record.get('body'));
				break;
			default:
				//case 'IPM.Note':
				//case 'IPM.Appointment':
				//case 'IPM.Schedule':
				subject = Ext.util.Format.htmlEncode(record.get('subject'));
				body = Ext.util.Format.htmlEncode(record.get('body'));
				break;
		}

		// Search results that were folded into one row per conversation show
		// the number of messages of the conversation.
		var counter = '';
		if (record.searchConversationCount > 1) {
			counter = Zarafa.common.ui.grid.Renderers.conversationCount(record.searchConversationCount);
		}

		return '<div>' + counter + ( subject ? '<span class="subject">' + subject + '</span>' : '' ) + ( body ? '<span class="body">' + body + '</span>' : '' ) + '</div>';
	},

	/**
	 * Render the name information of record
	 *
	 * @param {Object} value The data value for the cell.
	 * @param {Object} p An object with metadata
	 * @param {Ext.data.record} record The {Ext.data.Record} from which the data was extracted.
	 * @return {String} The formatted string
	 */
	nameColumn: function (value, p, record)
	{
		var messageClass = record.get('message_class');
		var userRecord = false;
		var isItemSentByUser = Ext.isFunction(record.senderIsReceiver) ? !record.senderIsReceiver() && record.senderIsUser() : false;

		if (isItemSentByUser && !Ext.isEmpty(record.get('display_to'))) {
			value = record.get('display_to');
		} else {
			value = record.get('sent_representing_name');
		}

		if (Ext.isEmpty(value) && Ext.isFunction(record.getSender)) {
			value = record.get('sender_name');
			userRecord = record.getSender();
		} else if (Ext.isFunction(record.getSentRepresenting)) {
			userRecord = record.getSentRepresenting();
		}

		switch (messageClass) {
			case 'IPM.Contact':
				return Ext.util.Format.htmlEncode(record.get('display_name'));
			case 'IPM.StickyNote':
				return Ext.util.Format.htmlEncode(record.get('subject'));
			case 'IPM.Schedule.Meeting.Request':
				value = _('With') + ': ' + value;
				break;
			case 'IPM.TaskRequest':
				if (!isItemSentByUser) {
					value = record.get('display_to');
				}
				break;
			case 'IPM.Note':
				if (isItemSentByUser) {
					value = _('To') + ': ' + value;
				}
		}
		var userName = Ext.util.Format.htmlEncode(value);
		return Zarafa.common.ui.grid.Renderers.presenceStatus(userName, p, userRecord);
	},

	/**
	 * Render the date field.
	 *
	 * @param {Object} value The data value for the cell.
	 * @param {Object} p An object with metadata
	 * @param {Ext.data.record} record The {Ext.data.Record} from which the data was extracted.
	 * @return {String} The formatted string
	 */
	dateColumn: function (value, p, record)
	{
		p.css = 'search-date';

		var date = '';
		if (Ext.isDate(value)) {
			if ( container.getSettingsModel().get('zarafa/v1/main/datetime_display_format') === 'short' ){
				// Add one class that the tooltip can use to recognize a 'nice' date.
				// Add one class so the tooltip can easily get the timestamp of the date.
				p.css += ' k-date-nice k-ts-'+value.getTime();

				date = value.getNiceFormat();
			} else {
				date = value.format(_('d/m/Y'));
			}
		} else if (record.isMessageClass('IPM.Task')){
			date = _('No date');
		}

		return '<table cellpadding=0 cellspacing=0 style="width:100%"><tr>' +
			'<td class="date"><div>' + date + '</div></td>' +
			'</tr></table>';
	},

	/**
	 * Render the phone number field.
	 *
	 * @param {Object} value The data value for the cell.
	 * @param {Object} p An object with metadata
	 * @param {Ext.data.record} record The {Ext.data.Record} from which the data was extracted.
	 * @return {String} The formatted string
	 */
	phoneNumber: function (value, p, record)
	{
		p.css = '';
		var phoneNumberProps = ['business_telephone_number', 'home_telephone_number', 'business_fax_number', 'cellular_telephone_number'];

		var phoneNumberProp = phoneNumberProps.find(function (prop) {
			if (!Ext.isEmpty(record.get(prop))) {
				return true;
			}
		});

		if (phoneNumberProp) {
			return Ext.util.Format.htmlEncode(record.get(phoneNumberProp));
		} else {
			return '';
		}
	}
};
Ext.namespace('Zarafa.calendar');

/**
 * @class Zarafa.calendar.Actions
 * Common actions which can be used within {@link Ext.Button buttons}
 * or other {@link Ext.Component components} with action handlers.
 * @singleton
 */
Zarafa.calendar.Actions = {
	/**
	 * Opens a {@link Zarafa.calendar.dialogs.AppointmentContentPanel AppointmentContentPanel} for
	 * viewing an appointment.
	 *
	 * @param {Zarafa.core.data.IPMRecord} records The record, or records to which will be responded.
	 * @param {Object} config (optional) configuration object used to create the ContentPanel
	 */
	openAppointmentContent: function(records, config)
	{
		Ext.each(records, function(record) {
			// If the appointment is a series, then we need to ask the user
			// if he wants to open the occurrence or the series.
			if (Ext.isDefined(record.isRecurringOccurrence) && record.isRecurringOccurrence()) {
				Zarafa.common.Actions.openRecurringSelectionContent(record, function(button, radio) {
					// Action cancelled.
					if (button != 'ok') {
						return;
					}
					if (Ext.isEmpty(record.getStore())) {
						record = this.getById(record.get('entryid'));
					}
					// Convert the record to the requested type
					if (radio.id !== 'recurrence_series') {
						record = record.convertToOccurrenceRecord();
					} else {
						record = record.convertToSeriesRecord();
					}

					Zarafa.core.data.UIFactory.openViewRecord(record, config);
				}, record.getStore());
			} else {
				Zarafa.core.data.UIFactory.openViewRecord(record, config);
			}
		}, this);
	},

	/**
	 * Opens a {@link Zarafa.calendar.dialogs.AppointmentContentPanel AppointmentContentPanel} for creating a new appointment
	 *
	 * @param {Zarafa.calendar.CalendarContextModel} model The context model, which is used to create a new record. The record is blank but contains default values, etc.
	 * @param {Object} config (optional) Configuration object used to create the ContentPanel
	 */
	openCreateAppointmentContent: function(model, config)
	{
		model.createRecord(function(record){
			Zarafa.core.data.UIFactory.openCreateRecord(record, config);
		}.createDelegate(this, [config], true));
	},

	/**
	 * Open a Panel in which the {@link Zarafa.core.data.IPMRecord meeting request}
	 * can be viewed, or further edited.
	 *
	 * @param {Zarafa.core.data.IPMRecord} records The records to open
	 * @param {Object} config (optional) Configuration object used to create
	 * the Content Panel.
	 */
	openMeetingRequestContent: function(records, config)
	{
		// Simple wrapper around openAppointmentContent
		Zarafa.calendar.Actions.openAppointmentContent(records, config);
	},

	/**
	 * Open a Panel in which a new {@link Zarafa.core.data.IPMRecord meeting request} can be
	 * further edited.
	 *
	 * @param {Zarafa.calendar.CalendarContextModel} model Context Model object that will be used
	 * to {@link Zarafa.calendar.CalendarContextModel#createRecord create} the Task.
	 * @param {Object} config (optional) Configuration object used to create
	 * the Content Panel.
	 */
	openCreateMeetingRequestContent: function(model, config)
	{
		model.createRecord(function(record){
			record.convertToMeeting();
			Zarafa.core.data.UIFactory.openCreateRecord(record, config);
		}.createDelegate(this, [config], true));
 	},

	/**
	 * Opens an address-book selection dialog so the user can choose
	 * recipients to forward the meeting request to. On confirmation the
	 * meeting request is forwarded and the organizer is notified.
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The appointment/meeting record to forward
	 * @param {Object} config (optional) Configuration object
	 */
	openForwardMeetingRequestContent: function(record, config)
	{
		var store = new Zarafa.core.data.IPMRecipientStore();

		Zarafa.common.Actions.openABUserMultiSelectionContent({
			title: _('Forward Meeting Request'),
			callback: function() {
				var recipients = [];
				store.each(function(r) {
					recipients.push({
						display_name: r.get('display_name') || '',
						email_address: r.get('email_address') || '',
						smtp_address: r.get('smtp_address') || r.get('email_address') || '',
						address_type: r.get('address_type') || 'SMTP',
						entryid: r.get('entryid') || '',
						search_key: r.get('search_key') || '',
						object_type: r.get('object_type')
					});
				});
				if (recipients.length === 0) {
					return;
				}

				var isOrganizer = record.isMeetingOrganized && record.isMeetingOrganized();

				// Delegates with Send As act on behalf of the store owner,
				// so they do not need the organizer notification hint.
				var isDelegate = false;
				var storeEntryId = record.get('store_entryid');
				if (storeEntryId) {
					var defaultStore = container.getHierarchyStore().getDefaultStore();
					if (defaultStore) {
						var defaultEntryId = defaultStore.get('store_entryid');
						isDelegate = defaultEntryId &&
							!Zarafa.core.EntryId.compareStoreEntryIds(storeEntryId, defaultEntryId);
					}
				}

				var doForward = function() {
					record.forwardMeetingRequest(recipients);
					container.getNotifier().notify('info.meeting',
						pgettext('calendar.actions', 'Meeting request forwarded'));
				};

				if (!isOrganizer && !isDelegate) {
					Ext.MessageBox.show({
						title: _('Forward Meeting Request'),
						msg: _('The meeting organizer will be notified that you forwarded this meeting request.'),
						cls: Ext.MessageBox.INFO_CLS,
						buttons: Ext.MessageBox.OKCANCEL,
						fn: function(btn) {
							if (btn === 'ok') {
								doForward();
							}
						}
					});
				} else {
					doForward();
				}
			},
			convert: function(user, field) {
				return user.convertToRecipient(Zarafa.core.mapi.RecipientType.MAPI_TO);
			},
			store: store,
			selectionCfg: [{
				xtype: 'zarafa.recipientfield',
				fieldLabel: _('To') + ':',
				height: 50,
				boxStore: store,
				filterRecipientType: Zarafa.core.mapi.RecipientType.MAPI_TO,
				defaultRecipientType: Zarafa.core.mapi.RecipientType.MAPI_TO,
				flex: 1
			}]
		});
	},

	/**
	 * Opens a {@link Zarafa.calendar.dialogs.AppointmentContentPanel AppointmentContentPanel} that will
	 * have start and end date changed to proposedd times so an update can be sent to all attendees.
	 *
	 * @param {Zarafa.core.data.IPMRecord} records The record, or records to which will be responded.
	 * @param {Object} config (optional) Configuration object used to create the ContentPanel
	 */
	openAppointmentContentToAcceptProposal: function(records, config)
	{
		if (Array.isArray(records)) {
			records = records[0];
		}

		var appointmentRecord = records.convertToAppointmentRecord(false);

		if(!Ext.isDefined(appointmentRecord)) {
			container.getNotifier().notify('error.proposal', _('Error'), _('Could not accept proposal as this meeting is not available in calendar; It may have been moved or deleted.'));
			return;
		}

		config = Ext.applyIf(config || {}, {
			useShadowStore: true,
			activeTab: 0,

			/*
			 * when we are actually accepting a proposal then we need to pass the proposed time data
			 * to appointment dialog so it can set that data after record is opened and all the data is loaded
			 * in the record
			 */
			newAppointmentProps: {
				// Apply the new time and duration
				'startdate': records.get('proposed_start_date'),
				'duedate': records.get('proposed_end_date'),
				'commonstart': records.get('proposed_start_date'),
				'commonend': records.get('proposed_end_date'),
				'duration': records.get('proposed_duration'),
				// Reset the counter_proposal property to prevent
				// the "Proposal grid" from appearing inside the dialog.
				'counter_proposal': false
			}
		});

		Zarafa.core.data.UIFactory.openCreateRecord(appointmentRecord, config);
	},

	/**
	 * Opens a {@link Zarafa.calendar.dialogs.AppointmentContentPanel AppointmentContentPanel} which
	 * will be opened in scheduling tab and will show all all proposed time in proposed time grid.
	 *
	 * @param {Zarafa.core.data.IPMRecord} records The record, or records to which will be responded.
	 * @param {Object} config (optional) Configuration object used to create the ContentPanels
	 */
	openAppointmentContentToViewAllProposals: function(records, config)
	{
		if (Array.isArray(records)) {
			records = records[0];
		}

		var appointmentRecord = records.convertToAppointmentRecord();

		if(!Ext.isDefined(appointmentRecord)) {
			// @FIXME what if delegate doesn't have shared store open then we should give him a different message
			container.getNotifier().notify('error.proposal', _('Error'), _('Could not view proposal as this meeting is not available in calendar; It may have been moved or deleted.'));
			return;
		}

		config = Ext.applyIf(config || {}, {
			useShadowStore: true,
			activeTab: 1
		});
		Zarafa.core.data.UIFactory.openCreateRecord(appointmentRecord, config);
	},

	/**
	 * Opens a {@link Zarafa.calendar.dialogs.ProposeNewTimeContentPanel ProposeNewTimeContentPanel} for
	 * proposing new time for Meeting Request.
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record, for which propose new time content panel
	 * is going to open.
	 * @param {Object} config (optional) Configuration object used to create the ContentPanel
	 */
	openProposeNewTimeContent: function(record, config)
	{

		config = Ext.applyIf(config || {}, {
			autoSave: false,
			modal: true
		});
		var componentType = Zarafa.core.data.SharedComponentType['calendar.dialogs.proposenewtimecontentpanel'];
		Zarafa.core.data.UIFactory.openLayerComponent(componentType, record, config);
	},

	/**
	 * Opens a {@link Zarafa.addressbook.dialogs.ABMultiUserSelectionContentPanel ABMultiUserSelectionContentPanel}
	 * for configuring the categories of the given {@link Zarafa.core.data.IPMRecord records}.
	 *
	 * @param {Zarafa.core.data.IPMRecord} records The record, or records for which the categories
	 * must be configured
	 * @param {Object} config (optional) Configuration object used to create the ContentPanel
	 */
	openRecipientSelectionContent: function(records, config)
	{
		if (Array.isArray(records) && !Ext.isEmpty(records)) {
			records = records[0];
		}

		// Create a copy of the record, we don't want the changes
		// to be activated until the user presses the Ok button.
		var copy = records.copy();
		var store = copy.getSubStore('recipients');

		Zarafa.common.Actions.openABUserMultiSelectionContent({
			callback: function() {
				records.applyData(copy);
			},
			convert: function(user, field) {
				// Rooms/equipment must always be treated as resources (MAPI_BCC),
				// regardless of which field (Required/Optional/Resource) they were
				// added through, so that server-side auto-accept works correctly.
				// display_type_ex can carry additional flag bits (e.g.
				// DTE_FLAG_ACL_CAPABLE) alongside the base type, so the flags must
				// be stripped via DTE_LOCAL() before comparing against DT_ROOM/DT_EQUIPMENT.
				var localDisplayTypeEx = Zarafa.core.mapi.DisplayTypeEx.DTE_LOCAL(user.get('display_type_ex'));
				var isResource = localDisplayTypeEx === Zarafa.core.mapi.DisplayTypeEx.DT_ROOM ||
				                  localDisplayTypeEx === Zarafa.core.mapi.DisplayTypeEx.DT_EQUIPMENT;
				var recipientType = isResource ?
					Zarafa.core.mapi.RecipientType.MAPI_BCC :
					(field ? field.defaultRecipientType : config.defaultRecipientType);
				return user.convertToRecipient(recipientType);
			},
			store: store,
			selectionCfg: [{
				xtype: 'zarafa.recipientfield',
				fieldLabel: _('Required') + ':',
				height: 50,
				boxStore: store,
				filterRecipientType: Zarafa.core.mapi.RecipientType.MAPI_TO,
				defaultRecipientType: Zarafa.core.mapi.RecipientType.MAPI_TO,
				flex: 1
			},{
				xtype: 'zarafa.recipientfield',
				fieldLabel: _('Optional') + ':',
				height: 50,
				boxStore: store,
				filterRecipientType: Zarafa.core.mapi.RecipientType.MAPI_CC,
				defaultRecipientType: Zarafa.core.mapi.RecipientType.MAPI_CC,
				flex: 1
			},{
				xtype: 'zarafa.recipientfield',
				fieldLabel: _('Resource') + ':',
				height: 50,
				boxStore: store,
				filterRecipientType: Zarafa.core.mapi.RecipientType.MAPI_BCC,
				defaultRecipientType: Zarafa.core.mapi.RecipientType.MAPI_BCC,
				flex: 1
			}]
		});
	},

	/**
	 * Opens the calendar options dialog for the given record.
	 *
	 * @param {Zarafa.core.data.IPMRecord|Zarafa.core.data.IPMRecord[]} records
	 * The record(s) for which the options are requested.
	 * @param {Object} config (optional) Configuration object used to create the Content Panel.
	 */
	openOptionsContent: function(records, config)
	{
		if (Array.isArray(records)) {
			records = records[0];
		}

		if (!records) {
			return;
		}

		config = Ext.applyIf(config || {}, {
			modal: true
		});

		var componentType = Zarafa.core.data.SharedComponentType['calendar.dialog.options'];
		Zarafa.core.data.UIFactory.openLayerComponent(componentType, records, config);
	},

	/**
	 * Opens a {@link Zarafa.calendar.dialogs.SendMeetingRequestConfirmationContentPanel}
	 * @param {Ext.data.Record} record The record, or records, for which the categories must be configured
	 * @param {Object} config (optional) Configuration object used to create the ContentPanel
	 */
	openSendConfirmationContent: function(record, config)
	{
		config = Ext.applyIf(config || {}, {
			record: record,
			modal: true
		});
		var componentType = Zarafa.core.data.SharedComponentType['calendar.dialogs.sendmeetingrequestconfirmation'];
		Zarafa.core.data.UIFactory.openLayerComponent(componentType, record, config);
	},

	/**
	 * Opens a {@link Zarafa.calendar.dialogs.SendMeetingRequestCancellationContentPanel}
	 * @param {Ext.data.Record} record
	 * @param {Object} config (optional) Configuration object used to create the ContentPanel
	 */
	openSendCancellationContent: function(record, config)
	{
		config = Ext.applyIf(config || {}, {
			record: record,
			modal: true
		});
		var componentType = Zarafa.core.data.SharedComponentType['calendar.dialogs.sendmeetingrequestcancellation'];
		Zarafa.core.data.UIFactory.openLayerComponent(componentType, record, config);
	},

	/**
	 * Function opens default calendar folder in the {@link Zarafa.calendar.CalendarContext CalendarContext}.
	 * it will also load day view containing the {@link Zarafa.core.data.IPMRecord IPMRecord}.
	 *
	 * @param {Zarafa.core.data.IPMRecord} record A Meeting Request record
	 * which should be shown in calendar in day view.
	 * @param {Object} config (optional) Configuration object used to create the ContentPanel
	 */
	showMeetingInCalendar: function(record, config)
	{
		var mapiStoreRecord, mapiFolderRecord;

		// Get correspondent appointment's store from the record.
		var mapiStoreId = record.get('appointment_store_entryid');
		if(mapiStoreId) {
			mapiStoreRecord = container.getHierarchyStore().getById(mapiStoreId);
		}

		if(!mapiStoreRecord) {
			/*
			 * If we don't have correspondent appointment's store then get user id from one of this
			 * 1) received_representing_entryid, Meeting request reciever's id for delegate
			 * 2) received_by_entryid, Meeting request reciever's id
			 * 3) sent_representing_entryid, if it is in sent items, then delegator's id,
			 *    where delegator is organizer
			 * 4) sender_entryid, if it is in sent items, then organizer's id
			 * and then open correspondent user's store.
			 */
			var ownerEntryId = record.get('received_representing_entryid') || record.get('received_by_entryid') || record.get('sent_representing_entryid') || record.get('sender_entryid');
			mapiStoreRecord = container.getHierarchyStore().getStoreByOwnerEntryId(ownerEntryId);
			if(!Ext.isDefined(mapiStoreRecord)) {
				container.getNotifier().notify('error', _('Error'), _('Could not open store.'));
				return;
			}
		}

		// Get correspondent appointment's calendar folder from the record.
		var mapiFolderId = record.get('appointment_parent_entryid');
		if(mapiFolderId) {
			mapiFolderRecord = mapiStoreRecord.getFolder(mapiFolderId);
		}

		// If we can't find appointment's calendar folder then
		// get the default calendar folder of the store.
		if(!mapiFolderRecord) {
			mapiFolderRecord = mapiStoreRecord.getDefaultFolder('calendar');
			if(!Ext.isDefined(mapiFolderRecord)) {
				container.getNotifier().notify('error', _('Error'), _('Could not open default calendar folder.'));
				return;
			}
		}

		// Obtain the context & contextModel
		var context = container.getContextByFolder(mapiFolderRecord);
		var model = context.getModel();

		// Load the folder if it is not
		var foldersToLoad = model.getFolders();
		var folderEntryid = mapiFolderRecord.get('entryid');
		var folderIndex = foldersToLoad.indexOf(mapiFolderRecord);
		if (folderIndex === -1) {
			foldersToLoad.push(mapiFolderRecord);
		}

		if (model.default_merge_state && folderIndex !== -1) {
			// Check if folder is already there in one of the groups, find it.
			for (var key in model.groupings) {
				var groupFolders = model.groupings[key].folders;
				if(groupFolders.indexOf(folderEntryid) > -1) {
					// Respective group found, Make it active
					model.active_group = key;
					model.groupings[key].active = folderEntryid;
				}
			}
		} else {
			// Create a new group, add it to groupings as an active one
			model.active_group = Ext.id(null, 'group-');
			model.groupings[model.active_group] = { folders: [ folderEntryid ], active: folderEntryid };
		}

		// Enable the context, but keep it suspended to prevent loading data
		container.switchContext(context, foldersToLoad, true);

		// If the view or viewmode is list, change it to blocks and day
		if (context.getCurrentView() == Zarafa.calendar.data.Views.LIST || context.getCurrentViewMode() == Zarafa.calendar.data.ViewModes.LIST) {
			context.switchView(Zarafa.calendar.data.Views.BLOCKS, Zarafa.calendar.data.ViewModes.DAYS);
		}

		// Define which date range to load
		var appointmentDate = record.get('appointment_startdate') || record.get('appointment_basedate');

		// If the current data mode is all (list) change to day so the appointment will be visible
		if (model.getCurrentDataMode() == Zarafa.calendar.data.DataModes.ALL) {
			model.setDataMode(Zarafa.calendar.data.DataModes.DAY);
		}
		model.setDate(appointmentDate);

		// Select the appointment
		model.setSelectedRecords([record.convertToAppointmentRecord()]);

		// We are done initializing the context & model.
		// Time to start loading
		model.resumeLoading();
	},

	/**
	 * Function which show the {@link Zarafa.common.dialogs.MessageBox.select selectMessageBox} dialog
	 * if selected record is recurring items and based on selected option record is converted to either
	 * {@link Zarafa.calendar.AppointmentRecord.convertToSeriesRecord seriesRecord} or
	 * {@link Zarafa.calendar.AppointmentRecord.convertToOccurrenceRecord OccurrenceRecord}. Also it will open
	 * the record if it is not.
	 *
	 * @param {Zarafa.core.data.IPMRecord} record A selected calendar item in calendar view.
	 * @param {Object} config Configuration object which contains {@link Ext.Component component}
	 * on which key event is fired and scope of the {@link Zarafa.calendar.KeyMapping KeyMapping} object.
	 */
	copyRecurringItemContent: function(record, config)
	{
		Zarafa.common.Actions.copyRecurringSelectionContent(record, function (button, radio) {
			// Action cancelled.
			if (button === 'cancel') {
				return;
			}

			var clipBoardRecord = '';
			var forceOpen = false;
			// Convert the record to the requested type
			if (radio.id === 'recurrence_series') {
				clipBoardRecord = record.convertToSeriesRecord();
				// If clipboard record is occurrence then it doesn't have recurrence props.
				// So need to open it first.
				forceOpen = !record.get("recurring");
			} else {
				clipBoardRecord = record.convertToOccurrenceRecord();
			}

			// Open record if record is not opened. we need to open record
			// because we requires "body", "recurrence_*" property information to
			// create proper normal/recurring record with proper recurring pattern .
			if (!record.isOpened() || forceOpen) {
				this.openRecord(config.component, clipBoardRecord);
			} else {
				config.component.doPaste(clipBoardRecord);
			}

		}, config.scope);
	},

	/**
	 * Converts record to an appointment and calls {@link Zarafa.core.data.UIFactory.openCreateRecord}
	 * to open the newly created appointment as editable record. The original record isn't removed.
	 *
	 * @param {Zarafa.core.data.IPMRecord} records The record which will be converted to an appointment
	 * @param {Zarafa.calendar.CalendarContextModel} model Used to create a new appointment record
	 */
	createAppointmentFromMail: function(records, model)
	{
		var record;

		if (Array.isArray(records) && !Ext.isEmpty(records)) {
			record = records[0];
		} else {
			return;
		}

		if (record.isOpened()) {
			this.openHandler(record.getStore(), record, model);
		} else {
			// If record is not opened, then we need to reopen it to get the body. (For example when the selected records store reloads)
			record.getStore().on('open', this.openHandler.createDelegate(this, [model], 2), this, {single: true});
			record.open();
		}
	},

	/**
	 * Handler for {@link Zarafa.core.data.IPMStore store} open event. Converts the opened record
	 * to an appointment and opens it as editable record.
	 *
	 * @param {Zarafa.core.data.IPMStore} store The store of the record.
	 * @param {Zarafa.core.data.IPMRecord} record The record which will be converted to an appointment
	 * @param {Zarafa.calendar.CalendarContextModel} model Used to create a new appointment record
	 */
	openHandler: function(store, record, model)
	{
		var newAppointmentRecord = record.convertToAppointment(model.getDefaultFolder());
		// Ensure organizer/sender fields are initialized for the new appointment.
		Zarafa.core.data.MessageRecordPhantomHandler(newAppointmentRecord);
		// Treat the new record as opened so substores exist for scheduling/recipients.
		newAppointmentRecord.afterOpen();
		// Ensure we have a valid start/due date; if not, round up to the next slot.
		if (!Ext.isDate(newAppointmentRecord.get('startdate')) || !Ext.isDate(newAppointmentRecord.get('duedate'))) {
			var settingsModel = container.getSettingsModel();
			var zoomLevel = settingsModel.get('zarafa/v1/contexts/calendar/default_zoom_level');
			var defaultPeriod = settingsModel.get('zarafa/v1/contexts/calendar/default_appointment_period');

			var startDate = new Date().ceil(Date.MINUTE, zoomLevel);
			var dueDate = startDate.add(Date.MINUTE, defaultPeriod);

			newAppointmentRecord.beginEdit();
			newAppointmentRecord.set('startdate', startDate, true);
			newAppointmentRecord.set('duedate', dueDate, true);
			newAppointmentRecord.set('commonstart', startDate);
			newAppointmentRecord.set('commonend', dueDate);
			newAppointmentRecord.set('duration', defaultPeriod);
			newAppointmentRecord.set('alldayevent', false);
			newAppointmentRecord.endEdit();
		}
		Zarafa.core.data.UIFactory.openCreateRecord(newAppointmentRecord);
	}
};
Ext.namespace('Zarafa.calendar.data');

/**
 * @class Zarafa.calendar.data.AppointmentBounds
 * @extends Object
 *
 * The configuration object for appointments as used in the
 * {@link Zarafa.calendar.ui.AbstractDateRangeView DateRangeView}
 * for configuration of the individual bodies or the header
 * of an appointment.
 */
Zarafa.calendar.data.AppointmentBounds = Ext.extend(Object, {
	/**
	 * @constructor
	 * @param {Object} config Configrution object
	 */
	constructor: function(config)
	{
		Ext.apply(this, config);
	},

	/**
	 * The left offset for the element from the left-side of the container
	 * in which this element is being rendered.
	 * @property
	 * @type Number
	 */
	left: 0,

	/**
	 * The right offset for the element from the right-side of the container
	 * in which this element is being rendered.
	 * @property
	 * @type Number
	 */
	right: 0,

	/**
	 * The top offset for the element from the top-side of the container
	 * in which this element is being rendered.
	 * @property
	 * @type Number
	 */
	top: 0,

	/**
	 * The bottom offset for the element from the bottom-side of the container
	 * in which this element is being rendered.
	 * @property
	 * @type Number
	 */
	bottom: 0,

	/**
	 * This indicates if this is the first {@link Zarafa.calendar.data.AppointmentBounds bound}
	 * for an {@link Zarafa.calendar.ui.AppointmentView appointment}. This implies that _if_ this
	 * is true, it will be the first element of an array, but at the same time it does not
	 * guarantee that a bound is present in the array with the {@link #firstBox} is present.
	 * This could occur when an appointment overlaps the start of the current view (in other words,
	 * the start date of the appointment is _before_ the first visible day in our view).
	 * @property
	 * @type Boolean
	 */
	firstBox: false,

	/**
	 * This indicates if this is the last {@link Zarafa.calendar.data.AppointmentBounds bound}
	 * for an {@link Zarafa.calendar.ui.AppointmentView appointment}. This implies that _if_ this
	 * is true, it will be the last element of an array, but at the same time it does not
	 * guarantee that a bound is present in the array with the {@link #lastBox} is present.
	 * This could occur when an appointment overlaps the end of the current view (in other words,
	 * the due date of the appointment is _after_ the last visible day in our view).
	 * @property
	 * @type Boolean
	 */
	lastBox: false
});
Ext.namespace('Zarafa.calendar.data');

/**
 * @class Zarafa.calendar.data.DayBoxConfiguration
 * @extends Object
 *
 * The configuration object for DayBoxes as used in the
 * {@link Zarafa.calendar.ui.AbstractCalendarBoxView CalendarBoxView}
 * for configuration of the individual DayBoxes.
 */
Zarafa.calendar.data.DayBoxConfiguration = Ext.extend(Object, {
	/**
	 * @constructor
	 * @param {Object} config Configrution object
	 */
	constructor: function(config)
	{
		Ext.apply(this, config);
	},

	/**
	 * The left offset for the DayBox from the left-side of the container
	 * in which this DayBox is being rendered.
	 * @property
	 * @type Number
	 */
	left: 0,

	/**
	 * The right offset for the DayBox from the right-side of the container
	 * in which this DayBox is being rendered.
	 * @property
	 * @type Number
	 */
	right: 0,

	/**
	 * The top offset for the DayBox from the top-side of the container
	 * in which this DayBox is being rendered.
	 * @property
	 * @type Number
	 */
	top: 0,

	/**
	 * The bottom offset for the DayBox from the bottom-side of the container
	 * in which this DayBox is being rendered.
	 * @property
	 * @type Number
	 */
	bottom: 0,

	/**
	 * The date which is being displayed within this DayBox.
	 * @property
	 * @type Date
	 */
	date: undefined,

	/**
	 * Indicates if {@link #date} is today.
	 * @property
	 * @type Boolean
	 */
	today: false,

	/**
	 * Indicates if the {@link #date} is a working day.
	 * @property
	 * @type Boolean
	 */
	workingDay: false,

	/**
	 * The {@link Zarafa.core.mapi.BusyStatus BusyStatus} which is active on this day.
	 * This value depends on the allday events which might be scheduled for this {@link #date}.
	 * @property
	 * @type Zarafa.core.mapi.BusyStatus
	 */
	busyStatus: undefined,

	/**
	 * True if there are more appointments then which can be rendered into this DayBox.
	 * @property
	 * @type Boolean
	 */
	overflow: false,

	/**
	 * True if this DayBox must be rendered as active. And non-active DayBox means that
	 * it is being rendered but the day which is represented by this box does not fall
	 * within the requested {@link Zarafa.core.DateRange daterange} for which the appointments
	 * have been requested.
	 *
	 * and the appointments for this day have been loaded. It can be
	 * false if this is a day whi
	 * @property
	 * @type Boolean
	 */
	active: false
});
Ext.namespace('Zarafa.calendar.data');

/**
 * @class Zarafa.calendar.data.DayLayoutPosition
 * @extends Object
 *
 * The configuration object for Day Columns as used in the
 * {@link Zarafa.calendar.ui.AbstractCalendarDaysView CalendarDaysView}
 * for configuration of the individual Day Columns.
 */
Zarafa.calendar.data.DayLayoutPosition = Ext.extend(Object, {
	/**
	 * @constructor
	 * @param {Object} config Configrution object
	 */
	constructor: function(config)
	{
		Ext.apply(this, config);
	},

	/**
	 * The left offset for the Day Column from the left-side of the container
	 * in which this DayBox is being rendered.
	 * @property
	 * @type Number
	 */
	left: 0,

	/**
	 * The right offset for the Day Column from the right-side of the container
	 * in which this DayBox is being rendered.
	 * @property
	 * @type Number
	 */
	right: 0,

	/**
	 * The date which is being displayed in the Day Column
	 * @property
	 * @type Number
	 */
	date: undefined,

	/**
	 * Indicates if {@link #date} is today.
	 * @property
	 * @type Boolean
	 */
	today: false,

	/**
	 * Indicates if the {@link #date} is a working day.
	 * @property
	 * @type Boolean
	 */
	workingDay: false
});
Ext.namespace('Zarafa.calendar.data');

/**
 * @class Zarafa.calendar.data.ReminderPeriods
 * @singleton
 */
Zarafa.calendar.data.ReminderPeriods = [{
	value: 0,
	name: Ext.util.Format.duration(0)
},{
	value: 5,
	name: Ext.util.Format.duration(5)
},{
	value: 10,
	name: Ext.util.Format.duration(10)
},{
	value: 15,
	name: Ext.util.Format.duration(15)
},{
	value: 30,
	name: Ext.util.Format.duration(30)
},{
	value: 1 * 60,
	name: Ext.util.Format.duration(1 * 60)
},{
	value: 2 * 60,
	name: Ext.util.Format.duration(2 * 60)
},{
	value: 4 * 60,
	name: Ext.util.Format.duration(4 * 60)
},{
	value: 8 * 60,
	name: Ext.util.Format.duration(8 * 60)
},{
	value: 12 * 60,
	name: Ext.util.Format.duration(12 * 60)
},{
	value: 18 * 60,
	name: Ext.util.Format.duration(18 * 60)
},{
	value: 1 * 24 * 60,
	name: Ext.util.Format.duration(1 * 24 * 60)
},{
	value: 2 * 24 * 60,
	name: Ext.util.Format.duration(2 * 24 * 60)
},{
	value: 3 * 24 * 60,
	name: Ext.util.Format.duration(3 * 24 * 60)
},{
	value: 4 * 24 * 60,
	name: Ext.util.Format.duration(4 * 24 * 60)
},{
	value: 5 * 24 * 60,
	name: Ext.util.Format.duration(5 * 24 * 60)
},{
	value: 7 * 24 * 60,
	name: Ext.util.Format.duration(7 * 24 * 60)
},{
	value: 14 * 24 * 60,
	name: Ext.util.Format.duration(14 * 24 * 60)
},{
	value: 28 * 24 * 60, 
	name: Ext.util.Format.duration(28 * 24 * 60)
}];
Ext.namespace('Zarafa.calendar.data');

/**
 * @class Zarafa.calendar.data.SearchFields
 * @singleton
 *
 * Search fields for search bar
 */
Zarafa.calendar.data.SearchFields = [{
	name: _('All text fields'),
	value: 'subject sender_name sender_email_address sent_representing_name sent_representing_email_address body display_to display_cc location'
}, {
	name: _('Subject'),
	value: 'subject'
}, {
	name: _('Sender'),
	value: 'sender_name sender_email_address sent_representing_name sent_representing_email_address'
}, {
	name: _('Body'),
	value: 'body'
}, {
	name: _('Subject or Sender'),
	value: 'subject sender_name sender_email_address sent_representing_name sent_representing_email_address'
}, {
	name: _('To or cc'),
	value: 'display_to display_cc'
}, {
	name: _('Location'),
	value: 'location'
}];
Ext.namespace('Zarafa.calendar.dialogs');

/**
 * @class Zarafa.calendar.dialogs.AppointmentPanel
 * @extends Ext.Panel
 * @xtype zarafa.appointmentpanel
 *
 * Panel that is used to create Appointments and Meeting Requests.
 */
Zarafa.calendar.dialogs.AppointmentPanel = Ext.extend(Ext.Panel, {
	// Insertion points for this class
	/**
	 * @insert context.calendar.appointmentcontentpanel.tabs
	 * can be used to add extra tabs to appointmentcontentpanel by 3rd party plugins
	 * @param {Zarafa.calendar.dialogs.AppointmentPanel} panel This appointmentpanel
	 */

	/**
	 * @cfg {Number} activeTab, the tab which should be active in {Zarafa.calendar.dialogs.AppointmentPanel tabs}
	 * 0 opens Appointment tab{@link Zarafa.calendar.dialogs.AppointmentTab}, this is defaultValue, we also want this tab when we accept any proposed time from attendee,
	 * 1 opens Freebusy tab{@link Zarafa.calendar.dialogs.FreebusyTab}, we want this tab to be opened when we view all propsed time from Attendees
	 * 2 opens Tracking tab{@link Zarafa.calendar.dialogs.TrackingTab}
	 */
	activeTab: undefined,

	/**
	 * True by default to display the information while switching between tab.
	 * This notification/message should be shown only once,
	 * but if there are any update/change in data it will be reset.
	 * @property
	 * @type Boolean
	 */
	enableNotifier: true,

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

		if (Ext.isDefined(config.activeTab)) {
			this.activeTab = config.activeTab;
		}

		config = Ext.applyIf(config, {
			xtype: 'zarafa.appointmentpanel',
			border: false,
			layout: 'fit',
			items: this.createTabPanel(this.activeTab)
		});

		Zarafa.calendar.dialogs.AppointmentPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Initialize {@link Ext.TabPanel tabpanel) that will contain all the fields and forms
	 * @return {Object} Configuration object for the form panel
	 * @private
	 */
	createTabPanel: function(activeTab)
	{
		return [{
			xtype: 'tabpanel',
			ref: 'tabPanel',
			activeTab: activeTab || 0,
			border: false,
			layoutOnTabChange: true,
			plugins: [ 'zarafa.recordcomponentupdaterplugin' ],
			update: this.onTabPanelUpdate.createDelegate(this),
			listeners: {
				render: this.onTabPanelRender,
				tabchange: this.onTabPanelChange,
				scope: this
			},
			items: [
			{
				xtype: 'zarafa.appointmenttab',
				title: _('Appointment'),
				ref: '../appointmentTab'
			},{
				xtype: 'zarafa.freebusytab',
				title: _('Scheduling'),
				ref: '../schedulingTab'
			},{
				xtype: 'zarafa.trackingtab',
				title: _('Tracking'),
				ref: '../trackingTab'
			},
			// Add insertion point
			container.populateInsertionPoint('context.calendar.appointmentcontentpanel.tabs', this)
			]
		}];
	},

	/**
	 * Update the TabPanel with the given record.
	 *
	 * @param {Zarafa.core.data.MAPIRecord} record The record to update in this component
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 */
	onTabPanelUpdate: function(record, contentReset)
	{
		// When the 'meeting' flag has been changed, and it is no longer a meeting request,
		// then force the user to the appointmentTab.
		if (!contentReset && record.isModifiedSinceLastUpdate('meeting') && record.get('meeting') == Zarafa.core.mapi.MeetingStatus.NONMEETING) {
			if (this.tabPanel.activeTab == this.schedulingTab || this.tabPanel.activeTab == this.trackingTab) {
				this.tabPanel.setActiveTab(this.appointmentTab);
			}
		}
		// Tracking tab should only be shown when meeting request is already sent
		if (record.isMeetingSent()) {
			this.tabPanel.unhideTabStripItem(this.trackingTab);
		}
	},

	/**
	 * Event handler which is fired when the TabPanel is being rendered, this will
	 * disable the TrackingTab by default (until it needs to be shown).
	 * @param {Ext.TabPanel} tabpanel The tabpanel which has been rendered
	 * @private
	 */
	onTabPanelRender: function(tabpanel)
	{
		this.tabPanel.hideTabStripItem(this.trackingTab);
	},

	/**
	 * Event handler which is fired when the TabPanel is being changed, this will
	 * display/notify useful information to the user while switching between the tab
	 * @param {Ext.TabPanel} tabPanel The tabPanel which has been rendered
	 * @param {Ext.FormPanel} activeTab The new active tab
	 * @private
	 */
	onTabPanelChange: function(tabPanel, activeTab)
	{
		if(activeTab && activeTab.record){
			var recipientStore = activeTab.record.getRecipientStore();
			if(recipientStore && recipientStore.getCount() > 0){
				this.mon(recipientStore, 'update', this.onRecipientsChange, this);
				this.mon(recipientStore, 'add', this.onRecipientsChange, this);
				this.mon(recipientStore, 'remove', this.onRecipientsChange, this);
				this.mon(recipientStore, 'datachanged', this.onRecipientsChange, this);

				if (this.enableNotifier){
					var unresolved = recipientStore.getUnresolvedRecipients();
					var invalid = recipientStore.getInvalidRecipients();

					// If there are unresolved recipients, then it should shown an information message to user
					if (!Ext.isEmpty(invalid) || !Ext.isEmpty(unresolved)){
						container.getNotifier().notify('info.unresolved_recipients', '', pgettext('calendar.dialog', 'Not all attendees could be resolved.'), {
							container: this.getEl(),
							persistent: true
						});
						this.enableNotifier = false;
					}
				}
			}
		}
	},

	/**
	 * Event handler which is fired when data in {@link Zarafa.core.data.IPMRecipientStore} has changed.
	 * This will reset the flag {@link Zarafa.calendar.dialogs.AppointmentPanel enableNotifier}
	 * @private
	 */
	onRecipientsChange: function()
	{
		this.enableNotifier = true;
	}
});

Ext.reg('zarafa.appointmentpanel', Zarafa.calendar.dialogs.AppointmentPanel);
Ext.namespace('Zarafa.calendar.dialogs');

/**
 * @class Zarafa.calendar.dialogs.AppointmentTab
 * @extends Ext.form.FormPanel
 * @xtype zarafa.appointmenttab
 *
 * Main tab in the {@link Zarafa.calendar.dialogs.AppointmentPanel}
 * that is used to create Appointments and Meeting Requests.
 */
Zarafa.calendar.dialogs.AppointmentTab = Ext.extend(Ext.form.FormPanel, {
	/**
	 * @cfg {String} trackingInfoString string which must be displayed in the dialog
	 * if any attendee has responded to the meeting request in {@link Zarafa.core.data.IPMRecord record}
	 */
	trackingInfoString: pgettext('calendar.dialog', '{0} attendees accepted, {1} tentatively accepted, {2} declined.'),

	/**
	 * @cfg {String} proposedTimeInfoString string which must be displayed in the dialog
	 * if there is any counter proposal for change in meeting time set in {@link Zarafa.core.data.IPMRecord record}
	 */
	proposedTimeInfoString: pgettext('calendar.dialog', '{0} attendees proposed a new time for this meeting. Click the Scheduling tab for details.'),

	/**
	 * @cfg {String} proposeNewTimeInfoString string which must be displayed in the dialog
	 * if there is any counter proposal for change in meeting time set in {@link Zarafa.core.data.IPMRecord record}
	 */
	proposeNewTimeInfoString: pgettext('calendar.dialog', 'You proposed a new time for this meeting on {0}'),

	/**
	 * @cfg {String} proposeNewTimeDelegateInfoString string which must be displayed in the dialog
	 * if there is any counter proposal for change in meeting time set in {@link Zarafa.core.data.IPMRecord record}
	 * and meeting is proposed by delegate
	 */
	proposeNewTimeDelegateInfoString: pgettext('calendar.dialog', '{0} proposed a new time for this meeting on {1}'),

	/**
	 * @cfg {String} acceptedInfoString string which must be displayed in the dialog
	 * if meeting accepted time set in {@link Zarafa.core.data.IPMRecord record}
	 */
	acceptedInfoString: pgettext('calendar.dialog', 'Accepted on {0}'),

	/**
	 * @cfg {String} acceptedDelegateInfoString string which must be displayed in the dialog
	 * if meeting accepted time set in {@link Zarafa.core.data.IPMRecord record}
	 * and meeting is accepted by delegate
	 */
	acceptedDelegateInfoString: pgettext('calendar.dialog', 'Accepted by {0} on {1}'),

	/**
	 * @cfg {String} tentativeInfoString string which must be displayed in the dialog
	 * if meeting tentatively accepted time set in {@link Zarafa.core.data.IPMRecord record}
	 */
	tentativeInfoString: pgettext('calendar.dialog', 'Tentatively Accepted on {0}'),

	/**
	 * @cfg {String} tentativeDelegateInfoString string which must be displayed in the dialog
	 * if meeting tentatively accepted time set in {@link Zarafa.core.data.IPMRecord record}
	 * and meeting is accepted by delegate
	 */
	tentativeDelegateInfoString: pgettext('calendar.dialog', 'Tentatively Accepted by {0} on {1}'),

	/**
	 * @cfg {String} elapsedTimeInfoString string which must be displayed in the dialog
	 * if meeting time set in {@link Zarafa.core.data.IPMRecord record} is already elapsed.
	 */
	elapsedTimeInfoString: pgettext('calendar.dialog', 'This appointment occurs in the past.'),

	/**
	 * @cfg {String} responseRequiredString string which must be displayed in the dialog
	 * if meeting accepted time set in {@link Zarafa.core.data.IPMRecord record} is empty.
	 * which means that, meeting request is not responded yet
	 */
	responseRequiredString: pgettext('calendar.dialog', 'Please respond.'),

	/**
	 * @cfg {String} noResponseReceivedString string which must be displayed in the dialog
	 * if no recipient has responded to meeting request
	 */
	noResponseReceivedString: pgettext('calendar.dialog', 'No responses have been received for this meeting.'),

	/**
	 * @cfg {String} meetingCanceledString string which must be displayed in the dialog
	 * if meeting has been canceled.
	 */
	meetingCanceledString: pgettext('calendar.dialog', 'Meeting has been canceled.'),

	/**
	 * @cfg {String} meetingUnsentString string which must be displayed in the dialog
	 * if meeting has not yet been sent.
	 */
	meetingUnsentString: pgettext('calendar.dialog', 'Invitations have not been sent for this meeting.'),

	/**
	 * @cfg {String} meetingOverwrittenString string which must be displayed in the dialog
	 * to inform the user his changes will be overwritten when the meeting organizer updates
	 * the meeting.
	 */
	meetingOverwrittenString: pgettext('calendar.dialog', 'Please note that any changes you make will be overwritten when this meeting request is updated by the organizer'),

	/**
	 * Property which is set to true when user changes location manually, If user has
	 * changed/set location manually. {@link updateLocation} function will use this
	 * property to check whether has changed location or not, if this property is false
	 * then it will change location generated using recipients without any confirmation.
	 * @property
	 * @type Boolean
	 * @private
	 */
	hasUserSetLocation: false,

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

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

		Ext.applyIf(config, {
			xtype: 'zarafa.appointmenttab',
			cls: 'k-appointmentcreatetab',
			layout: {
				type: 'vbox',
				align: 'stretch'
			},
			border: false,
			labelPad: 0,
			items: [
				this.createExtraInfoPanel(),
				this.createMeetingOrganizerPanel(),
				this.createRecipientPanel(),
				this.createSubjectPanel(),
				this.createLocationPanel(),
				this.createDateTimePanel(),
				this.createinPanel(),
				this.createAttachmentsPanel(),
				this.createBodyPanel()
			]
		});

		Zarafa.calendar.dialogs.AppointmentTab.superclass.constructor.call(this, config);
	},

	/**
	 * Create the {@link Ext.Panel panel} containing the information about response from attendees
	 * and some extra information regarding meeting item.
	 * @return {Object} Configuration object for the panel containing the fields
	 * @private
	 */
	createExtraInfoPanel: function()
	{
		return {
			xtype: 'container',
			cls: 'zarafa-calendar-appointment-extrainfo',
			ref: 'extraInfoPanel',
			autoHeight: true,
			hidden: true
		};
	},

	/**
	 * Create the {@link Ext.Panel panel} containing the information about meeting organizer
	 * @return {Object} Configuration object for the panel containing the fields
	 * @private
	 */
	createMeetingOrganizerPanel: function()
	{
		return {
			xtype: 'panel',
			ref: 'meetingOrganizerPanel',
			layout: 'form',
			autoHeight: true,
			border: false,
			items: [{
				xtype: 'displayfield',
				ref: '../meetingOrganizerField',
				fieldLabel: _('Organizer'),
				htmlEncode: true,
				flex: 1
			}],
			hidden: true
		};
	},

	/**
	 * Create the {@link Zarafa.common.ui.RecipientField RecipientField}
	 * where the recipients for the Meeting requests can be selected
	 * @return {Object} Configuration object for the panel containing the composite field
	 * @private
	 */
	createRecipientPanel: function()
	{
		return {
			xtype: 'zarafa.resizablecompositefield',
			cls: 'k-field-to',
			ref: 'recipientPanel',
			anchor: '99%',
			autoHeight: false,
			items: [{
				xtype: 'button',
				ref: '../toButton',
				autoHeight: true,
				text: _('To') + ':',
				handler: this.showRecipientContent,
				scope: this
			},{
				xtype: 'zarafa.recipientfield',
				ref: '../recipientField',
				fieldLabel: _('To'),
				plugins: [ 'zarafa.recordcomponentupdaterplugin' ],
				flex: 1
			}]
		};
	},

	/**
	 * Create the {@link Ext.Panel panel} containing the form element
	 * to set the subject
	 * @return {Object} Configuration object for the panel containing the fields
	 * @private
	 */
	createSubjectPanel: function()
	{
		return {
			xtype: 'panel',
			cls: 'k-subject-panel',
			layout: 'form',
			labelWidth: 85,
			labelAlign: 'left',
			autoHeight: true,
			border: false,
			items: [{
				xtype: 'textfield',
				name: 'subject',
				fieldLabel: _('Subject'),
				anchor: '99%',
				listeners: {
					change: this.onFieldChange,
					scope: this
				}
			}]
		};
	},

	/**
	 * Create the {@link Ext.Panel panel} containing the form element
	 * to set the location
	 * @return {Object} Configuration object for the panel containing the fields
	 * @private
	 */
	createLocationPanel: function()
	{
		return {
			xtype: 'panel',
			cls: 'k-location-panel',
			layout: 'form',
			labelWidth: 85,
			labelAlign: 'left',
			autoHeight: true,
			border: false,
			items: [{
				xtype: 'textfield',
				name: 'location',
				fieldLabel: _('Location'),
				anchor: '99%',
				enableKeyEvents: true,
				listeners: {
					change: this.onFieldChange,
					keypress: this.onLocationKeyPress,
					scope: this
				}
			}]
		};
	},

	/**
	 * Create the {@link Ext.Panel panel} containing the following elements
	 * in a table layout: the date panel, recurrence panel, busy status panel,
	 * reminder panel, and the label panel.
	 * @return {Object} Configuration object for the panel containing the fields
	 * @private
	 */
	createDateTimePanel: function()
	{
		return {
			xtype: 'panel',
			cls: 'k-datetime-panel',
			border: false,
			autoHeight: true,
			items: [
				this.createDatePanel(),
				this.createRecurrencePanel(),
				this.createBusyStatusPanel(),
				this.createReminderPanel(),
			]
		};
	},

	/**
	 * Create the {@link Ext.Panel panel} containing the form element
	 * to set the calendar in which the appointment or meeting-request will be
	 * created.
	 * @return {Object} Configuration object for the panel containing the fields
	 * @private
	 */
	createinPanel: function()
	{
		//config options necessary to create store which feeds the data to create-in-combo.
		const createInStore = {
			xtype: 'jsonstore',
			fields: ['entryid', 'store_entryid', 'displayString', 'iconColor', 'sortOrder'],
			data: this.getSortedCreateInData(),
			sortInfo: {
				field: 'sortOrder',
				direction: 'ASC' // or 'DESC' (case sensitive for local sorting)
			}
		};

		const tplString = '<tpl for=".">' +
			'<div class="x-combo-list-item">' +
				'{[Zarafa.calendar.ui.IconCache.getCalendarSvgStructure(values.iconColor)]}' +
				'{displayString}' +
			'</div>' +
		'</tpl>';

		return {
			xtype: 'panel',
			cls: 'k-createin-panel',
			layout: 'form',
			labelWidth: 85,
			labelAlign: 'left',
			ref: 'createInPanel',
			autoHeight: true,
			border: false,
			items: [{
				xtype: 'combo',
				tpl: tplString,
				fieldLabel: _('Create in'),
				ref: '../comboCreateIn',
				store: createInStore,
				cls: 'k-createin-combo',
				listClass: 'k-createin-combo-list-svg',
				mode: 'local',
				triggerAction: 'all',
				displayField: 'displayString',
				valueField: 'entryid',
				lazyInit: false,
				forceSelection: true,
				editable: false,
				listeners: {
					select: this.onCreateInSelect,
					beforeexpand: this.onCreateInBeforeExpand,
					collapse: this.setCursorPosition,
					expand: this.setCursorPosition,
					scope: this
				}
			}]
		};
	},

	/**
	 * Create the {@link Ext.Panel Panel} containing the
	 * {@link Zarafa.common.ui.DateTimePeriodField DateTimePeriodField}.
	 * @return {Object} Configuration object for the panel with time selection fields
	 * @private
	 */
	createDatePanel: function()
	{
		return {
			xtype: 'panel',
			cls: 'k-date-panel',
			layout: {
				type: 'table',
				columns: 2
			},
			ref: '../datePanel',
			autoHeight: true,
			autoWidth: true,
			border: false,
			items: [{
				xtype: 'zarafa.datetimeperiodfield',
				ref: '../../datetimePeriod',
				defaultValue: this.generateDefaultDate(),
				defaultPeriod: container.getSettingsModel().get('zarafa/v1/contexts/calendar/default_appointment_period'),
				defaultPeriodType: Date.MINUTE,
				timeIncrement: container.getSettingsModel().get('zarafa/v1/contexts/calendar/default_zoom_level'),
				width: 660,
				allowEqualValue: true,
				layout: 'hbox',
				listeners: {
					change: this.onDateRangeFieldChange,
					scope: this
				},
				startFieldConfig: {
					fieldLabel: _('Time'),
					labelWidth: 85,
					cls: 'from-field'
				},
				endFieldConfig: {
					fieldLabel: _('until'),
					labelWidth: 85,
					cls: 'to-field'
				}
			},{
				xtype: 'panel',
				border: false,
				items: [{
					xtype: 'checkbox',
					name: 'alldayevent',
					boxLabel: _('All Day Event'),
					handler: this.onToggleAllDay,
					scope: this
				}]
			}]
		};
	},

	/**
	 * Create the {@link Ext.Panel Panel} containing the form fields
	 * for displaying the recurrence information.
	 * @return {Object} Configuration object for the panel with recurrence fields
	 * @private
	 */
	createRecurrencePanel: function()
	{
		return {
			xtype: 'panel',
			cls: 'k-recurrence-panel',
			ref: '../recurrencePanel',
			layout: 'form',
			labelWidth: 85,
			autoHeight: true,
			border: false,
			items: [{
				xtype: 'displayfield',
				ref: '../../recurrencePatternField',
				fieldLabel: _('Recurrence'),
				htmlEncode: true
			}]
		};
	},

	/**
	 * Create the {@link Ext.Panel Panel} containing the form fields
	 * for setting the reminder information.
	 * @return {Object} Configuration object for the panel with reminder fields
	 * @private
	 */
	createReminderPanel: function()
	{
		var reminderStore = {
			xtype: 'jsonstore',
			fields: ['name', 'value'],
			data: Zarafa.calendar.data.ReminderPeriods
		};

		return {
			xtype: 'panel',
			cls: 'k-reminder-panel',
			layout: 'form',
			labelWidth: 85,
			autoHeight: true,
			border: false,
			items: [{
				xtype: 'zarafa.compositefield',
				fieldLabel: _('Reminder'),
				autoHeight: true,
				items: [{
					xtype: 'checkbox',
					name: 'reminder',
					handler: this.onToggleReminder,
					scope: this
				},{
					xtype: 'combo',
					ref: '../../../comboReminder',
					name: 'reminder_minutes',
					fieldLabel: _('Reminder'),
					store: reminderStore,
					mode: 'local',
					triggerAction: 'all',
					displayField: 'name',
					valueField: 'value',
					lazyInit: false,
					forceSelection: true,
					editable: false,
					listeners: {
						select: this.onFieldSelect,
						scope: this
					}
				}]
			}]
		};
	},

	/**
	 * Create the {@link Ext.Panel Panel} containing the form fields
	 * for setting the busy status.
	 * @return {Object} Configuration object for the panel with reminder fields
	 * @private
	 */
	createBusyStatusPanel: function()
	{
		var busyStore = {
			xtype: 'jsonstore',
			fields: ['name', 'value'],
			data: Zarafa.calendar.data.BusyStatus
		};

		return {
			xtype: 'panel',
			cls: 'k-busystatus-panel',
			layout: 'form',
			autoHeight: true,
			border: false,
			labelAlign: 'left',
			labelWidth: 85,
			items: [{
				xtype: 'combo',
				ref: '../../comboBusyStatus',
				name: 'busystatus',
				fieldLabel: _('Show as'),
				store: busyStore,
				mode: 'local',
				triggerAction: 'all',
				displayField: 'name',
				valueField: 'value',
				lazyInit: false,
				forceSelection: true,
				editable: false,
				listeners: {
					select: this.onFieldSelect,
					scope: this
				}
			}]
		};
	},

	/**
	 * Create the {@link Ext.Panel Panel} which contains all attachment selection fields
	 * @return {Object} configuration object for the panel containing the attachment fields
	 * @private
	 */
	createAttachmentsPanel: function()
	{
		return {
			xtype: 'zarafa.resizablecompositefield',
			hideLabel: true,
			anchor: '99%',
			cls: 'k-field-attachments',
			autoHeight: true,
			items: [{
				// FIXME: Remove after WA-4880 is implemented
				xtype: 'button',
				ref: '../occurrenceAttachmentsButton',
				text: _('Attachments') + ':',
				width: 100,
				handler: function() {
					Ext.MessageBox.show({
						title: _('Warning'),
						msg: _('Attachments cannot be modified for a single occurrence'),
						buttons: Ext.Msg.OK,
						cls: Ext.MessageBox.WARNING_CLS
					});
				}
			},{
				xtype: 'zarafa.attachmentbutton',
				ref: '../normalAttachmentsButton', // FIXME: Remove after WA-4880 is implemented
				plugins: [ 'zarafa.recordcomponentupdaterplugin' ],
				text: _('Attachments') + ':',
				autoHeight: true,
				width: 100
			},{
				xtype: 'spacer',
				width: 5
			},{
				xtype: 'zarafa.attachmentfield',
				plugins: [ 'zarafa.recordcomponentupdaterplugin' ],
				ref: '../attachField',
				flex: 1,
				hideLabel: true
			}]
		};
	},

	/**
	 * Create the {@link Ext.Panel Panel} containing the
	 * {@link Zarafa.common.ui.htmleditor.HtmlEditor HtmlEditor} form field.
	 * @return {Object} Configuration object containing the HtmlEditor
	 * @private
	 */
	createBodyPanel: function()
	{
		return {
			xtype: 'panel',
			cls: 'k-body-panel',
			layout: 'fit',
			border: false,
			flex: 1,
			autoHeight: false,
			items: [{
				xtype: 'zarafa.editorfield',
				ref: '../editorField',
				hideLabel: true,
				flex: 1,
				useHtml: true,
				readonly: false,
				listeners: {
					// Use the afterrender event to place the placeholder attribute
					afterrender: function(){
						this.editorField.getEditor().getEl().set({
							placeholder: _('Type your message here…')
						});
					},
					change: this.onBodyChange,
					scope: this
				}
			}]
		};
	},

	/**
	 * Generate the defaultValue start and end date for an appointment
	 * according to defaultPeriod and default Appointment timeslot.
	 * @return {Zarafa.core.DateRange} object which contains {@link Zarafa.core.DateRange DateRange} of an appointment.
	 */
	generateDefaultDate: function()
	{
		var settingsModel = container.getSettingsModel();
		var zoomLevel = settingsModel.get('zarafa/v1/contexts/calendar/default_zoom_level');
		var defaultPeriod = settingsModel.get('zarafa/v1/contexts/calendar/default_appointment_period');

		// The default should be the next (rounded up) default Appointment time slot.
		// e.g. When creating a new appointment on 11:55, with a defaultPeriod of 30 minutes,
		// will create an appointment starting on 12:00.
		var start = new Date().ceil(Date.MINUTE, zoomLevel);

		// The default should be the default appointment time after the start date.
		var end = start.add(Date.MINUTE, defaultPeriod);

		return new Zarafa.core.DateRange({ startDate: start, dueDate: end });
	},

	/**
	 * Event is fired when keypress event is fired on location field, ie. user changes location manually
	 * @param {HtmlElement} element The target of the event
	 * @param {Ext.EventObject} event The Ext.EventObject encapsulating the DOM event
	 */
	onLocationKeyPress: function(element, event)
	{
		this.hasUserSetLocation = true;
	},

	/**
	 * Enable/disable/hide/unhide all {@link Ext.Component Components} within the {@link Ext.Panel Panel}
	 * using the given {@link Zarafa.core.data.IPMRecord IPMRecord}.
	 * @param {Zarafa.core.data.IPMRecord} record The record to update the panel with
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 * @private
	 */
	updateUI: function(record, contentReset)
	{
		var layout = false;

		var settingsModel = container.getSettingsModel();

		if (contentReset === true || record.isModifiedSinceLastUpdate('meeting')) {
			switch (record.get('meeting')) {
				case Zarafa.core.mapi.MeetingStatus.NONMEETING:
				/* falls through */
				default:
					this.dialog.closeOnSave = true;
					this.dialog.closeOnSend = false;
					this.recipientPanel.setVisible(false);
					break;
				case Zarafa.core.mapi.MeetingStatus.MEETING:
					this.dialog.closeOnSave = false;
					this.dialog.closeOnSend = true;
					this.recipientPanel.setVisible(true);
					break;
				case Zarafa.core.mapi.MeetingStatus.MEETING_RECEIVED:
				case Zarafa.core.mapi.MeetingStatus.MEETING_CANCELED:
				case Zarafa.core.mapi.MeetingStatus.MEETING_RECEIVED_AND_CANCELED:
					// here we ensure that response status, is neither none nor organizer
					if(record.get('responsestatus') != Zarafa.core.mapi.ResponseStatus.RESPONSE_NONE || record.get('responsestatus') != Zarafa.core.mapi.ResponseStatus.RESPONSE_ORGANIZED) {
						this.meetingOrganizerPanel.setVisible(true);
						this.recipientPanel.setVisible(false);
					}
					break;
			}

			layout = true;
		}

		if (contentReset === true || record.isModifiedSinceLastUpdate('alldayevent')) {
			if (record.get('alldayevent')) {
				this.datetimePeriod.setEnabledTimeSelection(false);
				// For allday events, we only allow selections of entire days
				this.datetimePeriod.defaultPeriod = 1;
				this.datetimePeriod.defaultPeriodType = Date.DAY;
			} else {
				this.datetimePeriod.setEnabledTimeSelection(true);
				// For normal events, we have to configure the normal period options again
				this.datetimePeriod.defaultPeriod = settingsModel.get('zarafa/v1/contexts/calendar/default_appointment_period');
				this.datetimePeriod.defaultPeriodType = Date.MINUTE;
			}
		}

		if (contentReset === true || record.isModifiedSinceLastUpdate('reminder')) {
			if (record.get('reminder')) {
				this.comboReminder.enable();
			} else {
				this.comboReminder.disable();
			}
		}

		if (contentReset === true || record.isModifiedSinceLastUpdate('recurring')) {
			if (record.get('recurring') && Ext.isEmpty(record.get('basedate'))) {
				this.datePanel.setVisible(false);
				this.recurrencePanel.setVisible(true);
			} else {
				this.datePanel.setVisible(true);
				this.recurrencePanel.setVisible(false);
				this.recurrencePatternField.setValue('');
			}

			layout = true;
		}

		// FIXME: Remove after WA-4880 is implemented
		if (contentReset === true) {
			if (record.isRecurringOccurrence()) {
				this.occurrenceAttachmentsButton.setVisible(true);
				this.normalAttachmentsButton.setVisible(false);

				this.attachField.setEditable(false);
			} else {
				this.occurrenceAttachmentsButton.setVisible(false);
				this.normalAttachmentsButton.setVisible(true);

				this.attachField.setEditable(true);
			}

			layout = true;
		}

		if (contentReset === true || record.isModifiedSinceLastUpdate('reminder')) {
			if (record.get('alldayevent')) {
				this.comboReminder.setValue(settingsModel.get('zarafa/v1/contexts/calendar/default_allday_reminder_time'));
				this.comboBusyStatus.setValue(settingsModel.get('zarafa/v1/contexts/calendar/default_allday_busy_status'));
			} else {
				this.comboReminder.setValue(settingsModel.get('zarafa/v1/contexts/calendar/default_reminder_time'));
				this.comboBusyStatus.setValue(Zarafa.core.mapi.BusyStatus.BUSY);
			}
		}

		if (layout) {
			this.doLayout();
		}
	},

	/**
	 * Update the {@link Ext.Panel Panel} with the given {@link Zarafa.core.data.IPMRecord IPMRecord}
	 * @param {Zarafa.core.data.IPMRecord} record The record to 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;
		var meetingStatus = record.get('meeting');
		var isOrganizer = record.get('responsestatus') === Zarafa.core.mapi.ResponseStatus.RESPONSE_ORGANIZED;
		var isAttendee = Ext.isDefined(meetingStatus) && meetingStatus !== Zarafa.core.mapi.MeetingStatus.NONMEETING && !isOrganizer;

		this.editorField.setAllowEdit(!isAttendee);
		this.editorField.setReadOnly(isAttendee);

		this.updateUI(record, contentReset);
		this.getForm().loadRecord(record);

		// Freeze the whole form for read-only items. This runs after updateUI()
		// and loadRecord() (which re-enable fields for editable records) so it
		// only ever tightens the UI, and before the sub-message early-return
		// below so embedded appointments are frozen too.
		this.applyReadOnlyState(record);

		var startDate = record.get('startdate');
		var startDateUpdate = false;
		if (Ext.isDate(startDate)) {
			startDate = startDate.clone();
			startDateUpdate = contentReset || record.isModifiedSinceLastUpdate('startdate');
		}
		var dueDate = record.get('duedate');
		var dueDateUpdate = false;
		if (Ext.isDate(dueDate)) {
			dueDate = dueDate.clone();
			dueDateUpdate = contentReset || record.isModifiedSinceLastUpdate('duedate');
		}

		// For all day events we store the due date as 00:00 on the day after
		// it ends. For the UI, this means we have to subtract 1 day to get
		// the date on which the appointment actually ends for the user.
		if (record.get('alldayevent')) {
			startDate.clearTime();

			// Set time to 12:00 when using Date.add(Date.DAY)
			// to prevent problems with DST switches at 00:00
			// (like in Brasil).
			dueDate.setHours(12);
			dueDate = dueDate.add(Date.DAY, -1).clearTime();
		}

		if (startDateUpdate || dueDateUpdate) {
			this.datetimePeriod.getValue().set(startDate, dueDate);
		}

		if (contentReset && record.isOpened()) {
			this.editorField.setValue(record.getBody(this.editorField.isHtmlEditor()));
		}

		if (contentReset && this.comboCreateIn.isVisible()) {
			if (record.isSubMessage()) {
				this.createInPanel.setVisible(false);
				return;
			}
            const folderToSelect = record.get('parent_entryid');
            const folder = container.getHierarchyStore().getFolder(folderToSelect);
            const hasCreateRight = folder.hasCreateRights();
            // hide the createInPanel when calendar folder does not have
            // create rights or only one calendar folder.
			var store = this.comboCreateIn.getStore();
            this.createInPanel.setVisible(store.getRange().length > 1 && hasCreateRight);

		    if(this.comboCreateIn.isVisible() && hasCreateRight) {
                this.comboCreateIn.setValue(folderToSelect);
                const folderColor = this.getFolderColor(folderToSelect);
                this.comboCreateIn.el.setStyle('background-image', 'url(\'' + Zarafa.calendar.ui.IconCache.getCalendarSvgIcon(folderColor) + '\')');
            }
		}

		this.updateExtraInfoPanel();

		if (contentReset === true || record.isModifiedSinceLastUpdate('recurring_pattern')) {
			this.recurrencePatternField.setValue(record.get('recurring_pattern'));
		}

		/*
		 * When any property with reminder is changed then we need
		 * to update reminder_time and flagdueby as well, When
		 * 1) flagdueby is changed
		 * 2) reminder minutes is changed
		 * 3) startdate and reminder time not same
		 */
		if (record.get('reminder') === true) {
			if(!record.get('flagdueby') ||  record.isModifiedSinceLastUpdate('reminder_minutes') || record.get('reminder_time') !== record.get('startdate')) {
				var reminderDate;

				if (record.isRecurring() || record.isRecurringOccurrence()) {
					reminderDate = record.get('startdate_recurring');
				}

				if (!reminderDate) {
					reminderDate = record.get('startdate');
				}

				if (Ext.isDate(reminderDate)) {
					record.set('reminder_time', reminderDate.clone());
					record.set('flagdueby', reminderDate.add(Date.MINUTE, -record.get('reminder_minutes')));
				}
			}
		}

		if(record.isSubStoreModifiedSincelastUpdate('recipients')) {
			var recipients = record.updateSubStoreModifications.recipients;

			// Check if changes (Added/Removed/Updated) are room resource
			if(recipients && recipients.changes) {
				var changedRecipients = recipients.changes;
				var isResouceChanged = false;

				for(var i = 0; i < changedRecipients.length; i++) {
					// display_type_ex can carry additional flag bits (e.g.
					// DTE_FLAG_ACL_CAPABLE) alongside the base type, so the flags
					// must be stripped via DTE_LOCAL() before comparing against DT_ROOM.
					if(Zarafa.core.mapi.DisplayTypeEx.DTE_LOCAL(changedRecipients[i].get('display_type_ex')) === Zarafa.core.mapi.DisplayTypeEx.DT_ROOM) {
						isResouceChanged = true;
						break;
					}
				}

				if(isResouceChanged) {
					this.updateLocation();
				}
			}
		}
	},

	/**
	 * Make all editable controls in this tab non-editable when the given record
	 * is read-only (see {@link Zarafa.core.data.MAPIRecord#isReadOnlyRecord}).
	 * This is applied on top of the normal {@link #update} logic, so it only
	 * ever tightens the UI and never re-enables a control. It is a no-op for
	 * records the user is allowed to modify.
	 * @param {Zarafa.core.data.IPMRecord} record The record to check
	 * @private
	 */
	applyReadOnlyState: function(record)
	{
		// Only decide once the record is opened: a not-yet-opened record may
		// still carry the default (read) access instead of its authoritative
		// PR_ACCESS, which would otherwise wrongly freeze dialogs whose record
		// is built client-side (e.g. accepting a proposed meeting time).
		if (!record.isOpened() || !record.isReadOnlyRecord()) {
			return;
		}

		var form = this.getForm();
		Ext.each(['subject', 'location', 'alldayevent', 'reminder'], function(name) {
			var field = form.findField(name);
			if (field) {
				field.setDisabled(true);
			}
		});

		this.datetimePeriod.disable();
		this.comboReminder.setDisabled(true);
		this.comboBusyStatus.setDisabled(true);
		this.comboCreateIn.setDisabled(true);

		if (this.recipientField) {
			this.recipientField.setDisabled(true);
		}
		if (this.toButton) {
			this.toButton.setDisabled(true);
		}

		this.normalAttachmentsButton.setDisabled(true);
		this.attachField.setEditable(false);

		// Keep the body visible (so it can be read/copied) but non-editable.
		// setAllowEdit(false) also removes the double-click-to-edit escape hatch.
		this.editorField.setAllowEdit(false);
		this.editorField.setReadOnly(true);
	},

	/**
	 * Function updates the location in the {@link Zarafa.core.data.IPMRecord record}
	 * It uses {@link Zarafa.core.mapi.DisplayTypeEx.DT_ROOM room} resources to
	 * generate location from the recipient table.
	 *
	 * @private
	 */
	updateLocation: function()
	{
		var locations = [];
		var recipientStore = this.record.getSubStore('recipients');

		// Create location suggestion string using room resources of the recipient store
		recipientStore.each(function(recipient) {
			// display_type_ex can carry additional flag bits (e.g. DTE_FLAG_ACL_CAPABLE)
			// alongside the base type, so the flags must be stripped via DTE_LOCAL()
			// before comparing against DT_ROOM.
			if (Zarafa.core.mapi.DisplayTypeEx.DTE_LOCAL(recipient.get('display_type_ex')) === Zarafa.core.mapi.DisplayTypeEx.DT_ROOM){
				var name = recipient.get('display_name');

				if (!Ext.isEmpty(name)) {
					locations.push(name);
				}
			}
		});

		var locationSuggestion = locations.join('; ');
		var meetingLocation = this.record.get('location');

		// If suggested location and current location is same then no need to set/suggest.
		if(meetingLocation == locationSuggestion) {
			return;
		}

		if(this.hasUserSetLocation === false || Ext.isEmpty(meetingLocation) || meetingLocation.toUpperCase() == locationSuggestion.toUpperCase()) {
			// If user haven't changed location by himself or record's location is empty or
			// it is already suggested location in different case(upper/lower) then set it.
			this.doSetLocation(locationSuggestion);
		} else {
			//If suggested location seems different then ask user whether want to use suggested location or not.
			Ext.MessageBox.show({
				title: _('Update location'),
				msg: String.format(_('Do you want to update the location "{0}" with the new location "{1}"?'), Ext.util.Format.htmlEncode(meetingLocation), Ext.util.Format.htmlEncode(locationSuggestion)),
				buttons: Ext.Msg.YESNO,
				fn: this.setLocation,
				locationSuggestion: locationSuggestion,
				scope: this
			});
		}
	},

	/**
	 * Function sets location that is suggested to the user if user confirms.
	 *
	 * @param {String} buttonId The ID of the button pressed, here 'yes' or 'no'
	 * @param {String} text String Value of the input field if either prompt
	 * @param {Object} opt The config object passed to the messagebox.
	 *
	 * @private
	 */
	setLocation: function(button, text, opt)
	{
		if(button == "yes") {
			this.doSetLocation(opt.locationSuggestion);
		}
	},

	/**
	 * Function sets location which is given as suggestion is passed to the function.
	 * Function also clears {@link hasUserSetLocation} flag as we have overwritten
	 * user changed location with our generated location string so from next time this
	 * should not be considered as user change
	 * @param {String} locationString String value of the location.
	 *
	 * @private
	 */
	doSetLocation: function(locationString)
	{
		this.record.set('location', locationString);

		// We have automatically changed location, so this flag needs to be cleared.
		this.hasUserSetLocation = false;
	},

	/**
	 * Update the {@link Zarafa.core.data.IPMRecord IPMRecord} with the data from the {@link Ext.Panel Panel}.
	 * @param {Zarafa.core.data.IPMRecord} record The record which has to be updated
	 */
	updateRecord: function(record)
	{
		record.beginEdit();
		this.getForm().updateRecord(record);
		this.updateStartDueDate(record, this.datetimePeriod.getValue());

		/*
		 * When any property with reminder is changed then we need
		 * to update reminder_time and flagdueby as well, When
		 * 1) flagdueby is changed
		 * 2) reminder minutes is changed
		 * 3) startdate is changed
		 */
		if (record.get('reminder') === true) {
			if(!record.get('flagdueby') || record.isModifiedSinceLastUpdate('reminder_minutes') || record.isModifiedSinceLastUpdate('startdate')) {
				var reminderDate;

				if (record.isRecurring() || record.isRecurringOccurrence()) {
					reminderDate = record.get('startdate_recurring');
				}

				if (!reminderDate) {
					reminderDate = record.get('startdate');
				}

				if (Ext.isDate(reminderDate)) {
					record.set('reminder_time', reminderDate.clone());
					record.set('flagdueby', reminderDate.add(Date.MINUTE, -record.get('reminder_minutes')));
				}
			}
		}

		// Remove auxiliary_flags if unset copy meeting is going to send.
		if (record.hasMessageAction('send') && record.isCopied()) {
			record.set('auxiliary_flags', 0);
		}

		this.onBodyChange(this.editorField.getEditor(), this.editorField.getValue());

		record.endEdit();
	},

	/**
	 * Event handler which is fired when a field has been changed.
	 * This will update the corresponding field inside the {@link Zarafa.core.data.IPMRecord record}.
	 * @param {Ext.form.Field} field The field which has changed
	 * @param {Mixed} newValue The new value for the field
	 * @param {Mixed} oldValue The original value for the field
	 * @private
	 */
	onFieldChange: function(field, newValue, oldValue)
	{
		this.record.set(field.getName(), newValue);
	},

	/**
	 * Event handler which is triggered when one of the Input fields
	 * has been changed by the user. It will validate the new value,
	 * and if correct, will apply it to the {@link Zarafa.core.data.IPMRecord record}.
	 * @param {Ext.form.Field} field The {@link Ext.form.Field field} which was changed.
	 * @param {Mixed} newValue The new value
	 * @param {Mixed} oldValue The old value
	 * @private
	 */
	onBodyChange: function(field, newValue, oldValue)
	{
		var record = this.record;
		var isHtmlEditor = field.isXType && field.isXType('zarafa.htmleditor')

		record.beginEdit();
		record.setBody(newValue, isHtmlEditor);
		record.endEdit();
	},

	/**
	 * Event handler which is fired when a combobox selection has changed.
	 * This will update the corresponding field inside the {@link Zarafa.core.data.IPMRecord record}
	 * @param {Ext.form.ComboBox} combo The combobox which was selected
	 * @param {Ext.data.Record} record The selected record
	 * @param {Number} index The index of the selected record
	 * @private
	 */
	onFieldSelect: function(combo, record, index)
	{
		this.record.set(combo.getName(), record.get(combo.valueField));
	},

	/**
	 * Event handler which is fired when a combobox selection has changed.
	 * This will update the corresponding field inside the {@link Zarafa.core.data.IPMRecord record}
	 * @param {Ext.form.ComboBox} combo The combobox which was selected
	 * @param {Ext.data.Record} record The selected calendar-folder record
	 * @param {Number} index The index of the selected record
	 * @private
	 */
	onCreateInSelect: function(combo, record, index)
	{
		if (this.record.phantom) {
			this.record.beginEdit();
			this.record.set('parent_entryid', record.get('entryid'));
			this.record.set('store_entryid', record.get('store_entryid'));
			this.record.endEdit();
			this.resetOrganizer();
		} else {
			this.record.moveTo(record);
		}

		combo.el.setStyle('background-image', 'url(\'' + Zarafa.calendar.ui.IconCache.getCalendarSvgIcon(record.get('iconColor')) + '\')');
	},

	/**
	 * Event handler which is fired when combobox-list expanded or collapsed.
	 * This will put cursor at the beginning of combobox content to avoid
	 * text overlapping calendar-icon.
	 * @param {Ext.form.ComboBox} combo The combobox which was selected
	 * @private
	 */
	setCursorPosition: function(combo)
	{
		combo.el.dom.setSelectionRange(0, 0);
	},

	/**
	 * Helper function to change the organizer in case if the calendar gets changed
	 * from own to shared or vice versa.
	 */
	resetOrganizer: function()
	{
		Zarafa.core.data.MessageRecordPhantomHandler(this.record);

		if(this.record.userIsStoreOwner()) {
			// While folder gets changed from shared to own then there might be some
			// delegate properties available, unset such unnecessary properties.
			this.record.beginEdit();
			this.record.set('sent_representing_name', "");
			this.record.set('sent_representing_email_address', "");
			this.record.set('sent_representing_address_type', "");
			this.record.set('sent_representing_entryid', "");
			this.record.endEdit();
		}

		// Update the recipient-sub-store to change organizer based on new properties
		this.record.updateMeetingRecipients();
	},

	/**
	 * Helper function to obtain color for given folder entryid.
	 * @param {String} entryid Entry id of a folder
	 * @return {String} The color as defined in {@link Zarafa.calendar.ui.ColorSchemes color-scheme}
	 * for given folder
	 */
	getFolderColor: function(entryid)
	{
		var calendarContext = container.getContextByName('calendar');
		var scheme = calendarContext.getModel().getColorScheme(entryid);

		return scheme.base;
	},

	/**
	 * Helper function to return array containing options necessary to create
	 * store which feeds the data to create-in-combo along with entryid of respective
	 * calendar folder.
	 * This would also provide an extra field value on which store must be sorted.
	 * The sorting order will be as under:
	 * 		1) activePersonalCalendars
	 * 		2) activeSharedCalendars
	 * 		3) activePublicCalendars
	 * 		4) inactivePersonalCalendars
	 * 		5) inactiveSharedCalendars
	 * 		6) inactivePublicCalendars
	 * @return {Object} Config set which has all calendar folders.
	 */
	getSortedCreateInData: function()
	{
		var calendarFolders = container.getHierarchyStore().getByContainerClass('IPF.Appointment');
		var context = container.getContextByName('calendar');
		var groupings = context.getModel().getGroupings();
		var createInData = [];

		calendarFolders.forEach(function(dataItem) {
			if (!(dataItem.get('rights') & Zarafa.core.mapi.Rights.RIGHTS_CREATE)) {
				return;
			}

			// Workaround for KC-1270, backend should not return
			// folders with inaccessible parent folder.
			if (!dataItem.getParentFolder()) {
				return;
			}

			var displayString = dataItem.get('display_name');
			var mapiStore = dataItem.getParentFolder().getMAPIStore();

			if (mapiStore.isSharedStore()) {
				displayString += ' - ' + mapiStore.get('mailbox_owner_name');
			}

			var isActiveFolder = this.isActiveFolder(groupings, dataItem);
			var comboRecord = {
				'entryid': dataItem.get('entryid'),
				'store_entryid': dataItem.get('store_entryid'),
				'displayString': displayString,
				'iconColor': this.getFolderColor(dataItem.get('entryid'))
			};

			if (dataItem.getMAPIStore().isPublicStore()) {
				comboRecord['sortOrder'] = isActiveFolder ? '3' : '6';
			} else if (mapiStore.isSharedStore()) {
				comboRecord['sortOrder'] = isActiveFolder ? '2' : '5';
			} else {
				comboRecord['sortOrder'] = isActiveFolder ? '1' : '4';
			}

			createInData.push(comboRecord);
		}, this);

		return createInData;
	},

	/**
	 * Helps to find if the given calendar folder is active/checked or not.
	 * @param {Object} groupings Current state of {@link Zarafa.core.MultiFolderContextModel#groupings}.
	 * @param {Zarafa.core.data.IPFRecord} dataItem Calendar folder to check.
	 * @return {Boolean} true if folder is active, false otherwise.
	 * @private
	 */
	isActiveFolder: function(groupings, dataItem)
	{
		var isActive = false;
		for( var key in groupings ) {
			if (dataItem.get('entryid') === groupings[key].active) {
				isActive = true;
			}
		}

		return isActive;
	},

	/**
	 * Event handler which is fired before a combobox-list gets expanded.
	 * This will update combobox-store according to the current calendar folders available.
	 * @param {Ext.form.ComboBox} combo The combobox which was selected
	 * @private
	 */
	onCreateInBeforeExpand: function(combo)
	{
		const createInStore = combo.store;
		const readerData = createInStore.reader.readRecords(this.getSortedCreateInData());
		createInStore.removeAll();
		createInStore.add(readerData.records);
		createInStore.applySort();
		combo.view.bindStore(createInStore);

		// Add tool-tip
		this.addToolTip(combo);
	},

	/**
	 * Helper function which add tool-tip only if text-overflowed.
	 * @param {Ext.form.ComboBox} combo The combobox which was selected
	 * @private
	 */
	addToolTip: function(combo){
		var children = combo.innerList.dom.children;
		for (var i = 0; i < children.length; i++) {
			// check if text gets overflowed or not
			if (children[i].offsetWidth < children[i].scrollWidth) {
				children[i].setAttribute('ext:qtip', children[i].textContent);
			}
		}
	},

	/**
	 * Event handler which is fired when the {@link Zarafa.common.ui.DateRangeField} has been changed.
	 * This will update the start and due date inside the {@link #record} accordingly.
	 * @param {Ext.form.Field} field The field which has changed
	 * @param {Mixed} newValue The new value for the field
	 * @param {Mixed} oldValue The original value for the field
	 * @private
	 */
	onDateRangeFieldChange: function(field, newRange, oldRange)
	{
		this.updateStartDueDate(this.record, newRange);
	},

	/**
	 * Update the 'startdate' and 'duedate' in the given record from
	 * the given daterange. When the appointment is an allday event, then
	 * the times are always set to midnight. However when selecting
	 * the duedate the user selects on which day the appointment
	 * ends, so in reality the appointment ends on 00:00 hours on
	 * the following day.
	 * @param {Zarafa.core.data.MAPIRecord} record the Record to update
	 * @param {Zarafa.core.DateRange} daterange the Daterange to apply
	 * @private
	 */
	updateStartDueDate: function(record, daterange)
	{
		var startDate = daterange.getStartDate().clone();
		var dueDate = daterange.getDueDate().clone();

		if (record.get('alldayevent') === true) {
			startDate = startDate.clearTime();
			// Set time to 12:00 when using Date.add(Date.DAY)
			// to prevent problems with DST switches at 00:00
			// (like in Brasil).
			dueDate.setHours(12);
			dueDate = dueDate.add(Date.DAY, 1).clearTime();
		}

		record.beginEdit();
		record.set('startdate', startDate);
		record.set('duedate', dueDate);
		record.set('commonstart', startDate);
		record.set('commonend', dueDate);
		record.set('duration', (dueDate - startDate) / (60 * 1000));
		record.endEdit();
	},

	/**
	 * A function called when the checked value changes for the
	 * reminder checkbox.
	 * @param {Ext.form.Checkbox} checkbox The Checkbox being toggled.
	 * @param {Boolean} checked The new checked state of the checkbox.
	 * @private
	 */
	onToggleReminder: function(checkbox, checked)
	{
		this.record.set('reminder', checked);
	},

	/**
	 * A function called when the checked value changes for the
	 * all day event checkbox.
	 * @param {Ext.form.Checkbox} checkbox The Checkbox being toggled.
	 * @param {Boolean} checked The new checked state of the checkbox.
	 * @private
	 */
	onToggleAllDay: function(checkbox, checked)
	{
		if (this.record.get('alldayevent') !== checked) {
			this.record.beginEdit();
			this.record.set('alldayevent', checked);
			var settingsModel = container.getSettingsModel();
			if (checked) {
				this.updateStartDueDate(this.record, this.datetimePeriod.getValue());

				this.record.set('reminder_minutes', settingsModel.get('zarafa/v1/contexts/calendar/default_allday_reminder_time'));
				this.record.set('busystatus', settingsModel.get('zarafa/v1/contexts/calendar/default_allday_busy_status'));
			} else {
				var zoomLevel = settingsModel.get('zarafa/v1/contexts/calendar/default_zoom_level');
				var defaultPeriod = settingsModel.get('zarafa/v1/contexts/calendar/default_appointment_period');

				var startDate = new Date();
				if(this.record.get('startdate')) {
					// use existing date if it is set
					startDate = this.record.get('startdate').clearTime(true);
				}

				startDate = startDate.ceil(Date.MINUTE, zoomLevel);
				var dueDate = startDate.add(Date.MINUTE, defaultPeriod);

				this.record.set('reminder_minutes', settingsModel.get('zarafa/v1/contexts/calendar/default_reminder_time'));
				this.record.set('busystatus', Zarafa.core.mapi.BusyStatus.BUSY);
				this.record.set('startdate', startDate);
				this.record.set('duedate', dueDate);
				this.record.set('commonstart', startDate);
				this.record.set('commonend', dueDate);
				this.record.set('duration', (dueDate - startDate) / (60 * 1000));
			}
			this.record.endEdit();
		}
	},

	/**
	 * Function will open AddressBook dialog to add attendees
	 * @private
	 */
	showRecipientContent: function()
	{
		Zarafa.calendar.Actions.openRecipientSelectionContent(this.record, { defaultRecipientType: Zarafa.core.mapi.RecipientType.MAPI_TO });
	},

	/**
	 * Function will update the {@link #extraInfoPanel} with extra information that should be shown
	 * for the meeting item.
	 * @private
	 */
	updateExtraInfoPanel: function()
	{
		// clear the previous contents
		var el = this.extraInfoPanel.getEl();
		if(Ext.isDefined(el.dom)) {
			el.dom.innerHTML = '';
		}

		var visible = this.setOldAppointmentInfo(el);

		if(this.record.isRecurringOccurrence()){
			visible = (this.setRecurrencePatternInfo(el) === true) ? true : visible;
		}

		if(this.record.isMeeting()) {
			if(!this.record.phantom) {
				if (this.record.isMeetingOrganized()) {
					// set information for organizer
					if (!this.record.isMeetingSent()) {
						visible = (this.setMeetingUnsentInfo(el) === true) ? true : visible;
					} else {
						visible = (this.setMeetingResponseInfo(el) === true) ? true : visible;
					}
				} else {
					var responseStatus = this.record.get('responsestatus');

					if(responseStatus !== Zarafa.core.mapi.ResponseStatus.RESPONSE_NONE) {
						this.setOrganizerInfo();
						visible = (this.setMeetingOverwrittenInfo(el) === true) ? true : visible;
					}

					if (this.record.isMeetingCanceled()) {
						// set information for canceled meeting in attendee's calendar
						visible = (this.setMeetingCanceledInfo(el) === true) ? true : visible;
					} else if (this.record.isMeetingResponseRequired()) {
						// set information for attendees
						visible = (this.setReplyTimeInfo(responseStatus, el) === true) ? true : visible;
					}
				}
			}
		}

		this.extraInfoPanel.setVisible(visible);
		this.doLayout();
	},

	/**
	 * Function will display message regarding response's from attendee(s) in the dialog
	 * thus will update the status of attendee(s) to meeting organizer
	 * @param {HtmlElement} HTML element
	 * @return Boolean true if new component is added in {@link #extraInfoPanel} else false.
	 * @private
	 */
	setMeetingResponseInfo: function(el)
	{
		// check for meeting items which are not sent yet
		if(this.record.isMeetingSent()) {
			return false;
		}

		var accepted = 0, tentative = 0, declined = 0, numProposingAttendees = 0;
		var recipientStore = this.record.getRecipientStore();

		recipientStore.each(
			function (recipient) {
				switch(recipient.get('recipient_trackstatus'))
				{
					case Zarafa.core.mapi.RecipientTrackStatus.RECIPIENT_TRACKSTATUS_TENTATIVE: // tentative
						tentative++;
						break;
					case Zarafa.core.mapi.RecipientTrackStatus.RECIPIENT_TRACKSTATUS_ACCEPTED: //accepted
						accepted++;
						break;
					case Zarafa.core.mapi.RecipientTrackStatus.RECIPIENT_TRACKSTATUS_DECLINED: //decline
						declined++;
						break;
				}

				if(recipient.get('proposednewtime')) {
					numProposingAttendees++;
				}
			}, this);

		if(accepted !== 0 || tentative !== 0 || declined !== 0) {
			// set response string
			var trackString = String.format(this.trackingInfoString, accepted, tentative, declined);
			el.createChild({tag: 'div', html: trackString});

			if(this.record.get('counter_proposal') && numProposingAttendees !== 0) {
				var proposeString = String.format(this.proposedTimeInfoString, numProposingAttendees);
				el.createChild({tag: 'div', html: proposeString});
			}
		} else {
			// set no response string
			el.createChild({tag: 'div', html: this.noResponseReceivedString });
		}

		return true;
	},

	/**
	 * Function will display message regarding request accepted time for an attendee
	 * @param {Zarafa.core.mapi.ResponseStatus} responseStatus for the meeting
	 * @param {HtmlElement} HTML element
	 * @return Boolean true if new component is added in {@link #extraInfoPanel} else false.
	 * @private
	 */
	setReplyTimeInfo: function(responseStatus,el)
	{
		var responseString = '';
		var replyTime = this.record.get('reply_time');
		var replyName = this.record.get('reply_name');
		var dueDate = this.record.get('duedate');

		if(responseStatus !== Zarafa.core.mapi.ResponseStatus.RESPONSE_NOT_RESPONDED) {
			// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
			replyTime = Ext.isDate(replyTime) ? replyTime.formatDefaultTime(_('jS F Y {0}')) : _('None');

			if(this.record.get('counter_proposal')) {
				if(!Ext.isEmpty(replyName)) {
					responseString = String.format(this.proposeNewTimeDelegateInfoString, replyName, replyTime);
				} else {
					responseString = String.format(this.proposeNewTimeInfoString, replyTime);
				}
			} else {
				switch(responseStatus) {
					case Zarafa.core.mapi.ResponseStatus.RESPONSE_ACCEPTED:
						if(!Ext.isEmpty(replyName)) {
							responseString = String.format(this.acceptedDelegateInfoString, replyName, replyTime);
						} else {
							responseString = String.format(this.acceptedInfoString, replyTime);
						}
						break;
					case Zarafa.core.mapi.ResponseStatus.RESPONSE_TENTATIVE:
						if(!Ext.isEmpty(replyName)) {
							responseString = String.format(this.tentativeDelegateInfoString, replyName, replyTime);
						} else {
							responseString = String.format(this.tentativeInfoString, replyTime);
						}
						break;
				}
			}
		} else {
			if(Ext.isDate(dueDate) && dueDate.getTime() >= (new Date().getTime())) {
				// Meeting is in future, but not replied yet
				responseString = this.responseRequiredString;
			}
		}

		if(!Ext.isEmpty(responseString)) {
			el.createChild({tag: 'div', html: responseString});
			return true;
		}

		return false;
	},

	/**
	 * Function will set information string to show that appointment occurs in past.
	 * @param {HtmlElement} HTML element
	 * @return Boolean true if new component is added in {@link #extraInfoPanel} else false.
	 * @private
	 */
	setOldAppointmentInfo: function(el)
	{
		if(this.record.isAppointmentInPast()) {
			el.createChild({tag: 'div', html: this.elapsedTimeInfoString});

			return true;
		}

		return false;
	},

	/**
	 * Function will add information regarding organizer of the meeting.
	 * @private
	 */
	setOrganizerInfo: function()
	{
		this.meetingOrganizerField.setValue(this.record.getSenderString());
	},

	/**
	 * Function will set information string to show that meeting is canceled in the attendee's calendar.
	 * @param {HtmlElement} HTML element
	 * @return Boolean true if new component is added in {@link #extraInfoPanel} else false.
	 * @private
	 */
	setMeetingCanceledInfo: function(el)
	{
		el.createChild({tag: 'div', html: this.meetingCanceledString});
		return true;
	},

	/**
	 * Function will set information string to show that meeting invites have not yet been sent.
	 * @param {HtmlElement} HTML element
	 * @return Boolean true if new component is added in {@link #extraInfoPanel} else false.
	 * @private
	 */
	setMeetingUnsentInfo: function(el)
	{
		el.createChild({tag: 'div', html: this.meetingUnsentString});
		return true;
	},

	/**
	 * Function will set the information string to show that changes made by attendee will
	 * be overwritten when the meeting request is updated by the organizer.
	 * @param {HtmlElement} HTML element
	 * @return Boolean true if new component is added in {@link #extraInfoPanel} else false.
	 * @private
	 */
	setMeetingOverwrittenInfo: function(el)
	{
		el.createChild({tag: 'div', html: this.meetingOverwrittenString});
		return true;
	},

	/**
	 * Function will set information string to show the recurring pattern the occurrence is part of.
	 * @param {HtmlElement} HTML element
	 * @return Boolean true if new component is added in {@link #extraInfoPanel} else false.
	 * @private
	 */
	setRecurrencePatternInfo: function(el)
	{
		el.createChild({tag: 'div', html: Ext.util.Format.htmlEncode(this.record.get('recurring_pattern'))});
		return true;
	}
});

Ext.reg('zarafa.appointmenttab', Zarafa.calendar.dialogs.AppointmentTab);
Ext.namespace('Zarafa.calendar.dialogs');

/**
 * @class Zarafa.calendar.dialogs.CalendarOptionsPanel
 * @extends Ext.Panel
 * @xtype zarafa.calendaroptionspanel
 */
Zarafa.calendar.dialogs.CalendarOptionsPanel = Ext.extend(Ext.Panel, {
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

		config = Ext.applyIf(config, {
			xtype: 'zarafa.calendaroptionspanel',
			layout: {
				type: 'vbox',
				align: 'stretch'
			},
			border: false,
			defaults: {
				bodyStyle: 'padding-top: 5px; padding-left: 6px; padding-right: 5px; background-color: inherit;',
				border: false
			},
			items: [{
				xtype: 'zarafa.recordpropertiespanel',
				flex: 1
			}]
		});

		Zarafa.calendar.dialogs.CalendarOptionsPanel.superclass.constructor.call(this, config);
	}
});

Ext.reg('zarafa.calendaroptionspanel', Zarafa.calendar.dialogs.CalendarOptionsPanel);
Ext.namespace('Zarafa.calendar.dialogs');

/**
 * @class Zarafa.calendar.dialogs.CounterProposalGrid
 * @extends Ext.grid.GridPanel
 * @xtype zarafa.counterproposalgrid
 *
 * This view is used to list all proposed time from the attendees in appointments dialog
 */
Zarafa.calendar.dialogs.CounterProposalGrid = Ext.extend(Ext.grid.GridPanel, {
	/**
	 * @constructor
	 * @param {Object} config Configuration structure
	 */
	constructor: function(config)
	{
		config = config || {};

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

		Ext.applyIf(config, {
			xtype: 'zarafa.counterproposalgrid',
			store: new Ext.data.JsonStore({
				root: 'proposals',
				fields: ['startdate', 'duedate', 'duration', 'display_name']
			}),
			autoHeight: true,
			maxHeight: 300,
			viewConfig: {
				forceFit: true
			},
			columns: this.initColumnModel(),
			selModel: this.initSelectionModel()
		});

		Zarafa.calendar.dialogs.CounterProposalGrid.superclass.constructor.call(this, config);
	},

	/**
	 * creates and returns a column model object, used in {@link Ext.grid.GridPanel.colModel colModel} config
	 * @return {Ext.grid.ColumnModel} column model object
	 * @private
	 */
	initColumnModel: function()
	{
		return [{
			dataIndex: 'startdate',
			header: _('Proposed Date and Time'),
			sortable: true,
			renderer: Zarafa.common.ui.grid.Renderers.datetime
		},{
			dataIndex: 'duration',
			header: _('Duration'),
			sortable: true,
			renderer: Zarafa.common.ui.grid.Renderers.duration
		},{
			dataIndex: 'display_name',
			header: _('Proposed By'),
			sortable: true,
			renderer: Zarafa.common.ui.grid.Renderers.text
		}
		//@TODO:this column should show whether the proposed time is conflicting with any other organizer appointment
		/*,{
			dataIndex: 'conflict',
			header: _('Attendees'),
			sortable: true
		}*/
		];
	},

	/**
	 * creates and returns a selection model object, used in {@link Ext.grid.GridPanel.selModel selModel} config
	 * @return {Ext.grid.RowSelectionModel} selection model object
	 * @private
	 */
	initSelectionModel: function()
	{
		return new Ext.grid.RowSelectionModel({
			singleSelect: true
		});
	},

	/**
	 * initialize events for the grid panel
	 * @private
	 */
	initEvents: function()
	{
		Zarafa.calendar.dialogs.CounterProposalGrid.superclass.initEvents.call(this);

		this.mon(this.getSelectionModel(), 'selectionchange', this.onSelectionChange, this);
		this.on('viewready', this.onViewReady, this);
	},

	/**
	 * Update the {@link Ext.grid.GridPanel Panel} with the given {@link Zarafa.core.data.IPMRecord IPMRecord}
	 * Also it will build the proposed time information data of attendees
	 * @param {Zarafa.core.data.IPMRecord} record The record to update the panel with
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 */
	update: function(record, contentReset)
	{
		// we need opened record to get recipient store
		if(!record.isOpened()) {
			return;
		}

		/*
		 * if record is not changed then don't do any processing
		 * this check is added because we need to do below processing only when
		 * this function is called first time after record is opened
		 */
		if (this.record === record) {
			return;
		}

		this.record = record;

		// check if we need to populate this grid
		if (record.isMeetingOrganized() && record.get('counter_proposal')) {
			var recipientStore = record.getSubStore('recipients');
			var items = {
				proposals: []
			};

			for (var i = 0, len = recipientStore.getCount(); i < len; i++) {
				var recipient = recipientStore.getAt(i);

				if(recipient.isMeetingOrganizer()) {
					var duration = record.get('duration') ? record.get('duration') : Date.diff(Date.MINUTE, record.get('duedate'), record.get('startdate'));
					var organizer = {
						'display_name': _('Current Meeting Time'),
						'startdate': record.get('startdate'),
						'duedate': record.get('duedate'),
						'duration': duration
					};

					items.proposals.push(organizer);
				} else if(recipient.get('proposednewtime') === true) {
					var duration = Date.diff(Date.MINUTE, recipient.get('proposednewtime_end'), recipient.get('proposednewtime_start'));
					var item = {
						'display_name': recipient.get('display_name'),
						'startdate': recipient.get('proposednewtime_start'),
						'duedate': recipient.get('proposednewtime_end'),
						'duration': duration
					};

					items.proposals.push(item);
				}
			}

			this.store.loadData(items);

			if (contentReset) {
				// Select first row in proposal grid.
				this.getSelectionModel().selectFirstRow();
			}

			// now we have populated the data in grid so make it visible
			this.setVisible(true);
		}
	},

	/**
	 * Event handler which is fired when the {@link #getView view} is ready.
	 * Depending on the rendering/layouting order this might be called either before
	 * or after {@link #update} and should hence attempt to reselect what was marked
	 * as selected by {@link #update}.
	 * @private
	 */
	onViewReady: function()
	{
		this.getSelectionModel().selectFirstRow();
	},

	/**
	 * Event handler which is triggered when the {@link Zarafa.calendar.ui.CounterProposalGrid grid}
	 * {@link Zarafa.core.data.IPMRecord record} selection is changed. This will inform
	 *
	 * @param {Ext.grid.RowSelectionModel} selectionModel The selection model used by the grid.
	 * @private
	 */
	onSelectionChange: function(selectionModel)
	{
		var selections = selectionModel.getSelections();

		if (!Ext.isEmpty(selections) && selections.length == 1) {
			this.record.beginEdit();
			var startDate = selections[0].get('startdate');
			var dueDate = selections[0].get('duedate');

			this.record.set('startdate', startDate);
			this.record.set('duedate', dueDate);
			this.record.set('commonstart', startDate);
			this.record.set('commonend', dueDate);
			this.record.set('duration', (dueDate - startDate) / (60 * 1000));

			// If the first row is selected, counter_proposal remains true,
			// because the user hasn't selected a proposal and we must continue
			// showing the proposal grid in the future. When the user selected
			// a proposal we no longer need to show the proposals.
			this.record.set('counter_proposal', selectionModel.isSelected(0));
			this.record.endEdit();
		}
	}
});

Ext.reg('zarafa.counterproposalgrid', Zarafa.calendar.dialogs.CounterProposalGrid);
Ext.namespace('Zarafa.calendar.dialogs');

/**
 * @class Zarafa.calendar.dialogs.FreebusyTab
 * @extends Ext.form.FormPanel
 * @xtype zarafa.freebusytab
 *
 * Freebusy tab in the {@link Zarafa.calendar.dialogs.AppointmentPanel}
 * that is used to create Appointments and Meeting Requests.
 */
Zarafa.calendar.dialogs.FreebusyTab = Ext.extend(Ext.form.FormPanel, {
	/**
	 * @constructor
	 * @param {Object} config
	 */
	constructor: function(config)
	{
		config = config || {};

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

		Ext.applyIf(config, {
			xtype: 'zarafa.freebusytab',
			layout: {
				type: 'vbox',
				align: 'stretch'
			},
			border: false,
			defaults: {
				border: false
			},
			items: [
				this.createCounterProposalPanel(),
				this.createFreebusyPanel()
			]
		});

		Zarafa.calendar.dialogs.FreebusyTab.superclass.constructor.call(this, config);

		this.on('activate', this.onActivate, this);
	},

	/**
	 * Create the {@link Zarafa.calendar.dialogs.CounterProposalGrid CounterProposalGrid} object.
	 * @return {Object} The configuration object for the freebusy panel
	 * @private
	 */
	createCounterProposalPanel: function()
	{
		return {
			xtype: 'zarafa.counterproposalgrid',
			ref: 'counterproposalpanel',
			hidden: true,
			listeners: {
				scope: this,
				show: this.onProposalGridVisible
			}
		};
	},

	/**
	 * Create the {@link Zarafa.common.freebusy.ui.FreebusyPanel FreebusyPanel} object.
	 * @return {Object} The configuration object for the freebusy panel
	 * @private
	 */
	createFreebusyPanel: function()
	{
		return {
			xtype: 'zarafa.freebusypanel',
			cls: 'zarafa-freebusy-panel',
			ref: 'freebusyPanel',
			flex: 1,
			modelConfig: {
				listeners: {
					selectorrangeupdate: this.onSelectorRangeUpdate,
					scope: this
				}
			},
			items: [
				this.createOptionsPanel()
			]
		};
	},

	/**
	 * Create the {@link Ext.Panel Panel} containing the form fields which are used
	 * to control the {@link Zarafa.common.freebusy.ui.FreebusyPanel FreebusyPanel}.
	 * @return {Object} The configuration object for the Freebusy Options fields
	 * @private
	 */
	createOptionsPanel: function()
	{
		return {
			xtype: 'panel',
			region: 'north',
			layout: 'column',
			cls: 'zarafa-freebusy-datetime-toolbar',
			border: false,
			autoHeight: true,
			items: [{
				xtype: 'zarafa.datetimeperiodfield',
				ref: '../../datetimePeriod',
				defaultPeriod: container.getSettingsModel().get('zarafa/v1/contexts/calendar/default_appointment_period'),
				defaultPeriodType: Date.MINUTE,
				timeIncrement: container.getSettingsModel().get('zarafa/v1/contexts/calendar/default_zoom_level'),
				allowEqualValue: true,
				layout: 'hbox',
				listeners: {
					change: this.onDateRangeFieldChange,
					afterrender: this.onDateRangeFieldAfterRender,
					scope: this
				},
				startFieldConfig: {
					fieldLabel: _('Time'),
					labelConfig: {
						cls: 'label-startfield'
					}
				},
				endFieldConfig: {
					fieldLabel: _('until')
				},
				spacerConfig: {
					width: 10
				}
			},{
				xtype: 'checkbox',
				ref: '../../workingHoursCheckbox',
				boxLabel: _('Show only working hours'),
				handler: this.onOnlyWorkingHours,
				scope: this
			}]
		};
	},

	/**
	 * Event handler which is triggered when counter proposal grid is shown and it will
	 * re-layout {@link Zarafa.calendar.dialogs.FreebusyTab FreebusyTab} to show
	 * {@link Zarafa.calendar.dialogs.CounterProposalGrid CounterProposalGrid} at proper place.
	 * @param {Ext.Component} component Here Panel as a component
	 * @private
	 */
	onProposalGridVisible: function(component)
	{
		this.doLayout();
	},

	/**
	 * Automatically called by superclass on render
	 * This will initialize the {@link Ext.form.CheckBox checkbox} for
	 * toggling the visibility of the non-working hours.
	 * @param {Ext.Container} container The container into which the element is rendered
	 * @param {Number} position The position within the container
	 * @private
	 */
	onRender: function(container, position)
	{
		Zarafa.calendar.dialogs.FreebusyTab.superclass.onRender.call(this, container, position);

		var model = this.freebusyPanel.getModel();
		this.workingHoursCheckbox.setValue(model.showOnlyWorkingHours());

		this.mon(model, 'showworkinghourschange', this.onShowWorkingHoursChange, this);
	},

	/**
	 * Event handler which is triggered when this tab is activated. This will mark
	 * the appointment as meeting request.
	 * @param {Ext.Panel} panel The panel which raised the event
	 * @private
	 */
	onActivate: function(panel)
	{
		// set the meeting status to meeting only when a new Meeting is being or if the organizer has opened the meeting
		if(this.record && !this.record.isMeeting() && this.record.get('responsestatus') === Zarafa.core.mapi.ResponseStatus.RESPONSE_NONE) {
			this.record.convertToMeeting();
		}

		this.freebusyPanel.scrollTimelineToSelection();
	},

	/**
	 * Enable/disable/hide/unhide all {@link Ext.Component Components} within the {@link Ext.Panel Panel}
	 * using the given {@link Zarafa.core.data.IPMRecord IPMRecord}.
	 * @param {Zarafa.core.data.IPMRecord} record The record to update the panel with
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 * @private
	 */
	updateUI: function(record, contentReset)
	{
		if (contentReset === true || record.isModifiedSinceLastUpdate('alldayevent')) {
			if (record.get('alldayevent')) {
				this.datetimePeriod.setEnabledTimeSelection(false);
				// For allday events, we only allow selections of entire days
				this.datetimePeriod.defaultPeriod = 1;
				this.datetimePeriod.defaultPeriodType = Date.DAY;
			} else {
				this.datetimePeriod.setEnabledTimeSelection(true);
				// For normal events, we have to configure the normal period options again
				this.datetimePeriod.defaultPeriod = container.getSettingsModel().get('zarafa/v1/contexts/calendar/default_appointment_period');
				this.datetimePeriod.defaultPeriodType = Date.MINUTE;
			}
		}
	},

	/**
	 * Update the {@link Ext.Panel Panel} with the given {@link Zarafa.core.data.IPMRecord IPMRecord}
	 * @param {Zarafa.core.data.IPMRecord} record The record to 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;
		this.updateUI(record, contentReset);
		this.getForm().loadRecord(record);

		var startDate = record.get('startdate');
		var startDateUpdate = false;
		if (Ext.isDate(startDate)) {
			startDate = startDate.clone();
			startDateUpdate = contentReset || record.isModifiedSinceLastUpdate('startdate');
		}
		var dueDate = record.get('duedate');
		var dueDateUpdate = false;
		if (Ext.isDate(dueDate)) {
			dueDate = dueDate.clone();
			dueDateUpdate = contentReset || record.isModifiedSinceLastUpdate('duedate');
		}

		// For all day events we store the due date as 00:00 on the day after
		// it ends. For the UI, this means we have to subtract 1 day to get
		// the date on which the appointment actually ends for the user.
		if (record.get('alldayevent')) {
			startDate.clearTime();

			// Set time to 12:00 when using Date.add(Date.DAY)
			// to prevent problems with DST switches at 00:00
			// (like in Brasil).
			dueDate.setHours(12);
			dueDate = dueDate.add(Date.DAY, -1).clearTime();
		}

		if (contentReset === true) {
			this.freebusyPanel.setEditable(!record.isMeetingReceived());
		}

		if (startDateUpdate || dueDateUpdate) {
			this.datetimePeriod.getValue().set(startDate, dueDate);
		}

		if (record.isOpened()) {
			if (contentReset === true || startDateUpdate || dueDateUpdate) {
				this.freebusyPanel.getModel().selectRange(record.get('startdate'), record.get('duedate'));
			}

			if (contentReset === true) {
				this.freebusyPanel.getModel().setUserStore(record.getRecipientStore());
			}
		}
	},

	/**
	 * Update the {@link Zarafa.core.data.IPMRecord IPMRecord} with the data from the {@link Ext.Panel Panel}.
	 * @param {Zarafa.core.data.IPMRecord} record The record which has to be updated
	 */
	updateRecord: function(record)
	{
		record.beginEdit();
		this.getForm().updateRecord(record);
		this.updateStartDueDate(record, this.datetimePeriod.getValue());
		record.endEdit();
	},

	/**
	 * Event handler for when the {@link Zarafa.common.freebusy.ui.FreebusyPanel}
	 * fires the {@link Zarafa.common.freebusy.ui.FreebusyPanel#selectorrangeupdate}
	 * event. This will update the startdate and duedate in the {@link #record}
	 * accordingly.
	 * @param {Zarafa.core.DateRange} newRange The new daterange which was selected
	 * @param {Zarafa.core.DateRange} oldRange The old daterange which was selected
	 * @private
	 */
	onSelectorRangeUpdate: function(newRange, oldRange)
	{
		this.record.beginEdit();
		// Only when the selectorRange is updated we have a valid point
		// for toggling the alldayevent property as only then the user
		// can indicate he doesn't want to allday event. Note that we
		// only support disabling the alldayevent property through the
		// selectorrange.
		if (this.record.get('alldayevent') === true && !newRange.isAllDay()) {
			this.record.set('alldayevent', false);
		}
		this.updateRecurringInfo(this.record, newRange);
		this.updateStartDueDate(this.record, newRange);
		this.record.endEdit();
	},

	/**
	 * Update the 'recurrence_startocc' and 'recurrence_endocc' in the given record from
	 * the given daterange. When appointment is recurring meeting request then
	 * update the necessary recurring information of meeting request.
	 *
	 * @param {Zarafa.core.data.MAPIRecord} record the Record to update
	 * @param {Zarafa.core.DateRange} daterange the Daterange to apply
	 * @private
	 */
	updateRecurringInfo: function(record, daterange)
	{
		var startDate = daterange.getStartDate().clone();

		if (record.get('alldayevent') === true) {
			startDate = startDate.clearTime();
		}

		// Add some necessary properties while time information of
		// recurring meeting gets updated from scheduler.
		if(record.isRecurring() && record.isMeeting()) {
			record.updateTimezoneInformation();
			var startOcc = (startDate.getHours() * 60) + startDate.getMinutes();
			var endOcc = startOcc + daterange.getDuration(Date.MINUTE);
			record.beginEdit();
			record.set('recurring_reset', true);
			record.set('recurrence_startocc', startOcc);
			record.set('recurrence_endocc', endOcc);
			record.set('recurrence_start', startDate.clearTime(true).fromUTC());
			record.set('recurring_pattern', record.generateRecurringPattern());
			record.endEdit();
		}
	},

	/**
	 * Event handler which is fired when the {@link Zarafa.common.ui.DateRangeField} has been changed.
	 * This will update the start and due date inside the {@link #record} accordingly.
	 * @param {Ext.form.Field} field The field which has changed
	 * @param {Zarafa.core.DateRange} newRange The new daterange which was selected
	 * @param {Zarafa.core.DateRange} oldRange The old daterange which was selected
	 * @private
	 */
	onDateRangeFieldChange: function(field, newRange, oldRange)
	{
		this.updateRecurringInfo(this.record, newRange);
		this.updateStartDueDate(this.record, newRange);
	},


	/**
	 * Event handler which is fired when the {@link Zarafa.common.ui.DateRangeField} has been rendered.
	 * This will set width of field.
	 * @param {Ext.form.Field} field The field which is render
     */
	onDateRangeFieldAfterRender: function(field)
	{
		// Somehow before update() the static width is not applied and component is not displayed
		// and after onUpdateRecord() it is displayed with current date and time
		// So, need to set after DateRangeField successfully rendered
		field.setWidth(550);
	},

	/**
	 * Update the 'startdate' and 'duedate' in the given record from
	 * the given daterange. When the appointment is an allday event, then
	 * the times are always set to midnight. However when selecting
	 * the duedate the user selects on which day the appointment
	 * ends, so in reality the appointment ends on 00:00 hours on
	 * the following day.
	 * @param {Zarafa.core.data.MAPIRecord} record the Record to update
	 * @param {Zarafa.core.DateRange} daterange the Daterange to apply
	 * @private
	 */
	updateStartDueDate: function(record, daterange)
	{
		var startDate = daterange.getStartDate().clone();
		var dueDate = daterange.getDueDate().clone();

		if (record.get('alldayevent') === true) {
			startDate = startDate.clearTime();
			// Set time to 12:00 when using Date.add(Date.DAY)
			// to prevent problems with DST switches at 00:00
			// (like in Brasil).
			dueDate.setHours(12);
			dueDate = dueDate.add(Date.DAY, 1).clearTime();
		}

		record.beginEdit();
		record.set('startdate', startDate);
		record.set('duedate', dueDate);
		record.set('commonstart', startDate);
		record.set('commonend', dueDate);
		record.set('duration', (dueDate - startDate) / (60 * 1000));
		record.endEdit();
	},

	/**
	 * Event handler which is triggered when the "hideNonWorkingHours" option from
	 * the {@link Zarafa.common.freebusy.data.FreebusyModel FreebusyModel} has been
	 * changed. This will update the 'Show only working hours' checkbox accordingly.
	 * @param {Boolean} hideNonWorkingHours
	 * @private
	 */
	onShowWorkingHoursChange: function(hideNonWorkingHours)
	{
		this.workingHoursCheckbox.setValue(hideNonWorkingHours);
	},

	/**
	 * Event handler which is triggered when the 'Show only working hours'
	 * checkbox was checked.
	 * @param {Ext.form.CheckBox} checkbox The checkbox which was checked
	 * @param {Boolean} The current state of the checkbox
	 * @private
	 */
	onOnlyWorkingHours: function(checkbox, checked)
	{
		var model = this.freebusyPanel.getModel();
		model.hideNonWorkingHours(checked);
	}
});

Ext.reg('zarafa.freebusytab', Zarafa.calendar.dialogs.FreebusyTab);
Ext.namespace('Zarafa.calendar.dialogs');

/**
 * @class Zarafa.calendar.dialogs.SendMeetingRequestCancellationPanel
 * @extends Ext.form.FormPanel
 * @xtype zarafa.sendmeetingrequestcancellationpanel
 */
Zarafa.calendar.dialogs.SendMeetingRequestCancellationPanel = Ext.extend(Ext.form.FormPanel, {

	/**
	 * Info string that will be shown for organizer which is going to cancel the meeting request.
	 * @property
	 * @type String
	 */
	cancellationInfoString: _('This meeting will be cancelled. Do you want to include comments with your cancellation?'),

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

		Ext.applyIf(config, {
			// Override from Ext.Component
			xtype: 'zarafa.sendmeetingrequestcancellationpanel',
			layout: {
				type: 'vbox',
				align: 'stretch'
			},
			border: false,
			hideLabels: true,
			items: [{
				xtype: 'displayfield',
				value: this.cancellationInfoString,
				autoHeight: true,
				style: 'padding-bottom: 10px;'
			},{
				xtype:'radio',
				boxLabel: _('Edit the cancellation before Sending.'),
				name: 'sendmrcancellation',
				autoHeight: true,
				listeners: {
					check: this.onEditCancellationChecked,
					scope: this
				}
			},{
				xtype: 'textarea',
				name: 'cancellationText',
				fieldLabel: _('Cancellation message'),
				ref: 'cancellationTextField',
				disabled: true,
				flex: 1
			},{
				xtype:'radio',
				boxLabel: _('Send the cancellation now.'),
				checked: true,
				name: 'sendmrcancellation',
				autoHeight: true
			}]
		});

		Zarafa.calendar.dialogs.SendMeetingRequestCancellationPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Event handler for the {@link Ext.form.RadioButton#check check} event, this will
	 * enable/disable the cancellationTextField accordingly.
	 * @param {Ext.form.RadioButton} rb The radio button which was selected
	 * @param {Boolean} checked True if the radio button was checked
	 * @private
	 */
	onEditCancellationChecked: function(rb, checked)
	{
		this.cancellationTextField.setDisabled(!checked);
	},

	/**
	 * Called by the dialog check the settings from the user and either
	 * send the accept or decline message to the organizer.
	 * @param {Zarafa.core.data.IPMRecord} record The record which is being
	 * accepted or declined.
	 */
	updateRecord: function(record)
	{
		var values = this.getForm().getFieldValues();

		record.cancelInvitation(values.cancellationText);
	},

	/**
	 * Called when the panel is being resized. This will call {@link #doLayout} to update
	 * the heights of all fields inside the panel.
	 * @private
	 */
	onResize: function()
	{
		Zarafa.calendar.dialogs.SendMeetingRequestCancellationPanel.superclass.onResize.apply(this, arguments);
		this.doLayout();
	}
});

Ext.reg('zarafa.sendmeetingrequestcancellationpanel', Zarafa.calendar.dialogs.SendMeetingRequestCancellationPanel);
Ext.namespace('Zarafa.calendar.dialogs');

/**
 * @class Zarafa.calendar.dialogs.SendMeetingRequestConfirmationPanel
 * @extends Ext.form.FormPanel
 * @xtype zarafa.sendmeetingrequestconfirmpanel
 */
Zarafa.calendar.dialogs.SendMeetingRequestConfirmationPanel = Ext.extend(Ext.form.FormPanel, {
	/**
	 * @cfg {Zarafa.core.mapi.ResponseStatus} responseType The response type value selected by user.
	 */
	responseType: undefined,

	/**
	 * Info string that will be shown for attendee that is going to accept meeting request.
	 * @property
	 * @type String
	 */
	acceptInfoString: _('This meeting will be accepted and moved to your calendar. Do you want to include comments with your response?'),

	/**
	 * Info string that will be shown for attendee that is going to tentatively accept meeting request.
	 * @property
	 * @type String
	 */
	tentativeAcceptInfoString: _('This meeting will be tentatively accepted and moved to your calendar. Do you want to include comments with your response?'),

	/**
	 * Info string that will be shown for attendee that is going to decline meeting request.
	 * @property
	 * @type String
	 */
	declineInfoString: _('This meeting will be declined and moved to your Deleted Items folder. Do you want to include comments with your response?'),

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

		Ext.applyIf(config, {
			// Override from Ext.Component
			xtype: 'zarafa.sendmeetingrequestconfirmpanel',
			layout: {
				type: 'vbox',
				align: 'stretch'
			},
			border: false,
			hideLabels: true,
			items: [{
				xtype: 'displayfield',
				value: this.getDisplayText(config.responseType),
				autoHeight: true,
				style: 'padding-bottom: 10px;'
			},{
				xtype:'radio',
				boxLabel: _('Edit the response before sending'),
				name: 'sendmrconfirmation',
				autoHeight: true,
				listeners: {
					check: this.onEditResponseChecked,
					scope: this
				}
			},{
				xtype: 'textarea',
				name: 'responseText',
				fieldLabel: _('Response message'),
				ref: 'responseTextField',
				disabled: true,
				flex: 1
			},{
				xtype:'radio',
				boxLabel: _('Send the response now.'),
				checked: true,
				name: 'sendmrconfirmation',
				autoHeight: true
			},{
				xtype:'radio',
				boxLabel: _('Don\'t send a response.'),
				name: 'sendmrconfirmation',
				autoHeight: true
			}]
		});

		Zarafa.calendar.dialogs.SendMeetingRequestConfirmationPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Checks the {@link Zarafa.core.mapi.ResponseStatus responsetype} and either uses the {@link #acceptInfoString}
	 * or {@link #declineString} for generating the displaytext which should be shown on top of this panel.
	 * @param {Zarafa.core.mapi.ResponseStatus} responseType The response type for which this dialog is shown.
	 * @return {String} The display text which should be shown on top of this panel.
	 * @private
	 */
	getDisplayText: function(responseType)
	{
		switch(responseType)
		{
			case Zarafa.core.mapi.ResponseStatus.RESPONSE_ACCEPTED:
				return this.acceptInfoString;
			case Zarafa.core.mapi.ResponseStatus.RESPONSE_TENTATIVE:
				return this.tentativeAcceptInfoString;
			case Zarafa.core.mapi.ResponseStatus.RESPONSE_DECLINED:
				return this.declineInfoString;
		}

		return '';
	},

	/**
	 * Event handler for the {@link Ext.form.RadioButton#check check} event, this will
	 * enable/disable the responseTextField accordingly.
	 * @param {Ext.form.RadioButton} rb The radio button which was selected
	 * @param {Boolean} checked True if the radio button was checked
	 * @private
	 */
	onEditResponseChecked: function(rb, checked)
	{
		this.responseTextField.setDisabled(!checked);
	},

	/**
	 * Called by the dialog check the settings from the user and either
	 * send the accept or decline message to the organizer.
	 * @param {Zarafa.core.data.IPMRecord} record The record which is being
	 * accepted or declined.
	 */
	updateRecord: function(record)
	{
		var values = this.getForm().getFieldValues();

		// Check if the 'Don't send a response.' radio has been selected.
		var sendResponse = (values.sendmrconfirmation[2] !== true);

		record.respondToMeetingRequest(this.responseType, values.responseText, sendResponse);
	},

	/**
	 * Called when the panel is being resized. This will call {@link #doLayout} to update
	 * the heights of all fields inside the panel.
	 * @private
	 */
	onResize: function()
	{
		Zarafa.calendar.dialogs.SendMeetingRequestConfirmationPanel.superclass.onResize.apply(this, arguments);
		this.doLayout();
	}
});

Ext.reg('zarafa.sendmeetingrequestconfirmpanel', Zarafa.calendar.dialogs.SendMeetingRequestConfirmationPanel);
Ext.namespace('Zarafa.calendar.dialogs');

/**
 * @class Zarafa.calendar.dialogs.TrackingTab
 * @extends Ext.Panel
 * @xtype zarafa.trackingtab
 *
 * TrackingTab tab in the {@link Zarafa.calendar.dialogs.AppointmentPanel}
 * that is used to keep track of responses from attendees for Meeting Requests.
 */
Zarafa.calendar.dialogs.TrackingTab = Ext.extend(Ext.Panel, {
	/**
	 * @constructor
	 * @param {Object} config
	 */
	constructor: function(config)
	{
		config = config || {};

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

		Ext.applyIf(config, {
			xtype: 'zarafa.trackingtab',
			layout: {
				type: 'vbox',
				align: 'stretch'
			},
			cls: 'k-trackingtab',
			border: false,
			items: [{
				xtype: 'displayfield',
				height: 36,
				value: _('The following responses for this meeting have been received') + ':',
				hideLabel: true
			},{
				xtype: 'grid',
				ref: 'responseTrackList',
				flex: 1,
				store: new Zarafa.core.data.IPMRecipientStore(),
				viewConfig: {
					forceFit: true
				},
				columns: [{
					dataIndex: 'display_name',
					header: _('Name'),
					renderer: Ext.util.Format.htmlEncode,
					sortable: true
				},{
					dataIndex: 'recipient_type',
					header: _('Attendance'),
					sortable: true,
					renderer: Zarafa.common.ui.grid.Renderers.recipienttype
				},{
					dataIndex: 'recipient_trackstatus',
					header: _('Response'),
					sortable: true,
					renderer: Zarafa.common.ui.grid.Renderers.responsestatus
				}]
			}]
		});

		Zarafa.calendar.dialogs.TrackingTab.superclass.constructor.call(this, config);
	},

	/**
	 * Update the {@link Ext.Panel Panel} with the given {@link Zarafa.core.data.IPMRecord IPMRecord}
	 * Also it will build the tracking information data of attendees and pass it the the store
	 * @param {Zarafa.core.data.IPMRecord} record The record to update the panel with
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 */
	update: function(record, contentReset)
	{
		var recipientStore = record.getSubStore('recipients');

		if (recipientStore && this.responseTrackList.getStore() !== recipientStore) {
			this.responseTrackList.reconfigure(recipientStore, this.responseTrackList.getColumnModel());
		}
	}
});

Ext.reg('zarafa.trackingtab', Zarafa.calendar.dialogs.TrackingTab);
Ext.namespace('Zarafa.calendar.ui');

/**
 * @class Zarafa.calendar.ui.AppointmentPreviewPanel
 * @extends Ext.Panel
 * @xtype zarafa.appointmentpreviewpanel
 *
 * Panel that previews the contents of appointment.
 */
Zarafa.calendar.ui.AppointmentPreviewPanel = Ext.extend(Ext.Panel, {
	/**
	 * @constructor
	 * @param {Object} config configuration object.
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			xtype: 'zarafa.appointmentpreviewpanel',
			border: false,
			bodyCfg: {
				cls: 'preview-body'
			},
			layout: 'fit',
			items: [{
				xtype: 'zarafa.appointmentbody'
			}]
		});

		Zarafa.calendar.ui.AppointmentPreviewPanel.superclass.constructor.call(this, config);
	}
});

Ext.reg('zarafa.appointmentpreviewpanel', Zarafa.calendar.ui.AppointmentPreviewPanel);
Ext.namespace('Zarafa.calendar.ui');

/**
 * @class Zarafa.calendar.ui.AppointmentSelectionModel
 * @extends Ext.grid.AbstractSelectionModel
 *
 * The appointment selection model keeps a list of selected appointment records.
 * Used to share a common selection model across different calendar views.
 */
Zarafa.calendar.ui.AppointmentSelectionModel = Ext.extend(Ext.grid.AbstractSelectionModel, {
	/**
	 * @cfg {Boolean} singleSelect true to allow selection of only one row at a time (defaults to true disallowing multiple selections)
	 */
	singleSelect: true,

	/**
	 * The array of selected record.
	 * @property
	 * @type Ext.util.MixedCollection
	 * @private
	 */
	selections: undefined,

	/**
	 * @constructor
	 * @param {Object} config configuration object.
	 */
	constructor: function(config)
	{
		this.selections = new Ext.util.MixedCollection(false, function(o) {
			var id = o.id;
			if (Ext.isDate(o.data.basedate)) {
				id += o.data.basedate.getTime();
			}
			return id;
		});

		this.addEvents(
			/**
			 * @event appointmentselect
			 * Fires when a record has been selected.
			 * @param {Zarafa.calendar.ui.AppointmentSelectionModel} model this selection model.
			 * @param {Ext.data.Record} record The record which was selected.
			 */
			'appointmentselect',
			/**
			 * @event appointmentdeselect
			 * Fires when a record has been deselected.
			 * @param {Zarafa.calendar.ui.AppointmentSelectionModel} model this selection model.
			 * @param {Ext.data.Record} record The record which was deselected.
			 */
			'appointmentdeselect',
			/**
			 * @event selectionclear
			 * Fires when the record selection has been cleared.
			 * @param {Zarafa.calendar.ui.AppointmentSelectionModel} model this selection model.
			 */
			'selectionclear',
			/**
			 * @event selectionchange
			 * Fires when records have been added to or removed from the selection.
			 * @param {Zarafa.calendar.ui.AppointmentSelectionModel} model this selection model.
			 * @param {Ext.data.Record/Array} records currently selected appointment records.
			 */
			'selectionchange'
		);

		Zarafa.calendar.ui.AppointmentSelectionModel.superclass.constructor.call(this, config);
	},

	/**
	 * Clears all selections if the selection model {@link #isLocked is not locked}.
	 */
	clearSelections: function()
	{
		if (this.isLocked()) {
			return;
		}

		if (this.selections.getCount() === 0) {
			return;
		}

		this.selections.clear();

		this.fireEvent('selectionclear', this);
		this.fireEvent('selectionchange', this, this.getSelections());
	},

	/**
	 * Gets the number of selected appointments
	 * @return {Number} The number of selected appointments
	 */
	getCount: function()
	{
		return this.selections.getCount();
	},

	/**
	 * Returns the selected records
	 * @return {Array} Array of selected records
	 */
	getSelections: function()
	{
		return [].concat(this.selections.items);
	},

	/**
	 * Returns the first selected record.
	 * @return {Zarafa.core.data.IPMRecord}
	 */
	getSelected: function()
	{
		return this.selections.itemAt(0);
	},

	/**
	 * Returns <tt>true</tt> if there is a selection.
	 * @return {Boolean}
	 */
	hasSelection: function()
	{
		return this.selections.length > 0;
	},

	/**
	 * Returns <tt>true</tt> if the specified row is selected.
	 * @param {Record} record The record to check
	 * @return {Boolean}
	 */
	isSelected: function(record)
	{
		return (record && this.selections.key(this.selections.getKey(record)) ? true : false);
	},

	/**
	 * Selects a record. Before selecting a record, checks if the selection model
	 * {@link Ext.grid.AbstractSelectionModel#isLocked is locked}.
	 * If these checks are satisfied the record will be selected
	 * and followed up by firing the {@link #selectionchange} events.
	 * @param {Zarafa.core.data.IPMRecord} record The record to select
	 * @param {Boolean} keepExisting (optional) <tt>true</tt> to keep existing selections
	 */
	selectRecord: function(record, keepExisting)
	{
		if (this.isLocked() || !Ext.isDefined(record) || (keepExisting && this.isSelected(record))) {
			return;
		}

		// disable selection of private appointments
		if(record.get('access') === 0) {
			return;
		}

		if (!keepExisting || this.singleSelect) {
			this.clearSelections();
		}

		this.selections.add(record);
		this.fireEvent('appointmentselect', this, record);
		this.fireEvent('selectionchange', this, this.getSelections());
	},

	/**
	 * Deselects a record. Before deselecting a row, checks if the selection model
	 * {@link Ext.grid.AbstractSelectionModel#isLocked is locked}.
	 * If this check is satisfied the row will be deselected and followed up by
	 * firing the {@link #selectionchange} event.
	 * @param {Number} row The index of the row to deselect
	 */
	deselectRecord: function(record)
	{
		if (this.isLocked() || !Ext.isDefined(record)) {
			return;
		}

		this.selections.remove(record);
		this.fireEvent('appointmentdeselect', this, record);
		this.fireEvent('selectionchange', this, this.getSelections());
	}
});
Ext.namespace('Zarafa.calendar.ui');

/**
 * @class Zarafa.calendar.ui.CalendarBlockPanel
 * @extends Ext.Panel
 * @xtype zarafa.calendarblockpanel
 * The main panel for the calendar content context.
 */
Zarafa.calendar.ui.CalendarBlockPanel = Ext.extend(Ext.Panel, {
    /**
	 * @cfg {Zarafa.calendar.CalendarContext} context The context to which this panel belongs
	 */
	context: undefined,

	/**
	 * The {@link Zarafa.calendar.CalendarContextModel} which is obtained from
	 * the {@link #context}.
	 *
	 * @property
	 * @type Zarafa.calendar.CalendarContextModel
	 */
	model: undefined,

	/**
	 * @cfg {Zarafa.calendar.ui.AppointmentSelectionModel} selectionModel The
	 * selection model which can be used for selecting {@link Zarafa.core.data.IPMRecord records}.
	 */
	selectionModel: undefined,

	/**
	 * @cfg {Zarafa.calendar.ui.DateRangeSelectionModel} rangeSelectionModel The
	 * selection model which can be used for selecting a {@link Zarafa.core.DateRange daterange}
	 * within the calendar.
	 */
	rangeSelectionModel: undefined,

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

		if (!Ext.isDefined(config.model) && Ext.isDefined(config.context)) {
			config.model = config.context.getModel();
		}
		if (!Ext.isDefined(config.selectionModel)) {
			config.selectionModel = new Zarafa.calendar.ui.AppointmentSelectionModel();
		}
		if (!Ext.isDefined(config.rangeSelectionModel)) {
			config.rangeSelectionModel = new Zarafa.calendar.ui.DateRangeSelectionModel();
		}

		Ext.applyIf(config, {
			border: false,
			layout: 'fit',
			deferredRender: false,
			items: [{
				xtype: 'zarafa.calendarpanel',
				ref: 'calendarPanel',
				context: config.context,
				selectionModel: config.selectionModel,
				rangeSelectionModel: config.rangeSelectionModel,
				listeners: {
					scope: this,
					contextmenu: this.onContextMenu,
					dblclick: this.onDblClick,
					dayclick: this.onDayClick,
					calendarclose: this.onCalendarClose
				}
			}]
		});

		Zarafa.calendar.ui.CalendarBlockPanel.superclass.constructor.call(this, config);

		// Add hook for updating the ContextModel on Appointment Selections
		this.mon(this.selectionModel, 'selectionchange', this.onSelectionChange, this);

		// Register remove event on store so that when some record is removed from
		// the store we remove it from the selection as well.
		var store = config.model.getStore();
		if(store){
			this.mon(store, 'remove', this.onStoreRemove, this);
		}
	},

	/**
	 * Function is called when record is removed from the store. It will remove
	 * record from {@link #selectionModel} which are removed from the store.
	 *
	 * @param {Zarafa.calendar.AppointmentStore} store
	 * @param {Ext.data.Record} record The Record that was removed
	 * @param {Number} index The index at which the record was removed
	 *
	 * @private
	 */
	onStoreRemove: function(store, record, index)
	{
		this.selectionModel.deselectRecord(record);
	},

	/**
	 * Obtain a reference to the {@link Zarafa.calendar.ui.CalendarPanel CalendarPanel}
	 * which rerenders within this CalendarBlockPanel.
	 * @return {Zarafa.calendar.ui.CalendarPanel} The calendar panel.
	 */
	getCalendarPanel: function()
	{
		return this.calendarPanel;
	},

	/**
	 * Event handler which is fired when the user requests the contextmenu on this panel.
	 * @param {Object} event The event object
	 * @param {Zarafa.core.data.IPMRecord} record The record for which the contextmenu is requested
	 * @private
	 */
	onContextMenu: function(event, record)
	{
		var config = {
			position: event.getXY(),
			calendarPanel: this.getCalendarPanel()
		};
		Zarafa.core.data.UIFactory.openDefaultContextMenu(record, config);
	},

	/**
	 * Event handler which is fired when the user doubleclicks on the panel. This will open the
	 * selected record in a {@link Zarafa.calendar.dialogs.AppointmentContentPanel AppointmentContentPanel}.
	 * @param {Object} event The event object
	 * @param {Zarafa.core.data.IPMRecord} record The record which was double-clicked
	 * @private
	 */
	onDblClick: function(event, record)
	{
		Zarafa.calendar.Actions.openAppointmentContent(record);
	},

	/**
	 * Event handler which is triggered when the {@link Zarafa.mail.ui.MailGrid grid}
	 * {@link Zarafa.core.data.IPMRecord record} selection is changed. This will inform
	 * the {@link Zarafa.mail.MailContextModel contextmodel} about the change.
	 *
	 * @param {Ext.grid.RowSelectionModel} selectionModel The selection model used by the grid.
	 * @private
	 */
	onSelectionChange: function(selectionModel)
	{
		this.model.setSelectedRecords(selectionModel.getSelections());
	},

	/**
	 * Event handler which is fired when the user clicks on the Day header. This will change
	 * the {@link Zarafa.calendar.data.Views views} to {@link Zarafa.calendar.data.Views#BLOCKS},
	 * the {@link Zarafa.calendar.data.ViewModes ViewMode} to {@link Zarafa.calendar.data.ViewModes#DAYS},
	 * and the {@link Zarafa.calendar.data.DataModes DataMode} to {@link Zarafa.calendar.data.DataModes#DAY}.
	 * and moves to the selected day.
	 * @param {Object} source The object which fired the event
	 * @param {Date} date The date which was selected
	 * @private
	 */
	onDayClick: function(source, date)
	{
		this.selectionModel.clearSelections();
		this.rangeSelectionModel.clearSelections();

		this.context.switchView(Zarafa.calendar.data.Views.BLOCKS, Zarafa.calendar.data.ViewModes.DAYS);
		this.model.setModeAndDate(Zarafa.calendar.data.DataModes.DAY, date);
	},

	/**
	 * Event handler which is fired when the user closes one of the CalendarTabs. This will
	 * remove the selected folder from the {@link Zarafa.calendar.CalendarContextModel ContextModel}.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder The selected folder
	 * @private
	 */
	onCalendarClose: function(folder)
	{
		this.model.removeFolder(folder);
	},

	/**
	 * Called automatically by the superclass when this panel is being added to a container.
	 * This will add an {@link Zarafa.calendar.ui.CalendarMainPanel#switchview switchview}
	 * event handler for listening to view switches.
	 * @param {Ext.Container} container The contained on which this panel is added
	 * @param {Number} index The position of this panel within the container
	 * @private
	 */
	onAdded: function(container, index)
	{
		Zarafa.calendar.ui.CalendarBlockPanel.superclass.onAdded.call(this, container, index);
		this.mon(this.ownerCt, 'switchview', this.onSwitchView, this);
	},

	/**
	 * Called automatically by the superclass when this panel is being removed from a container.
	 * This will remove the {@link Zarafa.calendar.ui.CalendarMainPanel#switchview switchview}
	 * event handler.
	 * @private
	 */
	onRemoved: function()
	{
		this.mun(this.ownerCt, 'switchview', this.onSwitchView, this);
		Zarafa.calendar.ui.CalendarBlockPanel.superclass.onRemoved.call(this);
	},

	/**
	 * Event handler which is fired when the container is switching the main view.
	 * @param {Ext.Container} container The container which fired the event
	 * @param {Ext.Panel} newView The new view which has been loaded
	 * @param {Ext.Panel} oldView The old view which has been loaded
	 * @private
	 */
	onSwitchView: function(container, newView, oldView)
	{
		if (this == newView) {
			this.getCalendarPanel().bindStore(this.model.getStore());
		} else if (this == oldView) {
			this.getCalendarPanel().releaseStore(this.model.getStore());
		}
	}
});

Ext.reg('zarafa.calendarblockpanel', Zarafa.calendar.ui.CalendarBlockPanel);
Ext.namespace('Zarafa.calendar.ui');

/**
 * @class Zarafa.calendar.ui.CalendarPanel
 * @extends Ext.Panel
 * @xtype zarafa.calendarpanel
 */
Zarafa.calendar.ui.CalendarPanel = Ext.extend(Ext.Panel, {
	/**
	 * @cfg {Zarafa.calendar.CalendarContext} context The context to which this panel belongs
	 */
	context: undefined,

	/**
	 * The {@link Zarafa.calendar.CalendarContextModel} which is obtained from
	 * the {@link #context}.
	 *
	 * @property
	 * @type Zarafa.calendar.CalendarContextModel
	 */
	model: undefined,

	/**
	 * The store which is attached to this Calendar. This reference is obtained
	 * from the {@link #context}.
	 *
	 * @property
	 * @type Zarafa.core.data.IPMStore
	 */
	store: undefined,

	/**
	 * The clipBoardData which contains copied record.
	 *
	 * @property
	 * @type Zarafa.calendar.AppointmentRecord
	 */
	clipBoardData: undefined,

	/**
	 * @cfg {String} loadMaskText text shown when the panel is loading data from the store.
	 */
	loadMaskText: _('Loading') + '...',

	/**
	 * The selection model which manages the selection of {@link Zarafa.core.data.IPMRecord appointments}
	 * inside this calendar.
	 *
	 * @property
	 * @type Zarafa.calendar.ui.AppointmentSelectionModel
	 */
	selectionModel: undefined,

	/**
	 * The selection model which manages the selection of a {@link Zarafa.core.DateRange daterange}
	 * inside the calendar.
	 *
	 * @property
	 * @type Zarafa.calendar.ui.DateRangeSelectionModel
	 */
	rangeSelectionModel: undefined,

	/**
	 * @cfg {Object} viewConfig The configuration which must be applied to the {@link Zarafa.calendar.ui.CalendarMultiView}
	 * which belongs to this calendar panel.
	 */
	viewConfig: undefined,

	/**
	 * The {@link Zarafa.calendar.ui.CalendarMultiView calendarMultiView} which is attached to this panel, the creation
	 * of this view can be configured through {@link #viewConfig}.
	 *
	 * @property
	 * @type Zarafa.calendar.ui.CalendarMultiView
	 */
	view: undefined,

	/**
	 * @cfg {Boolean} showTooltip True to show tooltips when the cursor moves over an appointment.
	 */
	showTooltip: true,

	/**
	 * @cfg {String/Ext.XTemplate} tooltipTitleTpl If {@link #showTooltip tooltips are enabled},
	 * this template is used for the title of the tooltip. The data for the template will
	 * be the data object from the {@link Zarafa.calendar.AppointmentRecord appointment}.
	 */
	tooltipTitleTpl: new Ext.XTemplate(
		'<tpl if="!Ext.isEmpty(values.subject)">',
			'{values.subject:htmlEncode}',
		'</tpl>',
		{
			compiled: true
		}
	),

	/**
	 * @cfg {String/Ext.XTemplate} tooltipTextTpl If {@link #showTooltip tooltips are enabled},
	 * this template is used for the main contents of the tooltip. The data for the template will
	 * be the data object from the {@link Zarafa.calendar.AppointmentRecord appointment}.
	 */
	tooltipTextTpl: new Ext.XTemplate(
		'<tpl if="values.meeting !== Zarafa.core.mapi.MeetingStatus.NONMEETING && !Ext.isEmpty(this.formatOrganizer(values))">',
			_('Organizer') + ': {[this.formatOrganizer(values)]}<br>',
		'</tpl>',
		'<tpl if="values.meeting !== Zarafa.core.mapi.MeetingStatus.NONMEETING && !Ext.isEmpty(this.formatAttendees(values))">',
			_('Attendees') + ': {[this.formatAttendees(values)]}<br>',
		'</tpl>',
		'<tpl if="values.meeting !== Zarafa.core.mapi.MeetingStatus.NONMEETING && !Ext.isEmpty(this.formatOptionalAttendees(values))">',
			_('Optional attendees') + ': {[this.formatOptionalAttendees(values)]}<br>',
		'</tpl>',
		'<tpl if="values.alldayevent != true">',
			_('Time') + ': {[this.formatTime(values.startdate, values.duedate)]}<br>',
		'</tpl>',
		'<tpl if="values.alldayevent == true">',
			_('Date') + ': {[this.formatDate(values.startdate, values.duedate)]}<br>',
		'</tpl>',
		'<tpl if="!Ext.isEmpty(values.location)">',
			_('Location') + ': {values.location:htmlEncode}<br>',
		'</tpl>',
		'<tpl if="!Ext.isEmpty(values.recurring_pattern)">',
			_('Recurrence') + ': {values.recurring_pattern:htmlEncode}<br>',
		'</tpl>',
		{
			compiled: true,
			// Format the organizer of the meeting
			formatOrganizer: function(values)
			{
				var value = values.sent_representing_name;
				if (Ext.isEmpty(value)) {
					value = values.sender_name;
				}

				return Ext.util.Format.htmlEncode(value);
			},

			// Format the attendees of the meeting
			formatAttendees: function(values)
			{
				var organizer = this.formatOrganizer(values);
				var value = values.display_to;

				// If the value is empty or value is only the organizer
				// Set value to empty
				if (Ext.isEmpty(value) || value === organizer) {
					value = "";
				} else {
					// Remove the organizer from the "display_to" user
					value = value.replace("; "+organizer,'');
				}

				return Ext.util.Format.htmlEncode(value);
			},

			// Format the attendees of the meeting
			formatOptionalAttendees: function(values)
			{
				var value = values.display_cc;
				return Ext.isEmpty(value) === false ?  Ext.util.Format.htmlEncode(value) : "";
			},
			// Format the times for a normal appointment.
			formatTime: function(start, due)
			{
				if (start.clearTime(true).getTime() == due.clearTime(true).getTime()) {
					// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
					return start.formatDefaultTime() + ' - ' + due.formatDefaultTime();
				} else {
					// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
					return start.formatDefaultTime(_('jS F {0}')) + ' - ' + due.formatDefaultTime(_('jS F {0}'));
				}
			},
			// Format the dates for an allday appointment, this requires the duedate
			// to be reduced by one day, and doesn't print times.
			formatDate: function(start, due)
			{
				due = due.add(Date.HOUR, -1);
				if (Date.diff(Date.DAY, due, start) <= 1) {
					// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
					return start.format(_('jS F Y'));
				} else {
					// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
					return start.format(_('jS F Y')) + ' - ' + due.format(_('jS F Y'));
				}
			}
		}
	),

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

		Ext.apply(this, config, {
			xtype: 'zarafa.calendarpanel',
			border: false
		});

		// Declare events.
		this.addEvents(
			/**
			 * @event beforeappointmentcalendardrop
			 * Fires when an appointment is dragged from this calendar component onto another.
			 * @param {Zarafa.calendar.ui.CalendarPanel} calendar The calendar which fired the event
			 * @param {Zarafa.calendar.AppointmentRecord} appointment the appointment record that was dropped
			 * @param {Zarafa.calendar.core.MAPIFolder} sourceFolder source folder
			 * @param {Zarafa.calendar.core.MAPIFolder} targetFolder target folder
			 * @param {Zarafa.core.DateRange} dateRange The new daterange for the appointment
			 * @return {Boolean} False to cancel the event
			 */
			'beforeappointmentcalendardrop',
			/**
			 * @event appointmentcalendardrop
			 * Fires when an appointment is dragged from this calendar component onto another.
			 * @param {Zarafa.calendar.ui.CalendarPanel} calendar The calendar which fired the event
			 * @param {Zarafa.calendar.AppointmentRecord} appointment the appointment record that was dropped
			 * @param {Zarafa.calendar.core.MAPIFolder} sourceFolder source folder
			 * @param {Zarafa.calendar.core.MAPIFolder} targetFolder target folder
			 * @param {Zarafa.core.DateRange} dateRange The new daterange for the appointment
			 */
			'appointmentcalendardrop',
			/**
			 * @event appointmentmouseover
			 * Fires when the mouse is being mover over an appointment.
			 * @param {Zarafa.calendar.ui.CalendarPanel} calendar The calendar which fired the event
			 * @param {Zarafa.calendar.AppointmentRecord} record The appointment over which the mouse is moving
			 * @param {Ext.EventObject} event The original event object
			 */
			'appointmentmouseover',
			/**
			 * @event appointmentmouseover
			 * Fires when the mouse is being mover away from an appointment.
			 * @param {Zarafa.calendar.ui.CalendarPanel} calendar The calendar which fired the event
			 * @param {Zarafa.calendar.AppointmentRecord} record The appointment over which the mouse has moved out
			 * @param {Ext.EventObject} event The original event object
			 */
			'appointmentmouseout',
			/**
			 * @event beforeappointmentmove
			 * Fires before an appointment is moved.
			 * @param {Zarafa.calendar.ui.CalendarPanel} calendar The calendar which fired the event
			 * @param {Zarafa.calendar.AppointmentRecord} appointment the appointment record that was moved
			 * @param {Zarafa.core.DateRange} dateRange The new daterange for the appointment
			 * @return {Boolean} False to cancel the event
			 */
			'beforeappointmentmove',
			/**
			 * @event beforeappointmentresize
			 * Fires before an appointment is resized. Resizing in this context means that either the start
			 * date or the due date has been changed.
			 * @param {Zarafa.calendar.ui.CalendarPanel} calendar The calendar which fired the event
			 * @param {Zarafa.calendar.AppointmentRecord} appointment the appointment record that was resized
			 * @param {Zarafa.core.DateRange} dateRange The new daterange for the appointment
			 * @return {Boolean} False to cancel the event
			 */
			'beforeappointmentresize',
			/**
			 * @event beforeappointmentcreate
			 * Fires before a new appointment is created.
			 * @param {Zarafa.calendar.ui.CalendarPanel} calendar The calendar which fired the event
			 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder MAPI folder the appointment should be created in.
			 * @param {Zarafa.core.DateRange} dateRange Appointment date range.
			 * @param {String} text Appointment text
			 * @return {Boolean} False to cancel the event
			 */
			'beforeappointmentcreate',
			/**
			 * @event appointmentmove
			 * Fires when an appointment has been moved.
			 * @param {Zarafa.calendar.ui.CalendarPanel} calendar The calendar which fired the event
			 * @param {Zarafa.calendar.AppointmentRecord} appointment the appointment record that was moved
			 * @param {Zarafa.core.DateRange} dateRange The new daterange for the appointment
			 */
			'appointmentmove',
			/**
			 * @event appointmentresize
			 * Fires when an appointment has been resized. Resizing in this context means that either the start
			 * date or the due date has been changed.
			 * @param {Zarafa.calendar.ui.CalendarPanel} calendar The calendar which fired the event
			 * @param {Zarafa.calendar.AppointmentRecord} appointment the appointment record that was resized
			 * @param {Zarafa.core.DateRange} dateRange The new daterange for the appointment
			 */
			'appointmentresize',
			/**
			 * @event appointmentcreate
			 * Fires after a new appointment has been created.
			 * @param {Zarafa.calendar.ui.CalendarPanel} calendar The calendar which fired the event
			 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder MAPI folder the appointment should be created in.
			 * @param {Zarafa.core.data.IPMRecord} record Created appointment record.
			 */
			'appointmentcreate',
			/**
			 * @event contextmenu
			 * Fires when the user right-clicks on the calendar body or on an appointment.
			 * @param {Ext.EventObject} event right-click event.
			 * @param {Ext.data.Record} record (optional) if the user right-clicked on an appointment
			 * this will contain the appointment record, undefined otherwise
			 */
			'contextmenu',
			/**
			 * @event dblclick
			 * Fires when the user double-clicks on on an appointment.
			 * @param {Ext.EventObject} event right-click event.
			 * @param {Ext.data.Record} record appointment record.
			 */
			'dblclick',
			/**
			 * @event dayclick
			 * Fires when a user clicks on the header of a day, or on the expand button in the box view.
			 * @param {Zarafa.core.ui.View} source event source
			 * @param {Date} date date of the day.
			 */
			'dayclick',
			/**
			 * @event calendarclose
			 * Fires when the user closes a calendar using the close icon in the calendar tabs.
			 * @param {string} folderid MAPI folder id.
			 */
			'calendarclose'
		);

		Zarafa.calendar.ui.CalendarPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Initialises the component.
	 * @private
	 */
	initComponent: function()
	{
		Zarafa.calendar.ui.CalendarPanel.superclass.initComponent.apply(this, arguments);

		if (!Ext.isDefined(this.model) && Ext.isDefined(this.context)) {
			this.model = this.context.getModel();
		}

		if (!Ext.isDefined(this.store) && Ext.isDefined(this.model)) {
			this.store = this.model.getStore();
		}

		// If no appointment selection model was provided, create a new one.
		if (!Ext.isDefined(this.selectionModel)) {
			// Register remove event on store so that when some record is removed from
			// the store we remove it from the selection as well
			if(this.store){
				this.mon(this.store, 'remove', this.onStoreRemove, this);
			}

			this.selectionModel = new Zarafa.calendar.ui.AppointmentSelectionModel();
		}

		// If no range selection model was provided, create a new one.
		if (!Ext.isDefined(this.rangeSelectionModel)) {
			this.rangeSelectionModel = new Zarafa.calendar.ui.DateRangeSelectionModel();
		}

		// Create a new multi view. The multi view allows multiple calendars to be on screen
		// side-by-side.
		this.viewConfig = this.viewConfig || {};
		Ext.apply(this.viewConfig, {
			context: this.context,
			selectionModel: this.selectionModel,
			rangeSelectionModel: this.rangeSelectionModel
		});
		this.view = new Zarafa.calendar.ui.CalendarMultiView(this.viewConfig);

		// Hook into events of the view.
		this.mon(this.view, {
			'appointmentcalendardrop': this.onAppointmentCalendarDrop,
			'appointmentmouseover': this.onAppointmentMouseOver,
			'appointmentmouseout': this.onAppointmentMouseOut,
			'appointmentmove': this.onAppointmentMove,
			'appointmentresize': this.onAppointmentResize,
			'appointmentcreate': this.onAppointmentCreate,
			'appointmentinitdrag': this.onAppointmentInitDrag,
			'appointmentenddrag': this.onAppointmentEndDrag,
			'contextmenu': this.onViewContextMenu,
			'dblclick': this.onDoubleClick,
			'dayclick': this.onDayClick,
			'calendarclose': this.onCalendarClose,
			scope: this
		});

		// If a store was provided, bind it.
		if (this.store) {
			this.bindStore(this.store);
		}
	},

	/**
	 * Function is called when record is removed from the store. It will remove
	 * record from {@link #selectionModel} which are removed from the store.
	 *
	 * @param {Zarafa.calendar.AppointmentStore} store
	 * @param {Ext.data.Record} record The Record that was removed
	 * @param {Number} index The index at which the record was removed
	 *
	 * @private
	 */
	onStoreRemove: function(store, record, index){
		this.selectionModel.deselectRecord(record);
	},

	/**
	 * Relays the 'contextmenu' event.
	 * @param {Object} event The event object
	 * @param {Zarafa.core.data.IPMRecord} record The record on which the context menu was requested
	 * @private
	 */
	onViewContextMenu: function(event, record)
	{
		this.fireEvent('contextmenu', event, record);
		event.stopEvent();
	},

	/**
	 * Relays the 'dblclick' event.
	 * @param {Object} event The event object
	 * @param {Zarafa.core.data.IPMRecord} record The record on which was double clicked
	 * @private
	 */
	onDoubleClick: function(event, record)
	{
		this.fireEvent('dblclick', event, record);
	},

	/**
	 * Relays the 'dayclick' event.
	 * @param {Zarafa.calendar.ui.AbstractCalendarView} source Source calendar view.
	 * @param {Date} date The date on which was clicked
	 * @private
	 */
	onDayClick: function(source, date)
	{
		this.fireEvent('dayclick', source, date);
	},

	/**
	 * Relays the 'calendarclose' event.
	 * @param {Zarafa.core.IPMFolder} folder The folder which was closed
	 * @private
	 */
	onCalendarClose: function(folder)
	{
		this.fireEvent('calendarclose', folder);
	},

	/**
	 * Relays the 'appointmentcalendardrop' event.
	 * @param {Zarafa.calendar.ui.CalendarMultiView} multiview The Calendar MultiView which fired the event
	 * @param {Zarafa.calendar.AppointmentRecord} appointment the appointment record that was dragged
	 * @param {Zarafa.calendar.core.MAPIFolder} sourceFolder source folder
	 * @param {Zarafa.calendar.core.MAPIFolder} targetFolder target folder
	 * @param {Zarafa.core.DateRange} dateRange The new daterange for the appointment
	 * @param {Ext.EventObject} event The original event object
	 * @private
	 */
	onAppointmentCalendarDrop: function(multiview, appointment, sourceFolder, targetFolder, dateRange, event)
	{
		if (this.fireEvent('beforeappointmentcalendardrop', this, appointment, sourceFolder, targetFolder, dateRange) !== false) {
			// Check if target folder has not enough rights then show error message.
			if (!targetFolder.hasCreateRights()) {
				var message = _("You have insufficient privileges to move and copy this item. Ask the folder owner to grant you permissions or contact your system administrator.");
				if (event.ctrlKey) {
					message = _("You have insufficient privileges to copy this item. Ask the folder owner to grant you permissions or contact your system administrator.");
				}
				container.getNotifier().notify('error', _("Insufficient privileges"), message);
				return false;
			}

			// Create copy of selected record and update that particular copy with the specific drop location because
			// if we update original record then changes will be reflected to UI as well
			var copyAppointment = appointment.copy();
			this.doAppointmentChange(copyAppointment, dateRange);

			// Create the object of drop location props that's needs to be send with original record
			// we should apply drop location props to the target record on server side
			var modifiedProps = {};
			for (var key in copyAppointment.modified) {
				modifiedProps[key] = copyAppointment.get(key);
			}
			appointment.addMessageAction('dropmodifications', modifiedProps);

			var store = appointment.getStore();
			var isAppointmentFromPublicStore = sourceFolder.getMAPIStore().isPublicStore();

			// Note: for public store items (mail, appointments, etc), we are getting wrong PR_ACCESS value on list request.
			// But we get the right value on open request of that item. So need to add this check for public folders.
			// For other stores (i.e. own, shared) its working fine.
			if (isAppointmentFromPublicStore && !appointment.isOpened()) {
				const openHandler = function (store, record) {
					if (!Zarafa.core.EntryId.compareEntryIds(appointment.get('entryid'), record.get('entryid'))) {
						return;
					}

					this.onDropCopyOrMoveAppointment(appointment, store, sourceFolder, targetFolder, dateRange, event);
					store.un('open', openHandler, this);
				};
				store.on('open', openHandler, this);
				appointment.open();
				return;
			}

			this.onDropCopyOrMoveAppointment(appointment, store, sourceFolder, targetFolder, dateRange, event);
		}
	},

	/**
	 * This is helper function of {@link #onAppointmentCalendarDrop} function.
	 * This will perform copy/move action for given appointment and show messagebox to perform copy action
	 * when not enough access rights.
	 *
	 * @param {Zarafa.calendar.AppointmentRecord} appointment the appointment record that was dragged
	 * @param {Zarafa.calendar.data.AppointmentStore} store the store in which appointment exists.
	 * @param {Zarafa.calendar.core.MAPIFolder} sourceFolder source folder
	 * @param {Zarafa.calendar.core.MAPIFolder} targetFolder target folder
	 * @param {Zarafa.core.DateRange} dateRange The new daterange for the appointment
	 * @param {Ext.EventObject} event The original event object
	 * @private
	 */
	onDropCopyOrMoveAppointment: function(appointment, store, sourceFolder, targetFolder, dateRange, event)
	{
		var mapiStore = targetFolder.getMAPIStore();
		if (event.ctrlKey) {
			// Show notification when user try to copy a private appointment to 
			// shared or public store.
			if (appointment.get('private') && (mapiStore.isPublicStore() || mapiStore.isSharedStore())){
				const callBack = function() {
					this.fireEvent('appointmentcalendardrop', this, appointment, sourceFolder, targetFolder, dateRange);
				};
				appointment.unsetPrivate();
				var msg = _('Copying this appointment will unset it as private. Are you sure you want to continue?');
				Zarafa.common.Actions.showMessageBox(appointment, targetFolder, store, msg, this, callBack, {
					title: _('Private item'),
					actionBtn: _('Copy')
				});
				return;
			}

			appointment.copyTo(targetFolder);
		} else {
			// If user wants to perform 'move' operation and source folder has not delete right
			// or appointment does not belong to the user then 'move' operation can not be performed.
			// In that case ask user whether he wants to perform 'copy' operation instead.
			if (!sourceFolder.hasDeleteOwnRights() || !appointment.hasDeleteAccess()) {
				// Need to relay 'appointmentcalendardrop' event on click of copy button of messagebox.
				const callBack = function() {
					this.fireEvent('appointmentcalendardrop', this, appointment, sourceFolder, targetFolder, dateRange);
				};
				Zarafa.common.Actions.showMessageBox(appointment, targetFolder, store, undefined, this, callBack);
				return;
			}

			// Show notification when user try to move a private appointment to 
			// shared or public store.
			if (appointment.get('private') && (mapiStore.isPublicStore() || mapiStore.isSharedStore())) {
				const callBack = function() {
					this.fireEvent('appointmentcalendardrop', this, appointment, sourceFolder, targetFolder, dateRange);
				};
				appointment.unsetPrivate();
				var msg = _('Moving this appointment will unset it as private. Are you sure you want to continue?');
				Zarafa.common.Actions.showMessageBox(appointment, targetFolder, store, msg, this, callBack, {
					title: _('Private item'),
					actionBtn: _('Move')
				});
				return;
			}

			// If source folder has enough rights or for public store, item belongs to the user
			// then perform move operation.
			appointment.moveTo(targetFolder);
		}
		appointment.save();
		this.fireEvent('appointmentcalendardrop', this, appointment, sourceFolder, targetFolder,  dateRange);
	},

	/**
	 * Forwards the 'appointmentmouseover' event to the parent calendar panel
	 * @param {Zarafa.calendar.ui.CalendarMultiView} multiview The Calendar MultiView which fired the event
	 * @param {Zarafa.calendar.AppointmentRecord} record The appointment over which the mouse is moving
	 * @param {Ext.EventObject} event The original event object
	 * @private
	 */
	onAppointmentMouseOver: function(multiview, appointment, event)
	{
		if (this.showTooltip === true) {
			var tooltip = this.view.getTooltipInstance();
			if (tooltip) {
				if ( !Ext.isDefined(tooltip.targetXY) ){
					tooltip.targetXY = event.getXY();
				}

				var id = appointment.store.data.getKey(appointment);
				var title = this.tooltipTitleTpl.apply(appointment.data);
				var text = this.tooltipTextTpl.apply(appointment.data);
				var categories = Zarafa.common.categories.Util.getCategories(appointment);

				// As component ID we use the RecordKey, use the MixedCollection#getKey,
				// as that will generate a fully unique ID in case of recurring series.
				tooltip.show(id, { title: title, text: text, categories: categories }, event);
			}
		}

		this.fireEvent('appointmentmouseover', this, appointment, event);
	},

	/**
	 * Forwards the 'appointmentmouseout' event to the parent calendar panel
	 * @param {Zarafa.calendar.ui.CalendarMultiView} multiview The Calendar MultiView which fired the event
	 * @param {Zarafa.calendar.AppointmentRecord} record The appointment over which the mouse has moved out
	 * @param {Ext.EventObject} event The original event object
	 * @private
	 */
	onAppointmentMouseOut: function(multiview, appointment, event)
	{
		if (this.showTooltip === true) {
			var tooltip = this.view.getTooltipInstance();
			if (tooltip) {
				tooltip.hide(event);
			}
		}

		this.fireEvent('appointmentmouseout', this, appointment, event);
	},

	/**
	 * Event handler which is fired when the {@link Zarafa.calendar.ui.CalendarMultiView#appointmentmove appointmentmove}
	 * event has been fired on the {@link #view}. This will update the start/due date of the given {@link Zarafa.core.data.IPMRecord appointment},
	 * and fire the {@link #beforeappointmentmove} and {@link #appointmentmove} events.
	 *
	 * @param {Zarafa.calendar.ui.CalendarMultiView} multiview The Calendar MultiView which fired the event
	 * @param {Zarafa.calendar.AppointmentRecord} appointment the appointment record that was moved
	 * @param {Zarafa.core.DateRange} dateRange The new daterange for the appointment
	 * @param {Ext.EventObject} event The original event object
	 * @private
	 */
	onAppointmentMove: function(multiview, appointment, dateRange, event)
	{
		if (appointment.isMeetingSent() && !appointment.isAppointmentInPast() && appointment.getMessageAction('send') !== true) {
			this.doAppointmentChangeConfirmation(appointment, dateRange, this.doAppointmentMove);
		} else {
			this.doAppointmentMove(appointment, dateRange);
		}
	},

	/**
	 * This will update the given record with the new startDate object.
	 * This is called by {@link #onAppointmenMove}.
	 * @param {Zarafa.calendar.AppointmentRecord} appointment The appointment which was changed
	 * @param {Zarafa.core.DateRange} dateRange The new daterange for the appointment
	 * @private
	 */
	doAppointmentMove: function(appointment, dateRange)
	{
		if (this.fireEvent('beforeappointmentmove', this, appointment, dateRange) !== false) {
			this.doAppointmentChange(appointment, dateRange);

			if (appointment.isMeetingSent() && !appointment.isAppointmentInPast()) {
				appointment.addMessageAction('send', true);
			}

			appointment.save();
			this.fireEvent('appointmentmove', this, appointment, dateRange);
		}
	},

	/**
	 * Event handler which is fired when the {@link Zarafa.calendar.ui.CalendarMultiView#appointmentresize appointmentresize}
	 * event has been fired on the {@link #view}. This will update the start/due date of the given {@link Zarafa.core.data.IPMRecord appointment},
	 * and fire the {@link #beforeappointmentresize} and {@link #beforeappointmentresize} events.
	 *
	 * @param {Zarafa.calendar.ui.CalendarMultiView} multiview The Calendar MultiView which fired the event
	 * @param {Zarafa.calendar.AppointmentRecord} appointment the appointment record that was resized
	 * @param {Zarafa.core.DateRange} dateRange The dateRange which must be applied to the appointment
	 * @param {Ext.EventObject} event The original event object
	 * @private
	 */
	onAppointmentResize: function(multiview, appointment, dateRange, event)
	{
		if (appointment.isMeetingSent() && !appointment.isAppointmentInPast() && appointment.getMessageAction('send') !== true) {
			this.doAppointmentChangeConfirmation(appointment, dateRange, this.doAppointmentResize);
		} else {
			this.doAppointmentResize(appointment, dateRange);
		}
	},

	/**
	 * This will update the given record with the new {@link Zarafa.core.DateRange dateRange} object.
	 * This is called by {@link #onAppointmentResize}.
	 * @param {Zarafa.calendar.AppointmentRecord} appointment The appointment which was changed
	 * @param {Zarafa.core.DateRange} dateRange The DateRange used to update the record
	 * @private
	 */
	doAppointmentResize: function(appointment, dateRange)
	{
		if (this.fireEvent('beforeappointmentresize', this, appointment, dateRange) !== false) {
			this.doAppointmentChange(appointment, dateRange);

			if (appointment.isMeetingSent() && !appointment.isAppointmentInPast()) {
				appointment.addMessageAction('send', true);
			}

			appointment.save();
			this.fireEvent('appointmentresize', this, appointment, dateRange);
		}
	},

	/**
	 * Called when the time for a meeting has been changed by the user, by a drag & drop action,
	 * e.g. resizing or moving. This will ask the user if he wants to open the given meeting
	 * in a new {@link Zarafa.core.ui.ContentPanel ContentPanel} where he can modify his changes,
	 * or if he wants to directly send an update the attendees.
	 * @param {Zarafa.calendar.AppointmentRecord} appointment The appointment which was changed
	 * @param {Zarafa.core.DateRange} dateRange The daterange indicating the new time/duration
	 * @param {Function} changeCallback The callback function which should be called if the
	 * user wants to change the meeting immediately without a ContentPanel
	 * @private
	 */
	doAppointmentChangeConfirmation: function(appointment, dateRange, changeCallback)
	{
		Zarafa.common.dialogs.MessageBox.select(
			_('Update recipients'),
			_('The time of the meeting has changed. What would you like to do?'),
			function(button, select) {
				if (button == 'ok') {
					if (select.id == 'review_changes') {
						// The user has selected the option to review the changes in a ContentPanel.
						var tasks = [];
						tasks.push({
							fn: function(panel, contentRecord, task, callback) {
								var newDateRange = dateRange.clone();
								if (!contentRecord.isOpened()) {
									var fn = function(store, recordFromStore) {
										if (recordFromStore === contentRecord) {
											store.un('open', fn, task);
											this.doAppointmentChange(contentRecord, newDateRange);
											callback();
										}
									};

									// We defer the callback function by 1 ms, to get out of the
									// 'write' event loop which indicates that the data from the
									// server was obtained. During that loop all other stores
									// will be modified as well, and we don't want our changes
									// to be applied to any other store then the current.
									contentRecord.getStore().on('open', fn, this, { delay: 1 });
								} else {
									this.doAppointmentChange(contentRecord, newDateRange);
									callback();
								}
							},
							scope: this
						});
						var config = {
							recordComponentPluginConfig: {
								loadTasks: tasks
							}
						};
						Zarafa.calendar.Actions.openMeetingRequestContent(appointment, config);
					} else {
						// The user wants to directly save the changes
						changeCallback.call(this, appointment, dateRange);
					}
				}
			},
			this,
			[{
				boxLabel: _('Save changes and send an update to all recipients'),
				id: 'send_update',
				name: 'select',
				checked: true
			},{
				boxLabel: _('Open meeting request with changes'),
				id: 'review_changes',
				name: 'select'
			}]
		);
	},

	/**
	 * This will update the given record with the new {@link Zarafa.core.DateRange dateRange} object.
	 * @param {Zarafa.core.data.IPMRecord} record The record which must be updated
	 * @param {Zarafa.core.DateRange} dateRange The DateRange used to update the record
	 * @private
	 */
	doAppointmentChange: function(record, dateRange)
	{
		record.beginEdit();

		var startDate = dateRange.getStartDate();
		var dueDate = dateRange.getDueDate();

		record.set('alldayevent', dateRange.isAllDay());
		record.set('startdate', startDate, true);	// Force the change to PHP
		record.set('duedate', dueDate, true);		// Force the change to PHP
		record.set('commonstart', startDate);
		record.set('commonend', dueDate);

		// Appointment durations are stored in minutes.
		record.set('duration', dateRange.getDuration(Date.MINUTE));

		// update reminder times as well
		if (record.get('reminder') === true) {
			record.set('reminder_time', startDate);
			record.set('flagdueby', startDate.add(Date.MINUTE, -record.get('reminder_minutes')));
		}

		// set delegate properties if needed
		if(!record.userIsStoreOwner()) {
			var storeRecord = container.getHierarchyStore().getById(record.get('store_entryid'));
			if(storeRecord) {
				record.setDelegatorInfo(storeRecord, true);
			}
		}

		record.endEdit();
	},

	/**
	 * Event handler which is fired when the {@link Zarafa.calendar.ui.CalendarMultiView#appointmentcreate appointmentcreate}
	 * event has been fired on the {@link #view}. This will update the start/due date of the given {@link Zarafa.core.data.IPMRecord appointment},
	 * and fire the {@link #beforeappointmentcreate} and {@link #beforeappointmentcreate} events.
	 *
	 * @param {Zarafa.calendar.ui.CalendarMultiView} multiview The Calendar MultiView which fired the event
	 * @param {Zarafa.core.IPMFolder} folder The folder in which the appointment must be created
	 * @param {Zarafa.core.DateRange} dateRange The dateRange which must be applied to the appointment
	 * @param {String} text The subject for the new appointment
	 * @private
	 */
	onAppointmentCreate: function(multiview, folder, dateRange, text)
	{
		if (this.fireEvent('beforeappointmentcreate', this, folder, dateRange, text) !== false) {

			this.model.createRecord(function(record){
				record.set('subject', text);

				this.store.add(record);
				record.save();

				this.fireEvent('appointmentcreate', this, folder, record);
			}.createDelegate(this, [folder, text], true), folder, dateRange);
		}
	},

	/**
	 * Event handler for the {@link Zarafa.calendar.ui.CalendarMultiView#appointmentinitdrag} event
	 * This is initiated from {@link Zarafa.calendar.ui.CalendarViewDragZone} and passed through
	 * the {@link Zarafa.calendar.ui.AbstractCalendarDaysView individual calendar} and the {@link Zarafa.calendar.ui.CalendarMultiView mutliview}
	 *
	 * @param {Zarafa.calendar.ui.CalendarMultiView} multiview The Calendar MultiView which fired the event
	 * @param {Ext.EventObject} event The mouse event
	 * @param {Zarafa.calendar.ui.AppointmentView} The appointment on which the event occurred
	 */
	onAppointmentInitDrag: function(multiview, event, appointment)
	{
		if (this.showTooltip === true) {
			var tooltip = multiview.getTooltipInstance();
			if (tooltip) {
				tooltip.hide();
			}
		}
	},

	/**
	 * Event handler for the {@link Zarafa.calendar.ui.CalendarMultiView#appointmentenddrag} event
	 * This is initiated from {@link Zarafa.calendar.ui.CalendarViewDragZone} and passed through
	 * the {@link Zarafa.calendar.ui.AbstractCalendarDaysView individual calendar} and the {@link Zarafa.calendar.ui.CalendarMultiView mutliview}
	 *
	 * @param {Zarafa.calendar.ui.CalendarMultiView} multiview The Calendar MultiView which fired the event
	 * @param {Ext.EventObject} event The mouse event
	 * @param {Zarafa.calendar.ui.AppointmentView} The appointment on which the event occurred
	 */
	onAppointmentEndDrag: Ext.emptyFn,

	/**
	 * Called when the component is rendered.
	 * @param {Ext.Element} container The container in which the panel is being rendered
	 * @private
	 */
	onRender: function(container)
	{
		// Parent render.
		Zarafa.calendar.ui.CalendarPanel.superclass.onRender.apply(this, arguments);

		// Initialise and render the view.
		this.view.render(this.body);
	},

	/**
	 * Event handler which is fired when the panel has been resized.
	 * @private
	 */
	onResize: function()
	{
		Zarafa.calendar.ui.CalendarPanel.superclass.onResize.apply(this, arguments);

		if (this.rendered) {
			this.view.layout();
		}
	},

	/**
	 * Event handler which is fired when the panel has been rendered
	 * @private
	 */
	afterRender: function()
	{
		Zarafa.calendar.ui.CalendarPanel.superclass.afterRender.apply(this, arguments);
		this.loadMask = new Zarafa.common.ui.LoadMask(this.body, {msg: this.loadMaskText});
		this.view.layout();

		Zarafa.core.KeyMapMgr.activate(this, 'view.mapimessage');
	},

	/**
	 * Returns the calendar's CalendarView object.
	 * @return {Zarafa.calendar.ui.CalendarMultiView} The calendar view
	 */
	getView: function()
	{
		return this.view;
	},

	/**
	 * Gets the calendar's selection model. This can be used to listen for selection and de-selection of appointments,
	 * and to get a list of currently selected appointments.
	 * @return {Zarafa.calendar.ui.AppointmentSelectionModel} the calendar's selection model.
	 */
	getSelectionModel: function()
	{
		return this.selectionModel;
	},

	/**
	 * Gets the range selection model. Can be used to listen for selection and de-selection of date ranges in the calendar,
	 * and to get the currently selected range.
	 * @return {Zarafa.calendar.ui.DateRangeSelectionModel} date range selection model.
	 */
	getRangeSelectionModel: function()
	{
		return this.rangeSelectionModel;
	},

	/**
	 * Bind the given store to this view. This will add listeners for the
	 * {@link Ext.data.Store#beforeload}, {@link Ext.data.Store#load} and {@link Ext.data.Store#exception} events.
	 * @param {Zarafa.core.data.IPMStore} store The store to bind
	 */
	bindStore: function(store)
	{
		this.store = store;

		if (Ext.isDefined(store)) {
			this.mon(store, {
				'beforeload': this.onBeforeLoad,
				'load': this.onLoad,
				'exception': this.onLoadException,
				'notify': this.onStoreNotify,
				scope: this
			});
		}

		this.view.bindStore(store);
	},

	/**
	 * Release a store previously bound using {@link #bindStore}, this will
	 * unhook all event listeners.
	 * @param {Zarafa.core.data.IPMStore} store The store to release
	 */
	releaseStore: function(store)
	{
		if (!Ext.isDefined(store)) {
			this.mun(store, {
				'beforeload': this.onBeforeLoad,
				'load': this.onLoad,
				'exception': this.onLoadException,
				'notify': this.onStoreNotify,
				scope: this
			});
		}

		this.view.releaseStore(store);
		this.store = undefined;
	},

	/**
	 * Event handler which is fired before the {@link #store} begins loading new
	 * data from the server.
	 * @private
	 */
	onBeforeLoad: function()
	{
		if (this.loadMask) {
			this.loadMask.show();
		}
	},

	/**
	 * Event handler which is fired after the {@link #store} has loaded new
	 * data from the server.
	 * @private
	 */
	onLoad: function()
	{
		if (this.loadMask) {
			this.loadMask.hide();
		}
	},

	/**
	 * Event handler which is fired after the {@link #store} has encountered
	 * an exception while loading data from the server.
	 * @private
	 */
	onLoadException: function()
	{
		if (this.loadMask) {
			this.loadMask.hide();
		}
	},

	/**
	 * Event handler which is fired when the {@link #store} fires the
	 * {@link Zarafa.core.data.IPMStore#notify 'notify'} event. If this
	 * is a {@link Zarafa.core.data.Notifications#objectCreated objectCreated}
	 * notification, this will {@link Zarafa.calendar.ui.DateRangeSelectionModel#clearSelections clear}
	 * the {@link #rangeSelectionModel}.
	 * @param {Zarafa.core.data.IPMStore} store The store which fired the event
	 * @param {Zarafa.core.data.Notifications} notification 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 received 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 received.
	 * @private
	 */
	onStoreNotify: function(store, action, records, data, timestamp, success)
	{
		var selectionModel = this.getRangeSelectionModel();

		if (!Ext.isEmpty(records) && selectionModel.isActive() && action === Zarafa.core.data.Notifications.objectCreated) {
			var selection = selectionModel.getDateRange();
			var record = records[0];

			// Check if the current selection matches the new record
			if (selection.getStartTime() === record.get('startdate').getTime() && selection.getDueTime() === record.get('duedate').getTime()) {
				selectionModel.clearSelections();
			}
		}
	},

	/**
	 * Function which is used to paste the copied item into calendar. If copied item 
	 * is private and paste this item in shared or public store then 
	 * show {@link Zarafa.common.dialogs.MessageBox#addCustomButtons message box} to intimate that
	 * new copy will no more private item.
	 *
	 * @param {Zarafa.core.data.IPMRecord} clipBoardRecord copied calendar item which will paste in calendar view.
	 * @private
	 */
	doPaste: function(clipBoardRecord)
	{
		var record = this.createRecordCopy(clipBoardRecord);
		var store = container.getHierarchyStore().getById(record.get("store_entryid"));

		if (clipBoardRecord.isPrivate() && (store.isPublicStore() || store.isSharedStore())){
			var msg = _("Copying this appointment will unset it as private. Are you sure you want to continue?");
			Zarafa.common.dialogs.MessageBox.addCustomButtons({
				title: _('Private item'),
				msg: msg,
				cls: Ext.MessageBox.WARNING_CLS,
				width: 400,
				fn: function(button, clipBoardRecord, record) {
					if (button === 'cancel') {
						return;
					}
					record.unsetPrivate();
					record.beginEdit();
					record.set('private', false);
					record.endEdit();

					this.paste(clipBoardRecord, record);
				}.createDelegate(this, [clipBoardRecord, record], true),
				customButton: [{
					text: _('Copy'),
					name: 'copy'
				},{
					text: _('Cancel'),
					name: 'cancel'
				}],
				scope: this
			});
			return;
		}

		this.paste(clipBoardRecord, record);
	},

	/**
	 * Function used to set MessageAction and save the record in store.
	 *
	 * @param {Zarafa.core.data.IPMRecord} clipBoardRecord copied calendar item which will paste in calendar view.
	 * @param {Zarafa.core.data.IPMRecord} record new calendar item which will paste in calendar view.
	 * @private
	 */
	paste : function(clipBoardRecord, record)
	{
		// Added source record info in message action. which used to
		// copy attachments and recipients related information from source message to
		// new pasted record.
		record.addMessageAction("source_entryid", clipBoardRecord.get('entryid'));
		record.addMessageAction("source_store_entryid", clipBoardRecord.get('store_entryid'));
		record.addMessageAction("paste", true);

		var store = this.model.store;
		if(record.get('recurring')) {
			Zarafa.common.Actions.openRecurrenceContent(record, {
				store: store,
				pasteItem: true
			});
		} else {
			store.add(record);
			store.save(record);
		}
	},

	/**
	 * Function which is used to generate new date range as per the user selected in
	 * calendar view.
	 *
	 * @param {Zarafa.core.data.IPMRecord} copiedRecord copied calendar item.
	 * @return {Zarafa.core.DateRange} dateRange new date range.
	 * @private
	 */
	getNewDateRange: function(copiedRecord)
	{
		var views = this.getView();
		var dateModelType = this.model.getCurrentDataMode();
		var calendarView = views.getCalendarViewByFolder(this.model.getDefaultFolder());

		var dateRange = calendarView.selectionView.getDateRange();
		var copyStartDate = copiedRecord.get('startdate');
		var copyDueDate = copiedRecord.get('duedate');

		if(dateModelType === Zarafa.calendar.data.DataModes.MONTH) {
			// Selected only one day box in month view.
			if(dateRange.getNumDays() === 1) {
				var startDate = dateRange.getStartDate();

				startDate = startDate.add(Date.HOUR,copyStartDate.getHours());
				startDate = startDate.add(Date.MINUTE,copyStartDate.getMinutes());

				var diffTime = Date.diff(Date.MILLI, copyDueDate, copyStartDate);
				var dueDate = (startDate.getTime() / 1000) + (diffTime/1000);
				dateRange.set(startDate, new Date(dueDate * 1000), true, true);
			}
		} else {
			// If dateRange is undefined it means user pasting appointment
			// at the same place where copied appointment currently located (with same date and duration).
			if (!Ext.isDefined(dateRange)) {
				dateRange = new Zarafa.core.DateRange();
				dateRange.set(copyStartDate, copyDueDate, true, true);
			} else if(!dateRange.isAllDay()) {
				// If date range is not all day then we prepare new date range
				// with copied appointment duration.
				var startDate = dateRange.getStartDate();
				var diffTime = Date.diff(Date.MILLI, copyDueDate, copyStartDate);
				if(copiedRecord.get('alldayevent')) {
					startDate.clearTime();
					dateRange.setDueDate(startDate.add(Date.HOUR,24));
				} else if (diffTime !== Date.dayInMillis) {
					var dueDate = (startDate.getTime() / 1000) + (diffTime/1000);
					dateRange.setDueDate(new Date(dueDate * 1000), true, true);
				}
			}
		}
		return dateRange;
	},

	/**
	 * Function which is used to create new copy of record from original record
	 * with some updated information like date range, recurring pattern etc.
	 *
	 * @param {Zarafa.core.data.IPMRecord} copiedRecord copied calendar item.
	 * @return {Zarafa.calendar.AppointmentRecord} record which is going to paste in calendar.
	 * @private
	 */
	createRecordCopy: function (copiedRecord)
	{
		var record = this.model.createRecord(undefined, undefined, this.getNewDateRange(copiedRecord));
		var remainder = copiedRecord.get('reminder');

		// Outlook add's this 0x00000008 and 0x00000080 flags along with auxApptFlagCopied in
		// auxiliary_flags(value is 137), As of now we are not able to figure it out what
		// it is, so for now we follow Ol and added this flags. This flag is used to distinguish
		// between original and copied appointment/meeting record in calendar.
		var auxiliaryFlags = Zarafa.core.mapi.AppointmentAuxiliaryFlags.auxApptFlagCopied | 0x00000008 | 0x00000080;
		Ext.apply(record.data, {
			'subject': !copiedRecord.isCopied() ? _('Copy')+": "+copiedRecord.get('subject') : copiedRecord.get('subject'),
			'body': copiedRecord.get('body'),
			'location': copiedRecord.get('location'),
			'importance': copiedRecord.get('importance'),
			'label': copiedRecord.get('label'),
			'private': copiedRecord.get('private'),
			'busystatus': copiedRecord.get('busystatus'),
			'reminder': remainder,
			'categories': copiedRecord.get('categories'),
			'auxiliary_flags': auxiliaryFlags
		});

		if (remainder) {
			Ext.apply(record.data, {
				'reminder_minutes': copiedRecord.get('reminder_minutes'),
				'reminder_time': copiedRecord.get('reminder_time')
			});
		}

		// Copy all attachment from original message.
		var store = record.getAttachmentStore();
		var origStore = copiedRecord.getAttachmentStore();
		origStore.each(function (attach) {
			store.add(attach.copy());
		}, this);

		if(copiedRecord.isMeeting()) {
			this.copyMeetingProps(record, copiedRecord);
		}

		if (copiedRecord.get('recurring')) {
			this.copyRecurringProps(record, copiedRecord);
		}
		return record;
	},

	/**
	 * Function which copy necessary recurring properties of recurring appointment/meeting.
	 *
	 * @param {Zarafa.core.data.IPMRecord} record A new Meeting record
	 * @param {Zarafa.core.data.IPMRecord} copiedRecord Copy of original recurring appointment/meeting record
	 * @private
	 */
	copyRecurringProps: function (record, copiedRecord)
	{
		if(copiedRecord.get('alldayevent')) {
			record.get('startdate').setHours(12);
		}
		var startDate = record.get('startdate');
		var startOcc = 0;
		if (!record.get('alldayevent')) {
			startOcc = (startDate.getHours() * 60) + startDate.getMinutes();
		}

		Ext.apply(record.data , {
			'recurring': true,
			'recurrence_start': startDate,
			'recurrence_end': startDate,
			'recurrence_endocc': startOcc + record.get('duration'),
			'recurrence_startocc': startOcc,
			'recurrence_subtype': copiedRecord.get('recurrence_subtype'),
			'recurrence_term': copiedRecord.get('recurrence_term'),
			'recurrence_type': copiedRecord.get('recurrence_type'),
			'recurrence_weekdays': copiedRecord.get('recurrence_weekdays'),
			'recurrence_regen': copiedRecord.get('recurrence_regen'),
			'recurrence_numoccur': copiedRecord.get('recurrence_numoccur'),
			'recurrence_numexceptmod': copiedRecord.get('recurrence_numexceptmod'),
			'recurrence_numexcept': copiedRecord.get('recurrence_numexcept'),
			'recurrence_everyn': copiedRecord.get('recurrence_everyn'),
			'recurrence_month': copiedRecord.get('recurrence_month'),
			'recurrence_monthday': parseInt(startDate.format('d'),10),
			'recurrence_nday': copiedRecord.get('recurrence_nday')
		});
		record.set('recurring_pattern', record.generateRecurringPattern());
		record.updateTimezoneInformation();
	},

	/**
	 * Function which apply meeting related properties from copy of an original record to
	 * record which is going to paste in calendar.
	 *
	 * @param {Zarafa.core.data.IPMRecord} record New meeting record
	 * @param {Zarafa.core.data.IPMRecord} copiedRecord Copy of original recurring appointment/meeting record
	 * @private
	 */
	copyMeetingProps: function (record, copiedRecord)
	{
		if (copiedRecord.isMeetingOrganized()) {
			var store = record.getRecipientStore();
			var origStore = copiedRecord.getRecipientStore();
			origStore.each(function (recipient) {
				store.add(recipient.copy());
			}, this);
			Ext.apply(record.data, {
				'meeting': copiedRecord.get('meeting'),
				'responsestatus': Zarafa.core.mapi.ResponseStatus.RESPONSE_ORGANIZED,
				'sender_address_type': copiedRecord.get('sender_address_type'),
				'sender_email_address': copiedRecord.get('sender_email_address'),
				'sender_entryid': copiedRecord.get('sender_entryid'),
				'sender_name': copiedRecord.get('sender_name'),
				'sender_presence_status': copiedRecord.get('sender_presence_status'),
				'sender_search_key': copiedRecord.get('sender_search_key'),
				'sender_username': copiedRecord.get('sender_username'),
				'sent_representing_address_type': copiedRecord.get('sent_representing_address_type'),
				'sent_representing_email_address': copiedRecord.get('sent_representing_email_address'),
				'sent_representing_entryid': copiedRecord.get('sent_representing_entryid'),
				'sent_representing_name': copiedRecord.get('sent_representing_name'),
				'sent_representing_presence_status': copiedRecord.get('sent_representing_presence_status'),
				'sent_representing_search_key': copiedRecord.get('sent_representing_search_key'),
				'sent_representing_username': copiedRecord.get('sent_representing_username'),
				'reply_name': copiedRecord.get('reply_name')
			});
		} else if(copiedRecord.isMeetingReceived()) {
			Ext.apply(record.data, {
				'meeting': Zarafa.core.mapi.MeetingStatus.MEETING,
				'responsestatus': Zarafa.core.mapi.ResponseStatus.RESPONSE_ORGANIZED,
				'sensitivity': copiedRecord.get('sensitivity')
			});
		}
	},

	/**
	 * Called when the calendar is being destroyed, this will also
	 * destroy the {@link #view}.
	 */
	destroy: function()
	{
		this.view.destroy();
		// super class destroy
		Zarafa.calendar.ui.CalendarPanel.superclass.destroy.apply(this, arguments);
	}
});

Ext.reg('zarafa.calendarpanel', Zarafa.calendar.ui.CalendarPanel);
Ext.namespace('Zarafa.calendar.ui');

/**
 * @class Zarafa.calendar.ui.CalendarViewDragZone
 * @extends Ext.dd.DragZone
 *
 * The special DragZone which enables appointments to be dragged
 * from a calendar to a {@link Ext.dd.DropZone}. This class supports
 * dragging and resizing of appointments, but also selecting a
 * daterange in the calendar.
 */
Zarafa.calendar.ui.CalendarViewDragZone = Ext.extend(Ext.dd.DragZone, {
	/**
	 * The calendar on which this DragZone is installed
	 * @property
	 * @type Zarafa.calendar.ui.AbstractCalendarView
	 * @private
	 */
	calendar: undefined,

	/**
	 * The special Element which is displayed when dragging data
	 * over the DragZone.
	 * @property
	 * @type Ext.Element
	 * @private
	 */
	ddel: undefined,

	/**
	 * @cfg {Boolean} headerMode True of this DropZone is installed on the header of
	 * the calendar, or in the body. This determines if the {@link Zarafa.calendar.ui.AbstractCalendarView#header}
	 * or {@link Zarafa.calendar.ui.AbstractCalendarView#body} will be used to connect the event handlers.
	 */
	headerMode: false,

	/**
	 * @constructor
	 * @param {Zarafa.calendar.ui.AbstractCalendarView} calendar The calendar on which this Dragzone is installed
	 * @param {Object} config Configuration object
	 */
	constructor: function(calendar, config)
	{
		config = config || {};

		this.calendar = calendar;

		var element = this.calendar.body;
		if (config.headerMode === true) {
			element = this.calendar.header;
		}

		Ext.applyIf(config, {
			ddGroup: 'AppointmentDD'
		});

		Zarafa.calendar.ui.CalendarViewDragZone.superclass.constructor.call(this, element, config);

		this.ddel = document.createElement('div');
		this.ddel.className = 'x-data-dd-wrap';
	},

	/**
	 * <p>The provided implementation of the getDragData method which collects the data to be dragged from the DataView on mousedown.</p>
	 * <p>This data is available for processing in the {@link Ext.dd.DropZone#onNodeEnter onNodeEnter}, {@link Ext.dd.DropZone#onNodeOver onNodeOver},
	 * {@link Ext.dd.DropZone#onNodeOut onNodeOut} and {@link Ext.dd.DropZone#onNodeDrop onNodeDrop} methods of a cooperating {@link Ext.dd.DropZone DropZone}.</p>
	 * <p>The data object contains the following properties:<ul>
	 * <li><b>calendar</b>: Zarafa.calendar.ui.AbstractCalendarView<div class="sub-desc">The CalendarView from which the data is being dragged.</div></li>
	 * <li><b>ddel</b>: htmlElement<div class="sub-desc">An htmlElement which provides the "picture" of the data being dragged.</div></li>
	 * <li><b>selections</b>: Array<div class="sub-desc">An Array of the selected Records which are being dragged from the DataView.</div></li>
	 * <li><b>target</b: Zarafa.calendar.ui.AppointmentView<div class="sub-desc">The appointment on which the drag action started.</div></li>
	 * </ul></p>
	 * @private
	 */
	getDragData: function(e)
	{
		var sm = this.calendar.selectionModel;
		var origEvent = new Ext.EventObjectImpl(e);
		var appointment = this.getAppointmentFromEvent(e);
		var state = this.getDragStateFromEvent(e, appointment);

		return {
			calendar: this.calendar,
			ddel: this.ddel,
			selections: sm.getSelections(),
			target: appointment,
			state: state,
			origEvent: origEvent
		};
	},

	/**
	 * This function is provided so that it is possible to perform a custom action before the initial
	 * drag event begins and optionally cancel it.
	 * @param {Object} data An object containing arbitrary data to be shared with drop targets
	 * @param {Event} e The event object
	 * @return {Boolean} isValid True if the drag event is valid, else false to cancel
	 * @protected
	 */
	onBeforeDrag: function(data, e)
	{
		var selectedAppointment = data.target;

		// We don't allow dragging of private appointments.
		if (selectedAppointment && selectedAppointment.getRecord().get('access') === 0) {
			return false;
		}

		return true;
	},

	/**
	 * Called before the user starts dragging an item.
	 * @param {Number} x The x coordinate from where the dragging started
	 * @param {Number} y The y coordinate from where the dragging started
	 * @private
	 */
	b4StartDrag: function(x, y)
	{
		Zarafa.calendar.ui.CalendarViewDragZone.superclass.b4StartDrag.apply(this, arguments);

		// ExtJs will make the proxy visible at the end of the b4StartDrag function, but
		// if our parent has a DropZone installed, we hide our proxy by default, as
		// the CalendarViewDropZone will have a custom proxy installed.
		if (this.calendar.enableDD || this.calendar.enableDrop) {
			this.proxy.hide();
		}
	},

	/**
	 * Called when the user starts dragging an item.
	 * @param {Number} x The x coordinate from where the dragging started
	 * @param {Number} y The y coordinate from where the dragging started
	 * @private
	 */
	startDrag: function(x, y)
	{
		Zarafa.calendar.ui.CalendarViewDragZone.superclass.startDrag.apply(this, arguments);

		// ExtJs will make the proxy visible at the end of the startDrag function, but
		// if our parent has a DropZone installed, we hide our proxy by default, as
		// the CalendarViewDropZone will have a custom proxy installed.
		if (this.calendar.enableDD || this.calendar.enableDrop) {
			this.proxy.hide();
		}
	},

	/**
	 * <p>The provided implementation of the onInitDrag method. Sets the <tt>innerHTML</tt> of the drag proxy which provides the "picture"
	 * of the data being dragged.</p>
	 * <p>The <tt>innerHTML</tt> data is found by calling the owning GridPanel's {@link Ext.grid.GridPanel#getDragDropText getDragDropText}.</p>
	 * @param {Number} x The x coordinate from where the dragging started
	 * @param {Number} y The y coordinate from where the dragging started
	 * @return {Boolean} true to continue the drag, false to cancel
	 * @private
	 */
	onInitDrag: function(x, e)
	{
		var data = this.dragData;

		// Check if the dragData contains a origEvent and target (as initialized by getDragData).
		// If this is present, we have to manually call the onMouseDown event handler
		// on the view to ensure that the item we are about to drag is marked as selected.
		//
		// We can't do that in getDragData because of the ordering of the event handlers
		// of both this class as well as the DataView.
		if (data && data.origEvent) {
			var selectedAppointment = data.target;
			var sm = this.calendar.selectionModel;

			if (selectedAppointment) {
				this.calendar.onInitDrag(data.origEvent, selectedAppointment);
			}

			if (!selectedAppointment || !sm.isSelected(selectedAppointment.getRecord())) {
				this.calendar.onMouseDown(data.origEvent, selectedAppointment);

				// The selection has been changed, update the selections array
				data.selections = sm.getSelections();
			}

			delete data.origEvent;
		}

		this.ddel.innerHTML = this.calendar.getDragDropText();
		this.proxy.update(this.ddel);
		// fire start drag?
	},

	/**
	 * An empty function by default, but provided so that you can perform a custom action before the dragged
	 * item is dragged out of the target without dropping, and optionally cancel the onDragOut.
	 * @param {Ext.dd.DragDrop} target The drop target
	 * @param {Event} e The event object
	 * @param {String} id The id of the dragged element
	 * @return {Boolean} isValid True if the drag event is valid, else false to cancel
	 */
	beforeDragOut: function(target, e, id)
	{
		// If we are selecting a range, we want to preserve our drag action,
		// this allows the user to shortly exit the browser while dragging while
		// maintaining he selection.
		return this.dragData.state !== Zarafa.calendar.data.DragStates.SELECTING;
	},

	/**
	 * Event handler that fires when a drag/drop obj gets a mouseup
	 *
	 * When the {@link #dragData} still contains the 'origEvent' field, then this
	 * function was called without that anything has been dragged (normally 'origEvent')
	 * is cleared in {@link #onInitDrag}. In this case we call {@link Zarafa.calendar.ui.AbstractCalendarView#handleMouseDown}
	 * to update the selection inside the calendar.
	 *
	 * @param {Event} e the mouseup event
	 */
	onMouseUp: function(e)
	{
		var data = this.dragData;

		// If the dragData still contains the origEvent and target, then onInitDrag has not been
		// called which means the user only clicked on the row and we must update
		// our selection.
		if (data && data.origEvent) {
			this.calendar.onMouseDown(data.origEvent, data.target);

			delete data.origEvent;
		}

		this.hideProxy();
	},

	/**
	 * An empty immplementation. Implement this to provide behaviour after a repair of an invalid drop. An implementation might highlight
	 * the selected rows to show that they have not been dragged.
	 * @private
	 */
	afterRepair: function()
	{
		this.dragging = false;
	},

	/**
	 * <p>An empty implementation. Implement this to provide coordinates for the drag proxy to slide back to after an invalid drop.</p>
	 * <p>Called before a repair of an invalid drop to get the XY to animate to.</p>
	 * @param {EventObject} e The mouse up event
	 * @return {Array} The xy location (e.g. [100, 200])
	 * @private
	 */
	getRepairXY: function(e, data)
	{
		return false;
	},

	/**
	 * Called when the dragging has ended. This will call the
	 * {@link Zarafa.calendar.ui.AbstractCalendarView#onMouseUp}
	 * on the {@link #calendar} to which the dragged appointment belongs.
	 *
	 * @param {Object} data The data which was used during the drag
	 * @param {Ext.EventObject} e The event object
	 * @private
	 */
	onEndDrag: function(data, e)
	{
		var appointment = data.target;
		this.calendar.onEndDrag(e, appointment);
		this.calendar.onMouseUp(e, appointment);
	},

	/**
	 * Called when an item has been dropped
	 * @param {Ext.dd.DropZone} dd The zone in which the item was dropped
	 * @param {Ext.EventObject} e The event object
	 * @param {String} id The id of the Ext.Element where the item was dropped
	 * @private
	 */
	onValidDrop: function(dd, e, id)
	{
		// fire drag drop?
		this.hideProxy();
	},

	/**
	 * Called when an item has been dropped on an invalid location
	 * @param {Ext.EventObject} e The event object
	 * @param {String} id The id of the Ext.Element where the item was dropped
	 * @private
	 */
	beforeInvalidDrop: function(e, id)
	{
	},

	/**
	 * Search for the {@link Zarafa.calendar.ui.AppointmentView appointment} on which the
	 * given {@link Ext.EventObject event} tool place. This requires {@link Zarafa.calendar.ui.AppointmentView#eventOverBody}
	 * to be true when {@link #headerMode} is false, otherwise {@link Zarafa.calendar.ui.AppointmentView#eventOverHeader} must
	 * be true.
	 * @param {Ext.EventObject} e The event from where we obtain the appointment
	 * @return {Zarafa.calendar.ui.AppointmentView} The appointment on which the event occurred
	 * @private
	 */
	getAppointmentFromEvent: function(e)
	{
		var appointments = this.calendar.appointments;

		for (var i = 0, len = appointments.length; i < len; i++) {
			var app = appointments[i];

			if (this.headerMode !== true) {
				if (app.eventOverBody(e)) {
					return app;
				}
			} else {
				if (app.eventOverHeader(e)) {
					return app;
				}
			}
		}

		return undefined;
	},

	/**
	 * This function checks over which part of the appointment the event took place,
	 * and will return the appropriate {@link Zarafa.calendar.data.DragStates} value.
	 * @param {Ext.EventObject} e The event
	 * @param {Zarafa.calendar.ui.AppointmentView} appointment The appointment to check
	 * @return {Zarafa.calendar.data.DragStates} The Drag State for the current action
	 * @private
	 */
	getDragStateFromEvent: function(e, appointment)
	{
		if (this.headerMode !== true) {
			if (!Ext.isDefined(appointment)) {
				return Zarafa.calendar.data.DragStates.SELECTING;
			} else if (appointment.eventOverBodyStartHandle(e)) {
				return Zarafa.calendar.data.DragStates.RESIZING_START;
			} else if (appointment.eventOverBodyDueHandle(e)) {
				return Zarafa.calendar.data.DragStates.RESIZING_DUE;
			} else {
				return Zarafa.calendar.data.DragStates.DRAGGING;
			}
		} else {
			if (!Ext.isDefined(appointment)) {
				return Zarafa.calendar.data.DragStates.SELECTING;
			} else if (appointment.eventOverHeaderStartHandle(e)) {
				return Zarafa.calendar.data.DragStates.RESIZING_START;
			} else if (appointment.eventOverHeaderDueHandle(e)) {
				return Zarafa.calendar.data.DragStates.RESIZING_DUE;
			} else {
				return Zarafa.calendar.data.DragStates.DRAGGING;
			}
		}
	}
});
/**
 * This file defines the color fields for color schemes that we need for
 * the calendar context.
 */

// Add the fields we need for the calendar colors
// TODO: Gradients are removed by setting the start and end colors to the
// same value. So we now have more color fields than we need. These can
// be removed.
// TODO: The names of the fields are not always very well chosen. They could
// be changed to be more descriptive.
Zarafa.core.ColorSchemes.addField([
	{
		name: 'header',
		weight: 1
	},
	{
		name: 'border',
		weight: 1.80
	},
	{
		name: 'borderInner',
		weight: 1.42
	},
	{
		name: 'startcolorappointment',
		weight: 1
	},
	{
		name: 'endcolorappointment',
		weight: 1
	},
	{
		name: 'startcolorappointmentbox',
		weight: 1.29
	},
	{
		name: 'endcolorappointmentbox',
		weight: 1.29
	},
	{
		name: 'stripnormal',
		weight: 1.86
	},
	{
		name: 'stripworking',
		color: '#ffffff'
	},
	{
		name: 'linenormal',
		weight: 1.73
	},
	{
		name: 'hourline',
		weight: 1.33
	}
]);
Ext.namespace('Zarafa.calendar.ui');
/**
* @class Zarafa.calendar.ui.DatePicker
* @extends Ext.DatePicker
* @xtype zarafa.datepicker
* This overridden DatePicker takes the default calendar folder of the user, and displays days in which there are appointments differently.
* Currently the x-date-busy CSS class is applied to these days.
*/
Zarafa.calendar.ui.DatePicker = Ext.extend(Ext.DatePicker, {
	/**
	 * A {@link Zarafa.calendar.data.busytime.BusyTimeStore} store used for keeping appointments (containing only time and busy status)
	 * @property
	 * @type Zarafa.calendar.data.busytime.BusyTimeStore
	 * @private
	 */
	store: undefined,

	/**
	 * Beginning of the current month
	 * @property
	 * @type Date
	 * @private
	 */
	monthStart: undefined,

	/**
	 * End of the current month
	 * @property
	 * @type Date
	 * @private
	 */
	monthEnd: undefined,

	/**
	 * constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};
		if(!config.store){
			config.store = new Zarafa.calendar.data.busytime.BusyTimeStore();
		}
		Zarafa.calendar.ui.DatePicker.superclass.constructor.call(this, config);

		// When the component is destroyed, we have to destroy the store
		this.on('destroy', this.store.destroy, this.store);

		// Set timer to change highlighted date in date picker
		// after date change during the session
		this.setNextDayTimer();
	},

	/**
	 * function called after the component has been rendered (right before the 'afterrender' event has fired)
	 * calls {@link #reloadStore}, which loads data from the store
	 * @private
	 */
	afterRender: function()
	{
		Zarafa.calendar.ui.DatePicker.superclass.afterRender.call(this);

		// Attach event handlers
		// When store has finished loading, apply data from newly loaded appointments
		this.mon(this.store, 'load', this.onLoad, this);
	},

	/**
	 * used as a handler for the 'load' event. Invoked when the store has finished loading.
	 * @param {Zarafa.calendar.data.busytime.BusyTimeStore} store The store bound to this datepicker
	 * @param {Ext.data.Record[]} records an array of records loaded
	 * @param {Object} options The loading options specified
	 * @private
	 */
	onLoad: function(store, records, options)
	{
		this.clearCells();
		if (!this.settingIsSet()) {
			return false;
		}

		for (var i = 0, len = this.cells.elements.length; i < len; i++) {
			var cell = Ext.get(this.cells.elements[i]);
			var pickerDate = cell.dom.firstChild.dateValue;
			var foundAppt = false;

			for(var j = 0, len2 = records.length; j < len2; j++){
				var record = records[j];
				var storeDate = record.get('startdate').clearTime(true).getTime();
				if(pickerDate == storeDate && record.get('busystatus') != Zarafa.core.mapi.BusyStatus['FREE']){
					foundAppt=true;
					break;
				}
			}
			if (foundAppt) {
				cell.addClass('x-date-busy');
			} else {
				cell.removeClass('x-date-busy');
			}
		}
	},

	/**
	 * Event handler which is called when the component is made visible
	 * @private
	 */
	onShow: function()
	{
		this.reloadStore(this.cells.first().dom.firstChild.dateValue, this.cells.last().dom.firstChild.dateValue);
		Zarafa.calendar.ui.DatePicker.superclass.onShow.apply(this, arguments);
	},

	/**
	 * Event handler which is called when the component is hidden
	 * @private
	 */
	onHide: function()
	{
		this.store.cancelLoadRequests();
		Zarafa.calendar.ui.DatePicker.superclass.onHide.apply(this, arguments);
	},

	/**
	 * reload the store with date restrictions taken from the first and last cells of the table
	 * @param {Number} startDate beginning date for range in milliseconds
	 * @param {Number} dueDate final date for range in milliseconds
	 * @private
	 */
	reloadStore: function(startDate, dueDate)
	{
		var folder = container.getHierarchyStore().getDefaultFolder('calendar');
		if (folder) {
			this.monthStart = this.value.clone();
			this.monthStart.setDate(1);
			this.monthEnd = this.monthStart.add(Date.MONTH, 1);

			this.store.load({
				actionType: Zarafa.core.Actions['list'],
				params: {
					entryid: folder.get('entryid'),
					store_entryid: folder.get('store_entryid'),
					restriction: {
						startdate: startDate / 1000,
						duedate: dueDate / 1000
					}
				}
			});
		}
	},

	/**
	 * update function called when date has been changed (by clicking on date, through month picker, etc.)
	 * @param {Date} date The newly selected date
	 * @param {Boolean} forceRefresh
	 * @private
	 * @override
	 */
	update: function(date, forceRefresh)
	{
		Zarafa.calendar.ui.DatePicker.superclass.update.call(this, date, forceRefresh);
		if (!this.hidden && this.dateIsChanged(date)) {
			this.reloadStore(this.cells.first().dom.firstChild.dateValue, this.cells.last().dom.firstChild.dateValue);
		}
	},

	/**
	 * check if the passed date is in a different month or year than the current date
	 * @param {Date} date
	 * @return {Boolean}
	 * @private
	 */
	dateIsChanged: function(date)
	{
		var time = date.getTime();
		return (!this.monthStart || !this.monthEnd || time < this.monthStart.getTime() || this.monthEnd.getTime() <= time);
	},

	/**
	 * check whether setting for bolding days with appointments is set
	 * @return {Boolean}
	 * @private
	 */
	settingIsSet: function()
	{
		return container.getSettingsModel().get('zarafa/v1/contexts/calendar/datepicker_show_busy');
	},

	/**
	 * remove highlighting class from all cells
	 * @private
	 */
	clearCells: function()
	{
		this.cells.each(function(c){
			c.removeClass('x-date-busy');
		});
	},

	/**
	 * Function which is Sets a timer to update the highlighted date in the datepicker
	 * after the date has been changed to a new day during a session.
	 */
	setNextDayTimer: function ()
	{
		var diff = Date.diff(Date.MILLISECONDS, new Date().clearTime().add(Date.DAY, 1), new Date());
		Ext.defer(function () {
			if (this.activeDate.getTime() !== new Date().clearTime().getTime()) {
				this.activeDate = undefined;
				var model = this.navigationContext.model;
				var today = (new Date()).dateFormat(this.format);
				this.todayBtn.setTooltip(String.format(this.todayTip, today));
				model.setDate(new Date());
				model.load();
			}
			// Reset timer to refresh date picker
			this.setNextDayTimer();
		}, diff, this);
	}
});

Ext.reg('zarafa.datepicker', Zarafa.calendar.ui.DatePicker);
Ext.namespace('Zarafa.calendar.ui');

/**
 * @class Zarafa.calendar.ui.DateRangeSelectionModel
 * @extends Ext.util.Observable
 *
 * Model that manages the current selection on a calendar panel. The user can change the selection by dragging the mouse
 * across the body of one of the individual calendars. The selection is defined by a date range ({@link Zarafa.core.DateRange
 * DateRange}) and the ID of the folder the selection is currently on. The model provides events so that multiple actors can
 * share the model.
 * <p>
 * The range selection model is used by the individual calendar views to communicate about who has an active selection
 * (no two views can have an active selection at one time) and by the end-user of the {@link Zarafa.calendar.ui.CalendarPanel CalendarPanel}
 * component.
 */
Zarafa.calendar.ui.DateRangeSelectionModel = Ext.extend(Ext.util.Observable, {

	dateRange: undefined,
	calendarView: undefined,
	active: false,

	/**
	 * @constructor
	 * @param config
	 */
	constructor: function(config)
	{
		this.addEvents(
			/**
			 * @event selectionclear
			 * Fired when a range has is cleared.
			 * @param {Zarafa.calendar.ui.DateRangeSelectionModel} model The selection model.
			 * @param {Zarafa.core.DateRange} oldRange the date range of the selection before it was cleared.
			 * @param {Zarafa.calendar.ui.AbstractCalendarView} view the view the range was on before it was cleared.
			 */
			'selectionclear',
			/**
			 * @event selectionset
			 * Fired when a new range is set.
			 * @param {Zarafa.calendar.ui.DateRangeSelectionModel} model The selection model.
			 * @param {Zarafa.core.DateRange} range new date range.
			 * @param {Zarafa.calendar.ui.AbstractCalendarView} view view the range is on.
			 */
			'selectionset',
			/**
			 * @event selectionchange
			 * Fired when a new range is set.
			 * @param {Zarafa.calendar.ui.DateRangeSelectionModel} model The selection model.
			 * @param {Zarafa.core.DateRange} range date range if active, undefined otherwise.
			 * @param {Zarafa.calendar.ui.AbstractCalendarView} view if active, view the range is on, undefined otherwise.
			 * @param {Boolean} active if true the range is inactive, if false it is inactive.
			 */
			'selectionchange'
		);

		Zarafa.calendar.ui.DateRangeSelectionModel.superclass.constructor.call(this, config);
	},

	/**
	 * Clears the selection and sets the selection to 'inactive'.
	 */
	clearSelections: function()
	{
		var oldDateRange = this.dateRange;
		var oldCalendar = this.calendarView;

		this.dateRange = undefined;
		this.calendarView = undefined;
		this.active = false;

		this.fireEvent('selectionclear', this, oldDateRange, oldCalendar);
		this.fireEvent('selectionchange', this, this.dateRange, this.calendarView, this.active);
	},

	/**
	 * Sets the selection to the given date range and calendar view.
	 * @param {Zarafa.core.DateRange} range date range to set
	 * @param {Zarafa.calendar.ui.AbstractCalendarView} calendarView view the range is on
	 */
	set: function(dateRange, calendarView)
	{
		this.dateRange = dateRange;
		this.calendarView = calendarView;
		this.active = true;

		this.fireEvent('selectionset', this, this.dateRange, this.calendarView);
		this.fireEvent('selectionchange', this, this.dateRange, this.calendarView, this.active);
	},

	/**
	 * @return {Zarafa.core.DateRange} selected date range ([start,due>)
	 */
	getDateRange: function()
	{
		return this.dateRange;
	},

	/**
	 * @return {Zarafa.calendar.ui.AbstractCalendarView} the view the selected range is on. If inactive, returns undefined.
	 */
	getCalendarView: function()
	{
		return this.calendarView;
	},

	/**
	 * @return {Boolean} true iff the range is active.
	 */
	isActive: function()
	{
		return this.active;
	}

});
Ext.namespace('Zarafa.calendar.ui');

/**
 * @class Zarafa.calendar.ui.IconCache
 * @extends Object
 * @singleton
 *
 * Special class which contains a number of
 * icons which are used within the Calendar.
 * It also contains some svg icons which can
 * declared only once and used at multiple places.
 */
Zarafa.calendar.ui.IconCache = {

	/**
	 * Obtain a dashed Image object
	 * @return {Image} The dashed image
	 */
	getDashedImage: function()
	{
		var image = new Image();
		image.src = 'data:image/gif;base64,R0lGODlhCAAIAIABAP///wAAACH5BAEKAAEALAAAAAAIAAgAAAIPhIN2qRgKHIwPslZxpq8AADs=';
		return function () { return image; };
	}(),

	/**
	 * Obtain a icon for Private appointments
	 * @return {Image} The private icon
	 */
	getPrivateIcon: function()
	{
		var image = new Image();
		image.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAN1wAADdcBQiibeAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAADaSURBVCiRfZE9SkNRFIS/GbMBW0uthKQVBCEbMNroBrIE95BtBMGfgJDKFYQUWgQEwR1Y2VgYbQxMCl/kRd/NwMA9l++eM5xLEpoMnEt6kvQhaQb0klCCjyWl5kdJn8CJKmBNtp+BzqpOsi1pCixa/+gf7dQLSe/Vce7Cg7fC/UtT/v6f/CsvgO7aBEm7tveBe+ALGAMPkm6TtJNM6p1PJcX2je1roNu4wQq+kPRq+8r2CDja8D+cSZravrR9BxyW4CRQxRhKGgMHm+AkbAHfwF6SQZJZYZ2/WgIY6MDMF1W9zwAAAABJRU5ErkJggg==';
		return function() { return image; };
	}(),

	getPrivateIconActive: function()
	{
		var image = new Image();
		image.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAN1wAADdcBQiibeAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAADDSURBVCiRhZExakJREEWPNtbiDtIJsRJSCX8DUQnoBlyCZZaRBaRRYyUWtjaioIUgCNlBKrEzSfXhpPnKN7ynFwZmhjszd2ZQiVhX3akndas2VWLkZ6+xUX/UVkElgD1Qy8VlYAmksYIjUAnkv4shNnCI5D9D+nuGkarJ/wkPQBWYAb/ABFgDH8AjsMh3bmedRupQTUIXPDt99UsdqGO1EftPEegAL8AcKAFvwCqyNGQy3tWJ+nTj8xdJr+pUrd8jq/wBj4Qn+tCooUUAAAAASUVORK5CYII=';
		return function() { return image; };
	}(),

	/**
	 * Obtain a icon for Recurring appointments
	 * @return {Image} The recurring icon
	 */
	getRecurringIcon: function()
	{
		var image = new Image();
		image.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAMCAYAAABbayygAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAN1wAADdcBQiibeAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAC8SURBVBiVjdAxSoNhEITh5wsBK0EkeIUUVqaytAsWKTyBjW1IpaAewsbCI1h4gBQeQ8EiB7ASjIkQElmbTfj4IeLCwMK+DDsjItTCHlq5X+MgItTACG8ILPCCJR7yruAJn7jEEU4SDKxwCOf4QrdyP05orTG84qr5a1MF+5hGxMofU9aPbgVK6WDxH/ARO3CLKWaYY1iFOsMPerCL90z3gQFOcZ/V3GwKx0WCE3yn8zP6G/cEW7ir9naznl/h/Yxfc66Y/AAAAABJRU5ErkJggg==';
		return function() { return image; };
	}(),

	/**
	 * Obtain a icon for Recurring appointments that are active
	 * @return {Image} The recurring icon
	 */
	getRecurringIconActive: function()
	{
		var image = new Image();
		image.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAMCAYAAABbayygAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAN1wAADdcBQiibeAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAC+SURBVBiVjdA9SoNRFITh+ULAShARt2BhpZWlnVhYZAU2tmKloC7CJoVLsHABFi7DQIosIJVg/IFg5LHwfnATJGRg4MB5Gc6ZIAveQKfM19hGauACQ3+a4gXfuG/BBo94wyX2cFhAmGE3OMU7dqr0A/N6Cga4+ufWOTfYTDJJMssSNVi2T5KtJNNVwIcka8EtJvjAJ86r23r4wX6wjnH57hUnOEa/VHNTF35WwBG+SvIzjtr0Fuzgrpq7i/X8AnV2PLeO7j8sAAAAAElFTkSuQmCC';
		return function() { return image; };
	}(),

	/**
	 * Obtain a icon for Exception appointments
	 * @return {Image} The exception icon
	 */
	getExceptionIcon: function()
	{
		var image = new Image();
		image.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAN1wAADdcBQiibeAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAADlSURBVCiRfdG9LsRBFMbhZ3wUQmk7dFuQyMqWmyAhEbRb2HtwAUTpClBpJFpXoBGVXkMh8dFJkG0QiUJG4SxT/NdJTjFzzm/yvu/APmZyzqoa25gszjp4RAObOMcb7nCKT5z8AkFtxOAKa6hhGpfI0Ys5ZwN+qh7L4+jmnF8wgTl/dZBSGoQR3GAK63hCq6+nkDRUmFrBc09CJVCRzFJAy70HMdwXiKWFgFaxhcNS0hle8Y4PtOO+hW7M6mVKOxjDKB5wnVJqYh4pYp2FUsIRvnCBe9ziOP6jGel1SqCG3X88NbD3DfGjzICsYsYEAAAAAElFTkSuQmCC';
		return function() { return image; };
	}(),

	getExceptionIconActive: function()
	{
		var image = new Image();
		image.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAN1wAADdcBQiibeAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAADsSURBVCiRfdHNKsRRHMbxJy+zMSuZjYWNUuyU15IkUmY1WbsA5QJkaa/kDiyVS+AOrGdjITWi7OQlpI/FHPzNwlNP53ee0/fX+Z0THGMKqXgEA6Xew9j3WdBCB7M4xLWu3tEu61kVCLbxgXvsYg5NXPnVWhU4LZ3vMFOydX/VxmAwjjfMYxW3WOiZ6cffxWglXC7Q0n9ArxcLtFL2dTT+A1JerVOGPcJlFTjHI57wgq2ST+MBn9hA+tLVfpJ6kqEkd0kekzST7CQZTvKcpJbkz5VOSqfr8lmvuMAmJnGDVhVo4KDU/aj1zDSB4y/Ev7tGkhQziQAAAABJRU5ErkJggg==';
		return function() { return image; };
	}(),

	/**
	 * Obtain a icon for Meetings
	 * @return {Image} The meeting icon
	 */
	getMeetingIcon: function()
	{
		var image = new Image();
		image.src = 'data:image/png;base64,'+
			'iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAYAAAA71pVKAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz' +
			'AAAN1wAADdcBQiibeAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAHRSURB' +
			'VCiRnZNNa1pBFIafuc4dGxdSibaIgg2kKZSCItKNuHRhf4H9WInLLov5Af4GweX9A125cRPINhRE' +
			'IUWK2Un01uIm4tfccKeL4BdtF83AYQ7nzHvOe17OCGMMjz3WxnEcZ+U4zpP9pOM4f1Tej4kPn78a' +
			'gLPodwbT1xgjtg9fxa758evNAXg/JgGOw6csFt94FjlDqSOUrZBSMv95xcsXb7n37tGeRmvNfH7F' +
			'cfiU6d3NjvZSvscSNpawsCyLQCDA0+QXAoEAwhII8WBL+XHLQgJM724eJZj89O6ITCbz38But/vQ' +
			'GWA8HmPbNp7nYds26/UaKSVa6+29yadSqR1tACkl0WiU0WhEvV4HwPf9f5oxZgdeLpfc3t6yWq0I' +
			'h8Pkcjm01lSrVUKhEABaa5rNJovFgk6ns1NbKUUikUApRTAYpFKpkE6nqdVq9Pt9XNfl/PycZDJJ' +
			'sVhEKbXrbIxhMpmwWVfbtsnn8wghaDQaeJ5HqVQim80SiUQOZ47H4wdqGmPo9XoMh0PK5TKz2YzB' +
			'YEA8Ht+CRbvddn3ff74PbLVaFAoFTk5OyGazGGO2Il1eXuK6LhcXF4i//apyuWxisdiB4psCvu/j' +
			'eR6TyYTf1e/eakeXUAkAAAAASUVORK5CYII=';
		return function() { return image; };
	}(),

	/**
	 * Obtain a icon for appointments with reminders
	 * @return {Image} The reminder icon
	 */
	getReminderIcon: function()
	{
		var image = new Image();
		image.src = 'data:image/gif;base64,' +
			'R0lGODlhEgAMAPcAAAQCBPz+/AAAQQAAfk8AAB4A8AAA/QAAf5i/bB495AA/EgB3AABzWgC0iBVBQQB+' +
			'fg4gLADj5AASEgAAAAAsKgIAiAAAQQAAfgAADAEAAACFAACBAIi+2OKU1BJBewB+AOkt7OW01IFBe3x+' +
			'AABEFAADAAEIAAAAAFYgAQAAAAAAAAAAAJC+AOEDABI9AAAAAHMSAAAAAAAAAAACALAAEOIAABIAAAAA' +
			'ABieAO4CAJAAAHwAAHABAAUAAJEAAHwAAP/YAP/xAP8SAP8AAG0QAAX3AJFFAHwAAIUgAecAAIEAAHwA' +
			'AABONQBvNhV0MgAgNWBhIAMgJQBmIABpAIBsEOll6RsAEgAAAJAMV2UABBUARAAAfgAAMAAAiAAAQQAA' +
			'fn5g/wBj/wBQ/8AA/wABKgAAiAAAQQAAfv9gm/9juP9QQf8Afv+QAP/jAP8SAP8AAACYoADVnABBRQB+' +
			'AABEjgADAwAIQAAAAAAgDAAAABUAAAAAAPK+YGsDngA9gAAAfMASUOIA1RIAFQACAJ8BAOsAAIEAAHwA' +
			'AErYB+PjAIESAHwAAMBFAHbVAFBBAAB+AJBEAGUD0AEIFQAAAGwgAAAAAAAAAAAAAPzyAOFrABIAAAAA' +
			'ADTYAADjAAASAMAAAPiFAPcrABKDAAB8ABgAaO4AnpAAgHwAfHAA/wUA/5EA/3wA//8AYP8Anv8AgP8A' +
			'fG0APgUBAJEAAHwAAErpPvQrAICDAHx8AAD8SADj6xUSEgAAAADE/wAr/wCD/wB8/5AAAGUAABUAAAAA' +
			'AABQBAHV5QAVEgAAAAA0vgBkOwCDTAB8AFf/5PT/5ID/Enz/AOgAd+PlEBISTwAAAJBtGGVk5RWDEgB8' +
			'AOMMNOoUZJBPg3wAfOzUxuIy5RJPEgAAADgAwAAB/wAA/wAAfzj8iADj5QASEgAAAAIAUAAB1QAAFQAA' +
			'AAGINABkZACDgwB8fMwBUAUA1U4AFZ8AADAA8gAAawAAAAAAAACgAACcAABFAAAAANhs6dTkznsSRwAA' +
			'ACH5BAEAAAEALAAAAAASAAwABwg9AAMIHEiwYEEABxEeJKhQIICGDg0qfPhwIMSFFg0GoLhR4saKGBlq' +
			'7HiRJMaSEzN21JjSIsWJL0c65GgwIAA7';
		return function() { return image; };
	}(),

	/**
	 * Obtain calendar icon in svg format.
	 * @param {String} color The icon color.
	 * @return {String} The calendar icon
	 */
	getCalendarSvgIcon: function(color)
	{
		return 'data:image/svg+xml;charset=utf8,' + encodeURIComponent(this.getCalendarSvgStructure(color));
	},

	/**
	 * Obtain svg structure for calendar icon.
	 * @param {String} color The icon color.
	 * @return {String} The svg structure of calendar icon
	 */
	getCalendarSvgStructure: function(color)
	{
		return '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" style="color:'+color+';position:relative;top:5px;left:-1px;"><g><g class="icbg" style="fill:currentColor;stroke:none"><rect width="0" height="0" x="0" y="0" /></g><g><path class="icgr" style="fill:currentColor" d="M17.75 3A3.25 3.25 0 0 1 21 6.25v11.5A3.25 3.25 0 0 1 17.75 21H6.25A3.25 3.25 0 0 1 3 17.75V6.25A3.25 3.25 0 0 1 6.25 3h11.5Zm1.75 5.5h-15v9.25c0 .966.784 1.75 1.75 1.75h11.5a1.75 1.75 0 0 0 1.75-1.75V8.5Zm-11.75 6a1.25 1.25 0 1 1 0 2.5 1.25 1.25 0 0 1 0-2.5Zm4.25 0a1.25 1.25 0 1 1 0 2.5 1.25 1.25 0 0 1 0-2.5Zm-4.25-4a1.25 1.25 0 1 1 0 2.5 1.25 1.25 0 0 1 0-2.5Zm4.25 0a1.25 1.25 0 1 1 0 2.5 1.25 1.25 0 0 1 0-2.5Zm4.25 0a1.25 1.25 0 1 1 0 2.5 1.25 1.25 0 0 1 0-2.5Zm1.5-6H6.25A1.75 1.75 0 0 0 4.5 6.25V7h15v-.75a1.75 1.75 0 0 0-1.75-1.75Z"/></g></svg>';
	}
};
Ext.namespace('Zarafa.calendar.ui');

/**
 * @class Zarafa.calendar.ui.MeetingInfo
 * @extends Ext.Container
 * @xtype zarafa.meetinginfo
 * This will display start/end date and location of the appointment in the header.
 */
Zarafa.calendar.ui.MeetingInfo = Ext.extend(Ext.Container, {
	/**
	 * The record which has been loaded by this container. This record
	 * is provided through the {@link #update} function.
	 *
	 * @property
	 * @type Zarafa.core.data.IPMRecord
	 */
	record: undefined,

	/**
	 * @cfg {Ext.Template/String} meetingInfoTemplate The template which must be applied
	 * {@link #header} to build meeting info when the {@link Zarafa.core.data.IPMRecord record}
	 * has been {@link #update updated}.The arguments of this template will be
	 * the {@link Zarafa.core.data.IPMRecord#data record.data} field.
	 */
	meetingInfoTemplate:
			'<hr class="preview-title-hr">'+
			 '<table>'+
					'<tpl if="values.counter_proposal">' +
						'<tr class="preview-proposed">' +
	/* # TRANSLATORS: This message is used as label for the field which to indicates to whom the given mail was sent. */
							'<td class="preview-proposed-title">' + pgettext('mail.previewpanel', 'Proposed') + '</td>'+
							'<td>'+
								'<tpl if="!Ext.isEmpty(values.proposed_start_date)">' +
									// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
									'{proposed_start_date:formatDefaultTimeString("' + _("l jS F Y {0}") + '")}' +
									'<span>&nbsp;-&nbsp;</span>' +
								'</tpl>'+
								'<tpl if="!Ext.isEmpty(values.proposed_end_date)">' +
									// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
									'{proposed_end_date:formatDefaultTimeString("' + _("l jS F Y {0}") + '")}' +
								'</tpl>'+
							'</td>'+
						'</tr>'+
					'</tpl>'+

					'<tr class="preview-when">' +
					/* # TRANSLATORS: This message is used as label for the field which to indicates to whom the given mail was sent */
						'<td class="preview-when-title">' + pgettext('mail.previewpanel', 'When') + '</td>' +
						'<td>'+
							'<tpl if="values.appointment_recurring === true">' +
								'{appointment_recurring_pattern}' +
							'</tpl>' +
							'<tpl if="values.appointment_recurring !== true">' +
								'<tpl if="!Ext.isEmpty(values.appointment_startdate)">' +
									// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
									'{appointment_startdate:formatDefaultTimeString("' + _("l jS F Y {0}") + '")}' +
									'<span>&nbsp;-&nbsp;</span>' +
								'</tpl>' +
								'<tpl if="!Ext.isEmpty(values.appointment_duedate)">' +
									// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
									'{appointment_duedate:formatDefaultTimeString("' + _("l jS F Y {0}") + '")}' +
								'</tpl>' +
							'</tpl>' +
						'</td>'+
					'</tr>'+

					'<tpl if="!Ext.isEmpty(values.appointment_location)">' +
							'<tr class="preview-location">' +
							/* # TRANSLATORS: This message is used as label for the field which to indicates to whom the given mail was sent */
								'<td class="preview-location-title">' + pgettext('mail.previewpanel', 'Location') + '</td>' +
								'<td>'+
									'{appointment_location:htmlEncode}' +
								'</td>'+
							'</tr>'+
					'</tpl>'+
				'</table>',


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

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

		config =  Ext.applyIf(config, {
			xtype: 'zarafa.meetinginfo',
			cls: 'preview-header-meeting',
			hidden: true,
			forceLayout: true
		});

		Zarafa.calendar.ui.MeetingInfo.superclass.constructor.call(this, config);

		if (Ext.isString(this.meetingInfoTemplate)) {
			this.meetingInfoTemplate = new Ext.XTemplate(this.meetingInfoTemplate, {
				compiled: true
			});
		}
	},

	/**
	 * Update the {@link Zarafa.calendar.ui.MeetingInfo header} with the data
	 * from the {@link Zarafa.core.data.IPMRecord record}. Updates the panel
	 * by loading data from the record data into the template.
	 *
	 * @param {Zarafa.core.data.IPMRecord} record to update the header panel with
	 */
	update: function(record)
	{
		if ( !this.el.dom ) {
			return;
		}

		if (record instanceof Zarafa.calendar.MeetingRequestRecord) {
			this.meetingInfoTemplate.overwrite(Ext.get(this.el.dom), record.data);
			this.setVisible(true);
		} else {
			this.el.dom.innerHTML = '';
			this.setVisible(false);
		}
	}
});

Ext.reg('zarafa.meetinginfo', Zarafa.calendar.ui.MeetingInfo);
Ext.namespace('Zarafa.calendar.ui');

/**
 * @class Zarafa.calendar.ui.MeetingRequestButton
 * @extends Ext.Button
 * @xtype zarafa.meetingrequestbutton
 *
 * Singleton Base class for all meeting request buttons. It contains common functions of
 * all meeting request buttons and handlers for all meeting request buttons.
 */
Zarafa.calendar.ui.MeetingRequestButton = Ext.extend(Ext.Button, {
	/**
	 * The record for which this Button is shown.
	 * @property
	 * @type Ext.data.Record
	 */
	record: undefined,

	/**
	 * This property will keep track whether this component was visible or hidden last.
	 * it will be set in {@link update} function.
	 * @property
	 * @type Boolean
	 */
	visible: false,

	/**
	 * @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, {
			xtype: 'zarafa.meetingrequestbutton',
			hidden: true,
			forceLayout: true,
			cls: 'zarafa-mr-buttons',
			handler: this.onClickHanlder,
			scope: this,

			/**
			 * Note: This is a fix for a bug: when Ext.js converts buttons to menuitems,
			 * it takes button component's initial configs into consideration.
			 * Initially this button component is hidden so we need to show/hide menuitem
			 * on the basis of visible config which is being set.
			 * in {@link #update} function.
			 */
			beforeShow: function(item) {
				item.setVisible(this.visible);
			}
		});
		Zarafa.calendar.ui.MeetingRequestButton.superclass.constructor.call(this, config);

		/**
		 * We need to remove 'zarafa.recordcomponentupdaterplugin' plugin from initialConfig.
		 * Because Ext uses initialConfig to make a menu item from button component.
		 * This 'zarafa.recordcomponentupdaterplugin' plugin will call update function
		 * which will not be found in newly created menuitem.
		 * So it will call Ext's update function instead
		 * and because Ext's update function will get called with wrong parameters,
		 * text of menu item will be changed.
		 */
		this.initialConfig.plugins = [];
	},

	/**
	 * Update the components with the given record.
	 *
	 * @param {Zarafa.core.data.MAPIRecord} record The record to update in this component
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 * @private
	 */
	update: function(record, contentReset)
	{
		if (!(record instanceof Zarafa.calendar.AppointmentRecord)) {
			this.setVisible(false);
			return;
		}

		this.record = record;
		var isMeeting = false;
		var isMeetingRequest = false;
		var isMeetingResponse = false;
		var isMeetingProposal = false;
		var isMeetingRecurring = false;
		var isMeetingOccurrence = false;
		var isMeetingUpdated = false;
		var isMeetingCanceled = false;
		var isMeetingOutOfDate = false;

		// Check if we are having a sub message, if then don't display any of the buttons
		var isSubMessage = record.isSubMessage();

		// Check if the sender has sent himself the meeting request
		var senderIsReceiver = record.senderIsReceiver();

		// Check if the appointment was located in the calendar
		var apptNotFound = record.get('appointment_not_found');

		// Check whether message receiver is not the message creator.
		// Check whether message is in sent_items folder
		// Check whether message was sent to himself
		var senderIsStoreOwner = record.senderIsStoreOwner();

		// Check if this was a received meeting
		var requestReceived = record.isMeetingReceived();

		if (record instanceof Zarafa.calendar.MeetingRequestRecord) {
			isMeeting = record.isMeetingRequest();
			isMeetingRequest = isMeeting;
			isMeetingResponse = record.isMeetingRequestResponse();
			isMeetingProposal = isMeetingResponse && record.get('counter_proposal');
			isMeetingRecurring = record.get('appointment_recurring');
			isMeetingUpdated = record.get('meeting_updated');
			isMeetingCanceled = record.isMeetingRequestCanceled();
			isMeetingOutOfDate = record.isMeetingOutOfDate();
		} else {
			isMeeting = record.isMeeting();
			isMeetingCanceled = record.isMeetingCanceled();
			isMeetingRecurring = record.get('recurring');
			isMeetingOccurrence = record.hasIdProp('basedate');
			isMeetingOutOfDate = isMeetingOccurrence && record.get('duedate') < new Date();
		}

		switch(this.name) {
			case Zarafa.calendar.data.MeetingRequestButtonNames.REMOVEFROMCALENDAR:
				// When the meeting is canceled, the user is allowed to remove the appointment
				// from the calendar.
				this.visible = isMeetingCanceled && !isSubMessage && !apptNotFound;
				break;
			case Zarafa.calendar.data.MeetingRequestButtonNames.NORESPONSE:
				// When this meeting is current, but the user did send this meeting request to himself,
				// then the user doesn't need to respond.
				this.visible = isMeetingRequest && !isSubMessage && senderIsReceiver && !isMeetingOutOfDate;
				break;
			case Zarafa.calendar.data.MeetingRequestButtonNames.ACCEPT:
			case Zarafa.calendar.data.MeetingRequestButtonNames.TENTATIVE:
			case Zarafa.calendar.data.MeetingRequestButtonNames.DECLINE:
				// When this meeting is current, and the user didn't send the meeting request himself,
				// then the user is allowed to accept or decline the meeting.
				this.visible = isMeeting && !isSubMessage && !senderIsReceiver && !isMeetingOutOfDate && !isMeetingCanceled && !senderIsStoreOwner && requestReceived;

				// Determine the action button
				// @FIXME: find a better solution to determine action button.
				// we don't have calendar button access directly as we created separate buttons for meetingrequests.
				if (this.name === Zarafa.calendar.data.MeetingRequestButtonNames.ACCEPT) {
					var calendarButton = this.parentScope ? this.parentScope.calendarButton : false;
					if (this.visible && calendarButton) {
						calendarButton.getEl().removeClass('zarafa-action');
					} else if (calendarButton && calendarButton.isVisible()) {
						calendarButton.getEl().addClass('zarafa-action');
					}
				}
				break;
			case Zarafa.calendar.data.MeetingRequestButtonNames.PROPOSENEWTIME:
				// A user can only propose a new time for non-recurring meetings, or occurrences. For the rest,
				// the same rules apply as for accepting.
				this.visible = isMeeting && !isSubMessage && (!isMeetingRecurring || isMeetingOccurrence) && !senderIsReceiver && !isMeetingOutOfDate && !isMeetingCanceled && !senderIsStoreOwner && requestReceived;
				break;
			case Zarafa.calendar.data.MeetingRequestButtonNames.VIEWPROPOSALS:
			case Zarafa.calendar.data.MeetingRequestButtonNames.ACCEPTPROPOSAL:
				this.visible = isMeetingProposal && !isSubMessage && !isMeetingUpdated && !senderIsStoreOwner;
				break;
			case Zarafa.calendar.data.MeetingRequestButtonNames.CALENDAR:
				this.visible = (isMeetingRequest || isMeetingResponse) && !isSubMessage && !apptNotFound;
				break;
			case Zarafa.calendar.data.MeetingRequestButtonNames.FORWARD:
				this.visible = isMeeting && !isSubMessage && !isMeetingCanceled;
				break;
			default:
				break;
		}

		this.setVisible(this.visible);
	},

	/**
	 * This is common handler for meeting requests buttons
	 * of type {@link Zarafa.calendar.ui.MeetingRequestButton MeetingRequestButton}.
	 * It will call button's handler based on button's name.
	 *
	 * @param {Ext.Button} button button object.
	 * @param {EventObject} eventObject The click event object.
	 * @private
	 */
	onClickHanlder: function(button, eventObject)
	{
		switch(button.name) {
			case Zarafa.calendar.data.MeetingRequestButtonNames.REMOVEFROMCALENDAR:
				this.onRemoveFromCalendar(button, eventObject);
				break;
			case Zarafa.calendar.data.MeetingRequestButtonNames.NORESPONSE:
				this.onNoResponseRequired(button, eventObject);
				break;
			case Zarafa.calendar.data.MeetingRequestButtonNames.ACCEPT:
			case Zarafa.calendar.data.MeetingRequestButtonNames.TENTATIVE:
			case Zarafa.calendar.data.MeetingRequestButtonNames.DECLINE:
				this.openSendConfirmationContent(button, eventObject);
				break;
			case Zarafa.calendar.data.MeetingRequestButtonNames.PROPOSENEWTIME:
				this.openProposeNewTimeContent(button, eventObject);
				break;
			case Zarafa.calendar.data.MeetingRequestButtonNames.VIEWPROPOSALS:
				this.viewAllProposals(button, eventObject);
				break;
			case Zarafa.calendar.data.MeetingRequestButtonNames.ACCEPTPROPOSAL:
				this.acceptProposal(button, eventObject);
				break;
			case Zarafa.calendar.data.MeetingRequestButtonNames.CALENDAR:
				this.showMeetingInCalendar(button, eventObject);
				break;
			case Zarafa.calendar.data.MeetingRequestButtonNames.FORWARD:
				this.forwardMeetingRequest(button, eventObject);
				break;
			default:
				break;
		}
	},

	/**
	 * Opens a {@link Zarafa.calendar.dialogs.SendMeetingRequestConfirmationContentPanel SendMeetingRequestConfirmationContentPanel}
	 * @param {Ext.Button} button button object.
	 * @param {EventObject} eventObject The click event object.
	 * @private
	 */
	openSendConfirmationContent: function(button, eventObject)
	{
		if (this.record.get('appointment_not_found')) {
			Ext.MessageBox.show({
				title: _('Appointment not found'),
				msg:_('This appointment has been moved or deleted, do you want to continue?'),
				cls: Ext.MessageBox.WARNING_CLS,
				record: this.record,
				fn: this.onRespondAppointmentNotFoundConfirmation.createDelegate(this, [ button.responseStatus ], 1),
				scope: this,
				buttons: Ext.MessageBox.YESNO
			});
		} else {
			Zarafa.calendar.Actions.openSendConfirmationContent(this.record, { responseType: button.responseStatus });
		}
	},

	/**
	 * Callback function for {@link #openSendConfirmationContent}, which opens a {@link Ext.MessageBox} if
	 * the appointment is not found in the calendar, but we still want to accept it.
	 * @param {String} button The button which was clicked by the user
	 * @param {Zarafa.core.mapi.ResponseStatus} responseType The response type which was selected by the user
	 * @private
	 */
	onRespondAppointmentNotFoundConfirmation: function(button, responseType)
	{
		if (button === 'yes') {
			Zarafa.calendar.Actions.openSendConfirmationContent(this.record, { responseType: responseType });
		}
	},

	/**
	 * Opens a {@link Zarafa.calendar.dialogs.openAppointmentContentPanel}
	 * @param {Ext.Button} button button object.
	 * @param {EventObject} eventObject The click event object.
	 * @private
	 */
	acceptProposal: function(button, eventObject)
	{
		// When the button belongs to one of the currently opened popout windows then
		// it is required to bring the main webapp window to front prior to switching to the calendar context.
		if (!Zarafa.core.BrowserWindowMgr.isOwnedByMainWindow(button)) {
			Zarafa.core.BrowserWindowMgr.switchFocusToMainWindow();
		}

		Zarafa.calendar.Actions.openAppointmentContentToAcceptProposal(this.record);
	},

	/**
	 * Loads {@link Zarafa.calendar.CalendarContext CalendarContext} in container
	 * @param {Ext.Button} button button object.
	 * @param {EventObject} eventObject The click event object.
	 * @private
	 */
	showMeetingInCalendar: function(button, eventObject)
	{
		// When the button belongs to one of the currently opened popout windows then
		// it is required to bring the main webapp window to front prior to switching to the calendar context.
		if (!Zarafa.core.BrowserWindowMgr.isOwnedByMainWindow(button)) {
			Zarafa.core.BrowserWindowMgr.switchFocusToMainWindow();
		}

		Zarafa.calendar.Actions.showMeetingInCalendar(this.record);
	},

	/**
	 * Function sends request to remove Meeting Request mails which invites
	 * the organizer himself in the Meeting Request.
	 * @param {Ext.Button} button button object.
	 * @param {EventObject} eventObject The click event object.
	 * @private
	 */
	onNoResponseRequired: function(button, eventObject)
	{
		Ext.MessageBox.show({
			title: _('No additional response required'),
			msg:_('Your calendar has been updated automatically. This meeting request will now be deleted.'),
			record: this.record,
			fn: this.removeRecordOnOk,
			scope: this,
			buttons: Ext.MessageBox.OKCANCEL
		});
	},

    /**
	 * @param {String} buttonClicked The ID of the button pressed,
	 * here, one of: ok cancel.
	 * @param {String} text Value of the input field, not useful here
	 * @private
	 */
	removeRecordOnOk: function (buttonClicked, text)
	{
		if (buttonClicked == 'ok') {
			var store = this.record.getStore();
			store.remove(this.record);
			store.save(this.record);
		}
	},

	/**
	 * Opens the Propose New Time Content Panel
	 * @param {Ext.Button} button The clicked button
	 * @param {EventObject} eventObject The click event object
	 * @private
	 */
	openProposeNewTimeContent: function(button, eventObject)
	{
		if (this.record.get('appointment_not_found')) {
			Ext.MessageBox.show({
				title: _('Appointment not found'),
				msg:_('This appointment has been moved or deleted, do you want to continue?'),
				cls: Ext.MessageBox.WARNING_CLS,
				record: this.record,
				fn: this.onProposeNewTimeAppointmentNotFoundConfirmation,
				scope: this,
				buttons: Ext.MessageBox.YESNO
			});
		} else {
			Zarafa.calendar.Actions.openProposeNewTimeContent(this.record);
		}
    },

    /**
	 * Callback function for {@link #openProposeNewTimeContent}, which opens a {@link Ext.MessageBox} if
	 * the appointment is not found in the calendar, but we still wants to propose a new time.
	 * @param {String} button The button which was clicked by the user
	 * @private
	 */
	onProposeNewTimeAppointmentNotFoundConfirmation: function(button)
	{
		if (button === 'yes') {
			Zarafa.calendar.Actions.openProposeNewTimeContent(this.record);
		}
	},

	 /**
	 * Organizer has declined the Meeting Request, so now remove its instance
	 * from your calendar and remove the mail as well.
	 * @param {Ext.Button} button button object.
	 * @param {EventObject} eventObject The click event object.
	 * @private
	 */
	onRemoveFromCalendar: function(button, eventObject)
	{
		this.record.addMessageAction('action_type', 'removeFromCalendar');

		var store = this.record.getStore();
		store.remove(this.record);
		store.save(this.record);
	},

	/**
	 * Opens the forward meeting request dialog for the current record.
	 * Uses the original record directly (not converted) so that
	 * getStore() remains valid for the server request.
	 * @param {Ext.Button} button button object.
	 * @param {EventObject} eventObject The click event object.
	 * @private
	 */
	forwardMeetingRequest: function(button, eventObject)
	{
		Zarafa.calendar.Actions.openForwardMeetingRequestContent(this.record);
	},

	/**
	 * Opens a {@link Zarafa.calendar.dialogs.openAppointmentContentPanel}
	 * @param {Ext.Button} button button object.
	 * @param {EventObject} eventObject The click event object.
	 * @private
	 */
	viewAllProposals: function(button, eventObject)
	{
		// When the button belongs to one of the currently opened popout windows then
		// it is required to bring the main webapp window to front prior to switching to the calendar context.
		if (!Zarafa.core.BrowserWindowMgr.isOwnedByMainWindow(button)) {
			Zarafa.core.BrowserWindowMgr.switchFocusToMainWindow();
		}
		Zarafa.calendar.Actions.openAppointmentContentToViewAllProposals(this.record);
	}

});

Ext.reg('zarafa.meetingrequestbutton', Zarafa.calendar.ui.MeetingRequestButton);
Ext.namespace('Zarafa.calendar.ui');

/**
 * @class Zarafa.calendar.ui.ToolTip
 * @extends Ext.ToolTip
 *
 * This tooltip is shown when an appointment in the calendar (not the grid) is hovered.
 * Because we will only show one tooltip at a time, only one instance of this class will
 * be created by the {@link Zarafa.calendar.ui.CalendarMultiView calendar view} and that
 * instance will be used for all appointments.
 */
Zarafa.calendar.ui.ToolTip = Ext.extend(Ext.ToolTip, {
	/**
	 * @cfg {Zarafa.calendar.ui.CalendarMultiView} view The view for which
	 * this tooltip is used
	 */
	view: undefined,

	/**
	 * An id that identifies the appointment for which the
	 * tooltip is shown. (will be the entryid of the appointment)
	 *
	 * @property
	 * @type String
	 * @private
	 */
	appointmentId: undefined,

	/**
	 * A delayed task that is used to hide the tooltip with
	 * a delay.
	 *
	 * @property
	 * @type Ext.util.DelayedTask
	 * @private
	 */
	hideTask: undefined,

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

		Ext.applyIf(config, {
			cls: 'zarafa-appointment-tooltip',
			width: 250,
			target: Ext.get(document.body),
			autoHide: true, // Needed to not hide on click
			dismissDelay: 0,
			renderTo: Ext.getBody(),
			listeners: {
				afterrender: this.onAfterRender,
				scope: this
			}
		});

		Zarafa.calendar.ui.ToolTip.superclass.constructor.call(this, config);
	},

	/**
	 * Show the Tooltip, this will {@link Ext.Element#show show} the {@link #el tooltip}.
	 *
	 * @param {String} id The Object id referring to the Object on which we are showing the Tooltip
	 * @param {Object} config The properties used for creating the tooltop, this should at least contain
	 * either a 'title' or 'text' property.
	 * @param {Ext.EventObject} event The event object
	 */
	show: function(id, config, event)
	{
		// We only show a tooltip when we hover over an appointment, but since
		// the calendar view is a canvas element the appointments aren't html elements
		// and so we cannot use those as delegate elements. Hence the Ext will try to
		// show the tooltip for every mouse hover over the calendar view. When
		// The config is not set, we know this method was called by Ext and not by
		// our own code. We will not show the tooltip when Ext calls this method.
		if ( !Ext.isObject(config) ){
			return;
		}

		if ( Ext.isDefined(this.hideTask) ){
			this.hideTask.cancel();
		}

		// No need to rebuild and reposition the tooltip when it is already shown
		if ( id === this.appointmentId && this.isVisible() ){
			return;
		}

		this.appointmentId = id;

		var html = '';

		if ( !Ext.isEmpty(config.title) ){
			html += '<h2>' + config.title + '</h2>';
		}
		if ( !Ext.isEmpty(config.text) ){
			html += '<p>' + config.text.replace("\n", '<br>') + '</p>';
		}
		if ( !Ext.isEmpty(config.categories) ){
			html += Zarafa.common.categories.Util.getCategoriesHtml(config.categories);
		}

        this.body.dom.innerHTML = html;

		Zarafa.calendar.ui.ToolTip.superclass.show.call(this);

		// Reposition tooltip if needed
		var body = Ext.getBody();
		var bodyHeight = body.getHeight();
		var bodyWidth = body.getWidth();
		var tipHeight = this.getHeight();
		var tipWidth = this.getWidth();
		var position = this.getPosition();
		var newPosition = position.slice();

		if ( position[0] + tipWidth > bodyWidth ){
			newPosition[0] = bodyWidth - tipWidth;
		}
		if ( position[1] + tipHeight > bodyHeight ){
			newPosition[1] = bodyHeight - tipHeight;
		}
		this.setPosition(newPosition[0], newPosition[1]);
	},

	/**
	 * Hides the tooltip with the given delay or 500ms if none is given.
	 * @param {Number} delay The delay for hiding the tooltip in milliseconds.
	 */
	hide: function(delay)
	{
		if ( !Ext.isDefined(this.hideTask) ){
			this.hideTask = new Ext.util.DelayedTask(function(){
				Zarafa.calendar.ui.ToolTip.superclass.hide.call(this);
			}, this);
		}

		delay = Ext.isDefined(delay) ? delay : 500;
		this.hideTask.delay(delay);
	},

	/**
	 * Event handler for the afterender event of the tooltip. Will add a click
	 * handler to the tooltip's underlying element that will close the tooltip
	 * so users can click on the tooltip to close it if it gets in the way.
	 */
	onAfterRender: function()
	{
		this.el.on('mouseover', function(){
			if ( Ext.isDefined(this.hideTask) ){
				this.hideTask.cancel();
			}
		}, this);

		this.el.on('mouseout', function(){
			// Remove the appointmentOver property of all calendar views
			// That way the appointmentmouseover event will be triggered
			// again when we leave the tooltip and the mouse is still
			// hovering an appointment
			Ext.each(this.view.calendars, function(calendar){
				calendar.appointmentOver = null;
			}, this);
			this.hide();
		}, this);
	}
});Ext.namespace('Zarafa.calendar.widget');

/**
 * @class Zarafa.calendar.widget.CalendarFolderSelectionLink
 * @extends Ext.BoxComponent
 * @xtype zarafa.calendarfolderselectionlink
 *
 * This will show the currently selected {@link #folder} to the user, and allows
 * the user to select the desired folder to view appointments from.
 */
Zarafa.calendar.widget.CalendarFolderSelectionLink = Ext.extend(Ext.BoxComponent, {
	/**
	 * @cfg {String} fieldLabel The label which must be applied to template
	 * as a prefix to the list of attachments.
	 */
	emptyText:_('Select one…'),

	/**
	 * The folder which was selected by the user
	 * @property
	 * @type Zarafa.hierarchy.data.MAPIFolderRecord
	 */
	folder: undefined,

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

		Ext.applyIf(config,{
			xtype: 'zarafa.calendarfolderselectionlink',
			border: false,
			autoScroll:true,
			anchor: '100%',
			tpl: new Ext.XTemplate(
				'<div class="k-folder-link">' +
					'<tpl if="!Ext.isEmpty(values.display_name)">' +
						'&quot;{display_name:htmlEncode}&quot;' +
					'</tpl>' +
					'<tpl if="Ext.isEmpty(values.display_name)">' +
						'&quot;' + _('Unnamed folder') + '&quot;' +
					'</tpl>' +
				'</div>',
				{
					compiled: true
				}
			)
		});

		Zarafa.calendar.widget.CalendarFolderSelectionLink.superclass.constructor.call(this, config);
	},

	/**
	 * This function is called after the component has been rendered.
	 * This will update {@link Zarafa.widgets.folderwidgets.CalendarFolderSelectionLink} with selected folder
	 * and will register {@link #onClick} event handler.
	 * @private
	 */
	afterRender: function()
	{
		Zarafa.calendar.widget.CalendarFolderSelectionLink.superclass.afterRender.apply(this, arguments);

		this.update();
		this.mon(this.getEl(), 'click', this.onClick, this);
	},

	/**
	 * Called when user clicks on a {@link Zarafa.widgets.folderwidgets.CalendarFolderSelectionLink}
	 * It opens hierarchy folder selection dialog containing all calendar folders.
	 * @param {Ext.DataView} dataView Reference to this object
	 * @param {Number} index The index of the target node
	 * @param {HTMLElement} node The target node
	 * @param {Ext.EventObject} evt The mouse event
 	 * @protected
	 */
	onClick: function(dataView, index, node, evt)
	{
		var defaultCalendar = container.getHierarchyStore().getDefaultFolder('calendar');
		var folder = Ext.isEmpty(this.folder) ? defaultCalendar : this.folder;
		Zarafa.hierarchy.Actions.openFolderSelectionContent({
			folder: folder,
			hideTodoList: true,
			IPMFilter: 'IPF.Appointment',
			callback: this.update,
			scope: this,
			modal: true
		});
	},

	/**
	 * Update the contents of this dataview, this will apply the {@link #tpl} for
	 * the {@link #folder}.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder The folder to show
	 */
	update: function(folder)
	{
		// Avoid 'folderupdate' for the first time when this component loads.
		if (folder) {
			this.folder = folder;
			this.fireEvent('folderupdate', folder);
		} else {
			folder = this.folder;
		}

 		var data = folder ? folder.data : { display_name: this.emptyText };
		Zarafa.calendar.widget.CalendarFolderSelectionLink.superclass.update.call(this, this.tpl.apply(data));
	}
});

Ext.reg('zarafa.calendarfolderselectionlink', Zarafa.calendar.widget.CalendarFolderSelectionLink);
Ext.namespace('Zarafa.common');

/**
 * @class Zarafa.common.Actions
 * Common actions which can be used within {@link Ext.Button buttons}
 * or other {@link Ext.Component components} with action handlers.
 * @singleton
 */
Zarafa.common.Actions = {
	/**
	 * The internal 'iframe' which is hidden from the user, which is used for downloading
	 * attachments. See {@link #doOpen}.
	 * @property
	 * @type Ext.Element
	 */
	downloadFrame: undefined,

	/**
	 * The array holding broken eml files, if found.
	 * @property
	 * @type Array
	 */
	brokenFiles: undefined,

	/**
	 * Total number of files selected by user to upload.
	 * @property
	 * @type Number
	 */
	totalFiles: undefined,

	/**
	 * Defines if the imported item should be shown after import.
	 * @property
	 * @type Boolean
	 */
	showImported: false,

	/**
	 * Defines if the imported item is a single or multiple ICS or VCF file.
	 * @property
	 * @type Boolean
	 */
	isSingleImport: false,

	/**
	 * Open a {@link Zarafa.common.dialogs.CopyMoveContentPanel CopyMoveContentPanel} for
	 * copying or moving {@link Zarafa.core.data.IPMRecord records} to the
	 * preferred destination folder.
	 *
	 * @param {Zarafa.core.data.IPMRecord} records The record which must be copied or moved.
	 * @param {Object} config (optional) Configuration object to create the ContentPanel
	 */
	openCopyMoveContent: function(records, config)
	{
		config = Ext.applyIf(config || {}, {
			modal: true
		});
		var componentType = Zarafa.core.data.SharedComponentType['common.dialog.copymoverecords'];
		Zarafa.core.data.UIFactory.openLayerComponent(componentType, records, config);
	},

	/**
	 * Ensure that the given records remain operable by reattaching them to the provided store when needed.
	 *
	 * @param {Zarafa.core.data.MAPIRecord/Zarafa.core.data.MAPIRecord[]} records The record or records to normalize.
	 * @param {Zarafa.core.data.MAPIStore} store The store that should own the records.
	 * @return {Zarafa.core.data.MAPIRecord[]} Array containing the resolved records, excluding falsy entries.
	 */
	resolveRecords: function(records, store)
	{
		if (!Ext.isArray(records)) {
			records = Ext.isDefined(records) ? [ records ] : [];
		}

		var resolved = [];

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

			if (!record) {
				continue;
			}

			if (store) {
				var storeEntryId = store.storeEntryId;

				if (!storeEntryId && Ext.isDefined(store.folder) && store.folder) {
					storeEntryId = store.folder.get('store_entryid');
				}

				if (!storeEntryId && Ext.isFunction(store.getFolder)) {
					var storeFolder = store.getFolder();
					if (storeFolder) {
						storeEntryId = storeFolder.get('store_entryid');
					}
				}

				var recordStore = Ext.isFunction(record.getStore) ? record.getStore() : record.store;

				if (!recordStore) {
					var storeRecord = Ext.isFunction(store.getById) ? store.getById(record.id) : undefined;

					if (storeRecord) {
						record = storeRecord;
						records[i] = storeRecord;
						recordStore = Ext.isFunction(storeRecord.getStore) ? storeRecord.getStore() : store;
					} else if (Ext.isFunction(record.join)) {
						record.join(store);
						recordStore = Ext.isFunction(record.getStore) ? record.getStore() : store;
					} else {
						record.store = store;
						recordStore = store;
					}
				}

				if (!recordStore) {
					record.store = store;
				}

				if (storeEntryId && (!Ext.isFunction(record.get) || Ext.isEmpty(record.get('store_entryid')))) {
					if (record.data) {
						record.data.store_entryid = storeEntryId;
					} else if (Ext.isFunction(record.set)) {
						record.set('store_entryid', storeEntryId);
					}
				}
			}

			resolved.push(record);
		}

	return resolved;
	},

	/**
	 * Ensure that the given store reloads its content when it becomes empty after a write operation while
	 * more data is still available on the backend (e.g. after moving all currently loaded mails).
	 *
	 * @param {Ext.data.Store} store The store to monitor.
	 */
	ensureStoreReloadOnEmpty: function(store)
	{
		if (!store || !Ext.isFunction(store.getCount)) {
			return;
		}

		if (store._reloadGuardActive === true) {
			return;
		}

		store._reloadGuardActive = true;

		var handler = function(st, action) {
			st.un('write', handler, this);
			delete st._reloadGuardActive;

			if (action !== 'destroy' && action !== 'update') {
				return;
			}

			Ext.defer(function() {
				if (!Ext.isFunction(st.getCount) || st.getCount() !== 0) {
					return;
				}

				var options = st.lastOptions ? Ext.apply({}, st.lastOptions) : undefined;
				st.reload(options);
			}, 50);
		};

		store.on('write', handler, this, { single: true });
	},

	/**
	 * Opens a {@link Zarafa.common.recurrence.dialogs.RecurrenceContentPanel RecurrenceContentPanel} for configuring
	 * the recurrence of the given {@link Zarafa.core.data.IPMRecord record}.
	 *
	 * @param {Zarafa.core.data.IPMRecord} records The record for which the recurrence must be configured.
	 * @param {Object} config Configuration object
	 */
	openRecurrenceContent: function(records, config)
	{
		if (Array.isArray(records) && !Ext.isEmpty(records)) {
			records = records[0];
		}

		config = Ext.applyIf(config || {}, {
			autoSave: true,
			modal: true
		});

		var componentType = Zarafa.core.data.SharedComponentType['common.dialog.recurrence'];
		Zarafa.core.data.UIFactory.openLayerComponent(componentType, records, config);
	},

	/**
	 * Opens the {@link Zarafa.common.categories.ui.CategoriesContextMenu CategoriesContextMenu} for
	 * the given {@link Zarafa.core.data.IPMRecord records}.
	 *
	 * @param {Zarafa.core.data.IPMRecord} records The record, or records for which the categories
	 * menu will be shown.
	 * @param {Array} position An array with the [x, y] position where the menu will be shown.
	 */
	openCategoriesMenu: function(records, position)
	{
		Zarafa.core.data.UIFactory.openContextMenu(Zarafa.core.data.SharedComponentType['common.contextmenu.categories'], records, {
			position: position
		});
	},

	/**
	 * Opens the {@link Zarafa.common.flags.ui.FlagsMenu FlagsMenu} for
	 * the given {@link Zarafa.core.data.IPMRecord records}.
	 *
	 * @param {Zarafa.core.data.IPMRecord} records The record, or records for which the flags
	 * menu will be shown.
	 * @param {Array} position An array with the [x, y] position where the menu will be shown.
	 * @param {Boolean} shadowEdit True to create copy of this record and push it to ShadowStore.
	 */
	openFlagsMenu: function(records, position, shadowEdit)
	{
		if (!Ext.isArray(records)) {
			records = [ records ];
		}

		var component = Zarafa.core.data.SharedComponentType['common.contextmenu.flags'];
		Zarafa.core.data.UIFactory.openContextMenu(component, records, {
			position: position,
			shadowEdit: shadowEdit,
			store: records[0].getStore()
		});
	},

	/**
	 * Opens a {@link Zarafa.common.flag.dialogs.CustomFlagContentPanel CustomFlagContentPanel} for set
	 * the custom flag and reminder to given {@link Zarafa.core.data.IPMRecord records}.
	 *
	 * @param {Zarafa.core.data.IPMRecord} records The record, or records for which the custom
	 * flag or/and reminder going to set.
	 * @param {Object} config (optional) Configuration object for creating the ContentPanel
	 */
	openCustomFlagContent: function(records, config)
	{
		config = Ext.applyIf(config || {}, {
			modal: true,
			resizable: false
		});
		var componentType = Zarafa.core.data.SharedComponentType['common.flags.dialogs.customflag'];
		Zarafa.core.data.UIFactory.openLayerComponent(componentType, records, config);
	},

	/**
	 * Opens a {@link Zarafa.common.categories.dialogs.CategoriesContentPanel CategoriesContentPanel} for configuring
	 * the categories of the given {@link Zarafa.core.data.IPMRecord records}.
	 *
	 * @param {Zarafa.core.data.IPMRecord} records The record, or records for which the categories
	 * must be configured
	 * @param {Object} config (optional) Configuration object for creating the ContentPanel
	 */
	openCategoriesContent: function(records, config)
	{
		if (!Array.isArray(records)) {
			records = [ records ];
		}

		config = Ext.applyIf(config || {}, {
			autoSave: true,
			modal: true
		});

		// Callback function added in config object if
		// selected records is belongs to search store.
		var store = records[0].getStore();
		if(Ext.isFunction(store.isAdvanceSearchStore) && store.isAdvanceSearchStore()) {
			config.callback = function() {
				Ext.each(records, function(record) {
					var foundRecord = this.record.find(function(rec) {
						return rec.get('entryid') === record.get('entryid');
					});
					record.applyData(foundRecord);
				}, this);
			};
		}

		var componentType = Zarafa.core.data.SharedComponentType['common.dialog.categories'];
		Zarafa.core.data.UIFactory.openLayerComponent(componentType, records, config);
	},

	/**
	 * Opens a {@link Zarafa.common.categories.dialogs.NewCategoryPanel NewCategoryPanel} for creating
	 * a new category.
	 *
	 * @param {Object} config (optional) Configuration object for creating the NewCategoryPanel
	 */
	openNewCategoryContent: function(config)
	{
		config = Ext.applyIf(config || {}, {
			modal: true
		});
		var componentType = Zarafa.core.data.SharedComponentType['common.categories.dialogs.newcategory'];
		Zarafa.core.data.UIFactory.openLayerComponent(componentType, undefined, config);
	},

	/**
	 * Opens a {@link Zarafa.common.categories.dialogs.RenameCategoryPanel RenameCategoryPanel} for renaming
	 * a standard category.
	 *
	 * @param {Object} config (optional) Configuration object for renaming the {@link Zarafa.common.categories.dialogs.RenameCategoryPanel RenameCategoryPanel}.
	 */
	openRenameCategoryContent: function(config)
	{
		config = Ext.applyIf(config || {}, {
			modal: true
		});
		var componentType = Zarafa.core.data.SharedComponentType['common.categories.dialogs.renamecategory'];
		Zarafa.core.data.UIFactory.openLayerComponent(componentType, undefined, config);
	},

	/**
	 * Opens a {@link Zarafa.common.attachment.dialogs.AttachItemContentPanel Attach Item Content Panel} which is used
	 * to attach an item as embedded attachment to a message.
	 *
	 * @param {Zarafa.core.data.MAPIRecord} record record that will be used to add embedded attachment
	 * @param {Object} config (optional) Configuration object for creating the ContentPanel
	 */
	openAttachItemSelectionContent: function(record, config)
	{
		config = Ext.applyIf(config || {}, {
			modal: true
		});

		var componentType = Zarafa.core.data.SharedComponentType['common.attachment.dialog.attachitem'];
		Zarafa.core.data.UIFactory.openLayerComponent(componentType, record, config);
	},

	/**
	 * Opens a {@link Zarafa.core.ui.widget.WidgetContentPanel}
	 * for inserting widgets into the {@link Zarafa.core.ui.widget.WidgetPanel}
	 * @param {Object} config (optional) Configuration object for creating the ContentPanel
	 */
	openWidgetsContent: function(config)
	{
		var componentType = Zarafa.core.data.SharedComponentType['common.dialog.widgets'];
		Zarafa.core.data.UIFactory.openLayerComponent(componentType, undefined, config);
	},

	/**
	 * Will open the View ContentPanel for a recipient, before opening the recipient it will
	 * first check the exact type of the recipient to see if it is an AddressBook item
	 * or personal contact. If either of those two the record is converted to assure the
	 * correct panel is opened.
	 * @param {Zarafa.core.data.IPMRecipientRecord} recipient The recipient which must be opened
	 * @param {Object} config configuration object.
	 */
	openViewRecipientContent: function(recipient, config)
	{
		if (recipient.isResolved()) {
			if (recipient.isPersonalContact() || recipient.isSharedRecord()) {
				// A personal contact needs to be converted to a contact so the correct panel can be shown.
				recipient = recipient.convertToContactRecord();
				// FIXME: We put the abRecord into the ShadowStore to be able
				// to open it, and obtain all details. However, we also need to
				// find a point where we can remove it again.
				container.getShadowStore().add(recipient);
			} else if (recipient.isPersonalDistList() || recipient.isSharedRecord(Zarafa.core.mapi.ObjectType.MAPI_DISTLIST)) {
				// A personal distlist needs to be converted to a distlist so the correct dialog can be shown.
				recipient = recipient.convertToDistListRecord();
				// FIXME: We put the abRecord into the ShadowStore to be able
				// to open it, and obtain all details. However, we also need to
				// find a point where we can remove it again.
				container.getShadowStore().add(recipient);
			} else if (!recipient.isOneOff() && recipient.get('address_type') !== 'ZARAFA') {
				// A addressbook item needs to be converted to a AddressBook record so the correct dialog is shown.
				recipient = recipient.convertToABRecord();
				// FIXME: We put the abRecord into the ShadowStore to be able
				// to open it, and obtain all details. However, we also need to
				// find a point where we can remove it again.
				container.getShadowStore().add(recipient);

				// Passed user name in message action. which is use into error message
				// which is log into error log in case if recipient is AddressBook contact
				// and it is already deleted from server and still user trying to show details.
				recipient.addMessageAction("username", recipient.get('display_name'));
			}

			config = Ext.applyIf(config || {}, { manager: Ext.WindowMgr });
			Zarafa.core.data.UIFactory.openViewRecord(recipient, config);
		}
	},

	/**
	 * Opens a {@link Zarafa.common.delegates.dialogs.DelegatePermissionContentPanel DelegatePermissionContentPanel} for editing
	 * delegate permissions of a single delegate
	 * @param {Zarafa.common.delegates.data.DelegateRecord} delegateRecord record that should be opened in
	 * {@link Zarafa.common.delegates.dialogs.DelegatePermissionContentPanel DelegatePermissionContentPanel}.
	 * @param {Object} config configuration object that should be passed to {@link Zarafa.common.delegates.dialogs.DelegatePermissionContentPanel DelegatePermissionContentPanel}.
	 */
	openDelegatePermissionContent: function(record, config)
	{
		if(!record) {
			// can not continue without a record
			return;
		}

		config = config || {};
		Ext.apply(config, {
			modal: true
		});

		Zarafa.core.data.UIFactory.openCreateRecord(record, config);
	},

 	/**
	 * Opens a {@link @link Zarafa.common.sendas.dialogs.SendAsEditContentPanel SendAsEditContentPanel} for editing
	 * user name and email address of a sendAs.
	 * @param {Ext.data.Record} record record that should be opened in
	 * {@link Zarafa.common.sendas.dialogs.SendAsEditContentPanel SendAsEditContentPanel}.
	 * @param {Object} config configuration object that should be passed to {@link Zarafa.common.sendas.dialogs.SendAsEditContentPanel SendAsEditContentPanel}.
	 */
	openSendAsRecipientContent: function(record, config)
	{
		var componentType = Zarafa.core.data.SharedComponentType['common.managecc.dialog.managecceditcontentpanel'];
		this.openEditRecipientContent(componentType, record, config);
	},

	/**
	 * Opens a {@link @link Zarafa.common.sendas.dialogs.SendAsEditContentPanel SendAsEditContentPanel} Or
	 * {@link Zarafa.common.manageCc.dialogs.ManageCcEditContentPanel ManageCcEditContentPanel} Or
	 * {@link Zarafa.common.recipientfield.ui.EditRecipientContentPanel EditRecipientContentPanel} for editing
	 * user name and email address for a sendAs, manageCc and external recipients respectively.
	 *
	 * @param {Number} componentType A component type taken from the enumeration {@link Zarafa.core.data.SharedComponentType}
	 * @param {Zarafa.core.data.MAPIRecord} record The record(s) loaded in the component
	 * @param {Object} config Configuration object
	 */
	openEditRecipientContent: function(componentType, record, config)
	{
		if(!record) {
			// can not continue without a record
			return;
		}

		config = config || {};
		Ext.apply(config, {
			modal: true
		});

		Zarafa.core.data.UIFactory.openLayerComponent(componentType, record, config);
	},

	/**
	 * Opens a {@link Zarafa.common.rules.dialogs.RulesEditContentPanel RulesEditContentPanel} for editing
	 * a single {@link Zarafa.common.rules.data.RulesRecord RulesRecord}.
	 * @param {Zarafa.common.rules.data.RulesRecord} record record to edit in
	 * {@link Zarafa.common.rules.dialogs.RulesEditContentPanel RulesEditContentPanel}.
	 * @param {Object} config config object that will be passed to {@link Zarafa.core.data.UIFactoryLayer UIFactoryLayer}.
	 */
	openRulesEditContent: function(record, config)
	{
		if(!record) {
			// can not continue without a record
			return;
		}

		config = Ext.apply(config || {}, {
			modal: true
		});

		Zarafa.core.data.UIFactory.openCreateRecord(record, config);
	},

	/**
	 * Will create an object of {@link Zarafa.common.attachment.ui.AttachmentDownloader AttachmentDownloader}
	 * and call {@link Zarafa.common.attachment.ui.AttachmentDownloader#downloadMessage} method to start download the message as file
	 * by setting the dialogFrame's location to the download URL of the given {@link Zarafa.core.data.IPMRecord records}.
	 * @param {Zarafa.core.data.IPMRecord} records The record, or records which user want to save as file.
	 * @param {Boolean} allAsZip (optional) True to downloading all the attachments as ZIP
	 */
	openSaveEmlDialog: function(records, allAsZip)
	{
		records = [].concat(records);

		var downloadComponent;
		if(!allAsZip) {
			for (var i = 0; i < records.length; i++) {
				var record = records[i];
				// Create separate iframe for each url to handle requests individually
				downloadComponent = new Zarafa.common.attachment.ui.AttachmentDownloader();
				downloadComponent.downloadItem(record.getDownloadMessageUrl(false));
			}
		} else {
			downloadComponent = new Zarafa.common.attachment.ui.AttachmentDownloader();
			downloadComponent.downloadMessageAsZip(records);
		}
	},

	/**
	 * Opens a PrintDialog for printing the contents of the given object(s).
	 * @param {Zarafa.core.data.MAPIRecord|Zarafa.core.Context} objectToPrint The record(s)
	 * that will be printed, or the context for which the items in the store will be printed.
	 */
	openPrintDialog: function(objectToPrint)
	{
		if (Ext.isEmpty(objectToPrint)) {
			return;
		} else if (Array.isArray(objectToPrint)) {
			if (objectToPrint.length > 1) {
				Ext.MessageBox.alert(_('Print'), _('Printing of multiple items has not been implemented.'));
				return;
			}

			// We only need the first record
			objectToPrint = objectToPrint[0];
		}

		var openHandler = function (store, objectToPrint) {
			if (store) {
				if (this !== objectToPrint) {
					return;
				}
				store.un('open', openHandler, objectToPrint);
			}

			var componentType = Zarafa.core.data.SharedComponentType['common.printer.renderer'];
			var component = container.getSharedComponent(componentType, objectToPrint);
			if (component) {
				var renderer = new component();
				renderer.print(objectToPrint);
			} else if (objectToPrint instanceof Zarafa.core.data.MAPIRecord) {
				Ext.MessageBox.alert(
					_('Print'),
					_('Printing of this item is not yet available') + '\n' + _('Item type: ') + objectToPrint.get('message_class')
				);
			} else {
				Ext.MessageBox.alert(
					_('Print'),
					_('Printing of this view is not yet available')
				);
			}
		};

		if (objectToPrint instanceof Zarafa.core.data.MAPIRecord && !objectToPrint.isOpened()) {
			objectToPrint.getStore().on('open', openHandler, objectToPrint);
			objectToPrint.open();
		} else {
			openHandler(undefined, objectToPrint);
		}
	},

	/**
	 * Opens a {@link Zarafa.common.checknames.dialogs.CheckNamesContentPanel CheckNamesContentPanel}
	 *
	 * @param {Array} array of checkNames
	 * @param {Zarafa.core.data.IPMRecipientRecord} recipientrecord
	 * @param {Object} config (optional) Configuration object for creating the content panel
	 */
	openCheckNamesContent: function(checkNamesData, recipientRecord, config)
	{
		var componentType = Zarafa.core.data.SharedComponentType['common.dialog.checknames'];
		config = Ext.applyIf(config || {}, {
			checkNamesData: checkNamesData,
			modal: true
		});
		Zarafa.core.data.UIFactory.openLayerComponent(componentType, recipientRecord, config);
	},

	/**
	 * Opens a {@link Zarafa.common.reminder.dialogs.ReminderContentPanel remindercontentpanel}
	 * @param {Zarafa.common.reminder.ReminderRecord} records Records for which the reminder content panel will be displayed.
	 * @param {Object} config (optional) Configuration object
	 */
	openReminderContent: function(records, config)
	{
		var componentType = Zarafa.core.data.SharedComponentType['common.dialog.reminder'];
		var component = container.getSharedComponent(componentType, records);

		if ( window.showReminderPanelInPopout ) {
			config = Ext.applyIf(config || {}, {
				layerType: 'separateWindows',
				width: 450,
				height: 500
			});
		} else {
			config = Ext.applyIf(config || {}, {
				modal: false,
				manager: Ext.WindowMgr
			});
		}

		// check if panel is already open
		var contentPanelInstances = Zarafa.core.data.ContentPanelMgr.getContentPanelInstances(component);

		// there can be no reminder dialog or only one reminder dialog
		// multiple reminder dialogs are not allowed
		if(contentPanelInstances.getCount() === 0) {
			// create a new reminder dialog, if there are any reminders to show
			if(records.length > 0) {
				Zarafa.core.data.UIFactory.openLayerComponent(componentType, records, config);
			}
		} else if (contentPanelInstances.getCount() === 1) {
			// we already have a reminder dialog open, use it
			var reminderDialog = contentPanelInstances.first();

			if(records.length > 0) {
				// there are reminders to show, so give focus to existing reminder dialog
				Zarafa.core.BrowserWindowMgr.getOwnerWindow(reminderDialog).focus();
				reminderDialog.focus();
			} else {
				// no reminders to show, close existing dialog
				reminderDialog.close();
			}
		}
	},

	/**
	 * Function will dismiss the record and open it.
	 * @param {Zarafa.common.reminder.data.ReminderRecord|Zarafa.common.reminder.data.ReminderRecord[]} record
	 * The reminder record/records which should be opened.
	 * @param {Object} config configuration object.
	 */
	openReminderRecord: function(record, config)
	{
		config = config || {};
		var store = record.getStore();
		if (record) {
			store.dismissReminders(record, true);
		}

		// Set the focus to the main window because the reminder panel
		// could be shown in a separate window in DeskApp
		if (Zarafa.isDeskApp && window.global && window.global.DA && window.global.DA.winWebApp) {
			window.global.DA.winWebApp.focus();
		}
	},

	/**
	 * Opens a {@link Zarafa.common.dialogs.MessageBox.select MessageBox} for
	 * selecting if either a recurrence or the entire series must be opened for the Recurring
	 * Message.
	 *
	 * @param {Function} handler The handler which is invoked with the selected value
	 * from the dialog. This function only takes one argument and is either 'recurrence_occurrence'
	 * when the single-occurrence was selected or 'recurrence_series' when the series was selected.
	 * @param {Object} scope (optional) The scope on which the handler must be invoked.
	 */
	// TODO: Merge with deleteRecurringSelectionContentPanel
	openRecurringSelectionContent: function(record, handler, scope)
	{
		var title = _('Recurring Message');
		var text = _('This is a recurring message. Do you want to open only this occurrence or the series?');

		if (record.isMessageClass('IPM.Appointment', true)) {
			if (record.get('meeting') == Zarafa.core.mapi.MeetingStatus.NONMEETING) {
				title = _('Recurring Appointment');
				text = _('This is a recurring appointment. Do you want to open only this occurrence or the series?');
			} else {
				title = _('Recurring Meeting Request');
				text = _('This is a recurring meeting request. Do you want to open only this occurrence or the series?');
			}
		} else if (record.isMessageClass('IPM.TaskRequest', true)) {
			title = _('Recurring Task Request');
			text = _('This is a recurring task request. Do you want to open only this occurrence or the series?');
		}

		Zarafa.common.dialogs.MessageBox.select(
			title, text, handler, scope, [{
				boxLabel: _('Open this occurrence'),
				id: 'recurrence_occurrence',
				name: 'select',
				checked: true
			},{
				boxLabel: _('Open the series'),
				id: 'recurrence_series',
				name: 'select'
			}]
		);
	},

	/**
	 * Opens a {@link Zarafa.common.dialogs.MessageBox.select MessageBox} for
	 * selecting if either a recurrence or the entire series must be paste for the Recurring
	 * appointment/meeting.
	 *
	 * @param {Function} handler The handler which is invoked with the selected value
	 * from the dialog. This function only takes one argument and is either 'recurrence_occurrence'
	 * when the single-occurrence was selected or 'recurrence_series' when the series was selected.
	 * @param {Object} scope (optional) The scope on which the handler must be invoked.
	 */
	copyRecurringSelectionContent: function(record, handler, scope)
	{
		var title = _('Paste Recurring {0}');
		var text = _('This is a recurring {0}. Do you want to paste only this occurrence or the series?');

		var msgText = _('message');
		if (record.isMessageClass('IPM.Appointment', true)) {
			msgText = record.isMeeting() ? _('meeting request') : _('appointment');
		}

		title = String.format(title,Ext.util.Format.capitalize(msgText));
		text = String.format(text, msgText);

		Zarafa.common.dialogs.MessageBox.select(
			title, text, handler, scope, [{
				boxLabel: _('Paste this occurrence only'),
				id: 'recurrence_occurrence',
				name: 'select',
				checked: true,
				showButtonText: 'ok',
				hideButtonText: 'next'
			},{
				boxLabel: _('Paste the series…'),
				id: 'recurrence_series',
				name: 'select',
				showButtonText: 'next',
				hideButtonText: 'ok'
			}],
			undefined,
			[{
				text: _('Ok'),
				name: 'ok'
			}, {
				text: _('Cancel'),
				name: 'cancel'
			}]
		);
	},

	/**
	 * Opens a {@link Zarafa.common.dialogs.MessageBox.select MessageBox} for
	 * selecting if either a recurrence or the entire series must be deleted.
	 *
	 * @param {Function} handler The handler which is invoked with the selected value
	 * from the dialog. This function only takes one argument and is either 'recurrence_occurrence'
	 * when the single-occurrence was selected or 'recurrence_series' when the series was selected.
	 * @param {Object} scope (optional) The scope on which the handler must be invoked.
	 */
	// TODO: Merge with openRecurringSelectionContentPanel
	deleteRecurringSelectionContent: function(record, handler, scope)
	{
		var title = _('Recurring Message');
		var text = _('This is a recurring message. Do you want to delete only this occurrence or the series?');

		if (record.isMessageClass('IPM.Appointment', true)) {
			if (record.get('meeting') == Zarafa.core.mapi.MeetingStatus.NONMEETING) {
				title = _('Recurring Appointment');
				text = _('This is a recurring appointment. Do you want to delete only this occurrence or the series?');
			} else {
				title = _('Recurring Meeting Request');
				text = _('This is a recurring meeting request. Do you want to delete only this occurrence or the series?');
			}
		} else if (record.isMessageClass('IPM.TaskRequest', true)) {
			title = _('Recurring Task Request');
			text = _('This is a recurring task request. Do you want to delete only this occurrence or the series?');
		}

		Zarafa.common.dialogs.MessageBox.select(
			title, text, handler, scope, [{
				boxLabel: _('Delete this occurrence'),
				id: 'recurrence_occurrence',
				name: 'select',
				checked: true
			},{
				boxLabel: _('Delete the series'),
				id: 'recurrence_series',
				name: 'select'
			}]
		);
	},

	/**
	 * Opens a {@link Zarafa.common.dialogs.MessageBox.select MessageBox} for
	 * selecting if either a update need to be send to meeting Organizer or silently deleted items.
	 *
	 * @param {Function} handler The handler which is invoked with the selected value
	 * from the dialog. This function only takes one argument and is either 'sendResponseOnDelete'
	 * when the delete and response was selected or 'onResponseOnDelete' when the delete without response was selected.
	 * @param {Object} scope (optional) The scope on which the handler must be invoked.
	 */
	// TODO: may be Merge with deleteRecurringSelectionContentPanel
	deleteMeetingRequestConfirmationContent: function(record, handler, scope)
	{
		var title = _('Confirm Delete');
		var acceptedText = _('This "{0}" meeting was already accepted.');
		var noResponsedText = _('You have not responded to the meeting request "{0}".');

		var text;
		if(record.get('responsestatus') == Zarafa.core.mapi.ResponseStatus.RESPONSE_NOT_RESPONDED) {
			text = String.format(noResponsedText, record.get('subject'));
		} else {
			text = String.format(acceptedText, record.get('subject'));
		}

		Zarafa.common.dialogs.MessageBox.select(
			title, text, handler, scope, [{
				boxLabel: _('Delete and send a response to the meeting organizer'),
				id: 'sendResponseOnDelete',
				name: 'select',
				checked: true
			},{
				boxLabel: _('Delete without sending'),
				id: 'noResponseOnDelete',
				name: 'select'
			}]
		);
	},

	/**
	 * Deletes all {@link Zarafa.core.data.IPMRecord records} from the {@link Zarafa.core.data.IPMStore store}.
	 * If the records are deleted from the To-do list the deleting is delegated to
	 * {@link Zarafa.task.Actions.deleteRecordsFromTodoList} otherwise it is delegated to {#doDeleteRecords}
	 *
	 * @param {Array} records The array of records which must be deleted.
	 * @param {Boolean} askOcc (private) False to prevent a dialog to appear to ask if the occurrence or series must
	 * be deleted
	 * @param {Boolean} softDelete (optional) true to directly soft delete record(s) skipping deleted-items
	 * folder, false otherwise
	 *
	 * FIXME: This introduces Calendar-specific and To-do list (Task)-specific actions into the Common Context,
	 * but there is no clean solution for this at this time. But we need to split this up into context-specific
	 * actions while maintaining this single-entrypoint for deleting records.
	 */
	deleteRecords: function(records, askOcc, softDelete)
	{
		if (Ext.isEmpty(records)) {
			return;
		}
		if (!Array.isArray(records)) {
			records = [ records ];
		}

		// entryId is undefined if store is shadow store object.
		var folderEntryid = records[0].getStore().entryId;
		var folder = container.getHierarchyStore().getFolder(folderEntryid);

		// Check if the records are deleted from the todolist
		if (folder && folder.isTodoListFolder()) {
			Zarafa.task.Actions.deleteRecordsFromTodoList(records);
			return;
		}
		if (Zarafa.core.EntryId.compareEntryIds(records[0].get('parent_entryid'), folderEntryid) === false) {
			folderEntryid = records[0].get('parent_entryid');
			folder = container.getHierarchyStore().getFolder(folderEntryid);
		}

		if (folder && folder.hasDeleteOwnRights() === false) {
			Ext.MessageBox.show({
				title : _('Insufficient permissions'),
				msg : _("You have insufficient privileges to delete items in this folder."),
				cls: Ext.MessageBox.ERROR_CLS,
				buttons: Ext.MessageBox.OK
			});
			return;
		}

		this.doDeleteRecords(records, askOcc, softDelete);
	},

	/**
	 * Deletes all {@link Zarafa.core.data.IPMRecord records} from the {@link Zarafa.core.data.IPMStore store}.
	 * If any of the given {@link Zarafa.core.data.IPMRecord records} is an recurring item, then
	 * a {@link Zarafa.common.dialogs.MessageBox.select MessageBox} will be prompted which lets the user
	 * select between the series or the single occurrence.
	 * All given {@link Zarafa.core.data.IPMRecord records} must be located in the same
	 * {@link Zarafa.core.data.IPMStore store}.
	 *
	 * @param {Array} records The array of records which must be deleted.
	 * @param {Boolean} askOcc (private) False to prevent a dialog to appear to ask if the occurrence or series must
	 * be deleted
	 * @param {Boolean} softDelete (optional) true to directly soft delete record(s) skipping deleted-items
	 * folder, false otherwise
	 *
	 * FIXME: This introduces Calendar-specific actions into the Common Context, but there is no clean solution
	 * for this at this time. But we need to split this up into context-specific actions while maintaining this
	 * single-entrypoint for deleting records.
	 */
	doDeleteRecords: function(records, askOcc, softDelete)
	{
		var store;
		var saveRecords = [];

		for (var i = 0, len = records.length; i < len; i++) {
			var record = records[i];
			store = record.getStore();

			// Check if the item is recurring, and if we need to ask the user
			// if the occurrence or series must be deleted
			var deleteRecurring = Ext.isFunction(record.isRecurringOccurrence) && record.isRecurringOccurrence() && askOcc !== false;

			// Meeting and task requests are always deleted as normal,
			// we don't care for the recurring state of the record.
			var messageClass = record.get('message_class');
			if (Zarafa.core.MessageClass.isClass(messageClass, 'IPM.Schedule.Meeting', true) ||
				Zarafa.core.MessageClass.isClass(messageClass, 'IPM.TaskRequest', true)) {
					deleteRecurring = false;
			}

			if (deleteRecurring) {
				// Deleting an recurring series requires a confirmation dialog.
				this.deleteRecurringItem(record);
			} else if (Ext.isFunction(record.isMeeting) && record.isMeeting() && !record.isAppointmentInPast() && !record.isMeetingCanceled()) {
				// delete action on future meeting items
				if (record.isMeetingSent()) {
					// We are the organizer of the meeting, so lets ask if the recipients should be notified.
					Ext.MessageBox.show({
						title: _('Notify recipients'),
						msg: _('A cancellation message will be sent to all recipients, do you wish to continue?'),
						fn: this.cancelInvitation,
						scope: record,
						buttons: Ext.MessageBox.YESNO
					});
				} else if (record.isMeetingResponseRequired() && !record.isCopied()) {
					// We are the attendee of the meeting, lets ask if we should inform the organizer
					this.deleteMeetingRequestConfirmationContent(record, this.declineInvitation, record);
				} else {
					// We are neither, we don't care, just delete the thing
					store.remove(record);
					saveRecords.push(record);
				}
			} else if (record.isMessageClass('IPM.TaskRequest') || (Ext.isFunction(record.isTaskReceived) && record.isTaskReceived())) {
				// If task is assigned task by assigner and it is not completed then
				// ask for the user conformation like "Delete", "Mark complete and delete"
				// or "Mark decline and delete" and if task is already completed then we dont
				// require any confirmation from user.
				if (!record.get('complete')) {
					this.deleteAssignedTaskConfirmationContent(record, this.declineTask, record);
				} else {
					store.remove(record);
					saveRecords.push(record);
				}
			} else if (!record.isRead() && record.needsNonReadReceipt() &&
			    (softDelete || record.isInDeletedItems())) {
				switch (container.getSettingsModel().get("zarafa/v1/contexts/mail/readreceipt_handling")) {
				case 'never':
					store.remove(record, undefined, false);
					saveRecords.push(record);
					break;
				case 'always':
					store.remove(record, undefined, true);
					saveRecords.push(record);
					break;
				case 'ask':
				default:
					Ext.MessageBox.show({
						title: _("Notify sender"),
						msg: String.format(_("{0} has requested a notification be sent when message \"{1}\" is deleted without having been read. Do you want to send a receipt?"),
						     record.get("sender_name") || record.get("sender_email_address"),
						     record.get("subject")),
						buttons: Ext.MessageBox.YESNOCANCEL,
						fn: this.deleteWithNonReadReceipt,
						scope: record,
						softDelete: softDelete
					});
					saveRecords.push(record);
					break;
				}
			} else {
				// normal delete action
				store.remove(record);
				saveRecords.push(record);
			}
		}

		if(!Ext.isEmpty(saveRecords)) {
			// Check if records are required to be soft deleted
			if (softDelete === true) {
				Ext.each(saveRecords, function(saveRecord) {
					saveRecord.addMessageAction('soft_delete', true);
				}, this);
			}
			store.save(saveRecords);
		}

		// If number of delete records equal to total loaded records then show load mask until server send success response.
		if (store.totalLoadedRecord && records.length === store.totalLoadedRecord)
			store.showLoadMask();
	},

	/**
	 * Function delete an assigned task and sends decline/complete task response message to assigner.
	 * function was triggered in scope of the {@link Zarafa.task.TaskRecord TaskRecord}.
	 *
	 * @param {String} buttonClicked The ID of the button pressed,
	 * here, one of: ok cancel.
	 * @param {Ext.form.Radio} radio The Radio which was selected by the user.
	 * @private
	 */
	declineTask: function (buttonClicked, radio)
	{
		if (buttonClicked === 'ok') {
			this.deleteIncompleteTask(radio.id);
		}
	},

	/**
	 * Opens a {@link Zarafa.common.dialogs.MessageBox.select MessageBox} for
	 * selecting if decline and delete, complete and delete task response
	 * need to be sent to task assigner or silently deleted items.
	 *
	 * @param {Function} handler The handler which is invoked with the selected value
	 * from the dialog. This function only takes one argument and is either 'declineAndDelete'
	 * when the delete task and send decline response to assignor , 'completeAndDelete'
	 * when the delete task and send complete task response to assigner or 'delete'
	 * when the delete task without sending any response to assigner.
	 *
	 * @param {Object} scope (optional) The scope on which the handler must be invoked.
	 */
	deleteAssignedTaskConfirmationContent: function (record, handler, scope)
	{
		var title = _('Delete Incomplete Task');
		var text = _('The task "{0}" has not been completed. What do you want to do?');

		text = String.format(text, record.get('subject'));

		Zarafa.common.dialogs.MessageBox.select(
			title, text, handler, scope, [{
				boxLabel: _('Decline and delete'),
				id: 'declineAndDelete',
				name: 'select',
				checked: true
			},{
				boxLabel: _('Mark complete and delete'),
				id: 'completeAndDelete',
				name: 'select'
			},{
				boxLabel: _('Delete'),
				id: 'delete',
				name: 'select'
			}]
		);
	},

	/**
	 * Function which prompt user with deleting for recurring Meeting or normal recurring
	 * appointment and also manages sending response to meeting organizer.
	 *
	 * @param {Ext.data.Record} record that must be deleted
	 * @private
	 */
	deleteRecurringItem: function(record) {
		Zarafa.common.Actions.deleteRecurringSelectionContent(record, function(button, radio) {
			if (button != 'ok') {
				return;
			}

			if (radio.id != 'recurrence_series') {
				record = record.convertToOccurrenceRecord();
			} else {
				record = record.convertToSeriesRecord();
			}
			container.getShadowStore().add(record);

			Zarafa.common.Actions.deleteRecords(record, false);
		}, this);
	},

	/**
	 * Function cancels Meeting invitation and sends Meeting Cancellation message.
	 *
	 * @param {String} buttonClicked The ID of the button pressed,
	 * @param {String} text Value of the input field, not useful here
	 * @private
	 */
	cancelInvitation: function(buttonClicked, text)
	{
		if (buttonClicked == 'yes') {
			// Here scope is record so this refers to Appointment Record.
			this.cancelInvitation();
		}
	},

	/**
	 * Function displays a dialog for non read notify confirmation.
	 * @param {String} buttonClicked The ID of the button pressed,'yes', 'no' or 'cancel'
	 * @param {String} text Value of the input field, not used here
	 * @param {Object} opt The config object passed to the messagebox
	 */
	deleteWithNonReadReceipt: function(buttonClicked, text, opt) {
		if (buttonClicked == "cancel")
			return;
		// @this is a record
		if (opt?.softDelete) {
			this.addMessageAction("soft_delete", "1");
		}
		var non_read_notify = buttonClicked == "yes" ? "1" : "0";
		this.addMessageAction("non_read_notify", non_read_notify);
		var store = this.getStore();
		store.remove(this);
		store.save(this);
	},

	/**
	 * Function declines a Meeting invitation and sends Meeting Decline message.
	 *
	 * @param {String} buttonClicked The ID of the button pressed,
	 * here, one of: ok cancel.
	 * @param {Ext.form.Radio} radio The Radio which was selected by the user.
	 * @private
	 */
	declineInvitation: function(buttonClicked, radio)
	{
		if (buttonClicked == 'ok') {
			// Here scope is record so this refers to Appointment Record.
			var sendUpdateFlag = (radio.id == 'sendResponseOnDelete') ? true : false;
			this.declineMeeting(sendUpdateFlag);
		}
	},

	/**
	 * Opens a {@link Zarafa.common.restore.ui.RestoreContentPanel restoreContentPanel}
	 *
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder folder that is loaded for the new context
	 * @param {Object} config (optional) Configuration object for creating the content panel
	 */
	openRestoreContent: function(folder, config)
	{
		var componentType = Zarafa.core.data.SharedComponentType['common.dialog.restoreitems'];
		config = Ext.applyIf(config || {}, {
			folder: folder
		});
		Zarafa.core.data.UIFactory.openLayerComponent(componentType, undefined, config);
	},

	/**
	 * Opens attachment dialog.
	 *
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder folder that is loaded for the new context
	 * @param {Object} config (optional) Configuration object for creating the content panel
	 */
	openImportContent: function(folder, config)
	{
		config = Ext.applyIf(config || {}, {
			callback: this.importItemCallback.createDelegate(this, [ folder ], 1),
			multiple: true,
			accept: '.eml',
			scope: this
		});

		var attachComponent = new Zarafa.common.attachment.ui.UploadAttachmentComponent(config);
		attachComponent.openAttachmentDialog();
	},

	/**
	 * Opens a {@link Zarafa.addressbook.dialogs.ABUserSelectionContentPanel ABUserSelectionContentPanel}
	 *
	 * @param {Object} config Configuration object. For AB this normally includes:
	 * 	callback - Callback function to be called with the user selected in the ContentPanel
	 * 	hideContactsFolders - Restriction that has to be applied on the hierarchy of the Addressbook
	 * 	listRestriction - Restriction that has to be applied on the contents of the Addressbook
	 */
	openABUserSelectionContent: function(config)
	{
		var componentType = Zarafa.core.data.SharedComponentType['addressbook.dialog.abuserselection'];
		config = Ext.applyIf(config || {}, {
			modal: true
		});

		Zarafa.core.data.UIFactory.openLayerComponent(componentType, undefined, config);
	},

	/**
	 * Callback function for {@link Zarafa.addressbook.dialogs.ABUserSelectionContent AddressBook}
	 * This callback is used in {@link Zarafa.common.sendas.ui.SendAsPanel SendAsPanel}
	 * and {@link Zarafa.common.manageCc.ui.ManageCcPanel ManageCcPanel} to display a message
	 * that recipeint already exists if selected recipeint from adressbook is already present in the store.
	 * @param {Ext.data.Record} record user selected from AddressBook
	 * @private
	 */
	abCallBack: function(records)
	{
		var store = this.getStore();
		// find rowid value
		var data = Ext.pluck(store.getRange(), 'data');
		var rowId = Ext.max(Ext.pluck(data, 'rowid')) || 0;

		var duplicate = [];
		for (var i = 0; i < records.length; i++) {
			var record = records[i];
			if (store.isRecipientExists(record)) {
				duplicate.push(record.get('display_name'));
				continue;
			}

			var recipientType;
			if (store.customObjectType === Zarafa.core.data.RecordCustomObjectType.ZARAFA_CC_RECIPIENT) {
				recipientType = Zarafa.core.mapi.RecipientType.MAPI_CC;
			}
			var recipientRecord = record.convertToRecipient(recipientType, store.customObjectType);
			recipientRecord.set('rowid', ++rowId);

			store.add(recipientRecord);
		}

		// Show warning message box.
		if (!Ext.isEmpty(duplicate)) {
			if (duplicate.length > 1) {
				var msg = _('Following recipients are already exists');
				msg += '<br>' + duplicate.map(function (item) {
					return '<br>' + item;
				});

				return Ext.Msg.alert(_('Duplicate recipients'), msg);
			}
			Ext.Msg.alert(_('Duplicate recipient'), _('Recipient already exists.'));
		}
	},

	/**
	 * Opens a {@link Zarafa.addressbook.dialog.ABMultiUserSelectionContentPanel ABMultiUserSelectionContentPanel}
	 *
	 * @param {Object} config Configuration object for the dialog
	 */
	openABUserMultiSelectionContent: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			modal: true,
			convert: function(user) { return user; }
		});

		var componentType = Zarafa.core.data.SharedComponentType['addressbook.dialog.abmultiuserselection'];
		Zarafa.core.data.UIFactory.openLayerComponent(componentType, undefined, config);
	},

	/**
	 * Mark the given messages as read or unread. When a read receipt was requested
	 * for this message, the settings are consulted to see if we must automatically
	 * send the receipt or not, or if we should ask the user.
	 *
	 * @param {Zarafa.core.data.IPMRecord/Array} records The record or records which must
	 * be marked as read.
	 * @param {Boolean} read (optional) False to mark the messages as unread, otherwise
	 * the message will be marked as read.
	 */
	markAsRead: function(records, read)
	{
		records = !Array.isArray(records) ? [ records ] : records;
		read = !Ext.isDefined(read) ? true : read;

		var saveRecords = [];

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

			// If the read status already matches the desired state,
			// we don't need to do anything.
			if (read === record.isRead()) {
				continue;
			}
			if (read !== true || !record.needsReadReceipt()) {
				record.setReadFlags(read);
				saveRecords.push(record);
				continue;
			}
			switch (container.getSettingsModel().get('zarafa/v1/contexts/mail/readreceipt_handling')) {
			case 'never':
				record.setReadFlags(read);
				// Never send a read receipt.
				record.addMessageAction('send_read_receipt', false);

				saveRecords.push(record);
				break;
			case 'always':
				record.setReadFlags(read);
				// Always send a read receipt.
				record.addMessageAction('send_read_receipt', true);

				saveRecords.push(record);
				break;
			case 'ask':
				/* falls through*/
			default:
				const store = record.getStore();
				// Ask if a read receipt must be send.
				Ext.MessageBox.show({
					title: _('Notify sender'),
					msg: _('The sender of this email has requested a read receipt. Do you wish to notify the sender?'),
					buttons: Ext.MessageBox.YESNO,
					fn: // This function will execute when user provide some inputs,
					// So other external changes should not affect the record.
					function(buttonClicked) {
					// If the mailgrid has reloaded, retrieve the newly updated record.
						var resolved = Zarafa.common.Actions.resolveRecords([this], store);
						var targetRecord = resolved.length > 0 ? resolved[0] : this;

						if (!targetRecord) {
							return;
						}

						targetRecord.setReadFlags(read);
						targetRecord.addMessageAction('send_read_receipt', buttonClicked !== 'no');
						targetRecord.save();
					},
					scope: record
				});
				break;
			}
		}

		if (!Ext.isEmpty(saveRecords)) {
			saveRecords[0].store.save(saveRecords);
		}
	},

	/**
	 * Will start the download by setting the dialogFrame's location to the download URL of the file.
	 *
	 * @param {Zarafa.core.data.IPMAttachmentRecord} records The record of the file to be downloaded
	 * @param {Boolean} allAsZip (optional) True to downloading all the attachments as ZIP
	 */
	downloadAttachment: function(record, allAsZip)
	{
		if (this.downloadFrame) {
			// If download frame is not available in active browser window then
			// create new download frame under active browser window.
			if (!Ext.getBody().contains(this.downloadFrame.getEl().dom)) {
				this.downloadFrame = new Zarafa.common.attachment.ui.AttachmentDownloader();
			}
		} else {
			this.downloadFrame = new Zarafa.common.attachment.ui.AttachmentDownloader();
		}

		this.downloadFrame.checkForEmbeddedAttachments(record, allAsZip);
	},

	/**
	 * Opens a {@link Zarafa.common.rules.dialogs.RulesWordsEditContentPanel}
	 *
	 * @param {Object} config Configuration object for the dialog
	 */
	openRulesWordsEditContent: function(config)
	{
		var componentType = Zarafa.core.data.SharedComponentType['common.rules.dialog.ruleswordsedit'];
		Zarafa.core.data.UIFactory.openLayerComponent(componentType, undefined, config);
	},

	/**
	 * Function is used to download attachments, for embedded message attachments additionally it will
	 * convert the {@link Zarafa.core.data.IPMAttachmentRecord IPMAttachmentRecord} to {@link Zarafa.core.data.IPMRecord IPMRecord}
	 * and then will pass it to {@link Zarafa.core.ui.ContentPanel ContentPanel} to open it.
	 * @param {Zarafa.core.data.IPMAttachmentRecord} record The attachment record which should be opened.
	 * @param {Object} config configuration object.
	 */
	openAttachmentRecord: function(record, config)
	{
		var modal = false;
		if(record.isEmbeddedMessage()) {
			// if we are going to open embedded message then we need to first convert it into mail record
			record = record.convertToIPMRecord();
		} else {
			modal = Zarafa.common.Actions.isSupportedDocument(record.get("name"));
		}

		config = Ext.applyIf(config||{}, {
			modal: modal,
			autoResize: true
		});

		if(record) {
			Zarafa.core.data.UIFactory.openViewRecord(record, config);
		}
	},

	/**
	 * Raised a dialog to choose destination folder to import attachments.
	 * @param {Zarafa.core.data.IPMAttachmentRecord} record The attachment record which should be imported.
	 * @param {Object} config configuration object.
	 */
	importToFolder: function(record, config)
	{
		config = Ext.applyIf(config || {}, {
			modal: true
		});
		var componentType = Zarafa.core.data.SharedComponentType['common.attachment.dialog.importtofolder'];
		Zarafa.core.data.UIFactory.openLayerComponent(componentType, record, config);
	},

	/**
	 * Open a Panel in which the {@link Zarafa.core.data.IPMRecord record}
	 * can be viewed, or further edited.
	 *
	 * @param {Zarafa.core.data.IPMRecord} records The records to open
	 * @param {Object} config (optional) Configuration object used to create
	 * @param {Boolean} suppressException true to suppress popup of exception
	 * the Content Panel.
	 */
	openMessageContent: function(records, config, suppressException)
	{
		Ext.each(records, function(record) {
			if (suppressException) {
				record.suppressException();
			}
			if ((record.isUnsent() || record.isScheduled()) && !record.isFaultyMessage()) {
				Zarafa.core.data.UIFactory.openCreateRecord(record, config);
			} else {
				// Combine open + mark-as-read into one request when
				// the message is unread.  Content panels always use
				// ignoreReadFlagTimer: true, so the MarkAsReadPlugin
				// would fire a second save request immediately after
				// the open response.  By piggybacking mark_read on
				// the open, we avoid that extra HTTP round-trip.
				// Task requests are excluded because they are
				// transformed into task records below.
				if (!record.isRead() && !record.isMessageClass('IPM.TaskRequest', true)) {
					if (!record.needsReadReceipt()) {
						record.addMessageAction('mark_read', true);
					} else {
						var handling = container.getSettingsModel().get('zarafa/v1/contexts/mail/readreceipt_handling');
						if (handling === 'never') {
							record.addMessageAction('mark_read', true);
							record.addMessageAction('send_read_receipt', false);
						} else if (handling === 'always') {
							record.addMessageAction('mark_read', true);
							record.addMessageAction('send_read_receipt', true);
						}
						// 'ask': let MarkAsReadPlugin handle it so the
						// user gets the read-receipt confirmation dialog.
					}
				}

				if(record.isMessageClass('IPM.TaskRequest', true)) {
					record = Zarafa.core.data.RecordFactory.createRecordObjectByMessageClass('IPM.Task', {
						entryid: record.get('entryid'),
						store_entryid: record.get('store_entryid'),
						parent_entryid: record.get('parent_entryid'),
						task_goid: record.get('task_goid')
					}, record.get('entryid'));
					record.addMessageAction('open_task', true);
				}
				// When a message has been opened before and the body has been
				// cleared (e.g. after closing a tab in Firefox), the record is
				// still marked as opened and won't be reloaded automatically.
				// Force a reload when no body content is available.
				if (record.isOpened() && Ext.isEmpty(record.get('body')) && Ext.isEmpty(record.get('html_body'))) {
					record.open({forceLoad: true});
				}
				Zarafa.core.data.UIFactory.openViewRecord(record, config);
			}
		});
	},

	/**
	 * Helper function which used to get the all recipient based or the recipient type. The recipient type can be
	 * {@link Zarafa.core.mapi.RecipientType.MAPI_TO To}, {@link Zarafa.core.mapi.RecipientType.MAPI_CC Cc} or
	 * {@link Zarafa.core.mapi.RecipientType.MAPI_BCC BCc}.
	 *
	 * @param {Zarafa.core.data.IPMRecipientStore} store The {@link Zarafa.core.data.IPMRecipientStore store} which contains the recipients.
	 * @param {Number} recipientType The recipientType it can be {@link Zarafa.core.mapi.RecipientType.MAPI_TO To},
	 * {@link Zarafa.core.mapi.RecipientType.MAPI_CC Cc} or {@link Zarafa.core.mapi.RecipientType.MAPI_BCC BCc}.
	 *
	 * @returns {Zarafa.core.dat.IPMRecipientRecord[]} same record type of records from given store.
	 */
	getRecipientsByType: function(store, recipientType)
	{
		return store.getRange().filter(function(item) {
			return item.get('recipient_type') === recipientType;
		}, this);
	},

	/**
	 * Check if the given file is either a PDF file or a ODF file.
	 *
	 * @param path
	 * @returns {boolean}
	 * @private
	 */
	isSupportedDocument: function (path)
	{
		return path.match(/^.*\.(pdf|od[tps]|docx|xlsx|jpg|jpeg|png|bmp|gif|mp4|mp3|ogg|webm|wav)$/i) ? true : false;
	},

	/**
	 * Helper function which check files previewer is enabled by
	 * admin as well as by user.
	 *
	 * @return true to file previewer is enabled by the admin and user else false.
	 */
	isFilePreviewerEnabled: function ()
	{
		if (!container.getServerConfig().isFilePreviewerEnabled()) {
			return false;
		}
		// First of all check if filepreviewer plugin settings available else check main settings.
		return container.getSettingsModel().getOneOf('zarafa/v1/plugins/filepreviewer/enable', 'zarafa/v1/main/file_previewer/enable');
	},

	/**
	 * Copy email address(es) of the given recipient or type of recipient.
	 *
	 * @param {Zarafa.core.dat.IPMRecipientRecord} record The record is resolved
	 * @param {Zarafa.core.data.IPMRecipientStore} store The {@link Zarafa.core.data.IPMRecipientStore store} which contains the recipients.
	 * @param {Boolean} copyAll The copyAll true to copy all the recipients from TO,Cc or BCc fields
	 */
	copyEmailAddress: function (record, store, copyAll)
	{
		var email;
		if (copyAll) {
			var recipients = this.getRecipientsByType(store, record.get('recipient_type'));
			email = recipients.map(function(item) {
				return item.get('smtp_address') || item.get('email_address');
			}).join(',');

		} else {
			email = record.get('smtp_address') || record.get('email_address');
			if(Ext.isEmpty(email)) {
				return;
			}
		}

		Zarafa.core.Util.copyToClipboard(email);
	},

	/**
	 * It will create a new {@link Zarafa.core.data.MAPIRecord mail record}
	 * and generate new phantom recipient for new mail. Then
	 * it will open a new MailCreate ContentPanel for the new mail
	 *
	 * @param {Zarafa.core.dat.IPMRecipientRecord} record The record is recipient record
	 * @private
	 */
	onEmailRecipient: function(recipient)
	{
		var folder = container.getHierarchyStore().getDefaultFolder('drafts');
		var context = container.getContextByFolder(folder);
		var model = context.getModel();

		var record = model.createRecord(folder);

		var recipientStore = record.getRecipientStore();
		var recipientRecord = Zarafa.core.data.RecordFactory.createRecordObjectByCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_RECIPIENT, recipient.data);

		// Make sure the recipient is of type MAPI_TO, the original recipient might have
		// been a CC or BCC. But now we want to directly email him.
		recipientRecord.set('recipient_type', Zarafa.core.mapi.RecipientType.MAPI_TO);

		recipientStore.add(recipientRecord);

		Zarafa.core.data.UIFactory.openCreateRecord(record);
	},

	/**
	 * Callback function for {@link Zarafa.common.attachment.ui.UploadAttachmentComponent}.
	 * which is going to call necessary helper function.
	 *
	 * @param {Object/Array} files The files is contains file information.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder folder to which files needs to be imported.
	 * @param {Boolean} show Open the imported item
	 */
	importItemCallback: function(files, folder, show)
	{
		Zarafa.common.Actions.brokenFiles = [];
		Zarafa.common.Actions.totalFiles = files.length;
		Zarafa.common.Actions.showImported = show === true;
		this.readFiles(files, folder);
	},

	/**
	 * Proxy callback for import action
	 * @param {Object} response The response received
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder The selected folder
	 * @protected
	 */
	importDone: function(response, folder)
	{
		if (response.success === true) {
			if ( this.showImported !== true ) {
			  container.getNotifier().notify('info.import', _('Import'), String.format(_('Successfully imported item(s) to {0}'), folder.get('display_name')));
			}
		} else {
			container.getNotifier().notify('info.import', _('Import'), String.format(_('Failed to import item(s) to {0}'), folder.get('display_name')));
		}

		if ( this.showImported === true && Ext.isDefined(response.items)) {
			var record = Zarafa.core.data.RecordFactory.createRecordObjectByMessageClass('IPM.Note', {
				'entryid': response.items,
				'parent_entryid': folder.get('entryid'),
				'store_entryid': folder.get('store_entryid')
			});

			record.opened = false;
			Zarafa.core.data.UIFactory.openViewRecord(record);
		}
	},

	/**
	 * Helper function to read selected files.
	 * This will be executed recursively to read files synchronously.
	 * @param {Object/Array} files The files is contains file information.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder folder to which files needs to be imported.
	 * @param {Number} index Index of the file to process.
	 */
	readFiles: function(files, folder, index)
	{
		// Terminate the recursive calls and import the files
		if (index === this.totalFiles) {
			this.importFiles(files, folder);
			return;
		}

		// Start with first file if this is the first call
		if (index === undefined) {
			index = 0;
		}

		// Make sure we are processing only eml and ics/vcs files for broken check
		if (!this.isEmlFile(files[index]) && !this.isICSFile(files[index]) && !this.isVCFFile(files[index])) {
			this.brokenFiles.push(files[index]);
			index++;
			this.readFiles(files, folder, index);
		} else {
			var reader = new FileReader();
			reader.onload = this.checkBroken.createDelegate(this, [ files, index, folder ], true);
			reader.readAsText(files[index]);
		}
	},

	/**
	 * Handler for the load event of FileReader.
	 * Check if the file is broken or not. Prepares an array containing
	 * all the broken files, if found.
	 *
	 * @param {Ext.EventObject} e The event object.
	 * @param {Object/Array} files The files is contains file information.
	 * @param {Number} index Index of the file to process.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder folder to which files needs to be imported.
	 */
	checkBroken: function(e, files, index, folder)
	{
		var fileContent = e.target.result;
		var rawHeaderRegEx = /([^\n^:]+:)/g;

		// Get header part to process further
		var splittedContent = fileContent.split("/\r?\n\r?\n/");
		var indexOfAttachment = splittedContent[0].indexOf('Content-Disposition: attachment;');
		if (indexOfAttachment !== -1) {
			splittedContent = splittedContent[0].substr(0, indexOfAttachment);
		} else {
			splittedContent = splittedContent[0];
		}

		var rawHeaders = splittedContent.match(rawHeaderRegEx);

		// Restrict the eml files to import in calendar folder.
		var invalidImportingFolder = (this.isEmlFile(files[index]) && folder.isCalendarFolder()) ||
			(this.isEmlFile(files[index]) && folder.isContactFolder())||
			(this.isICSFile(files[index]) && !folder.isCalendarFolder()) ||
			(this.isICSFile(files[index]) && folder.isContactFolder()) ||
			(this.isVCFFile(files[index]) && !folder.isContactFolder());

		// Compare if necessary headers are present or not
		if (Ext.isEmpty(rawHeaders) || invalidImportingFolder) {
			this.brokenFiles.push(files[index]);
		} else if (this.isICSFile(files[index])) {
			splittedContent = splittedContent.toUpperCase();

			// Check if the file contains multiple events in single ics, if yes, validate each and every event.
			splittedContent = splittedContent.trim().split(/^\n$/gm);
			// If there is a single event in ics, we need to set the isSingleImport flag to true.
			if (splittedContent.length === 1) {
				this.isSingleImport = true;
			}
			splittedContent.forEach(function(contact) {
				this.isBrokenICSVCS(files[index], contact);
			}, this);
		} else if (this.isVCFFile(files[index])) {
			splittedContent = splittedContent.toUpperCase();

			// Check if the file contains multiple vCards, if yes, validate each and every vCard.
			splittedContent = splittedContent.trim().split(/^\n$/gm);

			// If there is a single vCard, we need to set the isSingleImport flag to true.
			if (splittedContent.length === 1) {
				this.isSingleImport = true;
			}
			splittedContent.forEach(function(contact) {
				// We need to check for the rawHeaders in each and every vCard, for proper validation
				rawHeaders = contact.match(rawHeaderRegEx);
				this.isBrokenVCF(files[index], contact, rawHeaders);
			}, this);
		}

		index++;
		this.readFiles(files, folder, index);
	},

	/**
	 * Helper function to check selected ics/vcs file(s) is valid or not.
	 *
	 * @param {Object} file The file is contains file information.
	 * @param {String} splittedContent The formatted content of ics/vcs file.
	 * @param {Array} rawHeaders The keys array of ics/vcs file contains.
	 */
	isBrokenICSVCS: function(file, splittedContent)
	{
		if (splittedContent.search(/VCALENDAR(\r\n|\n|\r)/) === -1) {
			this.brokenFiles.push(file);
		}
	},

	/**
	 * Helper function to check if the selected vcf file is valid or not.
	 *
	 * @param {Object} file The file which contains file information.
	 * @param {String} splittedContent The formatted content of vcf file.
	 * @param {Array} rawHeaders The keys array in the vcf file.
	 */
	isBrokenVCF: function(file, splittedContent, rawHeaders)
	{
		// Every vcf file should start and end with the 'BEGIN:VCARD' and 'END:VCARD' property respectively.
		var begin = splittedContent.match('BEGIN:VCARD');
		var end = splittedContent.match('END:VCARD');

		// The version of the vCard is required and it should be specified immediately after 'BEGIN:' property.
		var hasVersion = this.isPropertyExists(rawHeaders, ['VERSION:']) && (rawHeaders.indexOf('VERSION:') === 1);

		// If any of the above mentioned properties are not present, we consider
		// the vCard to be invalid.
		if (!begin || !end || !hasVersion) {
			this.brokenFiles.push(file);
		}
	},

	/**
	 * Function checks if the given property(s) is present in the given key array or not.
	 * @param {Array} rawHeaders The keys array of ics/vcs file contains.
	 * @param {Array/String} propertyName The property(s) in the rawHeader
	 * @return {boolean} exists True if any property exists, false otherwise.
	 */
	isPropertyExists: function(rawHeaders, propertyName)
	{
		var exists = false;
		if (!Array.isArray(propertyName)) {
			propertyName = [ propertyName ];
		}
		propertyName.forEach(function (property) {
			if (rawHeaders.indexOf(property) === -1) {
				return;
			}
			exists = true;
		});
		return exists;
	},

	/**
	 * Helper function to import selected files in given {@link Zarafa.hierarchy.data.MAPIFolderRecord}.
	 * Or raise proper error message box describing broken files.
	 *
	 * @param {Object/Array} files The files is contains file information.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder folder to which files needs to be imported.
	 */
	importFiles: function(files, folder)
	{
		if (this.brokenFiles.length > 0) {
			if (this.brokenFiles.length > 1) {
				// Show list if there is more than one broken-file
				var componentType = Zarafa.core.data.SharedComponentType['hierarchy.dialog.brokenfiles'];
				Zarafa.core.data.UIFactory.openLayerComponent(componentType, this.brokenFiles, {'modal': true});
			} else {
				Zarafa.common.dialogs.MessageBox.addCustomButtons({
					title: _('Import error'),
					msg: String.format(_('Unable to import "{0}". The file is not valid'), this.brokenFiles[0].name),
					cls: Ext.MessageBox.ERROR_CLS,
					fn: Ext.emptyFn,
					customButton: [{
						text: _('Close'),
						name: 'cancel'
					}],
					scope: this
				});
			}

			// if all files are broken then avoid making import request to server
			if (this.brokenFiles.length === files.length) {
				return;
			}
		}

		var request = container.getRequest();
		var responseHandler = new Zarafa.core.data.AbstractResponseHandler({
			doImport: this.importDone.createDelegate(this, [folder], true)
		});

		var filesData = new FormData();

		if (Array.isArray(files) || files instanceof FileList) {
			for (var i = 0, len = files.length; i < len; i++) {
				// Prevent broken files to be sent to server
				if (this.brokenFiles.indexOf(files[i]) === -1) {
					filesData.append('attachments[]', files[i]);
					// Need to pass 1 and 0 because on php side we get all formData in
					// string so 'false' can break the import eml feature.
					filesData.append('has_icsvcs_file', this.isICSFile(files[i]) ? 1: 0);
					filesData.append('is_single_import', this.isSingleImport ? 1: 0);
				}
			}

		} else {
			filesData.append('attachments', files);
			// Need to pass 1 and 0 because on php side we get all formData in
			// string so 'false' can break the import eml feature.
			filesData.append('has_icsvcs_file', this.isICSFile(files[i]) ? 1: 0);
			filesData.append('is_single_import', this.isSingleImport ? 1: 0);
		}

		var url = container.getBaseURL();
		url = Ext.urlAppend(url, 'dialog_attachments=' + Zarafa.generateId(32));
		url = Ext.urlAppend(url, 'load=upload_attachment');
		url = Ext.urlAppend(url, 'destination_folder=' + folder.get('entryid'));
		url = Ext.urlAppend(url, 'store=' + folder.get('store_entryid'));
		url = Ext.urlAppend(url, 'import=true');
		// For the normal attachment we concatenate the attachment id with attachment name
		// which will be extracted by upload_attachment module but here we are importing
		// some items like ics, eml, etc. so we don't need to concat the attach id with attachment name
		// so just pass the ignore_extract_attachid flag in request.
		url = Ext.urlAppend(url, 'ignore_extract_attachid=true');

		request.reset();
		var requestId = request.addDataRequest('attachments', 'import', filesData, responseHandler);

		url = Ext.urlAppend(url, 'module=attachments');
		url = Ext.urlAppend(url, 'moduleid=' + requestId);
		request.send(url, {});
	},

	/**
	 * Helper function to check if given file is of eml type or not.
	 * This is required as there is no file type available at all in case of IE11 and edge.
	 * @param {Object} file The file to be checked.
	 * @return {Boolean} True if the file is of type eml. false otherwise.
	 */
	isEmlFile: function (file)
	{
		if (!Ext.isEmpty(file.type)) {
			return file.type === 'message/rfc822';
		} else {
			var i = file.name.lastIndexOf('.');
			if (i === -1) {
				return false;
			}
			return file.name.substr(i + 1) === 'eml';
		}
	},

	/**
	 * Helper function to check if given file is of ics or vcs type or not.
	 * This is required as there is no file type available at all in case of IE11 and edge.
	 * @param {Object} file The file to be checked.
	 * @return {Boolean} True if the file is of type ics or vcs. false otherwise.
	 */
	isICSFile: function (file)
	{
		if (!Ext.isEmpty(file.type)) {
			return file.type === 'text/calendar';
		} else {
			var i = file.name.lastIndexOf('.');
			if (i === -1) {
				return false;
			}
			var extension = file.name.substr(i + 1);
			return extension === 'ics' || extension === 'vcs';
		}
	},

	/**
	 * Helper function to check if given file is of vcf type or not.
	 * This is required as there is no file type available at all in case of IE11 and edge.
	 *
	 * @param {Object} file The file to be checked.
	 * @return {Boolean} True if the file is of type vcf. false otherwise.
	 */
	isVCFFile: function(file)
	{
		if (!Ext.isEmpty(file.type)) {
			// windows system somehow show the file type to x-vcard.
			return file.type === 'text/vcard' || file.type === 'text/x-vcard';
		} else {
			var i = file.name.lastIndexOf('.');
			if (i === -1) {
				return false;
			}
			return file.name.substr(i + 1) === 'vcf';
		}
	},

	/**
	 * Helper function which show the {@link Zarafa.common.dialogs.MessageBox#addCustomButtons MessageBox} to ask user to
	 * copy instead of move records due to lack of record access or folder rights.
	 *
	 * @param {Zarafa.core.data.MAPIRecord[]} records The records which is going to copy in targeted folder.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} targetFolder The target folder on which records is going to copy.
	 * @param {Zarafa.core.data.ListModuleStore} store The which contains the records.
	 * @param {String} message The message which we show in message box.
	 * @param {Object} scope The scope in which this function was called.
	 * @param {Function} callBack function which needs to be called after performing copy action.
	 * @param {Object} options optional config items.
	 */
	showMessageBox: function(records, targetFolder, store, message, scope, callBack, options)
	{
		if (!Ext.isDefined(message)) {
			if (records.length > 1) {
				message = _("You have insufficient privileges to move these items. Would you like to copy instead?");
			} else {
				message = _("You have insufficient privileges to move this item. Would you like to copy instead?");
			}
		}

		var title = _('Insufficient privileges');
		var actionButton = _('Copy');
		if (!Ext.isEmpty(options)) {
			title = Ext.isDefined(options.title) ? options.title : title;
			actionButton = Ext.isDefined(options.actionBtn) ? options.actionBtn : actionButton;
		}

		records = this.resolveRecords(records, store);
		if (Ext.isEmpty(records)) {
			return;
		}

		Zarafa.common.dialogs.MessageBox.addCustomButtons({
			title: title,
			msg: message,
			cls: Ext.MessageBox.WARNING_CLS,
			width:400,
			dialog: this.dialog,
			fn: function(button) {
				if (button === 'cancel') {
					return;
				}

				var actionableRecords = Zarafa.common.Actions.resolveRecords(records, store);

				Ext.each(actionableRecords, function(record) {
					if (!record) {
						return;
					}

					var fnName = button + "To";
					if (Ext.isFunction(record[fnName])) {
						record[fnName](targetFolder);
					} else if (Ext.isFunction(record.copyTo)) {
						record.copyTo(targetFolder);
					}
				}, this);

				if (store && Ext.isFunction(store.save)) {
					store.save(actionableRecords);
				}

				if (this.dialog) {
					this.dialog.close();
				}

				if (Ext.isFunction(callBack)) {
					callBack.call(this);
				}
			},
			customButton: [{
				text: actionButton,
				name: actionButton.toLowerCase()
			}, {
				text: _('Cancel'),
				name: 'cancel'
			}],
			scope: Ext.isDefined(scope) ? scope : this
		});
	},

	/**
	 * Check if browser has permissions to show notifications
	 * @return {Boolean} true if permissions are granted to show desktop notifications else false
	 */
	hasPermission: function ()
	{
		if (!Ext.isDefined(window.Notification)) {
			return false;
		}

		return Notification.permission === 'granted';
	},

	/**
	 * Ask for permissions to show notifications
	 * In chrome this function will only work when you call it based on some user action
	 * like click of a button
	 * @param {Function} callback callback function that will be called after user has
	 * granted/rejected permission request
	 */
	authorize: function (callback)
	{
		if (!Ext.isDefined(window.Notification)) {
			if (Ext.isFunction(callback)) {
				callback('denied');
			}
			return;
		}

		if (!Ext.isFunction(Notification.requestPermission)) {
			return;
		}

		// Guard against double invocation: some browsers call both the
		// callback parameter and resolve the returned promise.
		var called = false;
		var wrappedCallback = function(perm) {
			if (!called && Ext.isFunction(callback)) {
				called = true;
				callback(perm || 'granted');
			}
		};

		// Pass callback as parameter for Safari compatibility, which
		// historically used the callback form instead of a promise.
		var result = Notification.requestPermission(wrappedCallback);
		if (result && Ext.isFunction(result.then)) {
			result.then(wrappedCallback);
		}
	},

	/**
	 * Function will show a desktop notification
	 * @param {String} title title to use when showing desktop notifications
	 * @param {Object} options object containing below key value pairs to provide extra information
	 * for the desktop notifications
	 * 		- icon: icon to show in desktop notifications
	 * 		- body: message to display
	 *		- tag: tag to group same type of notifications so multiple notifications
	 *				will not be showed multiple times
	 * @param {Object} handlers object containing handler function that can be registered on instance of
	 * notification object
	 * 		- possible handlers are click, show, error, close
	 */
	notify: function (title, options, handlers)
	{
		if (!Ext.isDefined(window.Notification)) {
			return;
		}

		if (!this.hasPermission()) {
			return;
		}

		// Note: Due to desktopnotifications has been moved into webapp core,
		// we first need to check if user plugin settings are still available.
		// Apply desktop notifications plugin settings if its available.
		// Otherwise use webapp main settings.
		var settingsModel = container.getSettingsModel();
		var pluginBasePath = 'zarafa/v1/plugins/desktopnotifications/';
		var isPlugInEnabled = Ext.isDefined(settingsModel.get(pluginBasePath+'enable'));
		var baseSettingPath = isPlugInEnabled ? pluginBasePath : 'zarafa/v1/main/desktop_notification/';
		var soundDisabled = settingsModel.get(baseSettingPath + 'disable_sound');

		var notification = new Notification(title, {
			icon: options.icon,
			body: options.body,
			requireInteraction: true,
			silent: !soundDisabled ? false : true
		});

		if (settingsModel.get(baseSettingPath + 'autohide_enable')) {
			var sleepTime = settingsModel.get(baseSettingPath + 'autohide_time') * 1000;
			setTimeout(function () {
				notification.close();
			}, sleepTime);
		}

		if (handlers) {
			for (var key in handlers) {
				notification['on' + key] = handlers[key];
			}
		}

		// Give audio feedback
		if (!soundDisabled) {
			this.playNotificationSound();
		}
	},

	/**
	 * Play notification sound, trying WebM first with MP3 fallback for Safari.
	 * @private
	 */
	playNotificationSound: function ()
	{
		// Clean up previous audio if still playing
		if (this.notificationAudio) {
			this.notificationAudio.pause();
			this.notificationAudio = null;
		}

		var self = this;
		var basePath = 'plugins/desktopnotifications/resources/audio';
		var audio = new Audio();
		this.notificationAudio = audio;

		audio.addEventListener('ended', function () {
			self.notificationAudio = null;
		});

		// Try WebM first (Chrome, Firefox, Edge), fall back to MP3 (Safari)
		if (audio.canPlayType('audio/webm')) {
			audio.src = basePath + '.webm';
		} else {
			audio.src = basePath + '.mp3';
		}

		try {
			var playPromise = audio.play();
			if (playPromise && Ext.isFunction(playPromise.catch)) {
				playPromise.catch(function() {
					self.notificationAudio = null;
				});
			}
		} catch (e) {
			this.notificationAudio = null;
		}
	}
};
Ext.namespace('Zarafa.common.attachment.dialogs');

/**
 * @class Zarafa.common.attachment.dialogs.AttachItemBaseRenderer
 * @extends Object
 *
 * Base renderer that can be used to extend and create different renderers for different type of messages.
 */
Zarafa.common.attachment.dialogs.AttachItemBaseRenderer = Ext.extend(Object, {
	/**
	 * Will store labels for corresponding record properties that will be used by {@link #addPlainRow} and {@link #addHTMLRow}
	 * to show labels in the data.
	 * @property labels
	 * @type Array
	 */
	labels: undefined,

	/**
	 * Will store object of {@link Ext.XTemplate XTemplate} that is created by {@link #generateText} for html data,
	 * this will make sure that template will only be generated once and then it will be reused.
	 * @property htmlTemplate
	 * @type Ext.XTemplate
	 */
	htmlTemplate: undefined,

	/**
	 * Will store object of {@link Ext.XTemplate XTemplate} that is created by {@link #generateText} for plain text data,
	 * this will make sure that template will only be generated once and then it will be reused.
	 * @property plainTemplate
	 * @type Ext.XTemplate
	 */
	plainTemplate: undefined,

	/**
	 * Constructor will initialize default properties
	 * @param {Object} config configuration object
	 */
	constructor: function(config)
	{
		this.labels = Ext.apply({}, {
			subject: _('Subject'),
			display_to: _('To'),
			display_cc: _('Cc'),
			display_bcc: _('Bcc'),
			from: _('From'),
			categories: _('Categories'),
			sensitivity: _('Sensitivity'),
			importance: _('Importance'),
			attachment_names: _('Attachments')
		});
	},

	/**
	 * Function will generate text that should be added into another message's body.
	 * @param {Zarafa.core.data.IPMRecord} record record that will be used to get data and generate template based on that data.
	 * @param {Boolean} htmlFormat decide if html or plain template should be used, this will depend on the user settings.
	 * @return {String} data that can be put in the record's body property.
	 * @public
	 */
	generateText: function(record, htmlFormat)
	{
		var template;

		if(htmlFormat) {
			if(!this.htmlTemplate) {
				this.htmlTemplate = new Ext.XTemplate(
					this.cleanTemplate(
						this.generateHTMLTemplate()
					)
				);
			}

			template = this.htmlTemplate;
		} else {
			if(!this.plainTemplate) {
				this.plainTemplate = new Ext.XTemplate(
					this.generatePlainTemplate()
				);
			}

			template = this.plainTemplate;
		}

		// Generate text from template
		var text = template.apply(this.prepareData(record, htmlFormat));

		// below processing is only needed when we have some data to return
		if(!Ext.isEmpty(text.trim()) && htmlFormat) {
			// Remove extra line separator from end of text
			text = text.replace(/(<hr width=\'100%\'>)*$/gi, '');

			// add a blockquote for styling purposes
			var html = '<blockquote style=\'border-left: 2px solid #325FBA; padding-left: 5px; margin-left: 10px; margin-top: 10px;\'>';
			html += text;
			html += '</blockquote><br>';

			text = html;
		}

		return text;
	},

	/**
	 * Helper function to add group of rows to template. This will also add conditional separator after group rows.
	 * @param {Object} group (optional) key value pair of tag and format that will be checked
	 * and if any one field is not empty then add the separator otherwise don't add separator. The tags can also contain another objects
	 * which will be recursively checked.
	 * @return {String} data template string for group of rows.
	 */
	addPlainRowGroup: function(group)
	{
		var html = '';
		// add rows
		Ext.iterate(group, function(tag, format) {
			if(Ext.isObject(format)) {
				// recursively call the function
				html += '<tpl if="!Ext.isEmpty(values.' + tag + ')">';
				html += this.addPlainRowGroup(format);
				html += '</tpl>';
			} else {
				html += this.addPlainRow(tag, format);
			}
		}, this);

		// add conditional separator
		html += this.addPlainSeperator(group);

		return html;
	},

	/**
	 * Helper function to add plain text row to template.
	 * @param {String} tag field/property of the record
	 * @param {String} format The template string which should be added
	 * @return {String} data template string for a row.
	 * @protected
	 */
	addPlainRow: function(tag, format)
	{
		var html = '';

		html += '<tpl if="!Ext.isEmpty(values.' + tag + ')">';
		html += this.getLabel(tag) + '\t' + format + '\n';
		html += '</tpl>';

		return html;
	},

	/**
	 * Helper function to add plain text separator row to template.
	 * @param {Object} group (optional) key value pair of tag and format that will be checked
	 * and if any one field is not empty then add the separator otherwise don't add separator. The tags can also contain another objects
	 * which will be recursively checked.
	 * @return {String} data template string for a row.
	 * @protected
	 */
	addPlainSeperator: function(group)
	{
		if(group) {
			// if group is provided then create condition which checks if separator should be added or not
			var condition = '';
			var separator = ' || ';
			var skip = false;

			Ext.iterate(group, function(tag, format) {
				if(Ext.isObject(format)) {
					// we have got a sub group, for which we will not add separator
					// that will be added when this same function will be called with the sub group
					skip = true;

					// break looping
					return false;
				} else {
					condition += ( Ext.isEmpty(condition) ? '' : separator ) + '!Ext.isEmpty(values.' + tag + ')';
				}
			});

			var html = '';

			if(skip !== true) {
				html += '<tpl if="' + condition + '">';
				html += '\n';
				html += '</tpl>';
			}

			return html;
		}

		// if no group is provided then directly add separator
		return '\n';
	},

	/**
	 * Helper function to add group of rows to template. This will also add conditional separator after group rows.
	 * @param {Object} group (optional) key value pair of tag and format that will be checked
	 * and if any one field is not empty then add the separator otherwise don't add separator. The tags can also contain another objects
	 * which will be recursively checked.
	 * @return {String} data template string for group of rows.
	 */
	addHTMLRowGroup: function(group)
	{
		var html = '';
		// add rows
		Ext.iterate(group, function(tag, format) {
			if(Ext.isObject(format)) {
				// recursively call the function
				html += '<tpl if="!Ext.isEmpty(values.' + tag + ')">';
				html += this.addHTMLRowGroup(format);
				html += '</tpl>';
			} else {
				html += this.addHTMLRow(tag, format);
			}
		}, this);

		// add conditional separator
		html += this.addHTMLSeperator(group);

		return html;
	},

	/**
	 * Helper function to add html row to template.
	 * @param {String} tag field/property of the record
	 * @param {String} format The template string which should be added
	 * @return {String} data template string for a row.
	 * @protected
	 */
	addHTMLRow: function(tag, format)
	{
		var html = '';

		html += '<tpl if="!Ext.isEmpty(values.' + tag + ')">';
		html += '<div width=\'100%\'><div style="display: inline-block; width: 20%;"><strong>' + this.getLabel(tag) + ':</strong></div><div style="display: inline-block; width: 80%">' + format + '</div></div>';
		html += '</tpl>';

		return html;
	},

	/**
	 * Helper function to add html separator row to template.
	 * @param {Object} group (optional) key value pair of tag and format that will be checked
	 * and if any one field is not empty then add the separator otherwise don't add separator. The tags can also contain another objects
	 * which will be recursively checked.
	 * @return {String} data template string for a row.
	 * @protected
	 */
	addHTMLSeperator: function(group)
	{
		if(group) {
			// if group is provided then create condition which checks if separator should be added or not
			var condition = '';
			var separator = ' || ';
			var skip = false;

			Ext.iterate(group, function(tag, format) {
				if(Ext.isObject(format)) {
					// we have got a sub group, for which we will not add separator
					// that will be added when this same function will be called with the sub group
					skip = true;

					// break looping
					return false;
				} else {
					condition += ( Ext.isEmpty(condition) ? '' : separator ) + '!Ext.isEmpty(values.' + tag + ')';
				}
			});

			var html = '';

			if(skip !== true) {
				html += '<tpl if="' + condition + '">';
				html += '<hr width=\'100%\'>';
				html += '</tpl>';
			}

			return html;
		}

		// if no group is provided then directly add separator
		return '<hr width=\'100%\'>';
	},

	/**
	 * Clean the template string by removing all illegal characters.
	 * @param {String} template The template string to clean
	 * @return {String} The clean template
	 * @private
	 */
	cleanTemplate: function(template)
	{
		// Conversions:
		// - \r is an illegal character which cannot be present in a string
		// - \ is a valid character, but shouldn't be escaped in an HTML text.
		return template.replace(/\r/g, '').replace(/\\/g, '\\\\');
	},

	/**
	 * Function should be used to generate template which can be used by {@link #generateText} to add data into template and return the string that is generated.
	 * This will generate template for the html format only.
	 * @return {String} The HTML for the XTemplate to use
	 */
	generateHTMLTemplate: Ext.emptyFn,

	/**
	 * Function should be used to generate template which can be used by {@link #generateText} to add data into template and return the string that is generated.
	 * This will generate template for the plain text format only.
	 * @return {String} The HTML for the XTemplate to use
	 */
	generatePlainTemplate: Ext.emptyFn,

	/**
	 * Function should be used to get row label based on record property. Function will be used by {@link #addPlainRow} and {@link #addHTMLRow}.
	 * @param {String} tag field name or property from the record.
	 * @return {String} The label to use for showing particular data from record.
	 */
	getLabel: function(tag)
	{
		return this.labels[tag];
	},

	/**
	 * Prepares data suitable for use in an XTemplate from the record.
	 * @param {Zarafa.core.data.IPMRecord} record The record to acquire data from.
	 * @param {Boolean} htmlFormat decide if html or plain data is requested, this will depend on the user settings.
	 * @return {Array} An array of data which is customized for our purpose.
	 */
	prepareData: function(record, htmlFormat)
	{
		// copy all properties
		var data = Ext.apply({}, record.data);

		// remove default values
		if (data['sensitivity'] === Zarafa.core.mapi.Sensitivity.NONE) {
			delete data['sensitivity'];
		}

		if (data['importance'] === Zarafa.core.mapi.Importance.NORMAL) {
			delete data['importance'];
		}

		// Get proper from address
		data['from'] = record.getSenderString();
		data['attachment_names'] = record.getAttachmentNames();

		data['body'] = record.getBody(htmlFormat);

		return data;
	}
});
Ext.namespace('Zarafa.common.attachment.dialogs');

/**
 * @class Zarafa.common.attachment.dialogs.AttachItemPanel
 * @extends Ext.Panel
 * @xtype zarafa.attachitempanel
 */
Zarafa.common.attachment.dialogs.AttachItemPanel = Ext.extend(Ext.Panel, {
	/**
	 * @cfg {Zarafa.core.data.MAPIRecord} record record in which we will add embedded attachment if user is adding it as attachment.
	 */
	record: undefined,

	/**
	 * @cfg {Zarafa.common.ui.EditorField} editor editor in which we will be adding message as text if user has selected to add text in body.
	 */
	editor: undefined,

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

		Ext.applyIf(config, {
			layout: {
				type: 'vbox',
				align: 'stretch',
				pack: 'start',
				padding: 10
			},
			items: [{
				xtype: 'container',
				flex: 1,
				layout: {
					type: 'hbox',
					align: 'stretch',
					pack: 'start'
				},
				items: [{
					xtype: 'zarafa.hierarchytree',
					flex: 1,
					border: true,
					enableDD: false,
					treeSorter: true,
					ref: '../hierarchyTree',
					listeners: {
						// Register event that will initially select a default folder
						'load': this.onTreeNodeLoad,
						scope: this
					}
				}, {
					xtype: 'container',
					width: 150,
					defaults: {
						style: {
							margin: '0px 0px 10px 5px'
						}
					},
					style: {
						padding: '0px 0px 0px 10px'
					},
					items: [{
						xtype: 'button',
						text: _('Ok'),
						disabled: true,
						cls: 'zarafa-action',
						ref: '../../okButton',
						handler: this.onOK,
						scope: this
					}, {
						xtype: 'button',
						text: _('Cancel'),
						ref: '../../cancelButton',
						handler: this.onCancel,
						scope: this
					}, {
						xtype: 'fieldset',
						title: _('Insert as'),
						padding: '0px 5px 5px 5px',
						layout: {
							type: 'fit'
						},
						items: [{
							xtype: 'radiogroup',
							vertical: true,
							columns: 1,
							ref: '../../../attachTypeRadioGroup',
							defaults: {
								style: {
									margin: '5px 0px 0px 5px'
								}
							},
							items: [{
								xtype: 'radio',
								boxLabel: _('Text only'),
								inputValue: 'text_only',
								name: 'attach_item'
							}, {
								xtype: 'radio',
								boxLabel: _('Attachment'),
								inputValue: 'attachment',
								name: 'attach_item'
							}],
							listeners: {
								// Register event that will select default radio group item
								'afterrender': this.onRadioGroupAfterRender,
								// Register event that will save current selected radio item in state settings
								'change': this.onRadioSelectionChange,
								scope: this
							}
						}]
					}]
				}]
			}, {
				xtype: 'zarafa.attachitemgrid',
				flex: 1,
				ref: 'attachItemGrid'
			}]
		});

		Zarafa.common.attachment.dialogs.AttachItemPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Initialize the event handlers
	 * @protected
	 */
	initEvents: function()
	{
		Zarafa.common.attachment.dialogs.AttachItemPanel.superclass.initEvents.apply(this, arguments);

		// Register event that will be fired when selection in hierarchy is changed
		var sm = this.hierarchyTree.getSelectionModel();
		this.mon(sm, 'selectionchange', this.onTreeSelectionChange, this);

		// Register event that will enable/disable ok button based on current selection
		this.mon(this.attachItemGrid.getSelectionModel(), 'selectionchange', this.onGridSelectionChange, this);
	},

	/**
	 * Function will call {@link Zarafa.common.attachment.dialogs.AttachItemGrid#attachItem}.
	 * @private
	 */
	onOK: function()
	{
		this.attachItem();
	},

	/**
	 * Handler will called when user presses the cancel button, this will simply close the {@link Zarafa.common.attachment.AttachItemContentPanel AttachItemContentPanel}
	 * @private
	 */
	onCancel: function()
	{
		this.dialog.close();
	},

	/**
	 * Fired when the {@link Zarafa.hierarchy.ui.Tree Tree} fires the {@link Zarafa.hierarchy.ui.Tree#load load}
	 * event. This function will try to select the {@link Ext.tree.TreeNode TreeNode} in
	 * {@link Zarafa.hierarchy.ui.Tree Tree} initially. When the given node is not loaded yet, it will try again
	 * later when the event is fired again.
	 * @param {Ext.tree.TreeNode} node node that has been loaded
	 * @private
	 */
	onTreeNodeLoad: function(node)
	{
		var folder = this.dialog.getSelectedFolder();

		// If the folder could be selected, then unregister the event handler.
		if (this.hierarchyTree.selectFolderInTree(folder)) {
			this.mun(this.hierarchyTree, 'load', this.onTreeNodeLoad, this);
		}
	},

	/**
	 * Invoked when {@link Ext.form.RadioGroup RadioGroup} is rendered fully and {@link Ext.form.RadioGroup#afterrender} event is fired.
	 * This function will try to select the {@link Ext.form.Radio Radio} in {@link Ext.form.RadioGroup RadioGroup} initially.
	 * @param {Ext.form.RadioGroup} radioGroup radio group that has been rendered.
	 * @private
	 */
	onRadioGroupAfterRender: function(radioGroup)
	{
		var option = this.dialog.getSelectedRadioItem();

		radioGroup.setValue(option);
	},

	/**
	 * Fired when a radio item is selected in {@link Ext.form.RadioGroup RadioGroup}.
	 * It will store inputValue of selected radio item into state settings.
	 * @param {Ext.form.RadioGroup} radioGroup radio group that has fired change event.
	 * @param {Ext.form.Radio} checkedRadio radio item which is selected.
	 * @private
	 */
	onRadioSelectionChange: function(radioGroup, checkedRadio)
	{
		// save new selection in state settings
		this.dialog.setRadioItemInState(checkedRadio);
	},

	/**
	 * Fired when a node is selected in {@link Zarafa.hierarchy.ui.Tree}. It loads items of the folder in grid.
	 * @param {Ext.tree.DefaultSelectionModel} selectionModel selection model that is used in {@link Zarafa.hierarchy.ui.Tree HierarchyTree}
	 * @param {Ext.tree.TreeNode} node node which is selected
	 * @private
	 */
	onTreeSelectionChange: function(selectionModel, node)
	{
		var folder = node.getFolder();

		var store = this.dialog.getStoreByFolder(folder);
		var colModel = this.dialog.getColumnModelByFolder(folder);

		this.attachItemGrid.reconfigure(store, colModel);

		// load the contents of the store
		store.load();

		// update state settings, and store newly selected folder entryid as last selected folder
		this.dialog.setFolderInState(folder);
	},

	/**
	 * Event handler which is triggered when the {@link Zarafa.calendar.ui.CounterProposalGrid grid}
	 * {@link Zarafa.core.data.IPMRecord record} selection is changed.
	 *
	 * @param {Ext.grid.RowSelectionModel} selectionModel The selection model used by the grid.
	 * @private
	 */
	onGridSelectionChange: function(selectionModel)
	{
		var count = selectionModel.getCount();

		this.okButton.setDisabled(count === 0);
	},

	/**
	 * Wrapper function to call {@link #attachItemAsAttachment} or {@link #attachItemAsText} based on selected radio item.
	 * @param {String} radioValue String that indicates which radio item is selected, based on this value different function will be called.
	 * @private
	 */
	attachItem: function(radioValue)
	{
		var selected = this.attachItemGrid.getSelectionModel().getSelected();
		var selectedRadio = this.attachTypeRadioGroup.getValue().inputValue;

		if (Ext.isEmpty(selected)) {
			Ext.MessageBox.alert(_('Attach Item'), _('No message selected'));
			return;
		}

		if(selectedRadio === 'text_only') {
			// add selected message data to parent message's body
			this.attachItemAsText(selected);
		} else {
			// add selected message as an attachment to parent message
			this.attachItemAsAttachment(selected);
		}
	},

	/**
	 * Function will be used to add selected message to another message as an embedded attachment.
	 * @param {Zarafa.core.data.IPMRecord} record record that is selected in grid and which should be added as attachment.
	 * @private
	 */
	attachItemAsAttachment: function(record)
	{
		// add new attachment into parent record
		this.record.getAttachmentStore().addAsAttachment(record);

		// now close the dialog
		this.dialog.close();
	},

	/**
	 * Function will be used to add selected message to another message as body contents.
	 * @param {Zarafa.core.data.IPMRecord} record record that is selected in grid and which should be added as text.
	 * @private
	 */
	attachItemAsText: function(record)
	{
		// if record is not opened then open it and get all contents of the record
		if(!record.isOpened()) {
			this.openRecord(record, this.attachItemAsText);
			return;
		}

		var renderer = this.dialog.getRendererByMessage(record);

		// Get the editor in which we need to add text and add text generated by renderer
		this.dialog.editor.insertAtCursor(renderer.generateText(record, this.record.get('isHTML')));

		// now close the dialog
		this.dialog.close();
	},

	/**
	 * Function will send an 'open' request to server to get all data of the passed {@link Zarafa.core.data.IPMRecord IPMRecord}.
	 * After record is opened function will call function specified in callback parameter.
	 * @param {Zarafa.core.data.IPMRecord} record record which needs to be opened to get all data.
	 * @param {Ext.Function} callback callback function that will be called after successfully opening the record.
	 */
	openRecord: function(record, callback)
	{
		// show load mask till we fetch full data from server
		this.dialog.showLoadMask();

		// store a reference of record's store which can be used to deregister events in exception handler
		var store = record.getStore();

		var fnOpen = function(recStore, rec) {
			if (record === rec) {
				// remove registered handlers
				store.un('open', fnOpen, this);
				store.un('exception', fnException, this);

				// hide load mask as data has arrived
				this.dialog.hideLoadMask();

				// call the callback function
				callback.call(this, record);
			}
		};

		var fnException = function(proxy, type, action, options, response, args) {
			if(action === Ext.data.Api.actions.open) {
				// remove registered handlers, this exception has been fired by Proxy so we will not get store in parameters
				store.un('open', fnOpen, this);
				store.un('exception', fnException, this);

				// actual error message will be shown by common exception handler
				// we will just hide the load mask
				this.dialog.hideLoadMask();
			}
		};

		store.on('open', fnOpen, this);
		store.on('exception', fnException, this);

		record.open();
	}
});

Ext.reg('zarafa.attachitempanel', Zarafa.common.attachment.dialogs.AttachItemPanel);
Ext.namespace('Zarafa.common.attachment.dialogs');

/**
 * @class Zarafa.common.attachment.dialogs.MixAttachItemPanel
 * @extends Ext.Panel
 * @xtype zarafa.mixattachitempanel
 *
 * Panel for users to list out the unsupported attachments while downloading all the attachments as ZIP.
 * It also allows user to provide his/her wish to show this warning again or not.
 */
Zarafa.common.attachment.dialogs.MixAttachItemPanel = Ext.extend(Ext.Panel, {
	/**
	 * @cfg {Array} records The {@link Zarafa.core.data.IPMRecord record(s)} which are being
	 * used in this panel
	 */
	records: undefined,

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

		Ext.applyIf(config, {
			xtype: 'zarafa.mixattachitempanel',
			border: false,
			items: [{
				xtype: 'fieldset',
				layout: 'form',
				anchor: '100% 30%',
				header: true,
				iconCls: 'mixattach-message-box-icon',
				border: false,
				items: this.getMixAttachComponents()
			}]
		});

		Zarafa.common.categories.dialogs.CategoriesPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Prepare all the components of warning dialog.
	 * @return {Array} Array containing all the required components
	 */
	getMixAttachComponents: function()
	{
		return [{
			xtype: 'displayfield',
			value: _('These attachments have an unsupported file type and can not be added to the ZIP file:'),
			hideLabel: true,
			htmlEncode: true
		}, {
			xtype: 'textarea',
			hideLabel: true,
			anchor: '100%',
			style: 'padding-left: 5px;',
			readOnly: true,
			listeners: {
				afterrender: this.onAfterRenderTextArea,
				scope: this
			}
		}, {
			xtype: 'displayfield',
			value: _("Would you like to continue downloading without adding these files?"),
			hideLabel: true,
			htmlEncode: true
		}, {
			xtype: 'checkbox',
			ref: '../../dontShowCheckBox',
			boxLabel: _('Always continue download of ZIP files without adding unsupported file types.'),
			hideLabel: true,
			scope: this
		}];
	},

	/**
	 * Handler which is called when 'textarea' is rendered. it prepares a list of all the unsupported attachments,
	 * which is to be shown in the text area, line by line.
	 * @param {Ext.form.TextArea} leftoutTextArea The textarea
	 * @return {String} list of unsupported attachments, separated by escaping sequence
	 */
	onAfterRenderTextArea: function(leftoutTextArea)
	{
		var leftOutList = "";

		Ext.each(this.records, function(record) {
			if(record.isEmbeddedMessage()){
				leftOutList += record.get('name') + "\n" ;
			}
		});

		leftoutTextArea.setValue(leftOutList);
	}
});

Ext.reg('zarafa.mixattachitempanel', Zarafa.common.attachment.dialogs.MixAttachItemPanel);
Ext.namespace('Zarafa.common.attachment.ui');

/**
 * @class Zarafa.common.attachment.ui.AttachmentButton
 * @extends Ext.SplitButton
 * @xtype zarafa.attachmentbutton
 *
 * Special button which can be used for attaching items to a {@link Zarafa.core.data.IPMRecord IPMRecord}.
 * This utilizes the {@link #main.attachment.method} insertion point to allow plugins to register
 * alternative methods for attaching items to the record. These options will be shown inside the dropdown
 * list, while the default button action will be opening the Browsers File Selection dialog
 *
 * If the {@link Zarafa.core.plugins.RecordComponentUpdaterPlugin} is installed
 * in the {@link #plugins} array of this component, this component will automatically
 * load the {@link Zarafa.core.data.MAPIRecord record} into the component.
 * Otherwise the user of this component needs to call {@link #bindRecord}.
 */
Zarafa.common.attachment.ui.AttachmentButton = Ext.extend(Ext.SplitButton, {
	/**
	 * @insert main.attachment.method
	 * Provide a new method for attaching files to a {@link Zarafa.core.data.IPMRecord IPMRecord}.
	 * This can be used by 3rd party plugins to insert a new MenuItem into the dropdown
	 * box for the {@link Zarafa.common.attachment.ui.AttachmentButton AttachmentButton}.
	 * This insertion point should return a {@link Ext.menu.Item item} instance of configuration
	 * @param {Zarafa.common.attachment.ui.AttachmentButton} button This button
	 */

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

		Ext.applyIf(config, {
			menu: {
				items: [{
					text: _('File upload'),
					handler: this.onFileUpload,
					scope: this,
					iconCls: 'icon_paperclip'
				}, {
					text: _('Attach item'),
					handler: this.onFileAttach,
					scope: this,
					iconCls: 'icon_embed_attachment'
				},
				container.populateInsertionPoint('main.attachment.method', this)
				]
			},
			handler: this.onFileUpload,
			scope: this
		});

		Zarafa.common.attachment.ui.AttachmentButton.superclass.constructor.call(this, config);
	},

	/**
	 * Event handler for opening the Browser's file selection dialog.
	 * See {@link #onFileInputChange} for the handling of the selected files.
	 * @param {Ext.Button} button the button on which click event is performed.
	 * @param {Ext.EventObject} event The event object
	 * @private
	 */
	onFileUpload: function(button, event)
	{
		var attachComponent = new Zarafa.common.attachment.ui.UploadAttachmentComponent({
			callback: this.uploadAttachmentCallback,
			multiple: true,
			scope: this
		});

		attachComponent.openAttachmentDialog();
	},

	/**
	 * The callback function of {@link Zarafa.common.attachment.ui.UploadAttachmentComponent}
	 * which used to upload the attachment file on server.
	 *
	 * @param {Object/Array} files The files contains file information.
	 * @param {Object} form the form is contains {@link Ext.form.BasicForm bacisform} info.
	 */
	uploadAttachmentCallback: function(files, form)
	{
		var store = this.record.getSubStore('attachments');
		store.uploadFiles(files, form);
	},

	/**
	 * Event handler for opening the {@link Zarafa.common.attachment.AttachItemContentPanel AttachItemContentPanel}.
	 * @private
	 */
	onFileAttach: function(field, event)
	{
		// get the parent from which we can find the editorfield
		var panel = this.findParentByType('zarafa.recordcontentpanel');
		var editor = panel.findByType('zarafa.editorfield');
		if(!Ext.isEmpty(editor)) {
			editor = editor[0];
		}

		Zarafa.common.Actions.openAttachItemSelectionContent(this.record, {editor: editor});
	},

	/**
	 * Apply the record to the button. The given record will be used by the attachment handlers
	 * for adding the attachments to the record.
	 * @param {Zarafa.core.data.IPMRecord} record
	 */
	bindRecord: function(record)
	{
		this.record = record;
	},

	/**
	 * Update the components with the given record.
	 *
	 * @param {Zarafa.core.data.MAPIRecord} record The record to update in this component
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 */
	update: function(record, contentReset)
	{
		if (record && record instanceof Zarafa.core.data.MAPIRecord) {
			// In case the recordcomponentupdaterplugin is installed
			// we have a special action to update the component.
			if (contentReset) {
				this.bindRecord(record);
			}
		} else {
			// The recordcomponentupdaterplugin is not installed and the
			// caller really wants to perform the update() function. Probably
			// a bad move, but lets not disappoint the caller.
			Zarafa.common.attachment.ui.AttachmentButton.superclass.update.apply(this, arguments);
		}
	}
});

Ext.reg('zarafa.attachmentbutton', Zarafa.common.attachment.ui.AttachmentButton);
Ext.namespace('Zarafa.common.attachment.ui');
/**
 * @class Zarafa.common.attachment.ui.AttachmentDownloader
 * @extends Ext.Component
 * @xtype zarafa.attachmentdownloader
 *
 * Independent component to encapsulate process of downloading attachments,
 * this is achieved by use of hidden iframe, in which we will set url of the
 * attachment that needs to be downloaded. The file pointed by url should
 * return attachment data with content disposition type as attachment, so
 * browser will show a dialog to open/save attachment, but if the server side
 * needs to send an error then make sure it returns it with content disposition
 * type as inline, so that iframe's onload event will be fired and proper error
 * message will be displayed to user.
 */
Zarafa.common.attachment.ui.AttachmentDownloader = Ext.extend(Ext.Component, {
	/**
	 * @constructor
	 * @param {Object} config Configuration structure
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			xtype: 'zarafa.attachmentdownloader',
			renderTo: Ext.getBody(),
			hidden: true,
			autoEl: {
				tag: 'iframe',
				src: Ext.SSL_SECURE_URL,
				title: '',
				'aria-hidden': 'true'
			}
		});

		Zarafa.common.attachment.ui.AttachmentDownloader.superclass.constructor.call(this, config);

		//Register a listener to onload event of iframe, but please take a note that
		//the 'load' event is fired only if content disposition type is configured as 'inline'.
		Ext.EventManager.on(this.getEl(), 'load', this.onIframeLoad, this);
	},

	/**
	 * Will get iframe element and set its src property to the supplied url.
	 * After successful response, iframe will pops up and ask user to start/cancel
	 * downloading of that particular message/attachment as file.
	 * @param {String} url The url to download message/attachment, containing necessary parameters.
	 */
	downloadItem: function(url)
	{
		var iframeElement = Ext.getDom(this.getEl());

		//setting iframe's location to the download url
		iframeElement.src = url;
	},

	/**
	 * Function prepares necessary HTML structure to send post request containing
	 * entryids of all the selected messages needs to be downloaded as eml in ZIP.
	 * Will get iframe element and create a form element dynamically.
	 * Create input elements for every entryid we need to send into post request,
	 * and append those dynamically created input elements into form.
	 *
	 * @param {Zarafa.core.data.IPMRecord} records The records which user want to save in eml format as ZIP.
	 */
	downloadMessageAsZip: function(records)
	{
		// Get the total size of all the messages which is requested to be included in ZIP
		var totalSize = 0;
		Ext.each(records, function(record) {
			totalSize += record.get('message_size');
		}, this);

		// Check if total size of eml is more than 30 MB, avoid ZIP creation as the request will
		// be timed out by the time while preparing eml for next message and there is no
		// technical provision to detect request-time-out exception.
		// 31457280 byte is equivalent to 30MB.
		if (totalSize < 31457280) {
			var url = records[0].getDownloadMessageUrl(true);

			var iframeBody = Ext.getDom(this.getEl()).contentDocument.body;
			var form = Ext.DomHelper.append( iframeBody || Ext.getBody(), {tag: 'form', action: url, method: 'POST'}, true);

			// Create and append input elements containing entryid as its value
			for (var i = 0; i < records.length; i++) {
				Ext.DomHelper.append(form, {tag: 'input', type: 'hidden', name: 'entryids[]', value: records[i].get('entryid')});
			}

			// Submit the form to send a POST request
			form.dom.submit();
		} else {
			container.getNotifier().notify(
				'error.attachment',
				_('Attachment error'),
				_('Cannot create ZIP, The allowed maximum size is 30 MB.')
			);
			return;
		}
	},

	/**
	 * Handler for the 'load' event of iframe, fired immediately after iframe is loaded.
	 * The exception response is received in json format, so we are using {@link Ext.util.JSON#decode}
	 * to decode (parse) a JSON string to an object.
	 * @private
	 */
	onIframeLoad: function()
	{
		var contentDocument;
		var responseText;

		contentDocument = this.getEl().dom.contentDocument;

		if (!Ext.isEmpty(contentDocument)) {
			responseText = contentDocument.body.textContent;
		}

		if (!Ext.isEmpty(responseText)) {
			var responseObject = Ext.util.JSON.decode(responseText);
			this.displaySaveEmailException(responseObject);
		}
	},

	/**
	 * Raise a warning dialog to inform user that there is some embedded attachments which can not be included in ZIP.
	 * otherwise continue with creating ZIP for normal attachments.
	 *
	 * @param {Zarafa.core.data.IPMAttachmentRecord} records The record of the file to be downloaded
	 * @param {Boolean} allAsZip (optional) True to downloading all the attachments as ZIP
	 */
	checkForEmbeddedAttachments: function(record, allAsZip)
	{
		var containsEmbedded = false;
		var attachmentStore = record.store;

		// We need all this additional checking only when we are going to download attachments as ZIP, skip otherwise.
		if(allAsZip) {
			// If user already made his/her decision to not show this warning again then it is there is state setting, just act accordingly.
			var dontShowWarning = container.getSettingsModel().get('zarafa/v1/state/dialogs/mixattachitemcontentpanel/dontshowagain');

			// Check if there is any embedded attachments only when user want this warning to be raised
			// and there is more than one attachments.
			if(!dontShowWarning && attachmentStore.getCount() > 1) {
				attachmentStore.each(function(record){
					if(record.isEmbeddedMessage()){
						containsEmbedded = true;
						return false;
					}
				});
			}
		}

		// Embedded attachment(s) found, raise warning dialog
		if(containsEmbedded) {
			this.openMixAttachmentsDialog(attachmentStore.getRange(), {'allAsZip': allAsZip});
		} else {
			this.downloadItem(record.getAttachmentUrl(allAsZip));
		}
	},

	/**
	 * Opens a {@link Zarafa.common.attachment.dialogs.MixAttachItemContentPanel MixAttachItemContentPanel} for informing
	 * user that there is some embedded attachments are requested to be included in ZIP, which is not possible.
	 *
	 * @param {Zarafa.core.data.IPMRecord} records The record, or records for which the attachments are
	 * requested to be downloaded as ZIP
	 * @param {Object} config (optional) Configuration object for creating the ContentPanel
	 */
	openMixAttachmentsDialog: function(records, config)
	{
		if (!Array.isArray(records)) {
			records = [ records ];
		}

		config = Ext.applyIf(config || {}, {
			modal: true,
			downloadItem: this.downloadItem.createDelegate(this, [ records[0].getAttachmentUrl(config.allAsZip) ], 1)
		});

		var componentType = Zarafa.core.data.SharedComponentType['common.attachment.dialog.mixattachitem'];
		Zarafa.core.data.UIFactory.openLayerComponent(componentType, records, config);
	},

	/**
	 * It displays proper message to user, as per the exception received
	 * while unsuccessful download.
	 * @param {Object} responseObject Object contained the exception details.
	 * @private
	 */
	displaySaveEmailException: function(responseObject)
	{
		Ext.MessageBox.show({
			title: _('Unable to download'),
			msg: responseObject.zarafa.error.info.display_message,
			cls: Ext.MessageBox.ERROR_CLS,
			buttons: Ext.MessageBox.OK
		});
	}
});
Ext.reg('zarafa.attachmentdownloader', Zarafa.common.attachment.ui.AttachmentDownloader);
Ext.namespace('Zarafa.common.attachment.ui');
/**
 * @class Zarafa.common.attachment.ui.UploadAttachmentComponent
 * @extends Ext.Component
 * @xtype zarafa.uploadattachmentcomponent
 */
Zarafa.common.attachment.ui.UploadAttachmentComponent = Ext.extend(Ext.Component, {
	/**
	 * @cfg {Function} callback The callback function which must be called when the
	 * file has be selected from Browser's file selection dialog.
	 */
	callback: Ext.emptyFn,

	/**
	 * @cfg {Object} scope The scope for the {@link #callback} function
	 */
	scope: undefined,

	/**
	 * @cfg {Boolean} multiple The multiple true to allow upload multiple files
	 * else allow single file only. by default it is false.
	 */
	multiple: false,

	/**
	 * @cfg {String} accept the accept define which type of files allow to
	 * show in Browser's file selection dialog. i.e image/* to allow all type of images.
	 */
	accept: undefined,

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

		Ext.applyIf(config,{
			xtype: 'zarafa.uploadattachmentcomponent'
		});

		Zarafa.common.attachment.ui.UploadAttachmentComponent.superclass.constructor.call(this, config);
	},

	/**
	 * Event handler for opening the Browser's file selection dialog.
	 * See {@link #onFileInputChange} for the handling of the selected files.
	 * @private
	 */
	openAttachmentDialog: function()
	{
		var attachEl = this.getAttachmentEl();

		// Register the change event handler
		// so we detect when the user selects a file.
		attachEl.on('change', this.onFileInputChange, this);

		// Mimic clicking on the <input> field
		// to open the File Selection dialog.
		attachEl.dom.click();
	},

	/**
	 * Obtain or instantiate the {@link Ext.Element attachment} &lt;input&gt; element used
	 * for opening the File selection dialog.
	 * @return {Ext.Element} The file input element
	 * @private
	 */
	getAttachmentEl: function()
	{
		var attachmentInputId = Ext.id(null, 'zarafa-attachment-input-');
		var attachEl = Ext.DomHelper.append(Ext.getBody(), {
			cls: 'x-hidden',
			tag: 'input',
			type: 'file',
			id: attachmentInputId,
			name: attachmentInputId
		});

		if(Ext.isDefined(this.multiple) && this.multiple) {
			attachEl.multiple = this.multiple;
		}

		if(Ext.isDefined(this.accept)) {
			attachEl.accept = this.accept;
		}

		attachEl = Ext.get(attachEl);
		return attachEl;
	},

	/**
	 * Event handler which is fired when the {@link #attachEl} has been changed.
	 * @param {Ext.EventObject} event The event
	 * @private
	 */
	onFileInputChange: function(event)
	{
		var browserEvent = event.browserEvent;
		var attachEl = Ext.get(browserEvent.target);
		var transfer = browserEvent.dataTransfer;
		var transferFile = transfer ? transfer.files : undefined;
		var files = attachEl.dom.files || transferFile;
		var record = this.scope.record;
		var store = Ext.isDefined(record) ? record.getAttachmentStore() : undefined;

		// If the record is not defined assume that this is the case of importing eml via upload
		if(!Ext.isDefined(record) || (Ext.isDefined(store) && store.canUploadFiles(files))) {
			if(this.accept === 'image/*') {
				var fileType = files[0].type;
				if (this.isSupportedImage(fileType)) {
					this.callback.call(this.scope, files);
				} else {
					this.showAttachmentError();
				}
			} else {
				this.callback.call(this.scope, files);
			}
		}

		// remove attachment element.
		attachEl.remove();
	},

	/**
	 * Function shows the attachment error message when picture format is not supported by webapp
	 */
	showAttachmentError: function()
	{
		var message = _('Picture format is not supported. ');
		message += _('Supported format for contact pictures are');
		message += '<br/>';
		message += 'JPEG, GIF, PNG, BMP';
		Ext.MessageBox.show({
			title: _('Attachment Error'),
			msg: message,
			cls: Ext.MessageBox.ERROR_CLS,
			buttons: Ext.MessageBox.OK
		});
	},

	/**
	 * Function check that selected file is supported image or not.
	 * @param {String} fileType the fileType is defined the mime type of selected file.
	 * @return {Boolean} return true if selected picture is supported by webapp else false.
	 */
	isSupportedImage: function(fileType)
	{
		var mimeType = ['image/bmp','image/jpg','image/jpeg','image/gif','image/png'];
		return mimeType.indexOf(fileType.toLowerCase()) >= 0;
	}
});

Ext.reg('zarafa.uploadattachmentcomponent', Zarafa.common.attachment.ui.AttachmentButton);
Ext.namespace('Zarafa.common.categories');

/**
 * @class Zarafa.common.categories.Util
 * @extends Object
 *
 * Collection of common functions to handle categories
 */
Zarafa.common.categories.Util = {
	/**
	 * An instance of the categoryStore class
	 * @property
	 * @type {Zarafa.common.categories.data.CategoriesStore}
	 */
	categoriesStore: null,

	/**
	 * The color that will be used for labels of categories that don't have a color. It is a rbg hex string.
	 * @property
	 * @type {String}
	 */
	defaultCategoryColor: '#BDC3C7',

	/**
	 * The template of the category blocks
	 * @property
	 * @type {Ext.Template/String}
	 */
	categoriesHtmlTemplate:
		'<tpl if="!Ext.isEmpty(values)">' +
			'<tpl for=".">' +
				'<span class="k-category-block {colorClass}" role="listitem" '+
						'<tpl if="!Ext.isEmpty(values.backgroundColor)">style="background-color:{backgroundColor}!important;"</tpl>'+
				'>' +
					'{name}' +
				'</span>' +
			'</tpl>' +
		'</tpl>',

	/**
	 * Will (re)create the {#categoriesStore}, so it will have the latest changes
	 * If any changes are done to the categories, the code that did the change must
	 * call this function!
	 */
	loadCategoriesStore: function()
	{
		this.categoriesStore = new Zarafa.common.categories.data.CategoriesStore();
	},

	/**
	 * Transforms the category property string of a {@link Zarafa.core.data.IPMRecord record}
	 * into an array with strings.
	 *
	 * @param {Zarafa.core.data.IPMRecord} record
	 * @return {String[]} An array of strings, one for each category
	 */
	getCategories: function(record)
	{
		// It can happen that callers pass a null/undefined record when clearing a view.
		// Guard the helper so it quietly returns an empty array instead of crashing.
		if (!record || typeof record.get !== 'function') {
			return [];
		}

		var categories = [];
		if ( !Ext.isEmpty(record.get('categories')) ){
			categories = record.get('categories').replace(/;?\s*$/, '').split('; ');
		}

		// Flags that are not 'follow-up' flags are set before we switched to the new categories/flags and will
		// be shown as categories.
		if ( record.get('flag_status')===Zarafa.core.mapi.FlagStatus.flagged && record.get('flag_request')!=='Follow up' ){
			var flagColor = record.get('flag_icon');
			var flagCategoryName = this.getCategoryNameByFlagColor(flagColor);

			// The flag could already be set as a real category. In that case
			// we will not add it again.
			if ( categories.indexOf(flagCategoryName) === -1 ){
				categories.push(flagCategoryName);
			}
		}

		// Since the new implementation of categories, labels are deprecated and will
		// also be displayed as categories. So if a label was set, we will add it
		// to the categories.
		if ( record.get('label') ) {
			var label = Zarafa.core.mapi.AppointmentLabels.getName(record.get('label'));
			if (label) {
				label = label.replace("_", " ");
				var index = categories.indexOf(label);
				if ( index > -1 ){
					// The set label is also set as a category. A label will always
					// be added as last category (because that defines the color of
					// an appointment), so we will remove it from the categories first,
					// and then add it as last.
					categories.splice(index, 1);
				}
				categories.push(label);
			}
		}

		// Instantiate the category store only once. If any changes are done to the
		// managed category list, the code that did the change must call {#loadCategoriesStore}!
		if ( !this.categoriesStore ){
			this.loadCategoriesStore();
		}

		// If a category exists in the managed category list we must make sure we will use
		// the name in the list to avoid case sensitivity issues.
		categories = categories.map(function(category){
			var index = this.categoriesStore.findExactCaseInsensitive('category', category);
			if ( index>=0 ){
				return this.categoriesStore.getAt(index).get('category');
			}

			return category;
		}, this);


		return categories;
	},

	/**
	 * Sets the given categories on the given records.
	 *
	 * @param {Zarafa.core.data.IPMRecord[]} records The record(s) on which the
	 * given categories must be set.
	 * @param {String[]} categories An array with the category names that must be
	 * set on the record(s)
	 * @param {Boolean} doSave Set to true to save the records to the backend
	 */
	setCategories: function(records, categories, doSave)
	{
		// Make sure we have a boolean
		doSave = doSave === true;

		var stores = [];

		if ( categories.length>0 ){
			categories = categories.join('; ') + ';';
		} else {
			categories = '';
		}

		Ext.each(records, function(record){
			record.set('categories', categories);
			if ( doSave && record.store && stores.indexOf(record.store)===-1 ){
				stores.push(record.store);
			}
		}, this);

		// Save the changes if requested
		if ( doSave ){
			Ext.each(stores, function(store){
				store.save();
			});
		}
	},

	/**
	 * Adds the given category to all given records. Saves the
	 * records if requested.
	 *
	 * @param {Zarafa.core.data.IPMRecord[]} records The records
	 * to which the category will be added.
	 * @param {String} category The category name that must be
	 * added to the record(s)
	 * @param {Boolean} doSave Set to true to save the records to the backend
	 * @param {Zarafa.core.data.IPMStore} store (optional) store holding
	 * records on which categories apply.
	 */
	addCategory: function(records, category, doSave, store)
	{
		// Make sure we have a boolean
		doSave = doSave === true;

		// Add the category to all records
		Ext.each(records, function(record){
			// when categories context menu is open and mean while
			// if grid gets reload in that case record.store is null.
			// to overcome this problem we again find that record from given store
			// in parameter.
			if(Ext.isEmpty(record.getStore()) && Ext.isDefined(store)) {
				record = store.getById(record.get('entryid'));
			}

			var categories = this.getCategories(record);
			if ( categories.indexOf(category) === -1 ){
				// If the record has a label, we will first remove that
				// (labels have been deprecated since the implementation
				// of the new categories)
				// Note: The getCategories function has already added the flag
				// to the categories
				if ( record.get('label') ){
					record.set('label', 0);
				}

				categories.push(category);
				this.setCategories(record, categories, false);

				if ( doSave ){
					record.save();
				}
			}
		}, this);
	},

	/**
	 * Returns an array with all categories that all the given records have in common.
	 *
	 * @param {Zarafa.core.data.IPMRecord[]} records
	 * @return {String[]} An array of strings, one for each category
	 */
	getCommonCategories: function(records)
	{
		if ( Ext.isEmpty(records) ){
			return [];
		}

		if ( !Ext.isArray(records) ){
			records = [records];
		}

		// We'll start with the categories of the first record, and will then remove
		// any category that isn't set on one of the other records
		var selectedCategories = Zarafa.common.categories.Util.getCategories(records[0]);
		Ext.each(records, function(record, index){
			if ( index === 0 ) {
				return;
			}
			var categories = Zarafa.common.categories.Util.getCategories(record);
			var categoriesToBeRemovedFromSelection = [];
			Ext.each(selectedCategories, function(category) {
				if ( categories.indexOf(category) === -1 ){
					categoriesToBeRemovedFromSelection.push(category);
				}
			}, this);
			Ext.each(categoriesToBeRemovedFromSelection, function(categoryToBeRemovedFromSelection){
				selectedCategories.splice(selectedCategories.indexOf(categoryToBeRemovedFromSelection), 1);
			});
		}, this);

		return selectedCategories;
	},

	/**
	 * Returns an array with all categories that are set on the given records.
	 *
	 * @param {Zarafa.core.data.IPMRecord[]} records
	 * @return {String[]} An array of strings, one for each category
	 */
	getAllCategories: function(records) {
		if ( !Ext.isArray(records) ){
			records = [records];
		}

		var categories = [];
		Ext.each(records, function(record){
			categories = categories.concat(this.getCategories(record));
		}, this);

		return Ext.unique(categories);
	},

	/**
	 * Removes a given category from the given records
	 *
	 * @param {Zarafa.core.data.IPMRecord[]} records The records for which the category
	 * will be removed
	 * @param {String} category The category that will be removed
	 * @param {Boolean} doSave Set to true to save the records to the backend
	 * @param {Zarafa.core.data.IPMStore} store (optional) holding
	 * records on which category going to remove.
	 */
	removeCategory: function(records, category, doSave, store)
	{
		if ( !Ext.isArray(records) ){
			records = [records];
		}

		// Make sure we have a boolean
		doSave = doSave === true;

		Ext.each(records, function(record){
			// when categories context menu is open and mean while
			// if grid gets reload in that case record.store is null.
			// to overcome this problem we again find that record from given store
			// in parameter.
			if(Ext.isEmpty(record.getStore()) && Ext.isDefined(store)) {
				record = store.getById(record.get('entryid'));
			}
			var categories = this.getCategories(record);
			var index = categories.indexOf(category);
			var label = record.get('label') ? Zarafa.core.mapi.AppointmentLabels.getDisplayName(record.get('label')) : '';
			if ( index > -1 ){
				categories.splice(index, 1);
				this.setCategories(record, categories, false);
			}

			// Since the new implementation of categories, flags will be
			// displayed as a category. This means that the category that
			// is being removed could also be a flag!
			// Note that the getCategories method has already added it.
			if ( record.get('flag_status') === Zarafa.core.mapi.FlagStatus.flagged && record.get('flag_request')!=='Follow up' ){
				var flagColor = record.get('flag_icon');
				var flagCategoryName = this.getCategoryNameByFlagColor(flagColor);
				if ( flagCategoryName === category ){
					var flagProperties = Zarafa.common.flags.Util.getFlagBaseProperties();
					Ext.apply(flagProperties, Zarafa.common.flags.Util.getFlagPropertiesNoDate());
					record.beginEdit();
					for ( var property in flagProperties ){
						record.set(property, flagProperties[property]);
					}
					record.endEdit();
				}
			}

			// Since the new implementation of categories, labels will be
			// displayed as a category. This means that the category that
			// is removed could also be a label
			// Note that the getCategories method has already added it!
			if ( label === category ){
				record.set('label', 0);
			}

			var recordModified = record.isModified('categories') || record.isModified('label') || record.isModified('flag_request');
			if ( recordModified && doSave ){
				record.save();
			}
		}, this);
	},

	/**
	 * Matches a flag color to a category name and returns this category name.
	 *
	 * @param {Zarafa.core.mapi.FlagIcon} flagColorIndex The flag color
	 * @return {String} The matching category name
	 */
	getCategoryNameByFlagColor: function(flagColorIndex)
	{
		var settingsModel = container.getPersistentSettingsModel();
		var categories = settingsModel.get('grommunio/main/categories', true);
		var retVal = '';
		Ext.iterate(categories, function(category){
			if ( category.standardIndex === flagColorIndex ){
				retVal = category.name;
				return true;
			}
		});

		if (!Ext.isEmpty(flagColorIndex)) {
			var mergedCategory = settingsModel.get('grommunio/main/merged_categories/'+flagColorIndex, true);
			if(mergedCategory) {
				retVal = mergedCategory;
			}
		}
		return retVal;
	},

	/**
	 * Returns the color (css) for the given category
	 *
	 * @param {String} category The category name
	 * @return {String} The color of the category (hex code)
	 */
	getCategoryColor: function(category)
	{
		// Instantiate the category store only once. If any changes are done to the
		// categories, the code that did the change must call {#loadCategoriesStore}!
		if ( !this.categoriesStore ){
			this.loadCategoriesStore();
		}

		var catIndex = this.categoriesStore.findExactCaseInsensitive('category', category);
		if ( catIndex > -1 ){
			return this.categoriesStore.getAt(catIndex).get('color');
		}

		return Zarafa.common.categories.Util.defaultCategoryColor;
	},

	/**
	 * Finds the unencoded category name of a record from the
	 * {@link Ext.util.Format.htmlEncode htmlEncoded} category name
	 *
	 * @param {String} encodedCategory The html encoded version of the
	 * category name
	 * @param {Zarafa.core.data.IPMRecord/Zarafa.core.data.IPMRecord[]} records The record(s)
	 * on which the category is set.
	 * @return {String} The unencoded category name
	 */
	getCategoryFromHtmlEncoded: function(encodedCategory, records)
	{
		var categories = this.getAllCategories(records);
		var unencoded = encodedCategory;
		Ext.each(categories, function(category){
			if ( Ext.util.Format.htmlEncode(category) === encodedCategory ){
				unencoded = category;
				return true;
			}
		}, this);

		return unencoded;
	},

	/**
	 * Returns the html of the categories as blocks (as displayed in the grid
	 * and item header)
	 *
	 * @param {String} categories The category name
	 * @return {String} The html of the categories as colored blocks
	 */
	getCategoriesHtml: function(categories)
	{
		// Compile the template string if not done yet
		if (Ext.isString(this.categoriesHtmlTemplate)) {
			this.categoriesHtmlTemplate = new Ext.XTemplate(this.categoriesHtmlTemplate, {
				compiled: true
			});
		}

		var data = categories.map(function(category){
			var dataEntry = {
				name: Ext.util.Format.htmlEncode(category),
				backgroundColor: this.getCategoryColor(category)
			};
			if ( dataEntry.backgroundColor ){
				dataEntry.colorClass = Zarafa.core.ColorSchemes.getLuma(dataEntry.backgroundColor) < 200 ? 'zarafa-dark' : '';
			}

			return dataEntry;
		}, this);

		return this.categoriesHtmlTemplate.apply(data);
	},

	/**
	 * Returns the SVG icon for a category of the given color
	 * @param {String} color The CSS color value of the category
	 * @return {String} The string with the SVG element tag
	 */
	getCategoryIconSVG: function(color,size)
	{
		return '<svg width="'+size+'" height="'+size+'" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M19.75 2A2.25 2.25 0 0 1 22 4.25v5.462a3.25 3.25 0 0 1-.952 2.298l-8.5 8.503a3.255 3.255 0 0 1-4.597.001L3.489 16.06a3.25 3.25 0 0 1-.003-4.596l8.5-8.51A3.25 3.25 0 0 1 14.284 2h5.465Zm0 1.5h-5.465c-.465 0-.91.185-1.239.513l-8.512 8.523a1.75 1.75 0 0 0 .015 2.462l4.461 4.454a1.755 1.755 0 0 0 2.477 0l8.5-8.503a1.75 1.75 0 0 0 .513-1.237V4.25a.75.75 0 0 0-.75-.75ZM17 5.502a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3Z" fill="'+color+'"/></svg>';
	},

	/**
	 * Will check all registered {@link Zarafa.core.data.IPMStore IPMStores} and send 'fake' update events
	 * to all records that have categories set. This will make sure that the views are updated.
	 */
	updateStoresAfterCategoryUpdate: function()
	{
		var IPMStores = Zarafa.core.data.IPMStoreMgr.IPMStores;

		IPMStores.each(function(store){
			store.each(function(record){
				// If record has categories as well as flag_status is to flagged and
				// flag_request is not equal to 'Follow up' then update that record
				// in store.
				if ( !Ext.isEmpty(record.get('categories')) ||
					(record.get('flag_status') === Zarafa.core.mapi.FlagStatus.flagged &&
						record.get('flag_request') !== 'Follow up')){
					store.fireEvent('update', store, record, Ext.data.Record.COMMIT);
				}
			}, this);

		}, this);
	}
};
Ext.namespace('Zarafa.common.categories.data');
/**
 * @class Zarafa.common.categories.data.CategoriesStore
 * @extends Ext.data.ArrayStore
 * @xtype zarafa.categoriesstore
 *
 * Store which will get the records from setting and insertion
 * points which has user-defined categories.
 */
Zarafa.common.categories.data.CategoriesStore = Ext.extend(Ext.data.ArrayStore, {
	// Insertion points for this class
	/**
	 * @insert main.categories
	 * can be used to add extra user-defined categories by 3rd party plugins
	 */

	/**
	 * The settings key that will be used to read and save the categories
	 * @property
	 * @type {String}
	 * @private
	 */
	settingsKey: 'grommunio/main/categories',

	/**
	 * @constructor
	 * @param {Object} config Configuration structure
	 */
	constructor: function(config)
	{
		config = config || {};
		var categories = [];

		var storedCategories = container.getPersistentSettingsModel().get(this.settingsKey);
		if ( storedCategories ) {
			categories = categories.concat(container.getPersistentSettingsModel().get(this.settingsKey));
		}
		categories = categories.concat(container.populateInsertionPoint('main.categories'));

		// Add additional categories defined by the admin. They are already defined by the default
		// persistent settings, but the user might have removed/changed them and since KW-2841 we
		// always want to have those categories available
		var additionalCategories = container.getServerConfig().getAdditionalDefaultCategories() || [];
		var newAdditionalCategories = additionalCategories.filter(function(category) {
			return !categories.some(function(c) {
				return c.name === category.name;
			});
		});

		// Remove stored additional categories that are not present in the configured additional categories
		categories = categories.filter(function(category) {
			return !category.additional ||
					additionalCategories.some(function(additionalCategory) {
						return additionalCategory.name === category.name;
					});
		});

		// Add new additional categories
		categories = categories.concat(newAdditionalCategories);

		categories = categories.filter(function(category){
							return Ext.isObject(category);
						}).map(function(category){
							return [
								category.name,
								category.color,
								category.standardIndex,
								additionalCategories.some(function(c) { return c.name === category.name; }),
								category.quickAccess===true,
								Ext.isDefined(category.sortIndex) ? category.sortIndex : 100000,
								true,
								category.used
							];
						});

		Ext.applyIf(config, {
			fields: ['category', 'color', 'standardIndex', 'additional', 'quickAccess', 'sortIndex', 'stored', 'used'],
			data: categories
		});

		Ext.apply(this, config);

		Zarafa.common.categories.data.CategoriesStore.superclass.constructor.call(this, config);
	},

	/**
	 * Will add the categories that are set on the passed records and that do not yet exist
	 * in this store to this store.
	 * @param {Zarafa.core.data.MAPIRecord} mapiRecords The records of which the categories
	 * will be added to this store.
	 */
	addCategoriesFromMapiRecords: function(mapiRecords)
	{
		var categories = Zarafa.common.categories.Util.getAllCategories(mapiRecords);
		categories.forEach(function(category){
			if ( this.findExactCaseInsensitive('category', category) === -1 ){
				// Add the category to the store
				this.add(new this.recordType({
					category: category,
					color: Zarafa.common.categories.Util.defaultCategoryColor,
					sortIndex: 100000,
					stored: false
				}));
			}
		}, this);
	},

	/**
	 * Adds a category to the store.
	 * @param {String} category The name of the category
	 * @param {String} color The color of the category in RGB Hex format
	 * @param {Boolean} quickAccess True if the category should be a 'pinned' category
	 */
	addCategory: function(category, color, quickAccess)
	{
		this.add(new this.recordType({
			category: category,
			color: color ? '#'+color : Zarafa.common.categories.Util.defaultCategoryColor,
			quickAccess: !!quickAccess,
			stored: true
		}));
	},

	/**
	 * Saves the categories in the store into the settings of the user
	 */
	save: function()
	{
		var categories = this.getRange().filter(function(categoryRecord){
			// Only save categories that were already stored before,
			// or that have been pinned or were given a color by the user
			return 	categoryRecord.get('stored') ||
					categoryRecord.get('quickAccess') ||
					categoryRecord.get('color')!==Zarafa.common.categories.Util.defaultCategoryColor;
		}).map(function(categoryRecord){
			return {
				name: categoryRecord.get('category'),
				color: categoryRecord.get('color'),
				standardIndex: categoryRecord.get('standardIndex'),
				quickAccess: categoryRecord.get('quickAccess'),
				sortIndex: categoryRecord.get('sortIndex'),
				used: categoryRecord.get('used'),
				additional: categoryRecord.get('additional')
			};
		});

		container.getPersistentSettingsModel().set(this.settingsKey, categories, true);
	},

	/**
	 * Finds the index of the first matching Record in this store by a specific field value.
	 * Matches case-insensitive.
	 * @param {String} fieldName The field that should be used to match the record
	 * @param {String} value The value that should be used to match the records
	 *
	 * @return {Number} The index of the first matching record or -1 if no match was found
	 */
	findExactCaseInsensitive: function(fieldName, value)
	{
		return this.findBy(function(category, index){
			return category.get(fieldName).toLowerCase() === value.toLowerCase();
		}, this);
	}
});

Ext.reg('zarafa.categoriesstore', Zarafa.common.categories.data.CategoriesStore);
Ext.namespace('Zarafa.common.categories.dialogs');

/**
 * @class Zarafa.common.categories.dialogs.CategoriesPanel
 * @extends Ext.Panel
 * @xtype zarafa.categoriespanel
 *
 * Panel for users to edit the categories on a given {@link Zarafa.core.data.IPMRecord record}
 */
Zarafa.common.categories.dialogs.CategoriesPanel = Ext.extend(Ext.Panel, {
	/**
	 * @cfg {Zarafa.core.data.IPMRecord} record The record(s) which are being
	 * edited through this panel
	 */
	record: undefined,
	/**
	 * @cfg {String} categorySeparator The string which must be used to separate the
	 * various categories.
	 */
	categorySeparator: ';',

	/**
	 * @cfg {Boolean} hideActionButtons If it is true then it will hide all row action button in CategoriesGrid.
	 */
	hideActionButtons: false,

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

		if (config.hideActionButtons) {
			this.hideActionButtons = config.hideActionButtons;
		}

		Ext.applyIf(config, {
			// Override from Ext.Component
			xtype: 'zarafa.categoriespanel',
			layout: 'fit',
			border: false,
			items: [
				this.createCategoriesGrid(config.record)
			]
		});

		Zarafa.common.categories.dialogs.CategoriesPanel.superclass.constructor.call(this, config);
	},

	/**
	 * create grid panel for categories which contains {@link Ext.grid.CheckboxSelectionModel CheckboxSelectionModel}
	 * which can be used by the user to select/change the category from a predefined list.
	 * @param {Zarafa.core.data.IPMRecord[]} records The records that are edited with this panel
	 * @return {Object} The configuration object for the grid panel.
	 * @private
	 */
	createCategoriesGrid: function(records)
	{
		var store = new Zarafa.common.categories.data.CategoriesStore();
		store.addCategoriesFromMapiRecords(records);
		store.sort([
			{field: 'quickAccess', direction: 'DESC'},
			{field: 'sortIndex', direction: 'ASC'},
			{field: 'category', direction: 'ASC'}
		]);

		var selectionModel = this.createSelectionModel();
		var columnModel = this.createColumnModel(selectionModel);

		return {
			xtype: 'editorgrid',
			ariaLabel: _('Categories'),
			trackMouseOver: true,
			enableHdMenu: false,
			deferRowRender:false,
			autoExpandColumn: 'category',
			ref: 'categoriesGrid',
			cls: 'k-no-column-headers k-categories-grid',
			viewConfig: {
				forceFit: true,
				markDirty: false
			},
			store: store,
			sm: selectionModel,
			colModel: columnModel,
			listeners: {
				scope: this,
				afterrender: this.onAfterRender,
				cellclick: this.onCellClick,
				rowclick: this.onRowClick,
				validateedit: this.onValidateEdit,
				afteredit: this.onAfterEdit
			},
			onCellDblClick: Ext.emptyFn
		};
	},

	/**
	 * Returns the selection model that will be used for the categories grid.
	 *
	 * @return {Ext.grid.CheckboxSelectionModel} The selection model
	 */
	createSelectionModel: function()
	{
		return new Ext.grid.CheckboxSelectionModel({
			checkOnly: true,
			moveEditorOnEnter: false
		});
	},

	/**
	 * Returns the column model that will be used for the categories grid.
	 *
	 * @param {Ext.grid.CheckboxSelectionModel} selectionModel The selection model of
	 * the categories grid.
	 * @return {Ext.grid.ColumnModel} The column model
	 */
	createColumnModel: function(selectionModel)
	{
		return new Ext.grid.ColumnModel({
			columns: [
				selectionModel,
				{
					id		: 'color',
					dataIndex	: 'color',
					sortable	: false,
					width		: 25,
					fixed		: true,
					renderer 	: this.categoryColorRenderer
				},
				{
					dataIndex	: 'category',
					editor		: new Ext.form.TextField({
						allowBlank: false,
						enableKeyEvents: true,
						listeners: {
							scope: this,
							keydown: function(field, e){
								var key = e.browserEvent.key;
								if ( key === ';' || key === ',' ){ // Don't allow ; or , in a category name
									e.preventDefault();
								}
							}
						}
					}),
					sortable	: false,
					hideActionButtons: this.hideActionButtons,
					renderer 	: this.categoryNameRenderer
				}
			]
		});
	},

	/**
	 * Function that will be used to render the category color cell in the grid.
	 *
	 * @param {String} color The color in hex rgb format
	 * @param {Object} metaData An object with some data of the cell that is being
	 * rendered.
	 * @param {Ext.data.Record} record The record from the
	 * {@link Zarafa.common.categories.data.CategoriesStore CategoriesStore} for
	 * which this cell is rendered.
	 * @return {String} The html that will be used to render the cell.
	 */
	categoryColorRenderer: function(color, metaData, record)
	{
		var html = Zarafa.common.categories.Util.getCategoryIconSVG(color,'16');

		var cls = Ext.isNumber(record.get('standardIndex')) ? ' k-category-fixed' : '';
		html += '<div class="k-colorpicker-ct' + cls + '" style="background-color:'+color+';"></div>';

		return html;
	},

	/**
	 * Function that will be used to render the category name cell in the grid.
	 *
	 * @param {String} value The name of the category
	 * @param {Object} metaData An object with some data of the cell that is being
	 * rendered.
	 * @param {Ext.data.Record} record The record from the
	 * {@link Zarafa.common.categories.data.CategoriesStore CategoriesStore} for
	 * which this cell is rendered.
	 * @return {String} The html that will be used to render the cell.
	 */
	categoryNameRenderer: function(value, metaData, record)
	{
		var cls = record.get('quickAccess') ? ' zarafa-pinned' : '';
		cls += Ext.isNumber(record.get('standardIndex')) ? ' k-category-fixed' : '';
		if ( record.get('additional') ) {
			cls += ' k-category-additional';
		}

		if(this.hideActionButtons) {
			return Ext.util.Format.htmlEncode(value);
		}

		return Ext.util.Format.htmlEncode(value) +
			'<div class="zarafa-grid-button-container' + cls + '">' +
				'<div class="zarafa-grid-button k-grid-button-edit" role="button" tabindex="0" aria-label="' + _('Rename category') + '"></div>' +
				'<div class="zarafa-grid-button k-grid-button-delete" role="button" tabindex="0" aria-label="' + _('Delete category') + '"></div>' +
				'<div class="zarafa-grid-button k-grid-button-pin" role="button" tabindex="0" aria-label="' + _('Quick access') + '"></div>' +
			'</div>';
	},

	/**
	 * Handler for afterrender event. Will select the categories that are set on the
	 * records to which this panel applies, will create the color pickers in the grid
	 * rows, and will add event listeners.
	 * listeners
	 * @param {Ext.grid.EditorGridPanel} grid The grid with all the categories
	 * @private
	 */
	onAfterRender: function(grid)
	{
		grid.getSelectionModel().suspendEvents(false);
		var records = this.getAvailableCategories();
		if(!Ext.isEmpty(records)) {
			grid.getSelectionModel().selectRecords(records);
		}

		grid.getSelectionModel().resumeEvents();

		var view = grid.getView();
		this.addColorPickers(view);

		this.mon(grid.store, 'add', function(){
			grid.store.sort([
				{field: 'quickAccess', direction: 'DESC'},
				{field: 'sortIndex', direction: 'ASC'},
				{field: 'category', direction: 'ASC'}
			]);
		});

		this.mon(view, 'refresh', this.addColorPickers, this);
		this.mon(view, 'rowupdated', this.onRowUpdated, this);

		// Add keyboard handler for action buttons in grid rows
		this.mon(grid.getEl(), 'keydown', function(e) {
			if (e.getKey() === e.ENTER || e.getKey() === e.SPACE) {
				var target = Ext.get(e.getTarget());
				if (target && target.hasClass('zarafa-grid-button')) {
					e.stopEvent();
					target.dom.click();
				}
			}
		}, this);
	},

	/**
	 * Adds a {@link Zarafa.common.ui.ColorPicker} to every color cell.
	 * @param {Ext.grid.GridView} view The view of the categories grid
	 */
	addColorPickers: function(view)
	{
		this.categoriesGrid.store.each(function(record, index){
			var el = Ext.get(view.getCell(index, 1).querySelector('.k-colorpicker-ct'));
			var color = (record.get('color') || '') + '';
			var colorPicker = new Zarafa.common.ui.ColorPicker({
				renderTo: el.dom,
				value: color.replace('#', '')
			});
			colorPicker.on('show', this.onColorPickerShowMenu.createDelegate(this, [colorPicker], true));
			colorPicker.on('hide', this.onColorPickerHideMenu.createDelegate(this, [colorPicker], true));
			colorPicker.on('select', this.onSelectColor.createDelegate(this, [record], true));

			record.colorPicker = colorPicker;
		}, this);
	},

	/**
	 * Event handler for the rowupdated event of the grid view
	 *
	 * @param {Ext.grid.GridView} view The view component of the grid
	 * @param {Number} rowIndex The number of the row that was updated
	 * @param {Ext.data.Record} record The record of the
	 * {@link Zarafa.common.categories.data.CategoriesStore CategoriesStore}
	 * for which the row was updated.
	 */
	onRowUpdated: function(view, rowIndex, record)
	{
		// Rerender the colorPicker
		if ( record.colorPicker ){
			var el = Ext.get(view.getCell(rowIndex, 1).querySelector('.k-colorpicker-ct'));
			record.colorPicker.renderTo = el.dom;
			record.colorPicker.rendered = false;
			record.colorPicker.el = undefined;
			record.colorPicker.render(el);
		}
	},

	/**
	 * Event handler for the show event of the menu of the color pickers. Will
	 * add the class <i>k-menu-visible</i> to the cell element.
	 *
	 * @param {Ext.menu.ColorMenu} menu The menu that is shown.
	 * @param {Zarafa.common.ui.ColorPicker} colorPicker the color picker that
	 * fired the event.
	 */
	onColorPickerShowMenu: function(menu, colorPicker)
	{
		var cellEl = colorPicker.el.up('div').up('div');
		cellEl.addClass('k-menu-visible');
	},

	/**
	 * Event handler for the hide event of the menu of the color pickers. Will
	 * remove the class <i>k-menu-visible</i> to the cell element.
	 *
	 * @param {Ext.menu.ColorMenu} The menu that is hidden.
	 * @param {Zarafa.common.ui.ColorPicker} colorPicker the color picker that
	 * fired the event.
	 */
	onColorPickerHideMenu: function(menu, colorPicker)
	{
		var cellEl = colorPicker.el.up('div').up('div');
		cellEl.removeClass('k-menu-visible');
	},

	/**
	 * Event handler for the select event of one of the color pickers. Will
	 * set the selected color on the category record.
	 *
	 * @param {Zarafa.common.ui.ColorPicker} colorPicker the color picker that
	 * fired the event.
	 * @param {String} color The color that was selected in hex RGB format.
	 * @param {Ext.data.Record} record The category record that this menu will
	 * act on.
	 */
	onSelectColor: function(colorPicker, color, record)
	{
		record.set('color', '#' + color);
	},

	/**
	 * Event handler for the cellclick event of the category grid. Will
	 * take care of the selection behavior of the grid.
	 *
	 * @param {Ext.grid.GridPanel} grid The category grid
	 * @param {Number} rowIndex The index of the row that was clicked.
	 * @param {Number} columnIndex The index of the column that was clicked.
	 * @param {Ext.EventObject} event The event object of the cellclick event.
	 */
	onCellClick: function(grid, rowIndex, columnIndex, event)
	{
		var targetEl = Ext.get(event.target);

		// Check the class to make sure we don't select when the user clicks
		// on one of the buttons (edit, delete, pin)
		if ( columnIndex === 2 && targetEl.hasClass('x-grid3-col-category') ){
			var sm = grid.getSelectionModel();
			if ( sm.isSelected(rowIndex) ){
				sm.deselectRow(rowIndex);
			} else {
				sm.selectRow(rowIndex, true);
			}
		}
	},

	/**
	 * Event handler for the rowclick event of the category grid. Will check
	 * if one of the pin/edit/delete icons was clicked and handle accordingly.
	 * also it will open {@link Zarafa.common.categories.dialogs.RenameCategoryPanel RenameCategoryPanel}
	 * when standard categories like 'Red', 'Green' etc. checkbox is checked for first time.
	 *
	 * @param {Ext.grid.GridPanel} grid The category grid
	 * @param {Number} rowIndex The index of the row that was clicked.
	 * @param {Ext.EventObject} event The event object of the rowclick event.
	 */
	onRowClick: function(grid, rowIndex, event)
	{
		// Make sure the grid will not scroll to the row that was focussed
		// before the click by putting the focus on the clicked row.
		grid.getView().focusRow(rowIndex);

		var targetEl = Ext.get(event.target);
		if ( targetEl.hasClass('k-grid-button-pin') && !targetEl.up('div').hasClass('zarafa-pin-fixed') ){
			this.toggleCategoryPin(rowIndex);
		} else if ( targetEl.hasClass('k-grid-button-edit') ){
			this.editCategoryName(rowIndex);
		} else if ( targetEl.hasClass('k-grid-button-delete') ){
			this.deleteCategory(rowIndex);
		} else if(targetEl.hasClass('x-grid3-row-checker')) {
			// If check box is unchecked then just return.
			if(!grid.getColumnModel().getColumnById('checker').isSelected(rowIndex)) {
				return;
			}
			var category = grid.getStore().getAt(rowIndex);
			var standardIndex = category.get('standardIndex');
			if(Ext.isEmpty(standardIndex)) {
				return;
			}

			if(category.get('used') === false) {
				Zarafa.common.Actions.openRenameCategoryContent({
					store: grid.getStore(),
					isCategoryGrid: true,
					categoryName: category.get('category'),
					color: category.get('color')
				});
			}
		}
	},

	/**
	 * Will handle clicks on the pin icon of a row.
	 *
	 * @param {Number} rowIndex The index of the row in which the pin icon
	 * was clicked.
	 */
	toggleCategoryPin: function(rowIndex)
	{
		var categoryRecord = this.categoriesGrid.getStore().getAt(rowIndex);
		categoryRecord.set('quickAccess', !categoryRecord.get('quickAccess'));
	},

	/**
	 * Will open the grid editor of the category name of a row.
	 *
	 * @param {Number} rowIndex The index of the row in which the pin icon
	 * was clicked.
	 */
	editCategoryName: function(rowIndex)
	{
		this.categoriesGrid.startEditing(rowIndex, 2);
	},

	/**
	 * Will delete the category from the store that is bound to the grid.
	 * <i>ote: It will not be deleted from the settings until the user clicks
	 * on the 'Apply' button.</i>
	 *
	 * @param {Number} rowIndex The number of the row in the grid that shows
	 * the category that must be deleted.
	 */
	deleteCategory: function(rowIndex)
	{
		var store = this.categoriesGrid.getStore();
		var categoryRecord = store.getAt(rowIndex);
		var categoryName = Ext.util.Format.htmlEncode(categoryRecord.get('category'));
		Zarafa.common.dialogs.MessageBox.addCustomButtons({
			width: 400,
			title: _('Delete Category'),
			msg: String.format(_('Are you sure you want to delete the category "{0}"? Other items keep this category, but lose the assigned color.'), categoryName),
			fn: function(buttonName){
				if ( buttonName === 'delete' ){
					var categoryStore = this.categoriesGrid.getStore();
					categoryStore.removeAt(rowIndex);
				}
			},
			customButton: [{
				name: 'delete',
				text: _('Delete')
			}, {
				name: 'cancel',
				text: _('Cancel')
			}],
			scope: this
		});
	},

	/**
	 * Handler for the validateedit event of the grid. Will check if
	 * the entered category name does not already exist.
	 *
	 * @param {Object} event The {@link Ext.grid.EditorGridPanelView.validateedit event object}
	 */
	onValidateEdit: function(event)
	{
		var categoryStore = event.grid.getStore();
		var categoryExists = false;
		categoryStore.each(function(categoryRecord){
			if ( categoryRecord === event.record ){
				return;
			}

			if ( categoryRecord.get('category').toLowerCase() === event.value.toLowerCase() ){
				categoryExists = true;
				return false;
			}
		}, this);

		if ( categoryExists ){
			Zarafa.common.dialogs.MessageBox.alert(
				_('Rename Category'),
				_('A category named "' + Ext.util.Format.htmlEncode(event.value) + '" already exists. Please enter a different category name.'),
				function(){
					// Open the editor again so the user can change the name again
					this.categoriesGrid.startEditing(event.row, 2);
				},
				this
			);

			event.cancel = true;
		}
	},

	/**
	 * Handler for the afteredit event of the grid. Will make sure the new category name will
	 * be saved.
	 *
	 * @param {Object} event The {@link Ext.grid.EditorGridPanelView.afteredit event object}
	 */
	onAfterEdit: function(event)
	{
		if ( event.value !== event.originalValue ){
			event.record.set('stored', true);
		}
	},

	/**
	 * return all categories records from the managed categories list
	 * @return {Array} of {@link Ext.data.Record Records}
	 * @private
	 */
	getAvailableCategories: function()
	{
		var activeAvailableCategories = [];
		var categoriesGrid = this.categoriesGrid;
		var categories = Zarafa.common.categories.Util.getAllCategories(this.record);

		for (var i = 0; i < categories.length; i++) {
			for (var j = 0; j < categoriesGrid.getStore().getCount(); j++) {
				if (categories[i] == categoriesGrid.getStore().getAt(j).get('category')) {
					activeAvailableCategories.push(categoriesGrid.getStore().getAt(j));
					break;
				}
			}
		}
		return activeAvailableCategories;
	},

	/**
	 * Returns an array with the names of all selected categories.
	 * @return {Array} An array with the names of the selected categories
	 */
	getSelectedCategories: function()
	{
		var records = this.categoriesGrid.getSelectionModel().getSelections();
		return records.map(function(record) {
			return record.get('category');
		}, this);
	}
});

Ext.reg('zarafa.categoriespanel', Zarafa.common.categories.dialogs.CategoriesPanel);
Ext.namespace('Zarafa.common.categories.ui');

/**
 * @class Zarafa.common.categories.ui.CategoriesContextMenu
 * @extends Ext.menu.Menu
 * @xtype zarafa.categoriescontextmenu
 *
 * The CategoriesContextMenu is the context menu that is shown when the user
 * right clicks on the category icon in the mail grid. It also serves as submenu
 * of the {@link Zarafa.common.categories.ui.CategoryContextMenu CategoryContextMenu}.
 */
Zarafa.common.categories.ui.CategoriesContextMenu = Ext.extend(Ext.menu.Menu, {
	/**
	 * @cfg {Zarafa.core.data.IPMRecord[]} The records to which the actions in
	 * this context menu will apply
	 */
	records: [],

	/**
	 * @cfg {Zarafa.core.data.MAPIStore} store contains {@link #records} on which
	 * categories is going to apply.
	 */
	store: undefined,

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

		if(!Array.isArray(config.records)) {
			this.records = [config.records];
		} else {
			this.records = config.records;
		}

		if(Ext.isDefined(this.records[0])) {
			this.store = this.records[0].getStore();
		}

		Ext.applyIf(config, {
			xtype: 'zarafa.categoriescontextmenu',
			cls: 'k-categories',
			items: [
				this.createCategoryItems(),
				{ xtype: 'menuseparator' },
				{
					text: _('Manage Categories'),
					cls: 'k-manage-categories',
					handler: function() {
						Zarafa.common.Actions.openCategoriesContent(this.records);
					},
					scope: this
				}
			],
			listeners: {
				afterrender: this.onAfterRenderCategoriesMenu,
				scope: this
			}
		});

		Zarafa.common.categories.ui.CategoriesContextMenu.superclass.constructor.call(this, config);

		this.mon(this.store,'load', this.onLoad, this);
	},

	/**
	 * Create the categories submenu
	 * @return {Ext.menu.Item[]} The list of menu items of
	 * the categories menu
	 * @private
	 */
	createCategoryItems: function()
	{
		var categoriesStore = new Zarafa.common.categories.data.CategoriesStore();
		// Add categories that are set on the record(s) but don't exist in the categoryStore
		categoriesStore.addCategoriesFromMapiRecords(this.records);

		// Only show the quick access categories and selected categories in the submenu
		var selectedCategories = Zarafa.common.categories.Util.getAllCategories(this.records);
		categoriesStore.filterBy(function(category){
			return category.get('quickAccess') || selectedCategories.indexOf(category.get('category'))>-1;
		});

		// Map all categories to a config object for a menu item
		return categoriesStore.getRange().map(function(category){
			return {
				text: '<span class="k-category-in-menu">' + Ext.util.Format.htmlEncode(category.get('category')) + '</span>',
				plainText: category.get('category'),
				color: category.get('color'),
				handler: this.onCategoryMenuItemClick,
				listeners: {
					beforerender: this.onBeforeRenderCategoriesMenuItem,
					afterrender: this.onAfterRenderCategoriesMenuItem,
					scope: this
				},
				scope: this
			};
		}.bind(this));
	},

	/**
	 * Event handler for the afterrender event of the "Remove category" menu item. Will
	 * create a {@link Zarafa.common.categories.ui.Tooltip tooltip} for categories that are
	 * truncated.
	 * @param {Ext.menu.Item} item The menu item that has been rendered.
	 */
	onAfterRenderCategoriesMenu: function(item)
	{
		new Zarafa.common.categories.ui.Tooltip({
			target: item.el,
			delegate: '.k-category-in-menu'
		});
	},

	/**
	 * Event handler for the beforerender event of the items in the categories submenu. If the
	 * category is set on all selected records, a css class will be added to the menu item.
	 * @param {Ext.menu.Item} item The item of the categories submenu
	 * that is about to be rendered
	 */
	onBeforeRenderCategoriesMenuItem: function(item)
	{
		var selectedCategories = Zarafa.common.categories.Util.getCommonCategories(this.records);
		if ( selectedCategories.indexOf(item.plainText)>-1 ){
			item.cls = 'x-menu-item-selected';
			item.selected = true;
		} else {
			item.cls = '';
			item.selected = false;
		}
	},

	/**
	 * Event handler for the afterrender event of the items in the categories submenu. It
	 * will add an svg icon with the category color to the menu item.
	 * @param {Ext.menu.Item} item The item of the categories submenu
	 * that was just rendered
	 */
	onAfterRenderCategoriesMenuItem: function(item)
	{
		var icon = item.el.down('img');
		var svgIcon = Zarafa.common.categories.Util.getCategoryIconSVG(item.color,'24');
		Ext.DomHelper.insertHtml('beforeBegin', icon.dom, svgIcon);
	},

	/**
	 * Event handler for the items in the categories submenu. Will add, rename or remove the
	 * clicked category to/from all selected records. function will be shows
	 * {@link Zarafa.common.categories.dialogs.RenameCategoryPanel RenameCategoryPanel} when
	 * standard categories(Red, Green etc.) are selected first time.
	 *
	 * @param {Ext.menu.Item} item The item of the categories submenu
	 * that was clicked
	 */
	onCategoryMenuItemClick: function(item)
	{
		if ( item.selected ){
			// Remove this category from all records
			Zarafa.common.categories.Util.removeCategory(this.records, item.plainText, true);
		} else {
			var categories = container.getPersistentSettingsModel().get('grommunio/main/categories');
			var category = categories.find(function (category) {
				if(!Ext.isEmpty(category.standardIndex) && (category.name === item.plainText)){
					return category;
				}
			});

			if(Ext.isDefined(category) && !category.used) {
				Zarafa.common.Actions.openRenameCategoryContent({
					categoryName: category.name,
					records: this.records,
					color: item.color,
					recordStore: this.store
				});
			} else {
				// Add this category to all records that don't have it yet'
				Zarafa.common.categories.Util.addCategory(this.records, item.plainText, true, this.store);
			}
		}
	}
});

Ext.reg('zarafa.categoriescontextmenu', Zarafa.common.categories.ui.CategoriesContextMenu);
Ext.namespace('Zarafa.common.categories.ui');

/**
 * @class Zarafa.common.categories.ui.CategoryContextMenu
 * @extends Ext.menu.Menu
 * @xtype zarafa.categorycontextmenu
 *
 * The context menu that will be shown when the user right-clicks on a category label
 */
Zarafa.common.categories.ui.CategoryContextMenu = Ext.extend(Ext.menu.Menu, {
	/**
	 * @cfg {Zarafa.core.data.IPMRecord[]} The records to which the actions in
	 * this context menu will apply
	 */
	records: [],

	/**
	 * @cfg {String} The category for which the context menu is shown
	 */
	category: '',

	/**
	 * @cfg {Zarafa.core.data.MAPIStore} store contains {@link #records} on which
	 * categories is going to apply.
	 */
	store: undefined,

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

		this.store = config.records[0].getStore();

		Ext.applyIf(config, {
			xtype: 'zarafa.categorycontextmenu',
			cls: 'zarafa-category-menu',
			items: [
				{
					text: _('Remove') + ' "<span class="k-category-in-menu">' +
							Ext.util.Format.htmlEncode(config.category) +
							'</span>"',
					category: config.category,
					iconCls: 'icon_remove',
					handler: this.onRemoveCategory,
					scope: this,
					listeners: {
						afterrender: this.onAfterRenderRemoveCategoryItem,
						scope: this
					}
				},
				{
					cls: 'k-unclickable',
					text: _('Categories'),
					iconCls: 'icon_categories',
					hideOnClick: false,
					menu: {
						xtype: 'zarafa.categoriescontextmenu',
						records: config.records
					}
				}
			]
		});

		Zarafa.common.categories.ui.CategoriesContextMenu.superclass.constructor.call(this, config);
	},

	/**
	 * Event handler for the click event of the "remove category" menu item
	 * @param {Ext.menu.Item} item The menu item that has been clicked
	 */
	onRemoveCategory: function(item)
	{
		var category = Zarafa.common.categories.Util.getCategoryFromHtmlEncoded(item.category, this.records);
		Zarafa.common.categories.Util.removeCategory(this.records, category, true, this.store);
	},

	/**
	 * Event handler for the afterrender event of the "Remove category" menu item. Will
	 * create a {@link Zarafa.common.categories.ui.Tooltip tooltip} for categories that are
	 * truncated.
	 * @param {Ext.menu.Item} item The menu item that has been rendered.
	 */
	onAfterRenderRemoveCategoryItem: function(item)
	{
		new Zarafa.common.categories.ui.Tooltip({
			target: item.el,
			delegate: '.x-menu-item'
		});
	}
});

Ext.reg('zarafa.categorycontextmenu', Zarafa.common.categories.ui.CategoryContextMenu);
Ext.namespace('Zarafa.common.categories.ui');

/**
 * @class Zarafa.common.categories.ui.Tooltip
 * @extends Ext.ToolTip
 * @xtype zarafa.categoriestooltip
 *
 * Tooltip for category blocks that are truncated because they are too long.
 * The tooltip will show the full category name.
 */
Zarafa.common.categories.ui.Tooltip = Ext.extend(Ext.ToolTip, {
	/**
	 * @constructor
	 * @param {Object} config Configuration object.
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			xtype: 'zarafa.categoriestooltip',
			target: Ext.get(document.body), // Should be overridden when creating this component
			delegate: '.k-category-block',
			dismissDelay: 0,
			trackMouse: true,
			renderTo: Ext.getBody(),
			listeners: {
				beforeshow: this.onBeforeshow,
				scope: this
			}
		});

		Zarafa.common.categories.ui.Tooltip.superclass.constructor.call(this, config);
	},

	/**
	 * Event handler for the beforeshow event of the categoryTooltip. Will update
	 * the contents of the category tooltip if necessary or return false to not
	 * show the tooltip otherwise.
	 * @param (Ext.ToolTip) tooltip The category tooltip
	 * @return {Boolean|undefined} False if the tooltip should not be shown,
	 * undefined otherwise
	 */
	onBeforeshow: function(tooltip)
	{
		// Don't show the tooltip for categories that aren't truncated
  	if ( !this.isCategoryNameTruncated(tooltip.triggerElement) ){
  		return false;
  	}

    tooltip.body.dom.innerHTML = Ext.util.Format.htmlEncode(tooltip.triggerElement.textContent);
	},

	/**
	 * Will check if a category name is truncated or not
	 * @param {HTMLElement} categoryEl The category element that needs to be checked
	 * for truncation.
	 * @return {Boolean} True if the category name is truncated, false otherwise
	 */
	isCategoryNameTruncated: function(categoryEl)
	{
		return categoryEl.offsetWidth < categoryEl.scrollWidth;
	}
});

Ext.reg('zarafa.categoriestooltip', Zarafa.common.categories.ui.Tooltip);
Ext.namespace('Zarafa.common.checknames.dialogs');

/**
 * @class Zarafa.common.checknames.dialogs.CheckNamesPanel
 * @extends Ext.Panel
 * @xtype zarafa.checknamespanel
 */
Zarafa.common.checknames.dialogs.CheckNamesPanel = Ext.extend(Ext.Panel, {
	/**
	 * The listView which is used to display all possible names.
	 * @property
	 * @type Ext.list.ListView
	 */
	checkNamesList: undefined,

	/**
	 * @cfg {Ext.data.JsonStore} store The store containing all suggestions
	 */
	store: undefined,

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

		// Prepare the suggestion store
		if (!Ext.isDefined(config.store)) {
			config.store = new Ext.data.JsonStore({
				autoDestroy: true,
				idProperty: 'smtp_address',
				fields: Zarafa.core.data.IPMRecipientResolveRecord,
				data: []
			});
		}

		Ext.applyIf(config, {
			xtype: 'zarafa.checknamespanel',
			layout: {
				type: 'vbox',
				align: 'stretch'
			},
			border: false,
			header: true,
			items: [{
				xtype: 'displayfield',
				value: _('Select an address to use') + ':',
				hideLabel: true
			},{
				xtype: 'grid',
				ref: 'checkNamesList',
				flex: 1,
				store: config.store,
				viewConfig: {
					forceFit: true
				},
				tabindex: 0,
				listeners: {
					afterrender: function(grid) { grid.getEl().dom.setAttribute('tabindex', 0); },
				},
				columns: [{
					dataIndex: 'display_name',
					header: _('Name'),
					renderer: Ext.util.Format.htmlEncode
				},{
					dataIndex: 'smtp_address',
					header: _('Email'),
					renderer: Ext.util.Format.htmlEncode
				}],
				listeners: {
					scope: this,
					viewready: this.onViewReady,
					dblclick: this.onDblClick
				},
				selModel: new Ext.grid.RowSelectionModel({
					singleSelect: true
				}),
				cls: 'zarafa-checknames-dialog-filelist'
			}]
		});

		Zarafa.common.checknames.dialogs.CheckNamesPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Load an {@link Zarafa.core.data.IPMRecipientRecord} and
	 * {@link Zarafa.core.data.IPMRecipientResolveRecord ChecknamesRecord} data into this panel.
	 *
	 * @param {Zarafa.core.data.IPMRecipientRecord} record The record for which the checknames dialog is shown
	 * @param {Array} data The {@link Zarafa.core.data.IPMRecipientResolveRecord ChecknamesRecord suggestions}
	 * which exist for the given record.
	 */
	update: function(record, data)
	{
		this.setTitle(String.format(_('More than one \'{0}\' found.'), Ext.util.Format.htmlEncode(record.get('display_name'))));
		this.store.add(data || []);
	},

	/**
	 * Enable focus navigation to the grid using the Tab key.
	 */
	focusGrid: function() {
		if (this.checkNamesList && this.checkNamesList.getEl()) {
			this.checkNamesList.getEl().focus();
		}
	},

	/**
	 * Event handler which is fired when the gridPanel is ready. This will automatically
	 * select the first row in the grid.
	 * @private
	 */
	onViewReady: function()
	{
		this.checkNamesList.getSelectionModel().selectFirstRow();
	},

	/**
	 * Update an {@link Zarafa.core.data.IPMRecipientRecord} with the selected
	 * {@link Zarafa.core.data.IPMRecipientResolveRecord record} from the {@link #checkNamesList}.
	 *
	 * @param Zarafa.core.data.IPMRecipientRecord} record The record which must be updated
	 * @return {Boolean} False if the record could not be updated.
	 */
	updateRecord: function(record)
	{
		var selection = this.checkNamesList.getSelectionModel().getSelected();

		if (Ext.isEmpty(selection)) {
			Ext.Msg.alert(_('Alert'), _('Please select a recipient'));
			return false;
		} else {
			record.applyResolveRecord(selection);
		}
	},

	/**
	 * Event handler which is fired when a row inside the {@link #checkNamesList}
	 * has been double-clicked.
	 * @private
	 */
	onDblClick: function()
	{
		this.dialog.onOk();
	},

	onFocus: function() { this.focusGrid(); }
});

Ext.reg('zarafa.checknamespanel', Zarafa.common.checknames.dialogs.CheckNamesPanel);
Ext.namespace('Zarafa.common.data');


/**
 * @class Zarafa.common.data.AbstractRulesFactory
 * The factory class for getting sub factory objects of type {@link Zarafa.common.data.ConditionRuleFactory},
 * {@link Zarafa.common.data.ActionRulesFactory}.
 *
 * This is an abstract class which manages sub factories related to Rules.
 *
 * @singleton
 */
Zarafa.common.data.AbstractRulesFactory = {

	/**
	 * Key-value map of Factory Definitions as registered by
	 * {@link Zarafa.common.data.AbstractRulesFactory#registerFactory}.
	 *
	 * @property
	 * @type Object
	 */
	factoryDefinitions: {},

	/**
	 * Register a new Factory Definition for the given key value.
	 * This function will be used to dynamically register a Rules Factories,
	 * like {@link Zarafa.common.data.ActionRulesFactory} and {@link Zarafa.common.data.ConditionRuleFactory}.
	 *
	 * @param {Zarafa.common.data.RulesFactoryType} key The key value on how the factory is stored
	 * in the definitions table.
	 * @param {Object} factoryType object of factory which needs to be registered.
	 * @return {Object} The factory Definition or false if given factory is not registered or
	 * If there exists factory definition for the key then return it.
	 */
	registerFactory: function(key, factoryType)
	{
		if (this.factoryDefinitions[key]) {
			return this.factoryDefinitions[key];
		}

		if (!Ext.isEmpty(key) && Ext.isObject(factoryType)) {
			this.factoryDefinitions[key] = factoryType;
			return this.factoryDefinitions[key];
		}
		return false;
	},

	/**
	 * Get Factory Definition for the given key value.
	 *
	 * @param {Zarafa.common.data.RulesFactoryType} key The key value on how the definition is stored
	 * in the definitions table.
	 * @return {Function} registered Factory Definition from definitions table
	 * or false if given factory is not registered.
	 */
	getFactoryById: function(key)
	{
		if (!Ext.isEmpty(this.factoryDefinitions[key])) {
			return this.factoryDefinitions[key];
		}
		return false;
	},

	/**
	 * Unregister the Factory Definition for the given key value.
	 * This function will be used to dynamically unregister definition on runtime.
	 *
	 * @param {Zarafa.common.data.RulesFactoryType} key The key value on how the definition is stored
	 * in the definitions table.
	 * @return {Boolean} The true if successfully definition removed else false.
	 */
	unregisterFactory: function(key)
	{
		if (!Ext.isEmpty(this.factoryDefinitions[key])) {
			delete this.factoryDefinitions[key];
			return true;
		}
		return false;
	}
};Ext.namespace('Zarafa.common.delegates.dialogs');

/**
 * @class Zarafa.common.delegates.dialogs.DelegatePermissionPanel
 * @extends Ext.form.FormPanel
 * @xtype zarafa.delegatepermissionpanel
 *
 * Will generate UI for {@link Zarafa.common.delegates.dialogs.DelegatePermissionContentPanel DelegatePermissionContentPanel}.
 */
Zarafa.common.delegates.dialogs.DelegatePermissionPanel = Ext.extend(Ext.form.FormPanel, {

	/**
	 * @cfg {Array} folderTypes array of folder type that will be used
	 * generate permissions combo box field
	 */
	folderTypes: {
		'calendar': _('Calendar'),
		'tasks': _('Tasks'),
		'inbox': _('Inbox'),
		'contacts': _('Contacts'),
		'notes': _('Notes'),
		'journal':_('Journal')
	},

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

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

		Ext.applyIf(config, {
			// Override from Ext.Component
			xtype: 'zarafa.delegatepermissionpanel',
			labelAlign: 'left',
			items: this.createPanelItems()
		});

		Zarafa.common.delegates.dialogs.DelegatePermissionPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Function will create panel items for {@link Zarafa.common.delegates.dialogs.DelegatePermissionPanel DelegatePermissionPanel}
	 * @return {Array} array of items that should be added to panel.
	 * @private
	 */
	createPanelItems: function()
	{
		return [{
			xtype: 'fieldset',
			style: {
				margin: '10px',
				padding: '10px'
			},
			title: _('This delegate has the following permissions'),
			autoHeight: true,
			autoWidth: true,
			cls: 'zarafa-fieldset',
			labelAlign: 'left',
			items: this.createFieldItems(this.folderTypes)
		}, {
			xtype: 'checkbox',
			boxLabel: _('Delegate can see my private items.'),
			style: 'margin-left: 10px;',
			ref: 'delegatePrivateCheck',
			name: 'can_see_private',
			hideLabel: true,
			checked: false,
			listeners: {
				check: this.onPrivateCheck,
				scope: this
			}
		}];
	},

	/**
	 * Generic function to create check box for delegate meeting rules check box.
	 * @return {Object} config object to create {@link Ext.form.CheckBox CheckBox}.
	 */
	createDelegateMeetingRuleCheck: function()
	{
		return{
			xtype: 'checkbox',
			boxLabel: _('Delegate receives copies of meeting-related messages sent to me.'),
			ref: '../delegateMeetingRuleCheck',
			name: 'has_meeting_rule',
			hideLabel: true,
			checked: false,
			listeners: {
				check: this.onDelegateRuleCheck,
				scope: this
			}
		};
	},

	/**
	 * Generic function to create comboboxes for different permission levels for default folders
	 * of the hierarchy.
	 * @param {Array} type type of the default folder (calendar, inbox, notes etc.)
	 * @return {Array} items array to create a {@link Ext.form.ComboBox ComboBox}.
	 * @private
	 */
	createFieldItems: function(folderTypes)
	{
		var items = [];

		Ext.iterate(folderTypes, function(key, value){
			var profileStore = {
				xtype: 'jsonstore',
				fields: ['name', 'value'],
				data: Zarafa.common.delegates.data.DelegatePermissionProfiles
			};

			var item = {
				xtype: 'combo',
				name: 'rights_' + key,
				boxMinWidth: 200,
				anchor: '100%',
				fieldLabel: value,
				store: profileStore,
				mode: 'local',
				triggerAction: 'all',
				displayField: 'name',
				valueField: 'value',
				lazyInit: false,
				// "Full Control", "No Rights" etc. folder permissions are not supported
				// by the delegate so we just show the "Other" as text in combo box.
				valueNotFoundText: _('Other'),
				forceSelection: true,
				editable: false,
				value: Zarafa.core.mapi.Rights.RIGHTS_NONE,
				listeners: {
					select: this.onProfileSelect,
					scope: this
				}
			};
			items.push(item);

			if(key === 'calendar') {
				items.push(this.createDelegateMeetingRuleCheck());
			}
		}, this);

		return items;
	},

	/**
	 * Updates the panel by loading data from the record into the form panel.
	 * @param {Zarafa.common.delegates.data.DelegateRecord} 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;

		this.getForm().loadRecord(record);

		this.updateUI(record, contentReset);
	},

	/**
	 * Updates the UI of the panel.
	 * @param {Zarafa.common.delegates.data.DelegateRecord} record The record update the panel with.
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 */
	updateUI: function(record, contentReset)
	{
		if(contentReset || record.isModifiedSinceLastUpdate('rights_calendar')) {
			var calendarRights = record.get('rights_calendar');
			if(!calendarRights || calendarRights === Zarafa.core.mapi.Rights.RIGHTS_NONE || calendarRights === Zarafa.core.mapi.Rights.RIGHTS_READONLY) {
				this.delegateMeetingRuleCheck.setDisabled(true);
			} else {
				this.delegateMeetingRuleCheck.setDisabled(false);
			}
		}
	},

	/**
	 * 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)
	{
		this.getForm().updateRecord(record);
	},

	/**
	 * Handler function that will be called when user selects permission level for
	 * any default folder type. This is a common function for every combobox.
	 * @param {Ext.form.Combobox} comboBox permission level combobox
	 * @param {Ext.data.Record} record currently selected record in the combobox
	 * @param {Number} index index of the currently selected record in combobox
	 * @private
	 */
	onProfileSelect: function(comboBox, record, index)
	{
		var type = comboBox.name;

		this.record.beginEdit();

		// update the record data so we can use it in next function
		this.record.set(type, record.get(comboBox.valueField));

		if(type === 'rights_calendar') {
			var calendarRights = this.record.get(type);

			if (!calendarRights || calendarRights === Zarafa.core.mapi.Rights.RIGHTS_NONE || calendarRights === Zarafa.core.mapi.Rights.RIGHTS_READONLY) {
				this.record.set('has_meeting_rule', false);
			}

			this.updateUI(this.record, false);
		}

		this.record.endEdit();
	},

	/**
	 * Handler function that will be called when user checks/unchecks checkbox of delegate meeting rule,
	 * it will save the checked value to record.
	 * @param {Ext.form.Checkbox} checkBox checkbox for delegate meeting rule.
	 * @param {Boolean} checked current state of the checkbox.
	 * @private
	 */
	onDelegateRuleCheck: function(checkBox, checked)
	{
		this.record.set('has_meeting_rule', checked);
	},

	/**
	 * Handler function that will be called when user checks/unchecks checkbox of delegate private flag,
	 * it will save the checked value to record.
	 * @param {Ext.form.Checkbox} checkBox checkbox for delegate meeting rule.
	 * @param {Boolean} checked current state of the checkbox.
	 * @private
	 */
	onPrivateCheck: function(checkBox, checked)
	{
		this.record.set('can_see_private', checked);
	}
});

Ext.reg('zarafa.delegatepermissionpanel', Zarafa.common.delegates.dialogs.DelegatePermissionPanel);
Ext.namespace('Zarafa.common.delegates.ui');

/**
 * @class Zarafa.common.delegates.ui.DelegatesGrid
 * @extends Ext.grid.GridPanel
 * @xtype zarafa.delegatesgrid
 *
 * {@link Zarafa.common.delegates.ui.DelegatesGrid DelegatesGrid} will be used to display
 * delegates of the current user.
 */
Zarafa.common.delegates.ui.DelegatesGrid = Ext.extend(Ext.grid.GridPanel, {
	/**
	 * @constructor
	 * @param {Object} config Configuration structure
	 */
	constructor: function(config)
	{
		config = config || {};

		if(!config.store) {
			config.store = new Zarafa.common.delegates.data.DelegateStore();
		}

		Ext.applyIf(config, {
			xtype: 'zarafa.delegatesgrid',
			border: true,
			store: config.store,
			viewConfig: {
				forceFit: true,
				deferEmptyText: false,
				emptyText: '<div class=\'emptytext\'>' + _('No delegates configured') + '</div>'
			},
			loadMask: this.initLoadMask(),
			columns: this.initColumnModel(),
			selModel: this.initSelectionModel(),
			listeners: {
				viewready: this.onViewReady,
				rowdblclick: this.onRowDblClick,
				scope: this
			}
		});

		Zarafa.common.delegates.ui.DelegatesGrid.superclass.constructor.call(this, config);
	},

	/**
	 * initialize events for the grid panel.
	 * @private
	 */
	initEvents: function()
	{
		Zarafa.common.delegates.ui.DelegatesGrid.superclass.initEvents.call(this);

		// select first delegate when store has finished loading
		this.mon(this.store, 'load', this.onViewReady, this, {single: true});
	},

	/**
	 * Creates a column model object, used in {@link #colModel} config
	 * @return {Ext.grid.ColumnModel} column model object
	 * @private
	 */
	initColumnModel: function()
	{
		return [{
			dataIndex: 'display_name',
			header: _('Name'),
			renderer: Zarafa.common.ui.grid.Renderers.text
		}];
	},

	/**
	 * Creates a selection model object, used in {@link #selModel} config
	 * @return {Ext.grid.RowSelectionModel} selection model object
	 * @private
	 */
	initSelectionModel: function()
	{
		return new Ext.grid.RowSelectionModel({
			singleSelect: true
		});
	},

	/**
	 * Initialize the {@link Ext.grid.GridPanel.loadMask} field
	 *
	 * @return {Ext.LoadMask} The configuration object for {@link Ext.LoadMask}
	 * @private
	 */
	initLoadMask: function()
	{
		return {
			msg: _('Loading delegates') + '...'
		};
	},

	/**
	 * Event handler which is fired when the gridPanel is ready. This will automatically
	 * select the first row in the grid.
	 * @private
	 */
	onViewReady: function()
	{
		this.getSelectionModel().selectFirstRow();
	},

	/**
	 * Event handler which is fired when the {@link Zarafa.common.delegates.ui.DelegatesGrid DelegatesGrid} is double clicked.
	 * it will call generic function to handle the functionality.
	 * @private
	 */
	onRowDblClick: function(grid, rowIndex)
	{
		this.openDelegatePermissions(grid.getStore().getAt(rowIndex));
	},

	/**
	 * Generic function to open {@link Zarafa.common.delegates.dialogs.DelegatePermissionContentPanel DelegatePermissionContentPanel} for
	 * the user which has been selected.
	 * @param {Zarafa.common.delegates.data.DelegateRecord} delegateRecord record that should be opened in {@link Zarafa.common.delegates.dialogs.DelegatePermissionContentPanel DelegatePermissionContentPanel}.
	 * if not passed then currently selected record will be used.
	 * @param {Object} config configuration object that should be passed to {@link Zarafa.common.delegates.dialogs.DelegatePermissionContentPanel DelegatePermissionContentPanel}.
	 */
	openDelegatePermissions: function(delegateRecord, config)
	{
		config = config || {};

		delegateRecord = delegateRecord || this.getSelectionModel().getSelected();
		if(!delegateRecord) {
			Ext.Msg.alert(_('Alert'), _('Please select a delegate.'));
			return;
		}

		Ext.apply(config, {
			recordComponentPluginConfig: {
				// we will open records ourself
				enableOpenLoadTask: false
			}
		});

		// open detailed permissions content panel
		Zarafa.common.Actions.openDelegatePermissionContent(delegateRecord, config);
	},

	/**
	 * Function will be called to remove a delegate.
	 */
	removeDelegate: function()
	{
		var selectionModel = this.getSelectionModel();
		var delegateRecord = selectionModel.getSelected();
		var rowToSelect;

		if(!delegateRecord) {
			Ext.Msg.alert(_('Alert'), _('Please select a delegate.'));
			return;
		}

		// before removing delegate we should store row index of next available delegate,
		// because deleting delegate will remove selection
		if (selectionModel.hasNext()) {
			rowToSelect = selectionModel.last;
		} else if (selectionModel.hasPrevious()) {
			rowToSelect = selectionModel.last-1;
		}

		this.store.remove(delegateRecord);

		if (Ext.isDefined(rowToSelect)) {
			selectionModel.selectRow(rowToSelect);
		}
	}
});

Ext.reg('zarafa.delegatesgrid', Zarafa.common.delegates.ui.DelegatesGrid);
Ext.namespace('Zarafa.common.delegates.ui');

/**
 * @class Zarafa.common.delegates.ui.DelegatesPanel
 * @extends Ext.Panel
 * @xtype zarafa.delegatespanel
 * Will generate UI for the {@link Zarafa.common.settings.SettingsDelegateWidget SettingsDelegateWidget}.
 */
Zarafa.common.delegates.ui.DelegatesPanel = Ext.extend(Ext.Panel, {
	/**
	 * @cfg {Zarafa.common.delegates.data.DelegateStore} store Delegate store that will be used to load delegates data
	 */
	store: undefined,

	/**
	 * The LoadMask object which will be shown when the {@link #record} is being opened, and
	 * the dialog is waiting for the server to respond with the desired data.
	 * @property
	 * @type Zarafa.common.ui.LoadMask
	 */
	loadMask: undefined,

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

		if(!config.store) {
			config.store = new Zarafa.common.delegates.data.DelegateStore();
		}

		Ext.applyIf(config, {
			// Override from Ext.Component
			xtype: 'zarafa.delegatespanel',
			border: false,
			layout: {
				type: 'vbox',
				align: 'stretch',
				pack: 'start'
			},
			items: this.createPanelItems(config.store)
		});

		Zarafa.common.delegates.ui.DelegatesPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Function will create panel items for {@link Zarafa.common.delegates.ui.DelegatesPanel DelegatesPanel}
	 * @return {Array} array of items that should be added to panel.
	 * @param {Zarafa.common.delegates.data.DelegateStore} store store that will be used to load delegates data.
	 * @private
	 */
	createPanelItems: function(store)
	{
		return [{
			xtype: 'displayfield',
			value: '<b>' + _('Delegates can send items on your behalf.') + '</b> <br>' + _('If you only want to set folder permissions, right-click on a folder to open the context menu and click on "Share folder…"'),
			fieldClass: 'x-form-display-field zarafa-settings-widget-extrainfo'
		}, {
			xtype: 'container',
			flex: 1,
			layout: {
				type: 'hbox',
				align: 'stretch',
				pack: 'start'
			},
			items: [{
				xtype: 'zarafa.delegatesgrid',
				ref: '../delegatesGrid',
				store: store,
				flex: 1
			}, {
				xtype: 'container',
				width: 160,
				layout: {
					type: 'vbox',
					align: 'center',
					pack: 'start'
				},
				items: [{
					xtype: 'button',
					text: _('Add') + '...',
					width: 130,
					handler: this.onDelegateAdd,
					ref: '../../addButton',
					scope: this
				}, {
					xtype: 'spacer',
					height: 10
				}, {
					xtype: 'button',
					text: _('Remove') + '...',
					width: 130,
					disabled: true,
					ref: '../../removeButton',
					handler: this.onDelegateRemove,
					scope: this
				}, {
					xtype: 'spacer',
					height: 10
				}, {
					xtype: 'button',
					text: _('Permission') + '...',
					width: 130,
					disabled: true,
					ref: '../../permissionButton',
					handler: this.onDelegatePermission,
					scope: this
				}]
			}]
		}];
	},

	/**
	 * initialize events for the panel.
	 * @private
	 */
	initEvents: function()
	{
		Zarafa.common.delegates.ui.DelegatesPanel.superclass.initEvents.call(this);

		// register event to open permissions dialog after adding new delegate
		this.mon(this.store, 'add', this.afterDelegateAdd, this);

		// register event to enable/disable buttons
		this.mon(this.delegatesGrid.getSelectionModel(), 'selectionchange', this.onGridSelectionChange, this);
	},

	/**
	 * If {@link #showLoadMask} is enabled, this function will display the {@link #loadMask}.
	 * @protected
	 */
	showLoadMask: function()
	{
		if (!this.loadMask) {
			this.loadMask = new Zarafa.common.ui.LoadMask(this.el);
		}

		this.loadMask.show();
	},

	/**
	 * If {@link #showLoadMask} is enabled, and {@link #showLoadMask} has been
	 * called to display the {@link #loadMask} this function will disable the loadMask.
	 * @protected
	 */
	hideLoadMask: function()
	{
		if (this.loadMask) {
			this.loadMask.hide();
		}
	},

	/**
	 * Handler function will be called when user clicks on 'Add' button,
	 * this will show addressbook dialog to select delegate user.
	 * @private
	 */
	onDelegateAdd: function()
	{
		Zarafa.common.Actions.openABUserSelectionContent({
			callback: this.abCallBack,
			scope: this,
			hideContactsFolders: true,
			listRestriction: {
				hide_users: ['contact', 'system', 'non_security', 'room', 'equipment', 'non_active'],
				hide_groups: ['non_security'],
				hide_companies: true
			}
		});
	},

	/**
	 * Callback function for {@link Zarafa.addressbook.dialogs.ABUserSelectionContentPanel AddressBook}
	 * @param {Ext.data.Record} record user selected from AddressBook
	 * @private
	 */
	abCallBack: function(record)
	{
		record = record.convertToDelegate();

		if(this.store.getById(record.get('entryid'))) {
			return Ext.Msg.alert(_('Duplicate delegate'), _('Delegate already exists in the list.'));
		}

		this.store.add(record);
	},

	/**
	 * Function will be called when a delegate record is added to the store and we want to
	 * open {@link Zarafa.common.delegates.dialogs.DelegatePermissionContentPanel DelegatePermissionContentPanel}
	 * for the currently added delegate.
	 * @param {Zarafa.common.delegates.data.DelegateStore} store store that was used to add delegate user.
	 * @param {Zarafa.common.delegates.data.DelegateRecord[]} record array of records that are added in store.
	 * @param {Number} index index of the store at where records are added in store.
	 */
	afterDelegateAdd: function(store, record, index)
	{
		if(Array.isArray(record)) {
			for(var i = 0, j = record.length; i < j; i++) {
				this.afterDelegateAdd(store, record[i], i);
			}

			return;
		}

		// only open permissions dialog for phantom records
		if(record.phantom) {
			this.openDelegatePermissions(record, true);
		}
	},

	/**
	 * Helper function that will be used when user adds a new {@link Zarafa.common.delegates.data.DelegateRecord DelegateRecord}
	 * so this will check if we need to get folder permissions that is already saved for the current delegate and fill it in delegates record
	 * and then {@link Zarafa.common.delegates.dialogs.DelegatePermissionContentPanel DelegatePermissionContentPanel} can be opened
	 * using the record.
	 * @param {Zarafa.common.delegates.data.DelegateRecord} record delegate record which is added in grid
	 */
	openDelegatePermissions: function(record)
	{
		if(!record.isOpened()) {
			this.openDelegateRecord(record, this.openDelegatePermissions);
			return;
		}

		this.delegatesGrid.openDelegatePermissions(record, { removeRecordOnCancel: true });
	},

	/**
	 * Helper function will get folder permissions already assigned to particular delegate when adding it to the delegates store.
	 * This will also show / hide load mask when requesting more data
	 * @param {Zarafa.common.delegates.data.DelegateRecord} record delegate record for which we need to get existing folder permissions
	 * @param {Ext.Function} callback callback function that will be called after successfully opening the delegate record.
	 */
	openDelegateRecord: function(record, callback)
	{
		// show load mask till we fetch full data from server
		this.showLoadMask();

		// store a reference of record's store which can be used to deregister events in exception handler
		var store = record.getStore();

		var fnOpen = function(recStore, rec) {
			if (record === rec) {
				// remove registered handlers
				store.un('open', fnOpen, this);
				store.un('exception', fnException, this);

				// hide load mask as data has arrived
				this.hideLoadMask();

				// call the callback function
				callback.call(this, record);
			}
		};

		var fnException = function(proxy, type, action, options, response, args) {
			if(action === Ext.data.Api.actions.open) {
				// remove registered handlers, this exception has been fired by Proxy so we will not get store in parameters
				store.un('open', fnOpen, this);
				store.un('exception', fnException, this);

				// actual error message will be shown by common exception handler
				// we will just hide the load mask
				this.hideLoadMask();
			}
		};

		store.on('open', fnOpen, this);
		store.on('exception', fnException, this);

		record.open();
	},

	/**
	 * Event handler will be called when selection in {@link Zarafa.common.delegates.ui.DelegatesGrid DelegatesGrid}
	 * has been changed
	 * @param {Ext.grid.RowSelectionModel} selectionModel selection model that fired the event
	 */
	onGridSelectionChange: function(selectionModel)
	{
		var noSelection = (selectionModel.hasSelection() === false);

		this.removeButton.setDisabled(noSelection);
		this.permissionButton.setDisabled(noSelection);
	},

	/**
	 * Handler function will be called when user clicks on 'Remove' button,
	 * this will remove currently selected delegate from delegates list.
	 * @private
	 */
	onDelegateRemove: function()
	{
		this.delegatesGrid.removeDelegate();
	},

	/**
	 * Handler function will be called when user clicks on 'Permission' button,
	 * this will open {@link Zarafa.common.delegates.dialogs.DelegatePermissionContentPanel DelegatePermissionContentPanel}
	 * to show detailed permissions of delegate user.
	 * @private
	 */
	onDelegatePermission: function()
	{
		this.delegatesGrid.openDelegatePermissions();
	},

	/**
	 * Function will be used to reload data in the {@link Zarafa.common.delegates.data.DelegateStore DelegateStore}.
	 */
	discardChanges: function()
	{
		this.store.load();
	},

	/**
	 * Function will be used to save changes in the {@link Zarafa.common.delegates.data.DelegateStore DelegateStore}.
	 */
	saveChanges: function()
	{
		this.store.save();
	}
});

Ext.reg('zarafa.delegatespanel', Zarafa.common.delegates.ui.DelegatesPanel);
Ext.namespace('Zarafa.common.dialogs');

/**
 * @class Zarafa.common.dialogs.CopyMovePanel
 * @extends Ext.Panel
 * @xtype zarafa.copymovepanel
 *
 * Panel for users to copy or move the given {@link Zarafa.core.data.IPMRecord[] records}
 * to a different {@link Zarafa.hierarchy.data.MAPIFolderRecord folder}.
 */
Zarafa.common.dialogs.CopyMovePanel = Ext.extend(Ext.Panel, {
	/**
	 * @cfg {Zarafa.core.data.IPMRecord} record The record(s) which are being
	 * copied or moved through this panel
	 */
	record: undefined,
	/**
	 * @cfg {Zarafa.core.mapi.ObjectType} objectType The Objecttype of the
	 * {@link #record} which have been set on this panel. This is needed
	 * to determine if we are copy/moving folders or messages.
	 */
	objectType: undefined,

	/**
	 * {Zarafa.mail.MailStore} store or {Zarafa.hierarchy.data.IPFSubStore} store
	 * depending on the objectType.
	 * This store is cached in the panel, because the store of a record is removed
	 * when a user receives new email.
	 */
	store: undefined,

	/**
	 * A boolean that will be used to know if this dialog is focused for the first
	 * time.
	 * @type Boolean
	 * @property
	 * @private
	 */
	firstFocus: true,

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

		if (config.record) {
			if (!Array.isArray(config.record)) {
				config.record = [ config.record ];
				this.store = config.record.getStore();
			} else {
				this.store = config.record[0].getStore();
			}

			if (!config.objectType) {
				config.objectType = config.record[0].get('object_type');
			}
		}

		Ext.applyIf(config, {
			// Override from Ext.Component
			xtype: 'zarafa.copymovepanel',
			layout: {
				type: 'vbox',
				align: 'stretch'
			},
			border: false,
			header: true,
			items: [
				this.createTreePanel(config)
			],
			buttonAlign: 'left',
			buttons: [{
				text: _('Move'),
				handler: this.onClickHandler,
				name: 'move',
				scope: this,
				ref: '../moveButton',
				disabled: true
			},{
				text: _('Copy'),
				name: 'copy',
				handler: this.onClickHandler,
				scope: this,
				ref: '../copyButton',
				disabled: true
			},{
				text: _('New folder'),
				handler: this.onCreateFolder,
				scope: this,
				ref: '../createFolderButton',
				disabled: true
			},
			'->',
			{
				text: _('Cancel'),
				handler: this.onCancel,
				cls: 'zarafa-normal',
				scope: this
			}],
			listeners: {
				render: function() {
					// Add a listener for the focus event of the dialog window
					// to move the focus back to the selected node of the tree.
					var win = this.findParentByType('window');
					if (win && win.focusEl) {
						win.focusEl.on('focus', this.onDialogFocussed, this);
					}

					// Add the keymap
					Zarafa.core.KeyMapMgr.activate(this, 'Zarafa.common.dialogs.CopyMovePanel');
				}
			}
		});

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

	/**
	 * Event handler for the focus event of the dialog window. Will put the focus
	 * on the selected folder.
	 */
	onDialogFocussed: function(){
		var folder = this.dialog.getSelectedFolder();
		var treeNode;
		var visible = true;
		if (folder) {
			if ( this.firstFocus ) {
				// On first focus we will make sure that the selected folder
				// is visible.
				treeNode = this.hierarchyTree.ensureFolderVisible(folder);
			} else {
				treeNode = this.hierarchyTree.getTreeNode(folder);
				// Check if the node is visible by traversing its parents
				// If a parent is not expanded the node is not visible.
				var parent = treeNode.parentNode;
				while ( parent ) {
					if ( !parent.isExpanded() ) {
						visible = false;
						break;
					}
					parent = parent.parentNode;
				}
			}

			if ( treeNode && visible ) {
				// Remember the scroll position, because we don't want to change that
				var scrollTop = this.hierarchyTree.body.dom.scrollTop;

				// Move the focus to the selected folder by simply selecting it again
				treeNode.select();

				if ( !this.firstFocus ) {
					// Set the scroll position back
					this.hierarchyTree.body.dom.scrollTop = scrollTop;
				}
			}
		}

		this.firstFocus = false;
},

	/**
	 * Creates a {@link Zarafa.hierarchy.ui.Tree treepanel}
	 * which contains all the {@link Zarafa.hierarchy.data.MAPIFolderRecord folders}
	 * to which the {@link Zarafa.core.data.IPMRecord records} can be
	 * copied or moved to.
	 * @param {Object} config Configuration structure
	 * @return {Object} Configuration object for the tree panel.
	 * @private
	 */
	createTreePanel: function(config)
	{
		var record = Ext.isDefined(config.record) ? config.record[0] : undefined;
		var permission = config.permissionFilter ? config.permissionFilter : undefined;
		return {
			xtype: 'panel',
			layout: 'vbox',
			border: false,
			flex: 1,
			layoutConfig: {
				align: 'stretch'
			},
			cls: 'copymove-tree-panel',
			bodyStyle: 'background-color: inherit;',
			items: [{
				xtype: 'container',
				ref: 'displayfieldContainer',
				items: [{
					xtype: 'displayfield',
					value: _('Destination folder') + ':',
					hideLabel: true,
					cls: 'tree-header',
					ref: '../displayfield'
				}],
				autoheight: true
			},{
				xtype: 'zarafa.hierarchytree',
				flex: 1,
				border: true,
				treeSorter: true,
				hideTodoList: true,
				hideSearchFolders: true,
				enableDD: false,
				permissionFilter: permission,
				IPMFilter: this.getIPMFilter(record),
				anchor: '100% 90%',
				ref: '../hierarchyTree'
			}],
			listeners: {
				// autoheight does not work well for the vbox, so we set the height of the container manually
				// This way we can use css to determine the height
				'afterlayout': function(){
					this.displayfieldContainer.setHeight(this.displayfield.getHeight());
				}
			}
		};
	},

	/**
	 * Allows any sub class to provide IPMFilter value if requires.
	 */
	getIPMFilter: Ext.emptyFn,

	/**
	 * Function which is called automatically by ExtJs when the {@link Ext.Panel panel}
	 * is being rendered. This will add the event handler for selection changes, and
	 * will load the hierarchy model.
	 * @param {Ext.Container} ct The parent container for this panel
	 * @param {Number} position The position of this panel inside its parent
	 * @private
	 */
	onRender: function(ct, position)
	{
		Zarafa.common.dialogs.CopyMovePanel.superclass.onRender.call(this, ct, position);

		if (this.objectType == Zarafa.core.mapi.ObjectType.MAPI_MESSAGE) {
			this.setTitle(String.format(ngettext('There is {0} message selected.', 'There are {0} messages selected.', this.record.length), this.record.length));
		} else if (this.objectType == Zarafa.core.mapi.ObjectType.MAPI_FOLDER) {
			this.setTitle(String.format(_('Folder \'{0}\' selected.'), Ext.util.Format.htmlEncode(this.record[0].getDisplayName())));
		}
	},

	/**
	 * Initialize the event handlers
	 * @protected
	 */
	initEvents: function()
	{
		Zarafa.common.dialogs.CopyMovePanel.superclass.initEvents.apply(this, arguments);

		// If there is a folder we should select, the enable the 'load' event handler
		// as we will have to wait until the correct node has been loaded.
		var folder = this.dialog.getSelectedFolder();
		if (folder) {
			this.mon(this.hierarchyTree, 'load', this.onTreeNodeLoad, this);
		}
		this.mon(this.hierarchyTree.getSelectionModel(), 'selectionchange', this.onSelectionChange, this);
	},

	/**
	 * Fired when the {@link Zarafa.hierarchy.ui.Tree Tree} fires the {@link Zarafa.hierarchy.ui.Tree#load load}
	 * event. This function will try to select the {@link Ext.tree.TreeNode TreeNode} in
	 * {@link Zarafa.hierarchy.ui.Tree Tree} initially. When the given node is not loaded yet, it will try again
	 * later when the event is fired again.
	 *
	 * @private
	 */
	onTreeNodeLoad: function() {
		// Select folder in hierarchy tree.
		var folder = this.dialog.getSelectedFolder();

		// If the folder could be selected, then unregister the event handler.
		if (this.hierarchyTree.selectFolderInTree(folder)) {
			this.mun(this.hierarchyTree, 'load', this.onTreeNodeLoad, this);
		}
	},

	/**
	 * Event handler which is triggered when the user select a {@link Zarafa.hierarchy.data.MAPIFolderRecord folder}
	 * from the {@link Zarafa.hierarchy.ui.Tree tree}. This will determine if a valid
	 * {@link Zarafa.hierarchy.data.MAPIFolderRecord folder} is selected to which the {@link Zarafa.core.data.IPMRecord records}
	 * can indeed be copied or moved to.
	 * @param {DefaultSelectionModel} selectionModel The selectionModel for the treepanel
	 * @param {TreeNode} node The selected tree node
	 * @private
	 */
	onSelectionChange: function(selectionModel, node)
	{
		if (!Ext.isDefined(node) || (node.getFolder().isIPMSubTree() && this.objectType == Zarafa.core.mapi.ObjectType.MAPI_MESSAGE)) {
			this.copyButton.disable();
			this.moveButton.disable();
			this.createFolderButton.disable();
			return;
		}

		this.createFolderButton.enable();

		if (!this.isContainerClassCompatible(node.getFolder())) {
			this.copyButton.disable();
			this.moveButton.disable();
			return;
		}

		this.copyButton.enable();
		this.moveButton.enable();
	},

	/**
	 * Check whether the {@link Zarafa.core.data.IPMRecord records} being copied or moved
	 * are compatible with the {@link Zarafa.hierarchy.data.MAPIFolderRecord#container_class container_class}
	 * of the destination folder. For example, a mail (IPM.Note) must not be moved into an
	 * IPF.Contact folder because the resulting item is unusable.
	 *
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder The target folder
	 * @return {Boolean} True when every record's message_class is compatible with the
	 * folder's container_class, false otherwise.
	 * @private
	 */
	isContainerClassCompatible: function(folder)
	{
		if (this.objectType !== Zarafa.core.mapi.ObjectType.MAPI_MESSAGE) {
			return true;
		}

		var containerClass = folder.get('container_class');
		if (Ext.isEmpty(containerClass)) {
			return true;
		}

		var records = this.record || [];
		for (var i = 0, len = records.length; i < len; i++) {
			var record = records[i];
			if (!record) {
				continue;
			}

			var messageClass = Ext.isFunction(record.get) ? record.get('message_class') : record.message_class;
			if (Ext.isEmpty(messageClass)) {
				continue;
			}

			if (!Zarafa.core.MessageClass.isContainerClassCompatible(messageClass, containerClass)) {
				return false;
			}
		}

		return true;
	},

	/**
	 * Event handler which is triggered when the user presses the Create new folder {@link Ext.Button button}.
	 * This will call {@link Zarafa.hierarchy.actions.openCreateFolderContent} with the selected {@link Zarafa.hierarchy.data.MAPIFolderRecord folder}.
	 * @private
	 */
	onCreateFolder: function()
	{
		var folder = this.hierarchyTree.getSelectionModel().getSelectedNode().getFolder();
		Zarafa.hierarchy.Actions.openCreateFolderContent(folder);
		this.mon(this.hierarchyTree, 'append', this.onTreeAppend, this, {delay: 10});
	},

	/**
	 * Event handler which is triggered when a new folder is appended to the tree and selects the newly created folder
	 * @param {Zarafa.hierarchy.ui.Tree} tree the folder tree
	 * @param {Ext.data.Node} parent the parent of the newly created node
	 * @param {Ext.data.Node} node the appended node
	 * @private
	 */
	onTreeAppend: function(tree, parent, node)
	{
		// Sometimes the 'append' is fired but the node is not rendered yet,so add a delay of 10 ms.
		if (!node.parentNode) {
			// The node is probably removed and appended again, so let's find the
			// correct node in the tree again
			node = tree.getNodeById(node.id);
		}
		tree.selectPath(node.getPath());
		this.mun(this.hierarchyTree, 'append', this.onTreeAppend, this);
	},

	/**
	 * Event handler which is triggered when the user presses the Copy or Move
	 * {@link Ext.Button button}. It will check necessary {@link Zarafa.core.mapi.Rights Rights} and
	 * {@link Zarafa.core.mapi.Access Access} of folder.
	 *
	 * @param {Ext.Button} item The button which is clicked.
	 * @private
	 */
	onClickHandler: function(item)
	{
		var isMoveAction = item.name === 'move';

		var folder = this.hierarchyTree.getSelectionModel().getSelectedNode().getFolder();
		var records = this.record;

		if (!Ext.isDefined(folder)) {
			return;
		}

		if (Ext.isEmpty(this.record)) {
			return;
		}

		if (!this.isContainerClassCompatible(folder)) {
			var msg = isMoveAction ?
				_("The selected items cannot be moved into this folder because it holds a different type of content.") :
				_("The selected items cannot be copied into this folder because it holds a different type of content.");
			container.getNotifier().notify('error', _("Incompatible folder"), msg);
			return false;
		}

		records = Zarafa.common.Actions.resolveRecords(records, this.store);

		if (Ext.isEmpty(records)) {
			container.getNotifier().notify('error', _('Unable to perform action'), _('The selected items are no longer available.'));
			return;
		}

		this.record = records;

		if (records[0] instanceof Zarafa.core.data.IPFRecord) {
			var access = folder.get('access') & Zarafa.core.mapi.Access.ACCESS_CREATE_HIERARCHY;
			if (!access) {
				var msg = isMoveAction ? _("You have insufficient privileges to move this folder. Ask the folder owner to grant you permissions or contact your system administrator.") :
					_("You have insufficient privileges to copy this folder. Ask the folder owner to grant you permissions or contact your system administrator.");
				container.getNotifier().notify('error', _("Insufficient privileges"), msg);
				return false;
			}
		} else if (!folder.hasCreateRights()) {
			// Check folder has create item rights.
			var msg = isMoveAction ? _("You have insufficient privileges to move and copy this item. Ask the folder owner to grant you permissions or contact your system administrator.") :
					_("You have insufficient privileges to copy this item. Ask the folder owner to grant you permissions or contact your system administrator.");
			container.getNotifier().notify('error', _("Insufficient privileges"), msg);
			return false;
		}

		if (isMoveAction) {
			this.onMove(records, folder);
		} else {
			this.onCopy(records, folder);
		}

	},

	/**
	 * Function will copy all {@link Zarafa.core.data.IPMRecord records}
	 * and will close the {@link Zarafa.common.dialogs.CopyMovePanel dialog} when it is done.
	 * @private
	 */
	onCopy: function(records, folder)
	{
		var isCalendarFolder = folder.isCalendarFolder();
		var showPrivateWarning = false;
		records = Zarafa.common.Actions.resolveRecords(records, this.store);

		if (Ext.isEmpty(records)) {
			return;
		}

		if (!this.isContainerClassCompatible(folder)) {
			container.getNotifier().notify('error', _("Incompatible folder"),
				_("The selected items cannot be copied into this folder because it holds a different type of content."));
			return;
		}

		Ext.each(records, function(record) {
			if (!record) {
				return;
			}

			// When we have this panel open and we receive a new email, the records store is
			// not accessible anymore, so we need to get a new record by the entryid of the old record.
			if (this.objectType === Zarafa.core.mapi.ObjectType.MAPI_MESSAGE) {
				if (isCalendarFolder && Ext.isFunction(record.isPrivate) && record.isPrivate()) {
					record.unsetPrivate();
					showPrivateWarning = true;
				}	
			}

			if (Ext.isFunction(record.copyTo)) {
				record.copyTo(folder);
			}
		}, this);

		if (showPrivateWarning) {
			var msg = _("Copying this appointment will unset it as private. Are you sure you want to continue?");
			Zarafa.common.Actions.showMessageBox(records, folder, this.store, msg, this, undefined, {
				title: _('Private item'),
				actionBtn : _('Copy')
			});
			return;
		}

		this.dialog.selectFolder(folder);

		this.store.save(records);

		this.dialog.close();
	},

	/**
	 * Function will move all {@link Zarafa.core.data.IPMRecord records}
	 * and will close the {@link Zarafa.common.dialogs.CopyMovePanel dialog} when it is done.
	 *
	 * @param {Zarafa.core.data.IPMRecord[]} records The array of {@link Zarafa.core.data.IPMRecord records} which are
	 * going to move the selected {@link Zarafa.core.data.IPFRecord folder}.
	 * @param {Zarafa.core.data.IPFRecord} folder The {@link Zarafa.core.data.IPFRecord folder } in which selected
	 * {@link Zarafa.core.data.IPMRecord records} are going to move.
	 * @private
	 */
	onMove: function(records, folder)
	{
		var isCalendarFolder = folder.isCalendarFolder();
		var showPrivateWaring = false;
		records = Zarafa.common.Actions.resolveRecords(records, this.store);

		if (Ext.isEmpty(records)) {
			return;
		}

		if (!this.isContainerClassCompatible(folder)) {
			container.getNotifier().notify('error', _("Incompatible folder"),
				_("The selected items cannot be moved into this folder because it holds a different type of content."));
			return;
		}

		var sourceFolder = container.getHierarchyStore().getFolder(records[0].get('parent_entryid'));
		var sourceStoreEntryId = sourceFolder ? sourceFolder.get('store_entryid') : undefined;
		// If targetFolder has create item rights and source folder does not have delete item rights,
		// in that case move operation is not possible, therefore show message box which indicate that
		// move operation is not possible and ask user to copy the item.
		if (folder.hasCreateRights() && !sourceFolder.hasDeleteOwnRights()) {
			Zarafa.common.Actions.showMessageBox(records, folder, this.store,undefined, this);
			return false;
		}

		var requireDeleteCheck = this.objectType !== Zarafa.core.mapi.ObjectType.MAPI_MESSAGE && !sourceFolder.hasDeleteOwnRights();
		var noAccessRecord = [];
		var moveRecords = [];
		Ext.each(records, function(record, index) {
			if (!record) {
				return;
			}

			if (sourceStoreEntryId) {
				if (Ext.isFunction(record.get)) {
					var currentStoreEntryId = record.get('store_entryid');
					if (Ext.isEmpty(currentStoreEntryId) && record.data) {
						record.data.store_entryid = sourceStoreEntryId;
					}
				} else if (Ext.isEmpty(record.store_entryid)) {
					record.store_entryid = sourceStoreEntryId;
				}
			}

			if (Ext.isFunction(record.get)) {
				var accessValue = record.get('access');
				if (!Ext.isNumber(accessValue) && record.data) {
					record.data.access = (record.data.access || 0) | Zarafa.core.mapi.Access.ACCESS_DELETE;
				}
			} else if (!Ext.isNumber(record.access)) {
				record.access = Zarafa.core.mapi.Access.ACCESS_DELETE;
			}

			if (!requireDeleteCheck) {
				moveRecords.push(record);
				return;
			}

			// When we have this panel open and we receive a new email, the records store is
			// not accessible anymore, so we need to get a new record by the entryid of the old record.
			if(this.objectType === Zarafa.core.mapi.ObjectType.MAPI_MESSAGE) {
				if (isCalendarFolder && Ext.isFunction(record.isPrivate) && record.isPrivate()) {
					record.unsetPrivate();
					showPrivateWaring = true;
				}
			}

			// Check record access. If record has no delete access (record not belongs to user)
			// user can't move this item.
			var hasDeleteAccess = record.hasDeleteAccess();
			if (!hasDeleteAccess && sourceFolder) {
				var parentEntryId = Ext.isFunction(record.get) ? record.get('parent_entryid') : record.parent_entryid;
				var sourceEntryId = sourceFolder.get('entryid');
				if (parentEntryId && sourceEntryId && Zarafa.core.EntryId.compareEntryIds(parentEntryId, sourceEntryId)) {
					hasDeleteAccess = true;
				}
			}
			if (!hasDeleteAccess) {
				if (sourceFolder && sourceFolder.hasDeleteOwnRights() && sourceStoreEntryId) {
					var recordStoreEntryId = Ext.isFunction(record.get) ? record.get('store_entryid') : record.store_entryid;
					if (recordStoreEntryId && Zarafa.core.EntryId.compareStoreEntryIds(sourceStoreEntryId, recordStoreEntryId)) {
						hasDeleteAccess = true;
					}
				}

				var defaultStore = container.getHierarchyStore().getDefaultStore();
				var recordStoreEntryId = Ext.isFunction(record.get) ? record.get('store_entryid') : record.store_entryid;
				if (defaultStore && recordStoreEntryId) {
					var defaultStoreEntryId = defaultStore.get('store_entryid');
					if (defaultStoreEntryId && Zarafa.core.EntryId.compareStoreEntryIds(defaultStoreEntryId, recordStoreEntryId)) {
						hasDeleteAccess = true;
					}
				}
			}
			if (!hasDeleteAccess) {
				var userOwnsStore = Ext.isFunction(record.userIsStoreOwner) && record.userIsStoreOwner();
				if (!userOwnsStore) {
					var defaultStore = container.getHierarchyStore().getDefaultStore();
					if (defaultStore) {
						var defaultStoreEntryId = defaultStore.get('store_entryid');
						var recordStoreEntryId = Ext.isFunction(record.get) ? record.get('store_entryid') : record.store_entryid;
						if (defaultStoreEntryId && recordStoreEntryId && Zarafa.core.EntryId.compareStoreEntryIds(defaultStoreEntryId, recordStoreEntryId)) {
							userOwnsStore = true;
						}
					}
				}
				var canDeleteAny = sourceFolder && (sourceFolder.get('rights') & Zarafa.core.mapi.Rights.RIGHTS_DELETE_ANY) > 0;
				if (userOwnsStore || canDeleteAny) {
					hasDeleteAccess = true;
				}
			}

			if (!hasDeleteAccess) {
				noAccessRecord.push({
					record: record,
					index:index
				});
			} else {
				moveRecords.push(record);
			}
		}, this);

		if (requireDeleteCheck && !Ext.isEmpty(noAccessRecord)) {
			if (noAccessRecord.length === records.length && sourceFolder && sourceFolder.hasDeleteOwnRights()) {
				noAccessRecord.forEach(function(item) {
					moveRecords.push(item.record);
				});
				noAccessRecord = [];
			}
		}

		if (!Ext.isEmpty(moveRecords)) {
			Zarafa.common.Actions.ensureStoreReloadOnEmpty(this.store);
			Ext.each(moveRecords, function(record) {
				record.moveTo(folder);
			});
		}

		// Show detailed warning message when record have no access to delete
		// ask user to copy that records.
		if (requireDeleteCheck && !Ext.isEmpty(noAccessRecord)) {
			var msg = undefined;
			if (noAccessRecord.length > 1) {
				msg = _("You have insufficient privileges to move following items.");
				msg += "<br/><br/>";
					noAccessRecord.forEach(function (item) {
						var subject = item.record.get('subject');
						subject = !Ext.isEmpty(subject) ? subject : _("None");
						msg += "<b>" +_("Subject:") + "</b> " + subject ;
						msg += "<br/>";
					}, this);
				msg += "<br/>" + _("Would you like to copy instead?");
			}
			var noAccessRecords = Ext.pluck(noAccessRecord, "record");

			Zarafa.common.Actions.showMessageBox(noAccessRecords, folder, this.store, msg, this);
			return;
		}

		// Show warning message when user try to move private calendar items.
		if (showPrivateWaring) {
			var msg = _("Moving this appointment will unset it as private. Are you sure you want to continue?");
			Zarafa.common.Actions.showMessageBox(moveRecords.length ? moveRecords : records, folder, this.store, msg, this, undefined, {
				title: _('Private item'),
				actionBtn : _('Move')
			});
			return;
		}

		this.dialog.selectFolder(folder);

		if(!Ext.isEmpty(moveRecords)) {
			this.store.save(moveRecords);
		}

		this.dialog.close();
	},

	/**
	 * Event handler which is triggered when the user presses the cancel
	 * {@link Ext.Button button}. This will close the {@link Zarafa.common.dialogs.CopyMovePanel dialog}
	 * without copying or moving any {@link Zarafa.core.data.IPMRecord records}.
	 * @private
	 */
	onCancel: function()
	{
		this.dialog.close();
	}
});

Ext.reg('zarafa.copymovepanel', Zarafa.common.dialogs.CopyMovePanel);
Ext.namespace('Zarafa.common.dialogs');

/**
 * @class Zarafa.common.dialogs.CustomMessageBox
 * @extends Ext.Window
 *
 * Custom message box display message box with given message.
 * It will offers create message box with custom elements and custom buttons.
 */
Zarafa.common.dialogs.CustomMessageBox = Ext.extend(Ext.Window, {

	/**
	 * List of {@link Ext.Element} which will append into body of message box.
	 * @type Array
	 */
	customItems: [],

	/**
	 * List of {@link Ext.Button} which will append into footer bar of message box.
	 * @type Array
	 */
	customButtons: [],

	/**
	 * The maximum width in pixels of the message box (defaults to 600)
	 * @type Number
	 */
	maxWidth: 600,
	/**
	 * The minimum width in pixels of the message box (defaults to 100)
	 * @type Number
	 */
	minWidth: 320,

	/**
	 * A callback function which is called when the dialog is dismissed either by clicking on the configured buttons,
	 * or on the dialog close button, or by pressing the return button to enter input.
	 * @type Function
	 */
	fn: Ext.emptyFn,

	/**
	 * A CSS class that provides a background image to be used as the body icon for the dialog.
	 *@type String
	 */
	icon: '',

	/**
	 * A string that will replace the existing message box body text
	 * @type String
	 */
	msg: '&#160;',

	/**
	 * Body element of the dialog.
	 * @type Ext.Element
	 */
	bodyEl: undefined,

	/**
	 * Body icon for the dialog.
	 * @type Ext.Element
	 */
	iconEl: undefined,

	/**
	 * Body message element of the dialog.
	 * @type Ext.Element
	 */
	msgEl: undefined,

	/**
	 * @constructor
	 * @param {Object} config Configuration structure
	 */
	constructor: function (config)
	{
		config = config || {};
		Ext.each(config.customButtons, function (btn) {
			Ext.applyIf(btn, {
				handler: this.handleButton,
				hideMode: 'offsets',
				scope: this
			});
		}, this);

		Ext.applyIf(config, {
			xtype: 'panel',
			modal: true,
			shim: true,
			resizable: false,
			constrain: true,
			constrainHeader: true,
			minimizable: false,
			maximizable: false,
			stateful: false,
			buttonAlign: 'center',
			autoHeight: true,
			fbar: config.customButtons,
			listeners: {
				beforeshow: this.onBeforeShow,
				scope: this
			}
		});

		Zarafa.common.dialogs.CustomMessageBox.superclass.constructor.call(this, config);
		this.show();
	},

	/**
	 * Handler which is fires before the component is shown by calling the show method.
	 * It will create child elements of message body and initialize value of the child elements.
	 */
	onBeforeShow: function ()
	{
		this.bodyEl = this.body.createChild({
			html:'<div class="ext-mb-icon" aria-hidden="true"></div><div class="ext-mb-content"><span class="ext-mb-text"></span></div>'
		});
		this.iconEl = Ext.get(this.bodyEl.dom.firstChild);
		var contentEl = this.bodyEl.dom.childNodes[1];
		this.msgEl = Ext.get(contentEl.firstChild);
		this.addClass('x-window-dlg');
		this.initDialog([{
			xtype: 'container',
			anchor: '100% 100%',
			items: [this.customItems]
		}]);
		this.setIcon();
		this.updateText();

		// Set default button which has 'zarafa-action' class.
		var defaultButton = this.buttons.find(function (btn) {
			if (btn.getEl()) {
				return btn.getEl().hasClass('zarafa-action');
			}
		});
		Ext.applyIf(this, {
			defaultButton: defaultButton
		});
	},


	/**
	 * Initialize the {@link Zarafa.common.dialogs.CustomMessageBox Dialog} with {#customItems}.
	 * @param {Array} items The array of items which must be added to the Custom MessageBox.
	 * @private
	 */
	initDialog: function (items)
	{
		// Add a container to be able to position the items after a possible msg text
		if (!this.dlgItemContainer) {
			this.dlgItemContainer = this.dlgItemContainer || new Ext.Container({
					border: false
				});
			this.dlgItemContainer.render(this.body);
		}

		// Before showing the dialog, we must first
		// add all items to the dialog.
		this.on('show', function () {
			this.dlgItemContainer.add(items);
			this.dlgItemContainer.doLayout();
		}, this, {single: true});
	},

	/**
	 * Updates the custom message box body text with {#msg} and
	 * resize the dialog width as per content of message.
	 */
	updateText: function ()
	{
		this.msgEl.update(this.msg);
		var iw = this.iconCls != '' ? (this.iconEl.getWidth() + this.iconEl.getMargins('lr')) : 0,
			mw = this.msgEl.getWidth() + this.msgEl.getMargins('lr'),
			fw = this.getFrameWidth('lr'),
			bw = this.body.getFrameWidth('lr'),
			w;

		// Width of {@link #msgEl message element} including frame width, {@link #iconEl icon width} and
		// it should be smaller than {@link #maxWidth}.
		var msgElWidth = Math.min(this.width || iw + mw + fw + bw, this.maxWidth);

		// Width of footer toolbar, It is considering width of all toolbar buttons.
		var fbarWidth = 0;
		this.buttons.forEach(function (button) {
			fbarWidth += button.getResizeEl() ? button.getWidth() : button.minWidth;
		});

		w = Math.max(msgElWidth, this.minWidth, fbarWidth);

		this.msgEl.update(this.msg || '&#160;');
		this.setSize(w, 'auto').center();
	},

	/**
	 * Adds {#icon} class into the {#iconEl} icon element of the dialog.
	 * If icon is not available then set hidden class for the {#iconEl} icon element
	 */
	setIcon: function ()
	{
		if (!Ext.isEmpty(this.icon)) {
			this.iconEl.addClass(this.icon);
			this.bodyEl.addClass('x-dlg-icon');
		} else {
			this.iconEl.addClass('x-hidden');
			this.bodyEl.removeClass('x-dlg-icon');
		}
	},

	/**
	 * Handler for click event of all {#customButtons buttons}
	 * It will call callback of button and close the custom message box.
	 * @param {Ext.Button} button button which is clicked.
	 */
	handleButton: function (button)
	{
		if (!button.keepOpenWindow) {
			this.close();
		}
		Ext.callback(this.fn, this, [button]);
	}
});
Ext.namespace('Zarafa.common.dialogs');

/**
 * @class Zarafa.common.dialogs.MessageBox
 * @extends Ext.MessageBox
 *
 * Extension to the default {@link Ext.MessageBox MessageBox} which
 * offers some special features like displaying a selection list.
 */
// Use Ext.apply instead of Ext.extend because Ext.MessageBox is not
// a class but an instantiated object. The Ext.apply({}, a, b) trick
// is used to create a full copy of Ext.MessageBox instead of changing
// the existing object.
Zarafa.common.dialogs.MessageBox = Ext.apply({}, {
	/**
	 * The custom buttons which are added in {@link Ext.MessageBox messagebox}.
	 * it will be removed when {@link Ext.MessageBox messagebox} gets hide.
	 *
	 * @property
	 * @type Array
	 * @private
	 */
	customButton: undefined,

	/**
	 * Initialize the {@link Ext.MessageBox.dlg Dialog}.
	 * Because the {@link Ext.MessageBox MessageBox} hides the usable
	 * interface from use, we must apply a hack to access the Dialog
	 * before it is displayed to the user.
	 *
	 * This function will add a list of {@link Ext.Component Components}
	 * to the dialog, which can be used to fine-tune the look&feel.
	 *
	 * @param {Array} items The array of items which must be added to the
	 * MessageBox.
	 * @private
	 */
	initDialog: function(items)
	{
		var dlg = this.getDialog();

		// Add a container to be able to position the items after a possible msg text
		if ( !this.dlgItemContainer ){
			this.dlgItemContainer = this.dlgItemContainer || new Ext.Container({
				border: false,
				cls: 'my-cont'
			});
			this.dlgItemContainer.render(dlg.body);
		}

		// Automatically remove all items which we had added.
		// This makes sure we can use the same Dialog multiple times
		dlg.on('hide', function(dlg) {
			this.dlgItemContainer.removeAll();
		}, this, {single: true});

		// In case the 'hide' event was not fired,
		// we also listen to the destroy event as fallback.
		dlg.on('destroy', function(dlg) {
			this.dlgItemContainer.removeAll();
		}, this, {single: true});

		// Before showing the dialog, we must first
		// add all items to the dialog.
		dlg.on('show', function(dlg) {
			this.dlgItemContainer.add(items);
			this.dlgItemContainer.doLayout();
		}, this, {single: true});
	},

  /**
   * Extension of {@link Ext.MessageBox.show} that adds the following configuration options:<ul>
   * <li><b>checkbox</b>: Boolean<div class="sub-desc">When set to true a checkbox will be added to the bottom
   * of the MessageBox.</div></li>
   * <li><b>checkboxLabel</b>: String<div class="sub-desc">The label of the checkbox that will be added when
   * the checkbox config option is set to true. (defaults to 'Do not show this message again')</div></li>
   * <li><b>fn</b>: Function<div class="sub-desc">A callback function which is called when the dialog is dismissed either
   * by clicking on the configured buttons, or on the dialog close button, or by pressing
   * the return button to enter input.
   * <p>Progress and wait dialogs will ignore this option since they do not respond to user
   * actions and can only be closed programmatically, so any required function should be called
   * by the same code after it closes the dialog. Parameters passed:<ul>
   * <li><b>buttonId</b>: String<div class="sub-desc">The ID of the button pressed, one of:<div class="sub-desc"><ul>
   * <li><tt>ok</tt></li>
   * <li><tt>yes</tt></li>
   * <li><tt>no</tt></li>
   * <li><tt>cancel</tt></li>
   * </ul></div></div></li>
   * <li><b>text</b>: String<div class="sub-desc">Value of the input field if either <tt><a href="#show-option-prompt" ext:member="show-option-prompt" ext:cls="Ext.MessageBox">prompt</a></tt>
   * or <tt><a href="#show-option-multiline" ext:member="show-option-multiline" ext:cls="Ext.MessageBox">multiline</a></tt> is true</div></li>
   * <li><b>checked</b>: Boolean<div class="sub-desc">Value of the checkbox field. (Will only be passed when the
   * checkbox option was set to true in the config object)</div></li>
   * <li><b>opt</b>: Object<div class="sub-desc">The config object passed to show.</div></li>
   * </ul></p></div></li>
   * </ul>
   *
   * @param {Object} config The configuration options of the the MessageBox. See above and {@link Ext.MessageBox.show}
   * for more information.
   * @return {Zarafa.common.dialogs.MessageBox} this
   */
	show: function(config)
	{
		config.title = config.title || _('grommunio Web');

		if ( config.checkbox !== true ){
			return Ext.MessageBox.show.call(this, config);
		}

		// If no checkboxState was set we will set it to false (=unchecked)
		var checkboxState = !!config.checkboxState;

		var cb = {
			xtype: 'container',
			cls: 'k-cb-dontshowagain',
			items: [{
				xtype: 'checkbox',
				ctCls: 'k-cb-dontshowagain',
				boxLabel: config.checkboxLabel || _('Do not show this message again.'),
				checked: checkboxState,
				listeners: {
					check: function(cb, checked){
						checkboxState = checked;
					}
				},
				scope: this
			}]
		};

		this.initDialog([cb]);

		// Make sure the checkbox state is also passed to the callback function
		if ( Ext.isFunction(config.fn) ){
			var origFn = config.fn;
			config.fn = function(buttonid, text, opts){
				origFn.call(this, buttonid, text, checkboxState, opts);
			}.bind(config.scope || null);
		}

		return Ext.MessageBox.show.call(this, config);
	},

	/**
	 * Displays a message box with OK and Cancel buttons prompting the user to make a selection
	 * from a list of {@link Ext.form.Radio radio} buttons.
	 * If a callback function is passed it will be called after the user
	 * clicks either button, and the id of the button that was clicked (could also be the top-right
	 * close button) and the selected {@link Ext.form.Radio radio} as the two parameters to the callback.
	 * @param {String} title The title bar text
	 * @param {String} msg The message box body text
	 * @param {Function} fn (optional) The callback function invoked after the message box is closed
	 * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the callback is executed. Defaults to the browser window.
	 * @param {Array} selections (optional) Array of configuration objects for the {@link Ext.form.Radio radios} from which the user can select.
	 * @param {String} value (optional) Default value for the {@link Ext.form.RadioGroup radiogroup}.
	 * @param {String} customButton (optional) buttons which need to show in message box. this buttons contains custom name's.
	 * @return {Zarafa.common.dialogs.RadioMessageBox} this
	 */
	select: function(title, msg, fn, scope, selections, value, customButton)
	{
		var radioGroup = Ext.create({
			xtype: 'radiogroup',
			hideLabel: true,
			style: 'padding-left: 50px;',
			columns: 1,
			items: selections,
			value: value,
			listeners: {
				change: this.onChange,
				scope: this
			}
		});
		this.initDialog([{
			xtype: 'container',
			anchor: '100% 100%',
			items: [ radioGroup ]
		}]);

		if(!Ext.isEmpty(customButton)) {
			var config = {};
			Ext.apply(config, {
				title: title,
				msg: msg + '<br />',
				minWidth: this.minPromptWidth,
				maxWidth: 250,
				scope: scope,
				fn: fn,
				prompt: false,
				value: value,
				customButton: customButton,
				radioGroup: radioGroup
			});
			this.addCustomButtons(config);
		} else {
			this.show({
				title: title,
				msg: msg + '<br />',
				buttons: Ext.MessageBox.OKCANCEL,
				fn: function(button) {
					Ext.callback(fn, scope || window, [button, radioGroup.getValue()], 1);
				},
				minWidth: this.minPromptWidth,
				maxWidth: 250,
				scope: scope,
				prompt: false,
				value: value
			});
		}
		return this;
	},

	/**
	 * Event handler triggered when radio button of {@link #select} message box
	 * gets changed.
	 *
	 * @param {Ext.form.RadioGroup} radioGroup the radioGroup which triggers this event.
	 * @param {Ext.form.Radio} radio the radio which listen the event.
	 */
	onChange: function (radioGroup, radio) {
		if(radio.hideButtonText) {
			var fbar = this.getDialog().getFooterToolbar();
			var btn = fbar.find('name', radio.hideButtonText)[0];
			if(Ext.isDefined(btn)) {
				btn.setText(Ext.util.Format.capitalize(radio.showButtonText));
				btn.name = radio.showButtonText;
			}
		}
	},

	/**
	 * Display {@link Ext.MessageBox messagebox} with custom buttons.
	 * @param {Object} config The config contains the configuration
	 * options of message box as wall as custom buttons.
	 */
	addCustomButtons: function(config)
	{
		var dlg = this.getDialog();
		this.customButton = dlg.getFooterToolbar().add(config.customButton);
		this.show(config);

		if(!Ext.isEmpty(this.customButton)) {
			Ext.each(this.customButton, function(button) {
				var args = [];
				args.push(config.fn);
				if(Ext.isDefined(config.radioGroup)) {
					args.push(config.radioGroup);
				}
				dlg.mon(button, 'click', Ext.createDelegate(this.onButtonClick, config.scope, args, true), this);
			}, this);
		}

		dlg.on('hide', this.onDestroy, this, {single: true});
		dlg.on('destroy', this.onDestroy, this, {single: true});
	},

	/**
	 * Event handler triggered when custom button is clicked.
	 * it will hide the {@link Ext.MessageBox messagebox}.
	 * @param {Ext.Button} button The button which user pressed.
	 * @param {Ext.EventObject} event the event object
	 * @param {Function} callback The callback function to call when button is pressed.
	 * @param {Ext.form.RadioGroup} radioGroup The radioGroup contains if message box triggered from {@link #select} message box.
	 */
	onButtonClick: function(button, event, callback, radioGroup)
	{
		var buttonName = button.name || 'cancel';
		Zarafa.common.dialogs.MessageBox.hide();
		if(radioGroup) {
			callback.call(this, buttonName, radioGroup.getValue());
		} else {
			callback.call(this, buttonName);
		}
	},

	/**
	 * Event handler which is triggered when {@link Ext.MessageBox messagebox} gets hide.
	 * also it will remove all custom buttons from message box.
	 * @param {Ext.Window} dlg The window
	 */
	onDestroy: function(dlg)
	{
		if(!Ext.isEmpty(this.customButton)) {
			for(var i = 0; i < this.customButton.length; i++) {
				dlg.getFooterToolbar().remove(this.customButton[i]);
			}
			this.customButton = [];
		}
	}
}, Ext.MessageBox);
Ext.namespace('Zarafa.common.dialogs');

/**
 * Ensure that the record has the formatted Object ID available.
 * @param {Zarafa.core.data.IPMRecord} record
 */
Zarafa.common.dialogs.ensureRecordObjectId = function(record)
{
	if (!record) {
		return;
	}

	var objectIdText = record.get('x_midtext');
	if (!objectIdText) {
		objectIdText = Zarafa.core.EntryId.formatObjectId(record.get('entryid'));
		if (objectIdText && record.get('x_midtext') !== objectIdText) {
			record.set('x_midtext', objectIdText);
		}
	}
};

/**
 * @class Zarafa.common.dialogs.RecordPropertiesPanel
 * @extends Ext.form.FormPanel
 * @xtype zarafa.recordpropertiespanel
 *
 * Generic panel for showing the Object ID of a record.
 */
Zarafa.common.dialogs.RecordPropertiesPanel = Ext.extend(Ext.form.FormPanel, {

	/**
	 * @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, {
			xtype: 'zarafa.recordpropertiespanel',
			title: _('Properties'),
			layout: 'form',
			border: false,
			items: [{
				xtype: 'textfield',
				fieldLabel: _('Object ID'),
				name: 'x_midtext',
				readOnly: true,
				anchor: '100%',
				autoScroll: true,
				border: false,
				ref: 'objectIdField'
			}]
		});

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

	/**
	 * Update the panel with the given record.
	 * @param {Zarafa.core.data.IPMRecord} record
	 */
	update: function(record)
	{
		this.record = record;

		if (record) {
			Zarafa.common.dialogs.ensureRecordObjectId(record);
			this.getForm().loadRecord(record);
		} else if (this.objectIdField) {
			this.objectIdField.setValue('');
		}
	},

	/**
	 * Update the record with the data from the panel.
	 * @param {Zarafa.core.data.IPMRecord} record
	 */
	updateRecord: function(record)
	{
		this.getForm().updateRecord(record);
	}
});

Ext.reg('zarafa.recordpropertiespanel', Zarafa.common.dialogs.RecordPropertiesPanel);
Ext.namespace('Zarafa.common.flags');

/**
 * @class Zarafa.common.flags.Util
 * @extends Object
 *
 * Collection of common functions to handle flags
 */
Zarafa.common.flags.Util = {
	/**
	 * Number of the last working day of the week as set in the user's settings. 0 for Sunday,
	 * 1 for Monday, etc.
	 *
	 * @type Number
	 * @property
	 * @private
	 */
	lastWorkingDay: undefined,

	/**
	 * Number of the first working day of the week as set in the user's settings. 0 for Sunday,
	 * 1 for Monday, etc.
	 *
	 * @type Number
	 * @property
	 * @private
	 */
	firstWorkingDay: undefined,

	/**
	 * The start of the working day of the user in minutes after 0:00 am.
	 *
	 * @type Number
	 * @property
	 * @private
	 */
	startWorkingHour: undefined,

	/**
	 * Base flag properties
	 *
	 * @return {Object} Object with base properties.
	 */
	getFlagBaseProperties: function()
	{
		return {
			flag_icon: 		Zarafa.core.mapi.FlagIcon.red,
			flag_request: 		'Follow up',
			flag_status: 		Zarafa.core.mapi.FlagStatus.flagged,
			flag_complete_time:	null,
			reminder:		true
			// TODO: Do we need to set is_marked_as_task for Outlook???
			// (here and in the functions below)
		};
	},

	/**
	 * Returns the properties and their values that should be set on a record to
	 * flag without a date.
	 *
	 * @return {Object} Object with property/value as key/value pairs.
	 */
	getFlagPropertiesNoDate: function()
	{
		return {
			startdate:     null,
			duedate:      null,
			reminder:		false,
			reminder_time:		null,
			flag_due_by:		null
		};
	},

	/**
	 * Returns the properties and their values that should be set on a record to
	 * flag it for today and add a reminder when due.
	 *
	 * @return {Object} Object with property/value as key/value pairs.
	 */
	getFlagPropertiesToday: function()
	{
		var now = new Date();
		var date = now.clone().setToNoon();

		var reminderTime = now.clone().add(Date.HOUR, 1);
		// Make sure it will not be set tomorrow
		if ( reminderTime.getDay() !== now.getDay() ){
			reminderTime = now;
			reminderTime.setHours(23);
			reminderTime.setMinutes(59);
			reminderTime.setSeconds(59);
		}

		return {
			startdate:     date,
			duedate:      date,
			reminder_time:		reminderTime,
			flag_due_by:		reminderTime
		};
	},

	/**
	 * Returns the properties and their values that should be set on a record to
	 * flag it for tomorrow and add a reminder when due.
	 *
	 * @return {Object} Object with property/value as key/value pairs.
	 */
	getFlagPropertiesTomorrow: function()
	{
		var date = new Date().add(Date.DAY, 1).setToNoon();

		var reminderTime = this.getReminderTimeForDate(date);

		return {
			startdate:     date,
			duedate:      date,
			reminder_time:		reminderTime,
			flag_due_by:		reminderTime
		};
	},

	/**
	 * Returns the properties and their values that should be set on a record to
	 * flag it for this week and add a reminder when due.
	 *
	 * @return {Object} Object with property/value as key/value pairs.
	 */
	getFlagPropertiesThisWeek: function()
	{
		// Make sure the firstWorkingDay and lastWorkingDay properties are set
		this.retrieveWorkingDays();

		var today = new Date().setToNoon();
		var startDate;
		var dueDate;

		// If the work week has not started yet, set the start date to the first working day,
		// otherwise set it to today
		if ( today.getDay() < this.firstWorkingDay ) {
			startDate = today.clone().add(Date.DAY, this.firstWorkingDay - today.getDay());
		} else {
			startDate = today;
		}

		// If the work week has not ended yet, set the due date to the last working day,
		// otherwise set it to today
		if ( this.lastWorkingDay > today.getDay() ) {
			dueDate = today.clone().add(Date.DAY, this.lastWorkingDay - today.getDay());
		} else {
			dueDate = today;
		}

		// The reminder time will be set to the start of the work day at the due date,
		// unless that has already passed. Then it will be set to 1 hour from now.
		var reminderTime = this.getReminderTimeForDate(dueDate);
		var now = new Date();
		var nowPlus1 = now.add(Date.HOUR, 1);
		if ( reminderTime < nowPlus1 ){
			reminderTime = nowPlus1;
			// Make sure it will not be set tomorrow
			if ( reminderTime.getDay() !== now.getDay() ){
				reminderTime = now;
				reminderTime.setHours(23);
				reminderTime.setMinutes(59);
				reminderTime.setSeconds(59);
			}
		}

		return {
			startdate:     startDate,
			duedate:      dueDate,
			reminder_time:		reminderTime,
			flag_due_by:		reminderTime
		};
	},

	/**
	 * Returns the properties and their values that should be set on a record to
	 * flag it for next week and add a reminder when due.
	 *
	 * @return {Object} Object with property/value as key/value pairs.
	 */
	getFlagPropertiesNextWeek: function()
	{
		// Make sure the firstWorkingDay and lastWorkingDay properties are set
		this.retrieveWorkingDays();

		var today = new Date().setToNoon();
		var startDate = today.add(Date.DAY, 7 + this.firstWorkingDay - today.getDay());
		var dueDate = startDate.add(Date.DAY, this.lastWorkingDay - this.firstWorkingDay);

		var reminderTime = this.getReminderTimeForDate(dueDate);

		return {
			startdate:     startDate,
			duedate:      dueDate,
			reminder_time:		reminderTime,
			flag_due_by:		reminderTime
		};
	},

	/**
	 * Returns the properties and their values that should be set on a record to
	 * flag it as complete.
	 *
	 * @return {Object} Object with property/value as key/value pairs.
	 */
	getFlagPropertiesComplete: function()
	{
		return {
			flag_icon: 			Zarafa.core.mapi.FlagIcon.clear,
			flag_complete_time:	new Date(),
			complete:			true,
			flag_request: 		'',
			flag_status: 		Zarafa.core.mapi.FlagStatus.completed,
			reminder:		false
		};
	},

	/**
	 * Returns the properties and their values that should be set on a record to
	 * remove the flag and reminder.
	 *
	 * @return {Object} Object with property/value as key/value pairs.
	 */
	getFlagPropertiesRemoveFlag: function()
	{
		return {
			flag_icon: 			Zarafa.core.mapi.FlagIcon.clear,
			flag_request: 		'',
			flag_status: 		Zarafa.core.mapi.FlagStatus.cleared,
			reminder:		false,
			startdate:     null,
			duedate:      null,
			reminder_time:		null,
			flag_due_by:		null
		};
	},

	/**
	 * Retrieves the values for {#firstWorkingDay} and {#lastWorkingDay}.
	 */
	retrieveWorkingDays: function()
	{
		if ( !this.firstWorkingDay ) {
			// Set it to Monday and Friday if no working days are defined by the user
			var workingDays = container.getSettingsModel().get('zarafa/v1/main/working_days') || [1, 5];
			workingDays.sort();

			this.firstWorkingDay = workingDays[0];
			this.lastWorkingDay = workingDays[workingDays.length-1];
		}
	},

	/**
	 * Returns a date object that is set at the starting work time of the user of the given day.
	 *
	 * @param {Date} date The date object of which the day must be used to set the reminder.
	 * @return {Date} The date object of the reminder
	 */
	getReminderTimeForDate: function(date)
	{
		var reminderTime = date.clone();
		var startWorkingHours = parseInt(container.getSettingsModel().get('zarafa/v1/main/start_working_hour'), 10);
		reminderTime.setHours(parseInt(startWorkingHours/60, 10));
		reminderTime.setMinutes(startWorkingHours%60);
		reminderTime.clearSeconds();

		return reminderTime;
	},

	/**
	 * Returns configured flag from given record.
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record for which configured flag needs to be identified.
	 * @return {String/Boolean} Configured flag if any, false otherwise
	 */
	getConfiguredFlag: function(record)
	{
		var configuredFlag = '';
		var flagStatus = record.get('flag_status');
		if (flagStatus !== Zarafa.core.mapi.FlagStatus.flagged) {
			return false;
		}

		var taskStartDate = record.get('startdate');
		var taskDueDate = record.get('duedate');
		var todayNoon = new Date().clone().setToNoon();
		var tomorrowNoon = new Date().add(Date.DAY, 1).setToNoon();

		if (taskStartDate === null || taskDueDate === null) {
			return 'no_date';
		}

		taskStartDate = taskStartDate.getTime();
		taskDueDate = taskDueDate.getTime();
		if (taskStartDate === todayNoon.getTime() && taskDueDate === todayNoon.getTime()) {
			configuredFlag = _('today');
		} else if (taskStartDate === tomorrowNoon.getTime() && taskDueDate === tomorrowNoon.getTime()) {
			configuredFlag = _('tomorrow');
		} else {
			var thisWeekProps = this.getFlagPropertiesThisWeek();
			if (taskStartDate === thisWeekProps.startdate.getTime() && taskDueDate === thisWeekProps.duedate.getTime()) {
				configuredFlag = _('this week');
			} else {
				var nextWeekProps = this.getFlagPropertiesNextWeek();
				if (taskStartDate === nextWeekProps.startdate.getTime() && taskDueDate === nextWeekProps.duedate.getTime()) {
					configuredFlag = _('next week');
				}
			}
		}

		return configuredFlag;
	},

	/**
	 * Will update the categories of records with old-style flags to real categories. (e.g. a red flag will
	 * become a real red category)
	 * @param {Zarafa.core.data.IPMRecord[]} records The records which categories update with old-style flags
	 * to real categories.
	 */
	updateCategories: function(records)
	{
		if (!Array.isArray(records)) {
			records = [records];
		}
		records.forEach(function(record){
			const flagStatus = record.get('flag_status');
			const flagRequest = record.get('flag_request');
			if ( flagStatus===Zarafa.core.mapi.FlagStatus.flagged && flagRequest!=='Follow up'){
				const categories = Zarafa.common.categories.Util.getCategories(record);
				Zarafa.common.categories.Util.setCategories(record, categories);
			}
		});
	}
};
Ext.namespace('Zarafa.common.flags.dialogs');

/**
 * @class Zarafa.common.flags.dialogs.CustomFlagPanel
 * @extends Ext.Panel
 * @xtype zarafa.customflagpanel
 *
 * Panel for users to set the custom flag and reminder on a given {@link Zarafa.core.data.IPMRecord record}
 */
Zarafa.common.flags.dialogs.CustomFlagPanel = Ext.extend(Ext.form.FormPanel, {
	/**
	 *
	 */
	reminderTime: null,

	/**
	 * @constructor
	 * @param config Configuration structure
	 */
	constructor: function(config)
	{
		config = config || {};
		config = Ext.applyIf(config, {
			xtype: 'zarafa.customflagpanel',
			cls: 'k-custom-flag-panel',
			layout: {
				type: 'vbox',
				align: 'stretch'
			},
			border: false,
			labelAlign: 'left',
			items: [
				this.createDatePanel(),
				this.createReminderPanel()
			]
		});
		Zarafa.common.flags.dialogs.CustomFlagPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Create the {@link Ext.Panel Panel} containing the
	 * {@link Zarafa.common.ui.DateTimePeriodField DateTimePeriodField}.
	 * @return {Object} Configuration object for the panel with time selection fields
	 * @private
	 */
	createDatePanel: function()
	{
		return {
			xtype: 'panel',
			cls: 'k-custom-date-panel',
			autoHeight: true,
			items: [{
				xtype: 'zarafa.dateperiodfield',
				ref: '../dateField',
				allowBlank: true,
				defaultPeriod: container.getSettingsModel().get('zarafa/v1/contexts/task/default_task_period'),
				listeners: {
					change: this.onDateRangeFieldChange,
					scope: this
				},
				startFieldConfig: {
					fieldLabel: _('Start date'),
					labelWidth: 84,
					cls: 'from-field',
					width: 121
				},
				endFieldConfig: {
					fieldLabel: _('End date'),
					labelWidth: 84,
					cls: 'to-field',
					width: 121
				}
			}]
		};
	},

	/**
	 * Create the {@link Ext.Panel Panel} containing the form fields
	 * for setting the reminder information.
	 * @return {Object} Configuration object for the panel with reminder fields
	 * @private
	 */
	createReminderPanel: function()
	{
		return {
			xtype: 'panel',
			cls: 'k-custom-reminder-panel',
			autoHeight: true,
			items: [{
				xtype: 'zarafa.compositefield',
				autoHeight: true,
				items: [{
					xtype: 'checkbox',
					name: 'reminder',
					ref:'../../reminderCheckbox',
					width: 100,
					boxLabel: _('Reminder') + ':',
					handler: this.onToggleReminder,
					scope: this
				},{
					xtype:'zarafa.datetimefield',
					name: 'reminder_time',
					width: 220,
					timeIncrement: container.getSettingsModel().get('zarafa/v1/contexts/task/reminder_time_stepping'),
					listeners:{
						change: this.onReminderFieldChange,
						scope: this
					},
					dateFieldConfig: {
						flex: 0
					}
				}]
			}]
		};
	},

	/**
	 * Called automatically when the {@link Zarafa.common.flag.dialogs.CustomFlagPanel CustomFlagPanel}
	 * is being rendered.
	 * @private
	 */
	onRender: function ()
	{
		Zarafa.common.flags.dialogs.CustomFlagPanel.superclass.onRender.apply(this, arguments);
		if (this.records.length === 1) {
			var record = this.records[0];
			this.reminderTime = record.get('reminder_time');
			var startDate = record.get('startdate');
			var dueDate = record.get('duedate');
			this.dateField.getValue().set(startDate,dueDate);
			this.getForm().loadRecord(record);
		}
	},

	/**
	 * Event handler which is fired when the {@link Zarafa.common.ui.DateRangeField} has been changed.
	 * This will update the start and due date inside the {@link #record} accordingly.
	 * @param {Ext.form.Field} field The field which has changed
	 * @param {Mixed} newValue The new value for the field
	 * @param {Mixed} oldValue The original value for the field
	 * @private
	 */
	onDateRangeFieldChange: function(field, newRange, oldRange)
	{
		var startDate = newRange.getStartDate();
		var dueDate = newRange.getDueDate();

		this.records.forEach(function (record) {
			record.beginEdit();

			if(Ext.isDate(startDate)) {
				record.set('startdate', startDate.clone().setToNoon());
			} else {
				record.set('startdate', null);
			}

			if(Ext.isDate(dueDate)) {
				record.set('duedate', dueDate.clone().setToNoon());
			} else {
				record.set('duedate', null);
			}
			record.endEdit();
		}, this);
	},

	/**
	 * Event handler which is triggered when reminder date field
	 * has been changed by the user. It will apply new value to the
	 * {@link Zarafa.core.data.IPMRecord[] records}.
	 * @param {Ext.form.Field} field The {@link Ext.form.Field field} which was changed.
	 * @param {Mixed} newValue The new value
	 * @param {Mixed} oldValue The old value
	 * @private
	 */
	onReminderFieldChange: function(field, newValue, oldValue)
	{
		this.records.forEach(function (record) {
			record.set(field.getName(), newValue);
			record.set('flag_due_by', newValue);
		},this);
		this.getForm().loadRecord(this.records[0]);
	},

	/**
	 * A function called when the checked value changes for the
	 * reminder checkbox.
	 * @param {Ext.form.Checkbox} checkbox The Checkbox being toggled.
	 * @param {Boolean} checked The new checked state of the checkbox.
	 * @private
	 */
	onToggleReminder: function(checkbox, checked)
	{
		this.records.forEach(function (record) {
			record.beginEdit();
			record.set('reminder', checked);
			if (checked) {
				var reminderTime = this.reminderTime;
				if (!Ext.isDate(reminderTime)) {
					var dueDate = record.get('duedate');
					var time = container.getSettingsModel().get('zarafa/v1/contexts/task/default_reminder_time');

					if (!Ext.isDate(dueDate)) {
						dueDate = new Date().add(Date.DAY, 1);
					}
					reminderTime = dueDate.clearTime(true).add(Date.MINUTE, time);
				}
				record.set('reminder_time',reminderTime );
				record.set('flag_due_by', reminderTime);
			} else {
				record.set('reminder_time', null);
				record.set('flag_due_by', null);
			}
			record.endEdit();
		}, this);

		this.getForm().loadRecord(this.records[0]);
	}
});

Ext.reg('zarafa.customflagpanel', Zarafa.common.flags.dialogs.CustomFlagPanel);
Ext.namespace('Zarafa.common.form');

/**
 * @class Zarafa.common.form.TextArea
 * @extends Ext.form.TextArea
 * @xtype zarafa.textarea
 *
 * The Textarea which extends{@link Ext.form.TextArea} and adds extra functionality
 * like inserting text at cursor position.
 */
Zarafa.common.form.TextArea = Ext.extend(Ext.form.TextArea, {

	/**
	 * @cfg {Boolean} enableSystemContextMenu Enable the browser's default contextmenu
	 * to be opened on this {@link #el element}.
	 */
	enableSystemContextMenu: false,

	/**
	 * Called during {@link #render}. If {@link #enableSystemContextMenu} is enabled, this
	 * will apply the 'zarafa-contextmenu-enabled' CSS class on the {@link #el element}.
	 * @param {Ext.Container} ct The container in which the component is being rendered
	 * @param {Number} position The position inside the container where the component is being rendered
	 * @private
	 */
	onRender: function(ct, position)
	{
		Zarafa.common.form.TextArea.superclass.onRender.apply(this, arguments);

		if (this.enableSystemContextMenu) {
			this.el.addClass('zarafa-contextmenu-enabled');
		}

		this.el.on('dragover', this.onFileDragOver, this);
		this.el.on('drop', this.onFileDrop, this);
	},

	/**
	 * Prevent the browser from opening dropped files.
	 * @param {Ext.EventObject} e The drag event
	 * @private
	 */
	onFileDragOver: function(e)
	{
		var dt = e.browserEvent.dataTransfer;
		if (dt && (Array.prototype.indexOf.call(dt.types, 'Files') >= 0 || dt.files.length > 0)) {
			e.preventDefault();
		}
	},

	/**
	 * Block file drops on the plain-text body area. When the user drops a file
	 * that cannot be embedded (which is every file in plain-text mode), offer
	 * to add it as a message attachment instead.
	 * @param {Ext.EventObject} e The drop event
	 * @private
	 */
	onFileDrop: function(e)
	{
		var dt = e.browserEvent.dataTransfer;
		if (!dt || (Array.prototype.indexOf.call(dt.types, 'Files') < 0 && dt.files.length === 0)) {
			return;
		}

		e.preventDefault();

		var files = dt.files;
		if (!files || files.length === 0) {
			return;
		}

		// Build a list of filenames for the confirmation message. Each name is
		// HTML-encoded individually so that the '<br>' separators used to join
		// them remain real line breaks instead of being escaped as literal text.
		var names = [];
		for (var i = 0, len = files.length; i < len; i++) {
			names.push('\u2022 ' + Ext.util.Format.htmlEncode(files[i].name));
		}

		var self = this;
		var filesSnapshot = files; // keep a reference before the event is recycled

		Ext.MessageBox.confirm(
			_('Add as attachment?'),
			_('The following files cannot be embedded in the message body. Add them as attachments instead?') +
				'<br><br>' + names.join('<br>'),
			function(btn) {
				if (btn !== 'yes') {
					return;
				}

				// Walk up to the nearest panel that holds the mail record.
				var panel = self.findParentByType('zarafa.mailcreatepanel') ||
				            self.findParentByType('zarafa.recordcontentpanel');
				if (!panel || !panel.record) {
					return;
				}

				var store = panel.record.getSubStore('attachments');
				// Run the same validation (max attachment count/size limits) that a
				// direct drop on the Attachments field would perform, so the file is
				// added/rejected/notified in exactly the same way either way.
				if (store && store.canUploadFiles(filesSnapshot, { container: panel.getEl() })) {
					store.uploadFiles(filesSnapshot);
				}
			}
		);
	},

	/**
	 * Function adds passed text in textarea at cursor's position.
	 * @param {String} text The text value which you want to add in text area
	 */
	insertAtCursor: function(text) {
		var startPos = this.el.dom.selectionStart;
		var endPos = this.el.dom.selectionEnd;
		this.el.dom.value = this.el.dom.value.substring(0, startPos)
			+ text
			+ this.el.dom.value.substring(endPos, this.el.dom.value.length);

		this.el.focus();
		this.el.dom.setSelectionRange(endPos + text.length, endPos + text.length);
	},

	/**
	 * Function sets the cursor position to the start of the text
	 */
	setCursorLocation: function()
	{
		var textAreaDom = this.getEl().dom;

		// When a user replies to an email and the textarea contains text then by default
		// the cursor is set to the end of the text. The following code sets the cursor position to
		// the start of the text.
		var textLen = textAreaDom.textLength;
		var startPos = textAreaDom.selectionStart;
		var endPos = textAreaDom.selectionEnd;

		if(startPos === textLen && endPos === textLen) {
			textAreaDom.setSelectionRange(0,0);
		}
	},

	/**
	 * Function select the text in editor by given selector.
	 *
	 * @param {String} selector The selector query which used to select the text in editor.
	 * @return {boolean} return true if text is selected in editor else false.
	 */
	selectBySelector: Ext.emptyFn,
});

Ext.reg('zarafa.textarea', Zarafa.common.form.TextArea);
Ext.namespace('Zarafa.common.freebusy.data');

/**
 * Contains a description of what a single freebusy block item looks like. Is used by the JSON 
 * reader in the Zarafa.common.freebusy.data.FreebusyBlockStore.
 */
Zarafa.common.freebusy.data.FreebusyBlockRecord = Ext.data.Record.create([
	{ name: 'userid' },
	{ name: 'start', type: 'int' },
	{ name: 'end', type: 'int' },
	{ name: 'status', type: 'int' }
]);
Ext.namespace('Zarafa.common.freebusy.data');

/**
 * @class Zarafa.common.freebusy.data.FreebusyBlockStore
 * @extends Ext.data.Store
 * @xtype zarafa.freebusyblockstore
 */
Zarafa.common.freebusy.data.FreebusyBlockStore = Ext.extend(Ext.data.Store, 
{
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			batch: true,
			autoSave: false,
			writer: new Ext.data.JsonWriter(),
			remoteSort: true,
			proxy: new Zarafa.common.freebusy.data.FreebusyProxy(),
			reader: new Ext.data.JsonReader({
				root: 'item'
			}, Zarafa.common.freebusy.data.FreebusyBlockRecord)
		});

		Zarafa.common.freebusy.data.FreebusyBlockStore.superclass.constructor.call(this, config);
	}
});

Ext.reg('zarafa.freebusyblockstore', Zarafa.common.freebusy.data.FreebusyBlockStore);
Ext.namespace('Zarafa.common.freebusy.data');

/**
 * @class Zarafa.common.freebusy.data.FreebusyModel
 * @extends Ext.util.Observable
 */
Zarafa.common.freebusy.data.FreebusyModel = Ext.extend(Ext.util.Observable,
{
	/**
	 * @cfg {Boolean} nonWorkingHoursHidden
	 * The {@link Zarafa.common.freebusy.ui.TimelineView TimelineView} must only
	 * display the working hours for the user.
	 */
	nonWorkingHoursHidden: true,
	/**
	 * @cfg {Zarafa.core.DateRange} daterange
	 * The {@link Zarafa.core.DateRange} object that determines what time period will be shown
	 * (defaults to 7 days before today till 3 weeks after).
	 */
	daterange: null,
	/**
	 * @cfg {Zarafa.core.DateRange} selectorRange
	 * The {@link Zarafa.core.DateRange} object that determines what time period will be selected
	 * (defaults to next whole or half hour and will last by default for 30 minutes).
	 */
	selectorRange: null,
	/**
	 * The {@link Zarafa.core.DateRange} object that indicates the daterange for which the
	 * suggestions for the {@link #suggestionBlockStore} must be calculated.
	 * @property
	 * @type Zarafa.common.freebusy.data.FreebusyBlockStore
	 */
	suggestionRange: null,
	/**
	 * @cfg {Zarafa.core.data.IPMRecipientStore} userStore
	 * The {@link Zarafa.core.data.IPMRecipientStore store} object that handles the freebusy blocks displayed on the timeline.
	 */
	userStore: null,
	/**
	 * @cfg {Zarafa.common.freebusy.data.FreebusyBlockStore} blockStore
	 * The {@link Zarafa.common.freebusy.data.FreebusyBlockStore store} object that handles the freebusy blocks displayed on the timeline.
	 */
	blockStore: null,
	/**
	 * @cfg {Zarafa.common.freebusy.data.FreebusyBlockStore} sumBlockStore
	 * The {@link Zarafa.common.freebusy.data.FreebusyBlockStore} object that handles the freebusy sum blocks displayed in the header of the timeline.
	 */
	sumBlockStore: null,
	/**
	 * freeBlockStore Internal store which keeps track of all blocks which mark
	 * the periods in which no user is occupied. This is used to generate the {@link #suggestionBlockStore}.
	 * @property
	 * @type Zarafa.common.freebusy.data.FreebusyBlockStore
	 */
	freeBlockStore: null,
	/**
	 * @cfg {Zarafa.common.freebusy.data.FreebusyBlockStore} suggestionBlockStore
	 * The {@link Zarafa.common.freebusy.data.FreebusyBlockStore} object that handles the freebusy sum blocks which indicates which blocks are
	 * available for new meetings.
	 */
	suggestionBlockStore: null,
	/**
	 * This property is used to prevent the firing of the selectorrangeupdate event. When an outside
	 * source changes the selector range's values this model does not fire that event.
	 * @property
	 * @type Boolean
	 */
	updatingSelectorRangeExternally: false,
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

		// Use 12:00 as base time, this prevents problems for DST switches
		// at 00:00 like in Brasil.
		var now = new Date();
		now.setHours(12);

		var server = container.getServerConfig();
		var startOffset = server.getFreebusyLoadStartOffset();
		var startDate = now.add(Date.DAY, -startOffset).clearTime();
		var endOffset = server.getFreebusyLoadEndOffset();
		var endDate = now.add(Date.DAY, endOffset).clearTime();

		var selectStart, selectEnd;
		if(config.selectorRange){
			// If selectorRange has been supplied copy the startdate and duedate values.
			selectStart = config.selectorRange.getStartDate();
			selectEnd = config.selectorRange.getDueDate();
		} else {
			// Defaults to next whole or half hour and will last by default for 30 minutes
			selectStart = new Date().ceil(Date.MINUTE, 30);
			selectEnd = selectStart.add(Date.MINUTE, 30);
		}

		Ext.applyIf(config, {
			userStore: config.userStore || new Zarafa.core.data.IPMRecipientStore(),
			daterange: config.daterange || new Zarafa.core.DateRange({ startDate: startDate, dueDate: endDate}),
			selectorRange: new Zarafa.core.DateRange({ startDate: selectStart, dueDate: selectEnd}),
			blockStore: config.blockStore || new Zarafa.common.freebusy.data.FreebusyBlockStore(),
			sumBlockStore: config.sumBlockStore || new Zarafa.common.freebusy.data.FreebusyBlockStore({ remoteSort: false}),
			suggestionBlockStore: config.suggestionBlockStore || new Zarafa.common.freebusy.data.FreebusyBlockStore({ remoteSort: false})
		});

		Ext.apply(this, config);

		this.addEvents(
			/**
			 * @event showworkinghourschange
			 * Fires when the {@link #nonWorkingHoursHidden} has been changed
			 * @param {Boolean} hideNonWorkingHours True if only working hours
			 * should be shown.
			 */
			'showworkinghourschange',
			/**
			 * @event userstorechange
			 * Fires when the user store is changed.
			 * @param {Zarafa.core.data.IPMRecipientStore} newStore The new store
			 * @param {Zarafa.core.data.IPMRecipientStore} oldStore The old store
			 */
			'userstorechange',
			/**
			 * @event blockstorechange
			 * Fires when the block store is changed.
			 * @param {Zarafa.common.freebusy.data.FreebusyBlockStore} newStore The new store
			 * @param {Zarafa.common.freebusy.data.FreebusyBlockStore} oldStore The old store
			 */
			'blockstorechange',
			/**
			 * @event sumblockstorechange
			 * Fires when the sumblock store is changed.
			 * @param {Zarafa.common.freebusy.data.FreebusyBlockStore} newStore The new store
			 * @param {Zarafa.common.freebusy.data.FreebusyBlockStore} oldStore The old store
			 */
			'sumblockstorechange',
			/**
			 * @event suggestionblockstorechange
			 * Fires when the suggestionblock store is changed.
			 * @param {Zarafa.common.freebusy.data.FreebusyBlockStore} newStore The new store
			 * @param {Zarafa.common.freebusy.data.FreebusyBlockStore} oldStore The old store
			 */
			'suggestionblockstorechange',
			/**
			 * @event daterangechange
			 * Fires when the {@link Zarafa.core.DateRange DateRange} has been changed.
			 * @param {Zarafa.core.DateRange} newRange The new DateRange object.
			 * @param {Zarafa.core.DateRange} oldRange The old DateRange object.
			 */
			'daterangechange',
			/**
			 * @event selectorrangeupdate
			 * Fires when the selector {@link Zarafa.core.DateRange DateRange}
			 * has been changed.
			 * @param {Zarafa.core.DateRange} newRange The new DateRange object.
			 * @param {Zarafa.core.DateRange} oldRange The old DateRange object.
			 */
			'selectorrangeupdate'
		);

		Zarafa.common.freebusy.data.FreebusyModel.superclass.constructor.call(this, config);

		this.initEvents();

		this.setDateRange(config.daterange, true);
		this.setUserStore(config.userStore, true);
		this.setSelectorRange(config.selectorRange, true);
		this.setBlockStore(config.blockStore, true);
		this.setSumBlockStore(config.sumBlockStore, true);
		this.setSuggestionBlockStore(config.suggestionBlockStore, true);

		this.createFreeBlockStore();
		this.createSuggestionRange();
	},

	/**
	 * Initialize all {@link Zarafa.common.freebusy.data.FreebusyModel FreebusyModel} related events.
	 * @private
	 */
	initEvents: function()
	{
		this.on('showworkinghourschange', this.onShowWorkingHoursChange, this);
		this.on('userstorechange', this.onUserStoreChange, this);
	},

	/**
	 * Create a {@link Zarafa.common.freebusy.data.FreebusyBlockStore FreebusyBlockStore} which
	 * is used internally to keep track of the TimeBlocks in which all users are available.
	 * @return {Zarafa.common.freebusy.data.FreebusyBlockStore} The created store
	 * @private
	 */
	createFreeBlockStore: function()
	{
		if (!this.freeBlockStore) {
			this.freeBlockStore = new Zarafa.common.freebusy.data.FreebusyBlockStore({ remoteSort: false});
		}
		return this.freeBlockStore;
	},

	/**
	 * Create a {@link Zarafa.core.DateRange DateRange} object which is used internally to keep
	 * track of the range for which the suggestions must be calculated.
	 * @return {Zarafa.core.DateRange} The created daterange
	 * @private
	 */
	createSuggestionRange: function()
	{
		if (!this.suggestionRange) {
			this.suggestionRange = new Zarafa.core.DateRange();
			this.setSuggestionDate(this.selectorRange.getStartDate());
			this.suggestionRange.on('update', this.onSuggestionRangeUpdate, this, { buffer: 5 });
		}
		return this.suggestionRange;
	},

	/**
	 * Get the visibility of the non-working hours.
	 * @return {Boolean} True if the non-working hours are hidden
	 */
	showOnlyWorkingHours: function()
	{
		return this.nonWorkingHoursHidden;
	},

	/**
	 * Set the visibility of the non-working hours.
	 * @param {Boolean} hide True to hide the non-working hours
	 */
	hideNonWorkingHours: function(hide)
	{
		var oldNonWorkingHoursHidden = this.nonWorkingHoursHidden;
		this.nonWorkingHoursHidden = hide;
		if (oldNonWorkingHoursHidden != this.nonWorkingHoursHidden) {
			this.fireEvent('showworkinghourschange', this.nonWorkingHoursHidden);
		}
		return this.nonWorkingHoursHidden;
	},

	/**
	 * Returns the User store.
	 * @return {Zarafa.core.data.IPMRecipientStore} store
	 */
	getUserStore: function()
	{
		return this.userStore;
	},

	/**
	 * Sets the store.
	 * @param {Zarafa.core.data.IPMRecipientStore} store store
	 * @param {Boolean} initial (optional) True if this function is called
	 * during initialization.
	 * @return {Zarafa.core.data.IPMRecipientStore} store
	 */
	setUserStore: function(store, initial)
	{
		if (initial !== true && this.userStore === store) {
			return;
		}

		var oldUserStore = this.userStore;
		if (this.userStore) {
			this.userStore.un('resolved', this.onUserResolved, this);
			this.userStore.un('load', this.onUserLoad, this);
			this.userStore.un('add', this.onUserAdd, this);
			this.userStore.un('remove', this.onUserRemove, this);
			this.userStore.un('clear', this.onUserClear, this);
		}

		this.userStore = Ext.StoreMgr.lookup(store);

		if (this.userStore) {
			this.userStore.on({
				scope: this,
				resolved: this.onUserResolved,
				load: this.onUserLoad,
				add: this.onUserAdd,
				remove: this.onUserRemove,
				clear: this.onUserClear
			});
		}

		this.fireEvent('userstorechange', this.userStore, oldUserStore);
		if (this.userStore !== oldUserStore && oldUserStore && oldUserStore.autoDestroy) {
			oldUserStore.destroy();
		}

		return this.userStore;
	},

	/**
	 * Returns the block store.
	 * @return {Zarafa.common.freebusy.data.FreebusyBlockStore} store
	 */
	getBlockStore: function()
	{
		return this.blockStore;
	},

	/**
	 * Sets the store.
	 * @param {Zarafa.common.freebusy.data.FreebusyBlockStore} store
	 * @param {Boolean} initial (optional) True if this function is called
	 * during initialization.
	 * @return {Zarafa.common.freebusy.data.FreebusyBlockStore} store
	 */
	setBlockStore: function(store, initial)
	{
		if (initial !== true && this.blockStore === store) {
			return;
		}

		var oldBlockStore = this.blockStore;
		if (this.blockStore) {
			this.blockStore.un('load', this.onBlockLoad, this);
			this.blockStore.un('remove', this.onBlockRemove, this, { buffer: 100 });
			this.blockStore.un('clear', this.onBlockRemove, this);
		}

		this.blockStore = Ext.StoreMgr.lookup(store);

		if (this.blockStore) {
			this.blockStore.on('load', this.onBlockLoad, this);
			this.blockStore.on('remove', this.onBlockRemove, this, { buffer: 100 });
			this.blockStore.on('clear', this.onBlockRemove, this);
		}

		this.fireEvent('blockstorechange', this.blockStore, oldBlockStore);
		if (this.blockStore !== oldBlockStore && oldBlockStore && oldBlockStore.autoDestroy) {
			oldBlockStore.destroy();
		}

		return this.blockStore;
	},

	/**
	 * Returns the block store.
	 * @return {Zarafa.common.freebusy.data.FreebusyBlockStore} store
	 */
	getSumBlockStore: function()
	{
		return this.sumBlockStore;
	},

	/**
	 * Sets the block store.
	 * @param {Zarafa.common.freebusy.data.FreebusyBlockStore} store The sum block store
	 * @param {Boolean} initial (optional) True if this function is called
	 * during initialization.
	 * @return {Zarafa.common.freebusy.data.FreebusyBlockStore} The sum block store
	 * @private
	 */
	setSumBlockStore: function(store, initial)
	{
		if (initial !== true && this.sumBlockStore === store) {
			return;
		}

		var oldSumBlockStore = this.sumBlockStore;

		if (this.sumBlockStore) {
			this.sumBlockStore.un('load', this.onSumBlockLoad, this, { buffer: 5 });
		}

		this.sumBlockStore = Ext.StoreMgr.lookup(store);

		if (this.sumBlockStore) {
			this.sumBlockStore.on('load', this.onSumBlockLoad, this, { buffer: 5 });
		}

		this.fireEvent('sumblockstorechange', this.sumBlockStore, oldSumBlockStore);
		if (this.sumBlockStore !== oldSumBlockStore && oldSumBlockStore && oldSumBlockStore.autoDestroy) {
			oldSumBlockStore.destroy();
		}

		return this.sumBlockStore;
	},

	/**
	 * Returns the block store.
	 * @return {Zarafa.common.freebusy.data.FreebusyBlockStore} store
	 */
	getSuggestionBlockStore: function()
	{
		return this.suggestionBlockStore;
	},

	/**
	 * Sets the block store.
	 * @param {Zarafa.common.freebusy.data.FreebusyBlockStore} store The free block store
	 * @param {Boolean} initial (optional) True if this function is called
	 * during initialization.
	 * @return {Zarafa.common.freebusy.data.FreebusyBlockStore} The free block store
	 * @private
	 */
	setSuggestionBlockStore: function(store, initial)
	{
		if (initial !== true && this.suggestionBlockStore === store) {
			return;
		}

		var oldSuggestionBlockStore = this.suggestionBlockStore;

		this.suggestionBlockStore = Ext.StoreMgr.lookup(store);

		this.fireEvent('suggestionblockstorechange', this.suggestionBlockStore, oldSuggestionBlockStore);
		if (this.suggestionBlockStore !== oldSuggestionBlockStore && oldSuggestionBlockStore && oldSuggestionBlockStore.autoDestroy) {
			oldSuggestionBlockStore.destroy();
		}

		return this.suggestionBlockStore;
	},

	/**
	 * This returns the number of users inside the {@link #userStore}.
	 * @return {Number} The number of users inside the userStore.
	 */
	getUserCount: function()
	{
		return this.userStore.getCount();
	},

	/**
	 * Adds user to the user list. The freebusy information is automatically requested when the user is resolved.
	 * @param {String} name name of the user that will be resolved and added to the list
	 * @param {Zarafa.core.mapi.RecipientType} type (optional) The recipientType for the user (defaults to MAPI_TO)
	 * @return {Zarafa.core.data.IPMRecipientRecord} the record representing the newly added user.
	 */
	addUser: function(name, type)
	{
		type = Ext.isNumber(type) ? type : Zarafa.core.mapi.RecipientType.MAPI_TO;

		var record = Zarafa.core.data.RecordFactory.createRecordObjectByCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_RECIPIENT, {
			display_name: name,
			recipient_type: type
		});

		// Adding record to userlist store to update the userlist
		this.userStore.add(record);

		return record;
	},

	/**
	 * Returns the daterange
	 * @return {Zarafa.util.DateRange} Daterange set in this model
	 */
	getDateRange: function()
	{
		return this.daterange;
	},

	/**
	 * Sets the date range.
	 * @param {Zarafa.util.DateRange} dateRange Daterange set for this model
	 * @param {Boolean} initial (optional) True if this function is called
	 * during initialization.
	 * @return {Zarafa.util.DateRange} Daterange set for this model
	 */
	setDateRange: function(dateRange, initial)
	{
		if (initial !== true && this.daterange === dateRange) {
			return;
		}

		var oldDateRange = this.daterange;
		this.daterange = dateRange;
		this.fireEvent('daterangechange', this.daterange, oldDateRange);
		return this.daterange;
	},

	/**
	 * Returns the selector range
	 * @return {Zarafa.util.DateRange} Daterange set for the selector in this model
	 */
	getSelectorRange: function()
	{
		return this.selectorRange;
	},

	/**
	 * Sets the selector range.
	 * @param {Zarafa.util.DateRange} selectorRange Daterange set for the selector in this model
	 * @param {Boolean} initial (optional) True if this function is called
	 * during initialization.
	 * @return {Zarafa.util.DateRange} Daterange set for the selector in this model
	 * @private
	 */
	setSelectorRange: function(selectorRange, initial)
	{
		if (initial !== true && this.selectorRange === selectorRange) {
			return;
		}

		if (this.selectorRange) {
			this.selectorRange.un('update', this.onSelectorRangeUpdate, this, { buffer: 5 });
		}

		this.selectorRange = selectorRange;

		if (this.selectorRange) {
			this.selectorRange.on('update', this.onSelectorRangeUpdate, this, { buffer: 5 });
		}

		return this.selectorRange;
	},

	/**
	 * Modifies the selected range in the timeline by changing the selectorRange.
	 * @param {Date} startDate Start date
	 * @param {Date} dueDate Due date
	 */
	selectRange: function(startDate, dueDate){
		/* Set a temporary state to disable the firing of the selectorrangeupdate event. When the
		 * start and due date are equal to those currently set in the selectorRange the DateRange
		 * will not fire an update. This temporary state is only reset to false when this update is
		 * fired. To prevent problems with this we check whether the selectorRange will fire an
		 * update.
		 */
		if(!this.selectorRange.equals( new Zarafa.core.DateRange({ startDate: startDate, dueDate: dueDate}) )){
			this.updatingSelectorRangeExternally = true;
		}
		this.selectorRange.set(startDate, dueDate);
	},

	/**
	 * Returns the suggestion range
	 * @return {Zarafa.util.DateRange} Daterange set for the suggestions in this model
	 */
	getSuggestionRange: function()
	{
		return this.suggestionRange;
	},

	/**
	 * Set the date for which the suggestionlist must be calculated.
	 * This will set the {@link #suggestionRange} object to the given
	 * date, and will reset the timerange according to the working/non-working
	 * hour settings.
	 * @param {Ext.Date} startDate The start Date for the suggestions
	 * @param {Number} duration (optional) The period for the suggestions
	 */
	setSuggestionDate: function(startDate, duration)
	{
		var start = startDate.clone().clearTime();
		var due = startDate.clone().clearTime();

		if (this.nonWorkingHoursHidden) {
			start = start.add(Date.MINUTE, container.getSettingsModel().get('zarafa/v1/main/start_working_hour'));
			due = due.add(Date.MINUTE, container.getSettingsModel().get('zarafa/v1/main/end_working_hour'));
		} else {
			// Use 12:00 as time, this prevents problems
			// for DST switches at 00:00 like in Brasil.
			due.setHours(12);
			due = due.add(Date.DAY, 1).clearTime();
		}

		// If the current suggestionTime equals the new time, the
		// DateRange will not trigger the update event. However, when
		// the duration has been set, we must assume a forced update
		// must be performed. So just fire the update event manually.
		//
		if (this.suggestionRange.getStartTime() !== start.getTime()) {
			this.suggestionRange.set(start, due);
		} else if (Ext.isDefined(duration)) {
			this.suggestionRange.fireEvent('update', this.suggestionRange);
		}
	},

	/**
	 * Load freebusy data for the {@link #blockStore} for the provided users.
	 * @param {Zarafa.core.data.IPMRecipient[]} userRecords The users for which the
	 * freebusy data is requested
	 * @private
	 */
	loadFreebusyData: function(userRecords)
	{
		var dateRange = this.getDateRange();
		var loadData = {
			add: true, // All blocks will be appended to the existing list.
			actionType: Zarafa.core.Actions['list'],
			params: {
				users: [],
				start: dateRange.getStartTime() / 1000,
				end: dateRange.getDueTime() / 1000
			}
		};

		if (!Array.isArray(userRecords)) {
			userRecords = [ userRecords ];
		}

		// Collect all unique identifiers of the resolved users, we sent
		// both the record ID as the entryid. The entryid is used by the PHP
		// to collect the data for the given recipient, the userid is used
		// in the response to correlate the response to the correct recipientRecord.
		Ext.each(userRecords, function(userRecord) {
			if (userRecord.isResolved()) {
				loadData.params.users.push({
					userid: userRecord.id,
					entryid: userRecord.get('entryid')
				});
			}
		});

		// Perhaps none of the so-called "Resolved" users were
		// resolved. That's lame, but it does mean less work for us.
		if (!Ext.isEmpty(loadData.params.users)) {
			this.blockStore.load(loadData);
		}
	},

	/**
	 * When the {@link #userStore} has resolved users, we can request all freebusy data
	 * for the resolved users.
	 * @param {Zarafa.core.data.IPMRecipientStore} userStore
	 * @param {Zarafa.core.data.IPMRecipient} userRecords
	 * @private
	 */
	onUserResolved: function(userStore, userRecords)
	{
		this.loadFreebusyData(userRecords);
	},

	/**
	 * When the {@link #userStore} has been loaded, make sure that all users will be
	 * resolved.
	 * @param {Zarafa.core.data.IPMRecipientStore} userStore
	 * @param {Zarafa.core.data.IPMRecipient} userRecords
	 * @private
	 */
	onUserLoad: function(userStore, userRecords)
	{
		this.loadFreebusyData(userRecords);
	},

	/**
	 * When an user is added to the userStore make sure that it is being resolved.
	 * @param {Zarafa.core.data.IPMRecipientStore} userStore Store
	 * @param {Zarafa.core.data.IPMRecipientRecord[]} userRecords Records of the added users
	 * @param {Number} index Index
	 * @private
	 */
	onUserAdd: function(userStore, userRecords, index)
	{
		this.loadFreebusyData(userRecords);
	},

	/**
	 * When an user is removed from the userStore removing the related data in the blockStore.
	 * @param {Zarafa.core.data.IPMRecipientStore} userStore Store
	 * @param {Zarafa.core.data.IPMRecipientRecord[]} userRecords
	 * @param {Number} index Index
	 * @private
	 */
	onUserRemove: function(userStore, userRecords, index)
	{
		Ext.each(userRecords, function(userRecord) {
			var records = this.blockStore.getRange();
			Ext.each(records, function(record) {
				if(record.get('userid') == userRecord.id) {
					this.blockStore.remove(record);
				}
			}, this);
		}, this);
	},

	/**
	 * When all users are removed from the userStore, all blocks can be removed from the blockStore.
	 * @param {Zarafa.core.data.IPMRecipientStore} userStore Store
	 * @param {Zarafa.core.data.IPMRecipientStore[]} userRecords
	 * @private
	 */
	onUserClear: function(userStore, records)
	{
		this.blockStore.removeAll();
	},

	/**
	 * Construct a {@link Ext.data.Record record} for the {@link #sumBlockStore}
	 * based on the start and end information from the given {@link Ext.data.Record record}.
	 * @param {Ext.data.Record} record The original record from which the sum block is based
	 * @return {Ext.data.Record} the record for the sumBlockStore
	 */
	createSumBlock: function(record)
	{
		return new Zarafa.common.freebusy.data.FreebusyBlockRecord({
			start: record.get('start'),
			end: record.get('end'),
			status: record.get('status')
		});
	},

	/**
	 * Construct a number of {@link Ext.data.Record records} for the {@link #suggestionStore}
	 * based on the start and end information. Calculating the number of possible suggestions
	 * is based on the start and end time. Within this range, (starting with 'start') we check
	 * if an suggestion with the given duration is possible. If it is, then a new block is created,
	 * and we move our 'start' value with the 'interval' value. If at this point we still have
	 * sufficient space until the end time, we can create another suggestion. This continues
	 * until we have reached the end of the interval.
	 */
	createSuggestionBlocks: function(start, end, duration, interval)
	{
		var blocks = [];

		// Reduce the end of the period with the duration. This way we can
		// schedule a suggestion for each interval between start and end.
		end -= duration;

		for (var i = start; i <= end; i += interval) {
			blocks.push(new Zarafa.common.freebusy.data.FreebusyBlockRecord({
				start: i,
				end: i + duration
			}));
		}

		return blocks;
	},

	/**
	 * Merge a {@link Ext.data.Record record} from the {@link #blockStore} into
	 * the {@link Ext.data.Record sumRecord} if the two overlap.
	 * @param {Ext.data.Record} record The record to merge into sumRecord
	 * @param {Ext.data.Record} sumRecord The record containing the summed data
	 * @return {Boolean} True if the record has been merged into sumRecord.
	 * @private
	 */
	mergeRecordIntoSumBlock: function(record, sumRecord)
	{
		// Status is different, we can't merge the record into sumRecord
		if (record.get('status') !== sumRecord.get('status')) {
			return false;
		}

		// Obtain the start & end timestamps for each record
		var start = record.get('start');
		var end = record.get('end');
		var sumStart = sumRecord.get('start');
		var sumEnd = sumRecord.get('end');

		// If the block is completely outside the sumBlock,
		// we can't do merge the record.
		if (end < sumStart || sumEnd < start) {
			return false;
		}

		// If the sumblock completely encapsulates the block,
		// we don't need to do anything, except marking the record as merged.
		if (sumStart <= start && end <= sumEnd) {
			return true;
		}

		// If the block completely encapsulates the sumblock,
		// we need to replace the sumblock with the new block.
		if (start < sumStart && sumEnd < end) {
			sumRecord.set('start', start);
			sumRecord.set('end', end);
			return true;
		}

		// If the block starts earlier then the sumblock,
		// we need to expand the sumblock to start earlier.
		if (start < sumStart) {
			sumRecord.set('start', start);
			return true;
		}

		// If the block starts later then the sumblock,
		// we need to expand the sumblock the end later.
		if (sumEnd < end) {
			sumRecord.set('end', end);
			return true;
		}

		// We checked earlier if the record is within sumRecord,
		// outside sumRecord or partially overlaps. Why did we
		// arrive here then? Perhaps the record exists in a
		// parallel universe?
		return false;
	},

	/**
	 * Merge the given {@link Ext.data.Record records} with normal FB block information
	 * into the {@link #sumBlockStore}. Each {@link Ext.data.Record record}
	 * is compared to the existing {@link Ext.data.Record records} from the {@link #sumBlockStore}.
	 * If needed the existing {@link Ext.data.Record record} is expanded to include the updated time.
	 * If the original {@link Ext.data.Record record} does not match any sumBlock then a new
	 * sumBlock will be created.
	 * @param {Ext.data.Record[]} records The records which must be merged into the sum block store
	 * @param {Zarafa.common.freebusy.data.FreebusyBlockStore} sumBlockStore The store containing the sum blocks
	 * @param {Boolean} splitBusyStatus (optional) If true then sumBlocks will be generated per
	 * BusyStatus group, otherwise a single sumBlock will be generated.
	 */
	mergeBlocksToSumBlockStore: function(records, sumBlockStore, splitBusyStatus)
	{
		var lastBlock = {};

		// Sort all records by start date, this ensures that we can apply the merging safely.
		records.sort(function(a, b) {
			return a.get('start') - b.get('start');
		});

		Ext.each(records, function(record) {
			// We are not generating sumBlocks for the free status.
			var busyStatus = record.get('status');
			if (busyStatus === Zarafa.core.mapi.BusyStatus.UNKNOWN || busyStatus === Zarafa.core.mapi.BusyStatus.FREE) {
				return true;
			}

			if (splitBusyStatus === false) {
				busyStatus = Zarafa.core.mapi.BusyStatus.BUSY;
			}

			// The first item can be inserted directly
			if (sumBlockStore.getCount() === 0) {
				sumBlockStore.add(this.createSumBlock(record));
				return true;
			}

			var sumRecord = lastBlock[busyStatus];

			// Check if the new record can be merged into the sumRecord,
			// if this is not the case, we should add it as a new block.
			if (!Ext.isDefined(sumRecord) || this.mergeRecordIntoSumBlock(record, sumRecord) !== true) {
				var newSumBlock = this.createSumBlock(record);
				sumBlockStore.add(newSumBlock);
				lastBlock[busyStatus] = newSumBlock;
			}
		}, this);
	},

	/**
	 * Walk through the {@link Ext.data.Record records} from the {@link #sumBlockStore}
	 * to determine if any {@link Ext.data.Record record} overlapse. If this is the
	 * case the two {@link Ext.data.Record records} will be merged into a single block.
	 * @param {Zarafa.common.freebusy.data.FreebusyBlockStore} sumBlockStore The store containing the sumblocks
	 */
	mergeSumBlocks: function(sumBlockStore)
	{
		var lastBlock = {};

		// Start sorting the sumblocks based on their start date, they
		// could not have been added sorted into the store, since we
		// have been modifying the start/end values of the blocks after adding.
		sumBlockStore.sort('start', 'ASC');

		sumBlockStore.each(function(record) {
			var prevRecord = lastBlock[record.get('status')];

			// Check if the previous record and the current record overlap,
			// if this is the case the records can be merged. The comparison
			// is very simple since we only need to check if the end-time for
			// the previous item is after the start-time of the current item.
			if (Ext.isDefined(prevRecord)) {
				if (prevRecord.get('end') >= record.get('start')) {
					// Update the previous block
					prevRecord.set('end', record.get('end'));
					// And we don't need the new block anymore
					sumBlockStore.remove(record);
					return true;
				}
			}

			lastBlock[record.get('status')] = record;
		}, this);
	},

	/**
	 * Fires after a new set of Records has been loaded.
	 * @param {Zarafa.common.freebusy.data.FreebusyBlockStore} store
	 * @param {Ext.data.Record[]} records The Records that were loaded
	 * @param {Object} options The loading options that were specified (see {@link #load} for details)
	 * @private
	 */
	onBlockLoad: function(store, records, options)
	{
		// Always start with a clean store
		this.sumBlockStore.removeAll();

		if (this.getUserStore().getCount() == 1) {
			// One user, still easy, sumBlockStore is the same as blockStore
			this.blockStore.each(function(record) {
				// We are not generating sumBlocks for the free status.
				var busyStatus = record.get('status');
				if (busyStatus !== Zarafa.core.mapi.BusyStatus.UNKNOWN && busyStatus !== Zarafa.core.mapi.BusyStatus.FREE) {
					this.sumBlockStore.add(this.createSumBlock(record));
				}
			}, this);
		} else {
			// Multiple users, tricky, merge the blockstore into the sumBlockStore
			this.mergeBlocksToSumBlockStore(this.blockStore.getRange(), this.sumBlockStore);
		}

		// Sort all sumblocks based on the status. This will force the
		// TENTATIVE records to be rendered before the BUSY which in turn is before
		// the OUTOFOFFICE. This in turn forces the browser to position the OUTOFOFFICE
		// divs on top of the BUSY blocks (which in turn are on top of TENTATIVE) when
		// the blocks overlap.
		this.sumBlockStore.sort('status', 'ASC');

		this.sumBlockStore.fireEvent('load', this.sumBlockStore, this.sumBlockStore.getRange(), {});
	},

	/**
	 * Fires when a Record has been removed from the Store
	 *
	 * NOTE: Because of buffering which is applied for the invocation event handler,
	 * this handler will only be called for the first remove event
	 * in a batch. This implies that we cannot rely on the record and
	 * index arguments.
	 *
	 * @param {Zarafa.common.freebusy.data.FreebusyBlockStore} store
	 * @param {Ext.data.Record} record The Record that was removed
	 * @param {Number} index The index at which the record was removed
	 * @private
	 */
	onBlockRemove: function(store, record, index)
	{
		this.onBlockLoad(store, store.getRange(), {});
	},

	/**
	 * Fires after a new set of Records has been loaded.
	 * @param {Zarafa.common.freebusy.data.FreebusyBlockStore} store
	 * @param {Ext.data.Record[]} records The Records that were loaded
	 * @param {Object} options The loading options that were specified (see {@link #load} for details)
	 * @private
	 */
	onSumBlockLoad: function(store, records, options)
	{
		// Always start with a clean store
		this.freeBlockStore.removeAll();

		if (store.getCount() > 0) {
			// FIXME: We should actually build this store while building
			// the busy blocks. This is most likely faster then doing it
			// separately.
			this.mergeBlocksToSumBlockStore(records, this.freeBlockStore, false);
		}

		this.loadSuggestionBlocks();
	},

	/**
	 * Fires when the {@link #nonWorkingHoursHidden} has been updated.
	 * This will updated the {@link #suggestionRange} accordingly.
	 * @param {Boolean} hideNonWorkingHours True to only show working hours.
	 * @private
	 */
	onShowWorkingHoursChange: function(hideNonWorkingHours)
	{
		this.setSuggestionDate(this.suggestionRange.getStartDate());
	},

	/**
	 * Fires when the {@link #userStore} has been updated. This will force
	 * a load on the store to automatically update the {@link #blockStore}.
	 * @param {Zarafa.core.data.IPMRecipientStore} newStore
	 * @param {Zarafa.core.data.IPMRecipientStore} oldStore
	 * @private
	 */
	onUserStoreChange: function(newStore, oldStore)
	{
		newStore.fireEvent('load', newStore, newStore.getRange());
	},

	/**
	 * Fires when the {@link #selectorRange} has been updated. This
	 * will update the {@link #suggestionRange} accordingly.
	 * @param {Zarafa.core.DateRange} newRange The new selected range.
	 * @param {Zarafa.core.DateRange} oldRange The old selected range.
	 * @private
	 */
	onSelectorRangeUpdate: function(newRange, oldRange)
	{
		// Check to see if the update was triggered by outside sources
		if(!this.updatingSelectorRangeExternally){
			this.fireEvent('selectorrangeupdate', newRange, oldRange);
		}
		// Reset the state
		this.updatingSelectorRangeExternally = false;
		this.setSuggestionDate(newRange.getStartDate(), newRange.getDuration());
	},

	/**
	 * Fires when the {@link #suggestionRange} has been updated. This
	 * will recalculate the {@link #suggestionBlockStore} accordingly.
	 * @param {Zarafa.core.DateRange} newRange The new selected range.
	 * @param {Zarafa.core.DateRange} oldRange The old selected range.
	 * @private
	 */
	onSuggestionRangeUpdate: function(newRange, oldRange)
	{
		this.loadSuggestionBlocks();
	},

	/**
	 * This will recalculate the suggestion blocks in the {@link #suggestionBlockStore}
	 * based on the information from the {@link #freeBlockStore} and the {@link #selectorRange}.
	 * @private
	 */
	loadSuggestionBlocks: function()
	{
		// Always start with a clean store
		this.suggestionBlockStore.removeAll();

		if (this.freeBlockStore.getCount() > 0) {
			var start = this.suggestionRange.getStartTime() / 1000;
			var end = this.suggestionRange.getDueTime() / 1000;
			var duration = this.selectorRange.getDuration(Date.SECOND);
			var interval = Ext.min([duration, 30 * 60]); // FIXME: make configurable

			// But what if the appointment takes 0 minutes..
			// That would be dumb, but we won't be fooled!
			if (interval <= 0) {
				interval = 30 * 60;
			}

			this.freeBlockStore.each(function(sumBlock) {
				var sumStart = sumBlock.get('start');
				var sumEnd = sumBlock.get('end');

				if (sumEnd < start || sumStart > end) {
					// The entire block falls before the requested range,
					// just keep looping until we find the desired range.
					return;
				} else if (sumStart > end) {
					// The entire block falls after the requested range
					// we don't need to do anything anymore.
					return false;
				} else if (sumStart <= start) {
					// The block overlap our range, our new start
					// time is the end time of this block.
					start = sumEnd;
				} else {
					// The entire block falls after our start range,
					// simply add a suggestionblock from start to the sumBlock start.
					this.suggestionBlockStore.add(this.createSuggestionBlocks(start, Ext.min([sumStart, end]), duration, interval));
					start = sumEnd;
				}
			}, this);

			// Check if we still have a leftover...
			if (start < end) {
				this.suggestionBlockStore.add(this.createSuggestionBlocks(start, end, duration, interval));
			}
		}

		this.suggestionBlockStore.fireEvent('load', this.suggestionBlockStore, this.suggestionBlockStore.getRange(), {});
	},

	/**
	 * Function can be used to check if all added attendees are free on particular timeslot.
	 * @param {Date} periodStartTime object of start time
	 * @param {Date} periodEndTime object of end time
	 * @param {Boolean} modifiedOnly True if only the modified attendees should be checked
	 * @return {Boolean} return true if all attendees are free else false.
	 */
	checkAttendeesBusyStatus: function(periodStartTime, periodEndTime, modifiedOnly)
	{
		var userStore = this.getUserStore();
		if (!userStore) {
			return false;
		}

		var modified = userStore.getRange();
		for (var i = 0, len = modified.length; i < len; i++) {
			var user = modified[i];
			if ((modifiedOnly !== true || user.phantom === true) && !user.isMeetingOrganizer()) {
				if (this.checkAttendeeBusyStatus(user.id, periodStartTime, periodEndTime)) {
					return true;
				}
			}
		}

		return false;
	},

	/**
	 * Check if the given user is free on the given timeslot
	 * @param {String} userid The userid of the user to check for availability
	 * @param {Date} periodStartTime object of start time
	 * @param {Date} periodEndTime object of end time
	 * @return {Boolean} return true if the attendee is free.
	 */
	checkAttendeeBusyStatus: function(userid, periodStartTime, periodEndTime)
	{
		var blockStore = this.getBlockStore();
		if(!blockStore) {
			return false;
		}

		// Ensure that only the current userid is shown
		blockStore.filter('userid', userid, false, true, true);

		// Sort on start date
		blockStore.sort('start', 'ASC');

		// We need timestamps rather then Date objects
		periodStartTime = periodStartTime.getTime() / 1000;
		periodEndTime = periodEndTime.getTime() / 1000;

		// Lets search the block for Blocks that overlap with the requested period.
		var busy = false;

		for (var index = 0, len = blockStore.getCount(); index < len; index++) {
			var record = blockStore.getAt(index);

			/*
			 * First we need to remove appointments which are occurring extremely before/after our
			 * selected time, because then we have only set of appointments which are overlapping/inside
			 * our time slot.
			 * For that to achieve we first need sort the records based on start time and then to find
			 * out sum block record whose end time is greater then our selected start time
			 * and start time is less then our selected end time then we can say that
			 * the selected time is not proper for all the attendees.
			 */
			// remove appointments occurring extremely before our selected time
			if (record.get('end') > periodStartTime) {
				// check if we are really interested in this block
				if (record.get('status') === Zarafa.core.mapi.BusyStatus.FREE || record.get('status') === Zarafa.core.mapi.BusyStatus.UNKNOWN) {
					continue;
				}

				// before we have sorted the records based on start time so we will be having a record which either
				// overlaps current selected time or doesn't overlap it
				// below condition will check if the record overlaps current selected time
				if (record.get('start') < periodEndTime) {
					busy = true;
				}

				// if the above condition is not satisfied then we can say that
				// record is not overlapping current selected time and therefore
				// break the loop
				break;
			}
		}

		blockStore.clearFilter();

		return busy;
	}
});
Ext.namespace('Zarafa.common.freebusy.data');

/**
 * @class Zarafa.common.freebusy.data.TimelineSelector
 * @extends Ext.util.Observable
 */
Zarafa.common.freebusy.data.TimelineSelector = Ext.extend(Ext.util.Observable,
{
	// Private
	// Properties used to identify the mouse position.
	MOUSE_ON_EDGE_LEFT: 1,
	MOUSE_ON_CENTER: 2,
	MOUSE_ON_EDGE_RIGHT: 3,

	// Private
	// When set to true the user is in the middle of a selection.
	selecting: false,
	// Private
	// Timestamp of the start of the selection (not necessarily the start date).
	selectionStart: null,
	// Private
	// Timestamp of the end of the selection (necessarily the end date).
	selectionEnd: null,
	// Private
	// DateRange used by the TimelineSelector
	selectorRange: null,

	/**
	 * @cfg {Number} dragSelectionEdgeArea
	 * The number of pixels from the edge the cursor snaps to the edge of the selection and drags
	 * either the start date or end date. If the cursor is further away from the either egdge than
	 * the supplied number than the a click will create a whole new selection (defaults to 10).
	 */
	dragSelectionEdgeArea: 10,

	/**
	 * @constructor
	 * @param {Object} config The configuration options.
	 */
	constructor: function(config)
	{
		Ext.apply(this, config || {});
		Zarafa.common.freebusy.data.TimelineSelector.superclass.constructor.call(this, config);
	},

	/**
	 * Initializes the selector
	 * @param {Zarafa.common.freebusy.ui.TimelineView} parent Object of the parent TimelineView
	 */
	init: function(parent){
		this.parent = parent;

		this.masterTpl = new Ext.XTemplate(
			'<div class="x-freebusy-selector">',
			'</div>',
			{
				// Format functions like capitalize in the Ext.util.Format are not
				// used in this template anyways. Speeds up the apply time.
				disableFormats: true
			}
		);

		/**
		 * We can only render the selector when the TimelineView has been rendered. The TimelineView
		 * can also rerender the timeline to show/hide the non-working hours. In that case the
		 * render event is not called, but the rendertimeline event is always called when rendering
		 * the timeline HTML.
		 */
		this.parent.on("rendertimeline", this.onParentRenderTimeline, this);
		this.parent.on("timelinemousedown", this.onParentTimelineMouseDown, this);
		this.parent.on("timelinemousemove", this.onParentTimelineMouseMove, this);
		this.parent.on("timelinemouseup", this.onParentTimelineMouseUp, this);

		this.bindSelectorRange(this.parent.model.getSelectorRange(), true);
	},

	/**
	 * Registers update event to trigger visualizing changes in TimelineSelector
	 * @param {Zarafa.core.DateRange} selectorRange selectorRange
	 * @param {Boolean} initial Internally used to indicate that this is the first call after render.
	 */
	 bindSelectorRange: function(selectorRange, initial)
	{
		if(this.selectorRange && !initial){
			this.selectorRange.un('update', this.onSelectorRangeUpdate, this);
		}
		this.selectorRange = selectorRange;
		if(this.selectorRange){
			this.selectorRange.on({
				scope: this,
				// Listening in to updates in the selector daterange.
				update: this.onSelectorRangeUpdate
			});
		}
	},

	/**
	 * Renders the HTML needed for the selector and positions it. The parent is accessed to get the
	 * HTML Element to render the selector in.
	 */
	render: function(){
		// The parent contains a container to render the selector in
		var parentSelectorContainer = this.parent.getSelectorContainer();

		// Render the HTML Elements
		this.masterTpl.overwrite(parentSelectorContainer);
		this.selectorElem = Ext.get(parentSelectorContainer.dom.firstChild);
		this.selectorElem.setVisibilityMode(Ext.Element.DISPLAY);

		this.selectorElem.on('mousemove', this.onMouseMoveSelector, this);

		// Position the selector by using the selector daterange
		this.positionSelector(this.selectorRange);
	},

	/**
	 * @todo Perhaps this function does not need that argument any more?
	 * Position the selector on the start and end dates. When the selector indicates a daterange
	 * that is not visible on the timeline, the selector will be hidden.
	 * @param {Zarafa.core.DateRange} selectorRange Daterange for the new position of the selector.
	 */
	positionSelector: function(selectorRange){
		// Check whether the selector has to be shown inside the daterange of the timeline
		if(this.parent.model.getDateRange().overlaps(selectorRange)){
			// Transform start/end date into timestamps
			var start = selectorRange.getStartDate().getTime()/1000;
			var end = selectorRange.getDueDate().getTime()/1000;

			// Get the leftoffset of the start of the selector
			var pixelOffsetLeft = this.parent.findBlockPixelOffset(start, true);
			this.selectorElem.setLeft(pixelOffsetLeft);

			// Get the leftoffset of the end of the selector
			var pixelOffset = this.parent.findBlockPixelOffset(end, false);
			this.selectorElem.setWidth( pixelOffset - pixelOffsetLeft );
			this.selectorElem.setVisible(true);
		} else {
			// Hide the element when it is outside the visible period
			this.selectorElem.setVisible(false);
		}
	},

	/**
	 * Scrolls the timelineView to the date that is selected in the selectorRange.
	 */
	scrollTimelineToSelection: function(){
		this.parent.scrollDateIntoView( this.selectorRange );
	},

	/**
	 * Fired when the selector daterange is modified. When the daterange is changed the selector
	 * needs to update UI component to visualize the change.
	 * @param {Zarafa.core.DateRange} selectorRange Changed daterange.
	 */
	onSelectorRangeUpdate: function(daterange){
		this.positionSelector(daterange);

		if(!this.selecting){
			this.scrollTimelineToSelection();
		}
	},

	/**
	 * Fired when the parent TimelineView renders the timeline. When the TimelineView renders the UI
	 * the selector needs to render the UI part.
	 * @param {Zarafa.common.freebusy.ui.TimelineView} timelineView TimelineView
	 */
	onParentRenderTimeline: function(timelineView){
		this.render();
	},

	/**
	 * Fired when the parent TimelineView detects a mousedown event.
	 * @param {Ext.EventObject} evt The {@link Ext.EventObject} encapsulating the DOM event.
	 * @param {HtmlElement} target The target of the event.
	 * @param {Object} cfg The options configuration passed to the {@link #addListener} call.
	 */
	onParentTimelineMouseDown: function(evt, target, cfg){
		// Check to see if the mouse event is not done on the scrollbar
		if(this.isMouseEventOnScrollbar(evt, target)) {
			return true;
		}

		this.selecting = true;

		//Normalize the coordinate to the correct coordinate for the timeline and not the page
		var timelineElem = this.parent.bodyElem;
		var timestampCoordX = evt.getPageX() - timelineElem.getLeft() + timelineElem.getScroll().left;

		// Get the position of the mouse to determine whether it is on the outer edges or in the center.
		var mousePos = this.getMousePosition(evt.getPageX());

		// Transform X coordinate into a timestamp by using the TimelineView's methods.
		var selectionClick = this.parent.findTimestampByTimelineXCoord(timestampCoordX);

		var startDate, endDate;
		switch(mousePos){
			case this.MOUSE_ON_EDGE_LEFT:
				// Leave the end date as the start point for the selection
				endDate = this.selectorRange.getDueDate();
				this.selectionStart = endDate.getTime()/1000;
				break;
			case this.MOUSE_ON_EDGE_RIGHT:
				// Leave the start date as the start point for the selection
				startDate = this.selectorRange.getStartDate();
				this.selectionStart = startDate.getTime()/1000;
				break;
			default:
				// Snap the timestamp for the start to a half hour slot
				startDate = new Date(selectionClick*1000);
				startDate.round(Date.MINUTE, 30);
				this.selectionStart = startDate.getTime() / 1000;
				// Make the appointment duration 30 minutes by default
				endDate = startDate.add(Date.MINUTE, 30);
				this.selectorRange.set(startDate, endDate);
		}
		evt.preventDefault();
	},

	/**
	 * Fired when the parent TimelineView detects a mousemove event.
	 * @param {Ext.EventObject} evt The {@link Ext.EventObject} encapsulating the DOM event.
	 * @param {HtmlElement} target The target of the event.
	 * @param {Object} cfg The options configuration passed to the {@link #addListener} call.
	 */
	onParentTimelineMouseMove: function(evt, target, cfg){
		if(this.selecting){
			//Normalize the coordinate to the correct coordinate for the timeline and not the page
			var timelineElem = this.parent.bodyElem;
			var timestampCoordX = evt.getPageX() - timelineElem.getLeft() + timelineElem.getScroll().left;

			this.selectionEnd = this.parent.findTimestampByTimelineXCoord(timestampCoordX);

			// Snap the timestamp to a half hour slot
			var endDate = new Date(this.selectionEnd*1000);
			endDate.round(Date.MINUTE, 30);
			this.selectionEnd = endDate.getTime() / 1000;

			// Check if the range does not have a duration of zero
			if(this.selectionStart !== this.selectionEnd){
				var selectorStartDate, selectorEndDate;

				// Check if the start date is before the end date and swap if needed
				if(this.selectionStart <= this.selectionEnd){
					selectorStartDate = new Date(this.selectionStart*1000);
					selectorEndDate = endDate;
				} else {
					selectorStartDate = endDate;
					selectorEndDate = new Date(this.selectionStart*1000);
				}

				// Check if the start date or end date have been changed
				var startDateChanged = (this.selectorRange.getStartDate().getTime() !== selectorStartDate.getTime());
				var dueDateChanged = (this.selectorRange.getDueDate().getTime() !== selectorEndDate.getTime());
				if(startDateChanged || dueDateChanged){
					this.selectorRange.set(selectorStartDate, selectorEndDate);
				}
			}

			evt.preventDefault();
		}
	},

	/**
	 * Fired when the parent TimelineView detects a mouseup event.
	 * @param {Ext.EventObject} evt The {@link Ext.EventObject} encapsulating the DOM event.
	 * @param {HtmlElement} target The target of the event.
	 * @param {Object} cfg The options configuration passed to the {@link #addListener} call.
	 */
	onParentTimelineMouseUp: function(evt, target, cfg){
		if(this.selecting){
			this.selecting = false;
			evt.preventDefault();
		}
	},

	onMouseMoveSelector: function(evt, target, cfg)
	{
		if(!this.selecting){
			var mousePos = this.getMousePosition(evt.getPageX());
			switch(mousePos){
				case this.MOUSE_ON_EDGE_LEFT:
				case this.MOUSE_ON_EDGE_RIGHT:
					this.selectorElem.setStyle('cursor', 'w-resize');
					break;
				default:
					this.selectorElem.setStyle('cursor', '');
			}
		}
	},

	getMousePosition: function(clickX)
	{
		var selectorX = this.selectorElem.getXY()[0];
		var rightSelectorY = selectorX + this.selectorElem.getWidth();

		if(Math.abs(clickX - selectorX) < this.dragSelectionEdgeArea){
			return this.MOUSE_ON_EDGE_LEFT;
		} else if(Math.abs(rightSelectorY - clickX) < this.dragSelectionEdgeArea){
			return this.MOUSE_ON_EDGE_RIGHT;
		} else {
			return this.MOUSE_ON_EDGE_CENTER;
		}
	},

	/**
	 * Checks whether the mouse event takes place on a scrollbar or whether it takes place inside
	 * the element. Returns true if it takes plae on the scrollbar.
	 * @param {Ext.EventObject} evt The {@link Ext.EventObject} encapsulating the DOM event.
	 * @param {HtmlElement} target The target of the event.
	 * @return {Boolean} When mouse clicked on scrollbar returns true, otherwise false.
	 * @private
	 */
	isMouseEventOnScrollbar: function(evt, target){
		// Get the bodyElem of the TimelineView because this is the element that contains the scrollbars
		var scrollContainer = this.parent.bodyElem.dom;
		// Prevent the selector from making a selection when you are only dragging the scrollbar.
		if(evt && evt.browserEvent && scrollContainer){
			var topleft = Ext.get(scrollContainer).getXY();
			if(evt.browserEvent.clientX-topleft[0] > scrollContainer.clientWidth || evt.browserEvent.clientY-topleft[1] > scrollContainer.clientHeight) {
				// Clicking outside viewable area -> must be a click in the scrollbar, allow default action.
				return true;
			}
		}
		return false;
	}
});
Ext.namespace('Zarafa.common.freebusy.ui');

/**
 * @class Zarafa.common.freebusy.ui.FreebusyPanel
 * @extends Ext.Panel
 * @xtype zarafa.freebusypanel
 */
Zarafa.common.freebusy.ui.FreebusyPanel = Ext.extend(Ext.Panel, {
	/**
	 * @cfg {Zarafa.common.freebusy.data.FreebusyBlockStore} blockStore
	 * The {@link Ext.data.Store} object that handles the freebusy blocks displayed on the timeline.
	 */
	blockStore: null,
	/**
	 * @cfg {Ext.data.Store} userStore
	 * The {@link Ext.data.Store} object that handles the list of users loaded. It can be a normal
	 * store, but it is preferably an {@link Zarafa.core.data.IPMRecipientStore}.
	 */
	userStore: null,
	/**
	 * @cfg {Zarafa.common.freebusy.data.FreebusyModel} model
	 * The model that keeps track of the userStore, dates, etc.
	 */
	model: null,
	/**
	 * @cfg {Object} userlistConfig The configuration object which will be applied
	 * to the {@link Zarafa.common.freebusy.ui.UserListView UserListView} object.
	 */
	userlistConfig: null,
	/**
	 * @cfg {Object} timelineConfig The configuration object which will be applied
	 * to the {@link Zarafa.common.freebusy.ui.TimelineView TimelineView} object.
	 */
	timelineConfig: null,
	/**
	 * @cfg {Object} suggestionConfig The configuration object which will be applied
	 * to the {@link Zarafa.common.freebusy.ui.SuggestionListView SuggestionListView} object.
	 */
	suggestionConfig: null,
	/**
	 * @cfg {Object} legendaConfig The configuration object which will be applied
	 * to the {@link Ext.Panel legendaView} object.
	 */
	legendaConfig: null,
	/**
	 * @cfg {Boolean} showUserList
	 * When set to true it will show the userlist to the left. When set to false it will hide it
	 * (defaults to true).
	 */
	showUserList: true,
	/**
	 * @cfg {Boolean} showSuggestionList
	 * When set to true it will show the Suggestionlist to the left. When set to false it will hide it
	 * (defaults to true).
	 */
	showSuggestionList: true,
	/**
	 * @cfg {Boolean} editable
	 * When set to false it will make the userlist non-editable (defaults to true).
	 */
	editable: true,
	/**
	 * @cfg {Boolean} showLegenda
	 * When set to true it will show the legenda at the bottom. When set to false it will hide it
	 * (defaults to true).
	 */
	showLegenda: true,
	/**
	 * @cfg {Number} headerHeight
	 * Height of the header that will be set for the {@link Zarafa.common.freebusy.ui.UserlistView} and the
	 * {@link Zarafa.common.freebusy.ui.TimelineView} (defaults to 50).
	 */
//	headerHeight: 50,
	// Private
	// Height of inputfield that will be shown in the {@link Zarafa.common.freebusy.ui.UserlistView}.
	inputfieldHeight: 20,
	/**
	 * @cfg {Boolean} initialScrollToCurrentDay
	 * When set to true it will scroll to the current day or the selected period. This is done after
	 * the initial laying out (Defaults to true).
	 */
	initialScrollToCurrentDay: true,
	// Private
	// The afterlayout event triggers the scrolling of the timeline to the current day or selection.
	// This flag indicates whether we should react the afterlayout event or whether this has already
	// happened.
	initialScrollDone: false,

	/**
	 * @constructor
	 * @param {Object} config The configuration options.
	 */
	constructor: function(config)
	{
		config = config || {};

		var modelConfig = config.modelConfig || {};
		Ext.applyIf(modelConfig, {
			userStore: config.userStore,
			blockStore: config.blockStore
		});
		var model = new Zarafa.common.freebusy.data.FreebusyModel(modelConfig);

		Ext.applyIf(config, {
			layout: 'border',
			border: true,

			blockStore: model.getBlockStore(),
			userStore: model.getUserStore(),
			model: model,

			userlistConfig: {},
			timelineConfig: {},
			suggestionConfig: {},
			legendaConfig: {}
		});

		Zarafa.common.freebusy.ui.FreebusyPanel.superclass.constructor.call(this, config);

		this.add([
			this.createUserListView(),
			this.createSuggestionView(),
			this.createTimelineView(),
			this.createLegendaView()
		]);

		this.initFreebusyEvents();
	},

	/**
	 * This will create the listview panel containing all the users for
	 * which the freebusy should be shown.
	 * @return {Object} Configuration object for the user listview
	 * @private
	 */
	createUserListView: function()
	{
		return {
			xtype: 'panel',
			ref: 'userListView',
			region: 'west',
			layout: 'fit',
			title: _('Select attendees'),
			hidden: (!this.showUserList),
			cls: 'x-freebusy-userlist',
			footer: true,
			footerCfg: {
				tag: 'div',
				cls: 'x-panel-footer'
			},
			border: true,
			width: 200,
			collapsible: true,
			split: true,
			items: [Ext.applyIf(this.userlistConfig, {
				xtype: 'zarafa.freebusyuserlistview',

				model: this.model,

				inputFieldHeight: (this.editable) ? this.inputfieldHeight : 0,
				editable: this.editable
			})]
		};
	},

	/**
	 * This will create the suggestion listview which contains all possible
	 * times for an appointment for the selected users on a given day.
	 * @return {Object} Configuration object for the suggestion listview
	 * @private
	 */
	createSuggestionView: function()
	{
		return {
			xtype: 'panel',
			ref: 'suggestionListView',
			region: 'east',
			layout: 'fit',
			title: _('Suggested Times'),
			hidden: (!this.showSuggestionList),
			cls: 'zarafa-freebusy-suggestionlist',
			footer: true,
			footerCfg: {
				tag: 'div',
				cls: 'x-panel-footer'
			},
			border: true,
			width: 200,
			collapsible: true,
			split: true,
			items: [Ext.applyIf(this.suggestionConfig, {
				xtype: 'zarafa.freebusysuggestionlistpanel',

				model: this.model,

				listeners: {
					select: this.onSelectSuggestion,
					dateselect: this.onSuggestionDateSelect,
					scope: this
				}
			})]
		};
	},

	/**
	 * This will create the timeline panel which contains all the freebusy blocks
	 * for each user which has been selected.
	 * @return {Object} Configuration object for the timelineview
	 * @private
	 */
	createTimelineView: function()
	{
		return Ext.applyIf(this.timelineConfig, {
			xtype: 'zarafa.freebusytimelineview',
			ref: 'timelineView',
			region: 'center',
			blockStore: this.blockStore,
			model: this.model,
			selector: new Zarafa.common.freebusy.data.TimelineSelector(),
			extraBodyHeight: (this.editable) ? this.inputfieldHeight : 0,
			// TODO: Make this configurable and toggable
			hideNonWorkingHours: true,
			listeners: {
				bodyscroll: this.onTimelineScroll,
				scope: this
			}
		});
	},

	/**
	 * This will create the legenda panel which contains all the possible
	 * block colors which will be used in the timelineview.
	 * @return {Object} configuration object for the legenda
	 * @private
	 */
	createLegendaView: function()
	{
		return Ext.applyIf(this.legendaConfig, {
			xtype: 'container',
			region: 'south',
			border: false,
			layout: 'hbox',
			hidden: (!this.showLegenda),
			autoHeight: true,
			cls: 'x-freebusy-timeline-container x-freebusy-legenda',
			items: [{
				xtype: 'container',
				cls: 'x-freebusy-timeline-block-busy',
				width: 24,
				height: 24
			},{
				xtype: 'displayfield',
				value: _('Busy'),
				hideLabel: true,
				autoWidth: true
			},{
				xtype: 'container',
				cls: 'x-freebusy-timeline-block-tentative',
				width: 24,
				height: 24
			},{
				xtype: 'displayfield',
				value: _('Tentative'),
				hideLabel: true,
				autoWidth: true
			},{
				xtype: 'container',
				cls: 'x-freebusy-timeline-block-outofoffice',
				width: 24,
				height: 24
			},{
				xtype: 'displayfield',
				value: _('Out of Office'),
				hideLabel: true,
				autoWidth: true
			},{
				xtype: 'container',
				cls: 'x-freebusy-timeline-block-workingelsewhere',
				width: 24,
				height: 24
			},{
				xtype: 'displayfield',
				value: _('Working Elsewhere'),
				hideLabel: true,
				autoWidth: true
			},{
				xtype: 'container',
				cls: 'x-freebusy-timeline-block-blur',
				width: 24,
				height: 24
			},{
				xtype: 'displayfield',
				value: _('No Information'),
				hideLabel: true,
				autoWidth: true
			}]
		});
	},

	/**
	 * Initialize freebusy events
	 * @private
	 */
	initFreebusyEvents: function()
	{
		// Register to afterlayout to support the timeline in scrolling to the current day
		this.on('afterlayout', this.onAfterLayout, this);
	},

	/**
	 * Returns the model.
	 * @return {Zarafa.common.freebusy.data.FreebusyModel} Model
	 */
	getModel: function()
	{
		return this.model;
	},

	/**
	 * Change the {@link #editable} field on this panel
	 * @param {Boolean} value The new editable status
	 */
	setEditable: function(value)
	{
		if (this.editable !== value) {
			this.editable = value;
			this.userListView.get(0).setEditable(value);
		}
	},

	/**
	 * Is called when the layouting is done and the sizes are calculated. When the timeline needs to
	 * be scrolled to the current day or selection, this listener triggers the scrolling. It looks
	 * for the first usable afterlayout event and the ones after that will not trigger any scrolling
	 * anymore. The first usable event is the one where the container has a width larger than zero.
	 * @param {Ext.Container} container Container
	 * @private
	 */
	onAfterLayout: function(container)
	{
		// Calculate the desired header size, we have the configured header size, but we need
		// to subtract any padding/borders and margins which are applied to the header.
		if (this.userListView) {
			var height = this.headerHeight;

			height -= this.userListView.header.getMargins('tb');
			height -= this.userListView.header.getBorderWidth('tb');
			height -= this.userListView.header.getPadding('tb');

			this.userListView.header.setStyle('height', height + 'px');

			// Ext.getScrollBarWidth() is always off by 2 pixels, this is a hardcoded
			// thing, so we can fix it hardcoded...
			this.userListView.footer.setStyle('height', (Ext.getScrollBarWidth() - 2) + 'px');
		}
		if (this.suggestionListView) {
			var height = this.headerHeight;

			height -= this.userListView.header.getMargins('tb');
			height -= this.userListView.header.getBorderWidth('tb');
			height -= this.userListView.header.getPadding('tb');

			this.suggestionListView.header.setStyle('height', height + 'px');
			// Ext.getScrollBarWidth() is always off by 2 pixels, this is a hardcoded
			// thing, so we can fix it hardcoded...
			this.suggestionListView.footer.setStyle('height', (Ext.getScrollBarWidth() - 2) + 'px');
		}

		if (this.initialScrollToCurrentDay) {
			/**
			 * We only need to scroll to the current day or selection once at startup. We have to
			 * look for the first afterlayout event with a container that has a width larger than 0.
			 */
			 // @TODO every time on layout we must need to scroll timeline to current selection as "scroll" event is not working properly in IE.
			if (!Ext.isIE && (this.initialScrollDone || this.timelineView.getWidth() === 0)) {
				return;
			} else {
				this.initialScrollDone = true;
			}

			this.scrollTimelineToSelection();
		}
	},

	/**
	 * Scroll the TimelineView to the current Selector date, or to
	 * the current date if no selector is available.
	 */
	scrollTimelineToSelection: function()
	{
		if (this.timelineView.selector) {
			this.timelineView.selector.scrollTimelineToSelection();
		} else {
			this.timelineView.scrollDateIntoView(new Date());
		}
	},

	/**
	 * Scroll the TimelineView to the provided date argument.
	 * @param {Date} date Date object
	 */
	scrollTimelineToDate: function(date)
	{
		this.timelineView.scrollDateIntoView(date);
	},

	/**
	 * Used to synchronize the scrolling height of the timeline view with the user list
	 * @param {Object} scrollPos Scroll position
	 * @private
	 */
	onTimelineScroll: function(scrollPos)
	{
		this.userListView.body.scrollTo('top', scrollPos.top);
	},

	/**
	 * Fired when the user clicks on a suggestion from the SuggestionPanel.
	 * This will update the Selector DateRange to match the suggestion.
	 * @param {Zarafa.common.freebusy.ui.SuggestionListPanel} panel The panel which raised the event
	 * @param {Zarafa.common.freebusy.data.FreebusyBlockRecord} record The selected suggestion
	 * @private
	 */
	onSelectSuggestion: function(panel, record)
	{
		var start = record.get('start') * 1000;
		var end = record.get('end') * 1000;

		this.getModel().getSelectorRange().setTime(start, end);
	},

	/**
	 * Called when the {@link Zarafa.common.freebusy.ui.SuggestionListPanel SuggestionListPanel} fires
	 * the {@link Zarafa.common.freebusy.ui.SuggestionListPanel#dateselect dateselect} event.
	 * @param {Zarafa.common.freebusy.ui.SuggestionListPanel} this This panel
	 * @param {Date} date The selected date
	 * @private
	 */
	onSuggestionDateSelect: function(suggestionListPanel, date)
	{
		this.scrollTimelineToDate(date);
	}

});

Ext.reg('zarafa.freebusypanel',Zarafa.common.freebusy.ui.FreebusyPanel);
Ext.namespace('Zarafa.common.freebusy.ui');

/**
 * @class Zarafa.common.freebusy.ui.SuggestionListPanel
 * @extends Ext.Panel
 * @xtype zarafa.freebusysuggestionlistpanel
 *
 * The SuggestionList shows the list of suggested times
 * at which an appointment can be held.
 */
Zarafa.common.freebusy.ui.SuggestionListPanel = Ext.extend(Ext.Panel, {
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

		config = Ext.applyIf(config, {
			layout: {
				type: 'vbox',
				align: 'stretch'
			},
			border: false,
			items: [{
				xtype: 'panel',
				border: true,
				autoHeight: true,
				items: [{
					xtype: 'container',
					border: false,
					autoHeight: true,
					width: 195,
					layout: 'fit',
					items: [{
						xtype: 'datepicker',
						ref: '../../suggestionDate',
						showToday: false,
						minDate: config.model ? config.model.getDateRange().getStartDate() : null,
						minText: _('No freebusy information available for this date'),
						maxDate: config.model ? config.model.getDateRange().getDueDate() : null,
						maxText: _('No freebusy information available for this date'),
						style: 'border: none',
						listeners: {
							select: this.onDateSelect,
							scope: this
						}
					}]
				}]
			},{
				xtype: 'panel',
				flex: 1,
				layout: 'fit',
				cls: 'suggestionlist',
				border: false,
				items: [{
					xtype: 'zarafa.freebusysuggestionlistview',
					autoScroll: true,
					model: config.model,
					listeners: {
						click: this.onSuggestionClick,
						scope: this
					}
				}]
			}]
		});

		this.addEvents([
			/**
			 * @event dateselect
			 * This event is fired when a date is selected from the datepicker.
			 * @param {Zarafa.common.freebusy.ui.SuggestionListPanel} this This panel
			 * @param {Date} date The selected date
			 */
			'dateselect',
			/**
			 * @event select
			 * This event is fired when a suggestion has been selected from the
			 * main {@link Zarafa.common.freebusy.ui.FreebusySuggestionListView FreebusySuggestionListView}
			 * @param {Zarafa.common.freebusy.ui.SuggestionListPanel} this This panel
			 * @param {Zarafa.common.freebusy.data.FreebusyBlockRecord} record The record containing the suggestion which was selected
			 */
			'select'
		]);

		Zarafa.common.freebusy.ui.SuggestionListPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Initialize events
	 * @private
	 */
	initEvents: function()
	{
		Zarafa.common.freebusy.ui.SuggestionListPanel.superclass.initEvents.call(this);

		this.mon(this.model.getDateRange(), 'update', this.onDateRangeUpdate, this);
		this.mon(this.model.getSuggestionRange(), 'update', this.onSuggestionRangeUpdate, this);
	},

	/**
	 * Event handler which is raised when a suggestion from the {@link Ext.DataView DataView} has been
	 * clicked. This will search for the corresponding record and raise the {@link #selected} event.
	 * @param {Ext.DataView} view The DataView which raised the event
	 * @param {Number} index The index of the item which was clicked
	 * @param {HTMLElement} el The HTML element which was clicked
	 * @param {Object} event The event object
	 * @private
	 */
	onSuggestionClick: function(view, index, el, event)
	{
		var record = view.getStore().getAt(index);
		this.fireEvent('select', this, record);
	},

	/**
	 * Event handler which is raised when a date has been selected from the {@link Ext.DatePicker DatePicker}.
	 * This will update the suggestionlist to show the suggestions for the given {@link Ext.Date date}. It
	 * will also fire the {@link #dateselect} event.
	 * @param {Ext.DatePicker} datepicker The datepicker object which raised the event
	 * @param {Ext.Date} date The selected date
	 * @private
	 */
	onDateSelect: function(datepicker, date)
	{
		this.model.setSuggestionDate(date);

		this.fireEvent('dateselect', this, date);
	},

	/**
	 * Event handler which is raised when the daterange of the freebusy has been updated,
	 * this will update the minValue/maxValue of the {@link Ext.DatePicker DatePicker}
	 * accordingly.
	 * @param {Zarafa.core.DateRange} newRange The new DateRange
	 * @param {Zarafa.core.DateRange} oldRange The old DateRange
	 * @private
	 */
	onDateRangeUpdate: function(newRange, oldRange)
	{
		this.suggestionDate.setMinDate(newRange.getStartDate());
		this.suggestionDate.setmaxDate(newRange.getDueDate());
	},

	/**
	 * Event handler which is raised when the suggestion daterange has been updated, this will
	 * update the {@link Ext.DatePicker DatePicker} to display the new value.
	 * @param {Zarafa.core.DateRange} newRange The new DateRange
	 * @param {Zarafa.core.DateRange} oldRange The old DateRange
	 * @private
	 */
	onSuggestionRangeUpdate: function(newRange, oldRange)
	{
		this.suggestionDate.setValue(newRange.getStartDate());
	}
});

Ext.reg('zarafa.freebusysuggestionlistpanel', Zarafa.common.freebusy.ui.SuggestionListPanel);
Ext.namespace('Zarafa.common.freebusy.ui');

/**
 * @class Zarafa.common.freebusy.ui.SuggestionListView
 * @extends Ext.DataView
 * @xtype zarafa.freebusysuggestionlistview
 *
 * The SuggestionList shows the list of suggested times
 * at which an appointment can be held, this view will only
 * display the possible times for a particular date. Loading
 * of data (and date selection) occurs outside of this view.
 */
Zarafa.common.freebusy.ui.SuggestionListView = Ext.extend(Ext.DataView, {

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

		var suggestionBlockStore = config.model ? config.model.getSuggestionBlockStore() : undefined;

		Ext.applyIf(config, {
			deferEmptyText: false,
			emptyText: '<div class="zarafa-freebusy-suggestionrow emptytext">' + _('No suggestions available') + '</div>',
			tpl: new Ext.XTemplate(
				'<tpl for=".">',
				'<div class="x-btn zarafa-freebusy-suggestionrow" id="zarafa-freebusy-suggestionrow-{[xindex]}">',
					'<div class="x-btn-small x-btn-icon-small-left">',
						'<em class="" class="x-unselectable" unselectable="on">',
							'<button type="button" class=" x-btn-text">',
								'<img class="x-freebusy-suggestionrow-icon" src="' + Ext.BLANK_IMAGE_URL + '" alt="" />',
								'{[this.formatTime(values.start)]} - {[this.formatTime(values.end)]}',
							'</button>',
						'</em>',
					'</div>',
				'</div>',
				'</tpl>',
			{
				formatTime: function(time)
				{
					// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
					return new Date(time * 1000).formatDefaultTime();
				}

			}),
			store: suggestionBlockStore,
			itemSelector: 'div.zarafa-freebusy-suggestionrow'
		});

		Zarafa.common.freebusy.ui.SuggestionListView.superclass.constructor.call(this, config);

		this.mon(this.model, 'suggestionblockstorechange', this.onSuggestionBlockStoreChange, this);
	},

	/**
	 * Event listener which is triggered when a new SuggestionBlockStore has been
	 * loaded on the {@link Zarafa.common.freebusy.data.FreebusyModel FreebusyModel} class.
	 *
	 * @param {Ext.data.Store} store The new store which has been set
	 */
	onSuggestionBlockStoreChange: function(store)
	{
		this.bindStore(store);
	}
});

Ext.reg('zarafa.freebusysuggestionlistview', Zarafa.common.freebusy.ui.SuggestionListView);
Ext.namespace('Zarafa.common.freebusy.ui');

/**
 * @class Zarafa.common.freebusy.ui.TimelineView
 * @extends Ext.BoxComponent
 * @xtype zarafa.freebusytimelineview
 */
Zarafa.common.freebusy.ui.TimelineView = Ext.extend(Ext.BoxComponent,
{
	/**
	 * @cfg {Zarafa.common.freebusy.data.FreebusyModel} model
	 * <b>This is a required setting</b>. The {@link Zarafa.common.freebusy.data.FreebusyModel} holds the data for the
	 * Freebusy component.
	 */
	model: null,
	/**
	 * @cfg {Zarafa.common.freebusy.data.TimelineSelector} selector
	 * The used in the timeline.
	 */
	selector: null,
	/**
	 * @cfg {Number} headerHeight
	 * The height of the header (defaults to 50).
	 */
	headerHeight: 64,
	/**
	 * @cfg {Number} defaultHourCellWidth
	 * The width of the cells displaying an hour on the timeline at 100% zoomlevel (defaults to 60).
	 */
	defaultHourCellWidth: 60,
	/**
	 * @cfg {Number} bufferTimesViewportWidth
	 * To determine what range should be loaded this property is used to to see how many times the
	 * width of the viewport should be loaded in days. If the viewport is 100px wide and the
	 * bufferTimesViewportWidth is set to 5, the range that will be loaded is 500px.(defaults to 5).
	 */
	bufferTimesViewportWidth: 5,	// Times the width of viewport, not days
	/**
	 * The daysMap is an array mapping of all the days that the freebusy timeline will show. For
	 * each day there is an object defined with the following information.
	 * {
	 *  label: Label of the day "Tuesday 12 February 2010"(String)
	 *  currentDay: Indication whether this day is the current day (Boolean)
	 *  timestamp: Timestamp of the start of the day (Number)
	 *  displayNodeHeader: HTML node of the element that shows the day in the header, if not displayed set to false (HTML Element|boolean}
	 *  displayNodeBody: HTML node of the element that shows the day in the body, if not displayed set to false (HTML Element|boolean}
	 *  leftPos: The pixel offset for this day
	 * }
	 * This property is set by the buildDayMapping method.
	 */
	daysMap: null,
	/**
	 * The hoursEachDayMap is an array mapping of the hours that have to be shown for each day. Each
	 * day has the an object defined with the following information.
	 * {
	 *  label: Label of the hour "12:00"(String)
	 *  startDayOffset: Number of seconds since start of the day (Number)
	 *  workinghour: Indication whether this hour is a working hour(Boolean)
	 * }
	 * This property is set by the buildDayMapping method.
	 */
	hoursEachDayMap: null,
	/**
	 * @cfg {Number} daySpacing
	 * The spacing between two days in pixels (defaults to 10).
	 */
	daySpacing: 3,
	/**
	 * @cfg {Number} daySpacing
	 * The width of the borders. The cellspacing in the tables is used to create the borders (defaults to 1).
	 * Changing this property to another value alone is not without issues.
	 */
	borderSpacing: 0,
	/**
	 * @cfg {Number} blockRowHeight
	 * The height of the block rows (defaults to 22).
	 */
	blockRowHeight: 30,
	/**
	 * @cfg {Number} sumBlockRowHeight
	 * The height of the sumblock rows (defaults to 10).
	 */
	sumBlockRowHeight: 12,
	/**
	 * @cfg {Number} extraBodyHeight
	 * Height that is added to the body container to match the height of the userlist body (defaults to 0).
	 */
	extraBodyHeight: 0,
	/**
	 * @cfg {Number} workingHoursStart
	 * Defines the first working hour. This is defined as the number of minutes
	 * since the start of the day. (defaults to the default settings value for
	 * 'zarafa/v1/main/start_working_hour').
	 */
	workingHoursStart: 0,
	/**
	 * @cfg {Number} workingHoursEnd
	 * Defines the hour the working hours range ends. This is defined as the number of minutes
	 * since the start of the day. (defaults to the default settings value for
	 * 'zarafa/v1/main/end_working_hour').
	 */
	workingHoursEnd: 24,
	/**
	 * @cfg {Array} workDays
	 * Defines the array of day numbers containing all working days (default to
	 * the default settings value for 'zarafa/v1/main/working_days').
	 */
	workDays: undefined,
	/**
	 * @cfg {String} blockSelector
	 * <b>This is a required setting</b>. A simple CSS selector (e.g. <tt>div.some-class</tt> or
	 * <tt>span:first-child</tt>) that will be used retrieve the block nodes this for TimelineView.
	 */
	blockSelector: 'div.x-freebusy-timeline-block',

	/**
	 * The height of the top row of the timeline header. In this row the date of the day is placed.
	 * This value is calculated based on the headerHoursHeight and the headerSumRowHeight which are
	 * subtracted from the total available headerHeight.
	 * @property
	 * @type Number
	 * @private
	 */
	headerDayHeight: 0,

	/**
	 * The height of the hours row in the timeline header.
	 * @property
	 * @type Number
	 * @private
	 */
	headerHoursHeight: 24,

	/**
	 * The height of the sum row (all attendees bar) in the timeline header.
	 * @property
	 * @type Number
	 * @private
	 */
	headerSumRowHeight: 10,

	/**
	 * Number of seconds per hour slot. Zoom of 100% will have 60 minutes per slot (in seconds).
	 * @property
	 * @type Number
	 * @private
	 */
	slotDuration: null,

	/**
	 * The width of a day, set by the buildDayMapping method.
	 * @property
	 * @type Number
	 * @private
	 */
	dayWidth: null,

	/**
	 * The width of an hour, set by the buildDayMapping method.
	 * @property
	 * @type Number
	 * @private
	 */
	hourWidth: null,

	/**
	 * The width of the entire timeline, set by the buildDayMapping method
	 * @property
	 * @type Number
	 * @private
	 */
	timelineWidth: null,

	/**
	 * List of block elements in a {@link Ext.CompositeElementLite}.
	 * @property
	 * @type Array
	 * @private
	 */
	all: null,

	/**
	 * The user store which contains all the users for which the freebusy data will be shown.
	 * This store is obtained from the {@link #model}.
	 * @property
	 * @type Ext.data.Store
	 * @private
	 */
	userStore: null,

	/**
	 * The block store which contains all the blocks which must be rendered on the row for the user to which the block belongs to
	 * This store is obtained from the {@link #model}.
	 * @property
	 * @type Ext.data.Store
	 * @private
	 */
	blockStore: null,

	/**
	 * The sumblock store which contains all the sumblocks which must be rendered at the top of the timeline.
	 * This store is obtained from the {@link #model}.
	 * @property
	 * @type Ext.data.Store
	 * @private
	 */
	sumBlockStore: null,

	/**
	 * Id prefix to make freebusy blocks on the timeline unique in combination with the record ID's
	 * from the block store records.
	 * @property
	 * @type String
	 * @private
	 */
	uniqueBlockId: null,

	/**
	 * @constructor
	 * @param {Object} config The configuration options.
	 */
	constructor: function(config)
	{
		config = config || {};

		config = Ext.applyIf(config, {
			workingHoursStart: container.getSettingsModel().get('zarafa/v1/main/start_working_hour'),
			workingHoursEnd: container.getSettingsModel().get('zarafa/v1/main/end_working_hour'),
			workDays: container.getSettingsModel().get('zarafa/v1/main/working_days')
		});

		// No working days, default back to the entire week.
		// FIXME: Not sure if this is the right location for this check,
		// we should consider a more appropriate location for validating
		// the settings.
		if (Ext.isEmpty(config.workDays)) {
			config.workDays = [ 0, 1, 2, 3, 4, 5, 6 ];
		}

		this.addEvents(
			/**
			 * @event bodyscroll
			 * Fires when the body of the timeline is scrolled.
			 * @param {Object} An object containing the scroll position in the format {left: (scrollLeft), top: (scrollTop)}
			 */
			'bodyscroll',
			/**
			 * @event mousedown
			 * Fires when a mousedown is detected within the timeline.
			 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
			 * @param {HtmlElement} t The target of the event.
			 * @param {Object} o The options configuration passed to the {@link #addListener} call.
			 */
			'timelinemousedown',
			/**
			 * @event timelinemouseup
			 * Fires when a mouseup is detected within the timeline.
			 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
			 * @param {HtmlElement} t The target of the event.
			 * @param {Object} o The options configuration passed to the {@link #addListener} call.
			 */
			'timelinemouseup',
			/**
			 * @event timelinemousemove
			 * Fires when a mousemove is detected with the timeline.
			 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
			 * @param {HtmlElement} t The target of the event.
			 * @param {Object} o The options configuration passed to the {@link #addListener} call.
			 */
			'timelinemousemove',
			/**
			 * @event rendertimeline
			 * Fires when the timeline elements are rendered.
			 * @param {Zarafa.common.freebusy.ui.TimelineView} Reference to the TimelineView
			 */
			'rendertimeline',
			/**
			 * @event beforerefreshtimeline
			 * Fires before the timeline is refreshed.
			 * @param {Zarafa.common.freebusy.ui.TimelineView} Reference to the TimelineView
			 */
			'beforerefreshtimeline',
			/**
			 * @event afterrefreshtimeline
			 * Fires after the timeline is refreshed and the sizes of the timeline have been
			 * recalculated.
			 * @param {Zarafa.common.freebusy.ui.TimelineView} Reference to the TimelineView
			 */
			'afterrefreshtimeline'
		);

		Zarafa.common.freebusy.ui.TimelineView.superclass.constructor.call(this, config);

		this.daterange = this.model.getDateRange();
		this.mon(this.model, {
			'userstorechange': this.onUserStoreChange,
			'blockstorechange': this.onBlockStoreChange,
			'sumblockstorechange': this.onSumBlockStoreChange,
			'showworkinghourschange': this.onShowWorkingHoursChange,
			scope: this
		});
	},

	/**
	 * Sets up the required templates, prepares the list of freebusy blocks and seeks for the store.
	 * @private
	 */
	initComponent: function(){
		this.masterTpl = new Ext.XTemplate(
			'<div class="x-freebusy-header">',
				'<div class="x-freebusy-header-body"></div>',
				'<div class="x-freebusy-sumblockcontainer"></div>',
			'</div>',
			'<div class="x-freebusy-body">',
				'<div class="x-freebusy-background"></div>',
				'<div class="x-freebusy-blockcontainer"></div>',
				'<div class="x-freebusy-selectorcontainer"></div>',
			'</div>',
			{
				// Format functions like capitalize in the Ext.util.Format are not
				// used in this template anyways. Speeds up the apply time.
				disableFormats: true
			}
		);

		this.headerTemplate = new Ext.XTemplate(
			'<table class="x-freebusy-timeline-day-header" cellpadding="0" cellspacing="0" style="width: {dayWidth}px">',
				'<tr class="x-freebusy-timeline-day">',
					'<td colspan="{numHours}" style="height:{headerDayHeight}px;">',
					'{dayLabel}',
					'</td>',
				'</tr>',
				'<tr class="x-freebusy-timeline-hour">',
					'<tpl for="hours">',
						'<td style="width: {parent.hourWidth}px; height:{parent.headerHoursHeight}px;">{label}</td>',
					'</tpl>',
				'</tr>',
			'</table>',
			{ disableFormats: true }
		);
		this.headerSumTemplate = new Ext.XTemplate(
			'<table class="x-freebusy-timeline-day-sum" cellpadding="0" cellspacing="0" style="width: {dayWidth}px">',
				'<tr class="x-freebusy-timeline-hour x-freebusy-timeline-sum">',
					'<tpl for="hours">',
						'<td style="width: {parent.hourWidth}px; height:{parent.headerSumRowHeight}px;"></td>',
					'</tpl>',
				'</tr>',
			'</table>',
			{ disableFormats: true }
		);
		this.bodyBGTemplate = new Ext.XTemplate(
			'<table class="x-freebusy-timeline-day-body" cellpadding="0" cellspacing="0" style="width: {dayWidth}px">',
				'<tr class="x-freebusy-timeline-hour">',
					'<tpl for="hours">',
						'<td style="width: {parent.hourWidth}px">&nbsp;</td>',
					'</tpl>',
				'</tr>',
			'</table>',
			{ disableFormats: true }
		);
		this.blockTemplate = new Ext.XTemplate(
			'<tpl for=".">',
				'<div class="x-freebusy-timeline-block x-freebusy-timeline-block-{busyStatusName}" id="{blockId}" style="width: {blockWidth}px; left: {blockLeft}px; top:{blockTop}px; height: {blockHeight}px;"></div>',
			'</tpl>',
			{ disableFormats: true }
		);
		this.sumBlockTemplate = new Ext.XTemplate(
			'<tpl for=".">',
				'<div class="x-freebusy-timeline-sumblock x-freebusy-timeline-block-{busyStatusName}" style="width: {blockWidth}px; left: {blockLeft}px; height: {blockHeight}px;"></div>',
			'</tpl>',
			{ disableFormats: true }
		);

		this.uniqueBlockId = Ext.id();
		this.all = new Ext.CompositeElementLite();

		Zarafa.common.freebusy.ui.TimelineView.superclass.initComponent.call(this);

		if(this.selector){
			this.selector.init(this);
		}
	},

	/**
	 * Renders the background container and sets up the day and hour mappings.
	 * @private
	 */
	onRender: function(){
		this.autoEl = {
			cls: 'x-freebusy-timeline-container'
		};
		Zarafa.common.freebusy.ui.TimelineView.superclass.onRender.apply(this, arguments);

//TODO: The initial render should also contain a refresh call to directly put in all the blocks of the already loaded users.
		this.buildDayMapping();
		this.renderTimeline();
	},

	/**
	 * Called to re-render the entire TimelineView.
	 * @private
	 */
	refreshTimeline: function(){
		this.fireEvent("beforerefreshtimeline", this);

		// Capture the snapshot of the viewable area.
		var viewportSnapshot = this.captureViewportSnapshot();
		// Clearing the old HTML node references to the viewed days in the timeline background as
		// well as regenerating the list of hours and days shown on the timeline.
		this.buildDayMapping();
		// Rendering the HTML elements for the timeline (and discarding the old ones).
		this.renderTimeline();
		// Triggering the resizing of the timeline. Will also trigger the repainting of the timeline days.
		this.syncSize();
		// Restore the viewable area inside the viewport
		this.restoreViewportSnapshot(viewportSnapshot);
		// Triggers the repainting of the blocks on the timeline.
		this.refresh();

		this.fireEvent("afterrefreshtimeline", this);
	},

	/**
	 * Creates a snapshot object that will contain information to get the viewport of the timeline
	 * focussed on the same area.
	 * The object contains the following properties.
	 * <ul>
	 * <li>selectionInView {@link Boolean} indication whether the selection of the {@link #selectorRange}
	 * is visible inside the viewport.</li>
	 * <li>focusDateRange {@link Zarafa.core.DateRange} The period of time that is centered on in the
	 * viewport.</li>
	 * <li>diffOffset {@link Number} The number of pixels between the start of the selection of the
	 * {@link #selectorRange} and the center of the viewport. A negative values means the selection
	 * is before the center.</li>
	 * </ul>
	 * @return {Object} An object containing selectionInView, focussedDateRange and diffOffset properties.
	 */
	captureViewportSnapshot: function(){
		// Setup the viewportSnapshot
		var snapshot = {
			selectionInView: false,
			focusDateRange: null,
			diffOffset: null
		};

		// Check to see if the selection is inside the view
		var selectorRange = this.model.getSelectorRange();
		// FIXME check if we have a selectorRange (Freebusy without selection is possible)
		snapshot.selectionInView = this.getViewedDateRange().overlaps(selectorRange);

		// If selection is inside view calculate the offset between the start of the selection and
		// the center of the viewport in pixels
		if(snapshot.selectionInView){
			var selectionStart = selectorRange.getStartDate();
			var startOffset = this.findBlockPixelOffset(selectionStart.getTime()/1000);

			// Get the left side of the viewport
			var leftOffset = this.bodyElem.getScroll().left;
			// Use the half of the width of the viewport plus the leftOffset to get the center offset.
			var viewportSize = Ext.get(this.bodyElem).getViewSize();
			var centerOffset = leftOffset + (viewportSize.width/2);

			// Get difference between centerOffset(viewport) and the startOffset(start of selection)
			snapshot.diffOffset = startOffset - centerOffset;

		} else {
			// Retrieve the focus to be used after the refreshing has been done
			snapshot.focusDateRange = this.getFocusDateRange();
		}

		return snapshot;
	},

	/**
	 * Will restore the viewport based on the snapshot it will get passed. If the user selection was
	 * visible inside the viewport when the snapshot was created, than this method will keep the
	 * selection at the same place inside the viewport.
	 * If the selection is outside the viewable area than it will center on the time it was centered
	 * on when the snapshot was created.
	 * The difference between these two approaches is that when you have the selection in view it
	 * will not get scrolled away when it is visible near the edge of the viewport. Which would be
	 * the case if you were to center on the position that was in the middle of the viewport. If the
	 * selection was not visible then we do not need to go through all that trouble.
	 * @param {Object} snapshot Needs a snapshot that is returned by the
	 * {@link @createViewportSnapshot} method.
	 */
	restoreViewportSnapshot: function(snapshot){
		var focusDateRange;

		if(snapshot.selectionInView){
			// Get the start date of the selection
			var selectorRange = this.model.getSelectorRange();
			var selectionStart = selectorRange.getStartDate();

			// Use the start of the selection to get the offset in pixels from the start of the timeline.
			var startOffset = this.findBlockPixelOffset(selectionStart.getTime()/1000);
			// Based on the startOffset we now calculate the position the viewport should be centered on.
			var centerOffset = startOffset - snapshot.diffOffset;
			// Transform this center offset in pixels to a timestamp
			var centerTimestamp = this.findTimestampByTimelineXCoord(centerOffset);

			// Create a daterange to be used to focus the viewport on
			focusDateRange = new Zarafa.core.DateRange({ startDate: new Date(centerTimestamp*1000), dueDate: new Date(centerTimestamp*1000) });
		} else {
			// Just focus on the center when the selection was not visible
			focusDateRange = snapshot.focusDateRange;
		}

		// Scroll to the DateRange of the original focus
		this.scrollDateIntoView(focusDateRange);
	},

	/**
	 * Returns the {@link Zarafa.core.data.DateRange DateRange} that the focus is centered on. It
	 * looks for the hour slot that is in the center of the viewport. That slot will be returned in
	 * a DateRange.
	 * @return {Zarafa.core.DateRange} Range of the focus
	 */
	getFocusDateRange: function(){
		// Get the dimensions and scroll offset
		var leftOffset = this.bodyElem.getScroll().left;
		var viewportSize = Ext.get(this.bodyElem).getViewSize();

		// Get the offset of the center (focus) of the viewport
		var focusOffset = leftOffset + (viewportSize.width/2);
		// The dayWidth is excluding the spaces between the days so we have to include those too
		var dayIndex = focusOffset / (this.dayWidth + this.daySpacing);

		// Get the decimal part from the dayIndex, to be used for calculating what part of the day to focus on
		var dayRatioIndex = dayIndex % 1;
		dayIndex = Math.floor(dayIndex);

		var dayMap = this.daysMap[ dayIndex ];
		var numDisplayedHours = this.hoursEachDayMap.length;
		// Get the index of the hour the focus is centered on using the decimal part of the dayIndex
		var hourIndex = Math.floor(numDisplayedHours * dayRatioIndex);

		// Get the start and the end of the hour slot that is focus is centered on
		var focusStartTimestamp = dayMap.timestamp + this.hoursEachDayMap[ hourIndex ].startDayOffset;
		var focusEndTimestamp = focusStartTimestamp + this.slotDuration;

		return new Zarafa.core.DateRange({ startDate: new Date(focusStartTimestamp*1000), dueDate: new Date(focusEndTimestamp*1000) });
	},

	/**
	 * Returns the {@link Zarafa.core.data.DateRange DateRange} that the viewport is showing. The
	 * start date is set to the date that the left edge of the viewport is on and the due date is
	 * set to the date the right edge of the viewport is on.
	 * @return {Zarafa.core.DateRange} Range of the viewed area
	 */
	getViewedDateRange: function(){
		// Get scroll offset of the left and right edges of the viewport
		var leftOffset = this.bodyElem.getScroll().left;
		var viewportSize = Ext.get(this.bodyElem).getViewSize();
		var rightOffset = leftOffset + viewportSize.width;

		// Transform the scroll offsets to timestamps
		var startTimestamp = this.findTimestampByTimelineXCoord(leftOffset);
		var endTimestamp = this.findTimestampByTimelineXCoord(rightOffset);

		return new Zarafa.core.DateRange({ startDate: new Date(startTimestamp*1000), dueDate: new Date(endTimestamp*1000) });
	},

	/**
	 * Rendering the HTML elements needed for the TimelineView. Will also create references of to
	 * important HTML elements and will header and body elements.
	 * @private
	 */
	renderTimeline: function(){
		// Remove the old scroll event from the body and header
		if(this.bodyElem){
			this.mun(this.bodyElem, {
				scope: this,
				"scroll": this.onBodyScroll,
				"mousedown": this.onBodyMouseDown,
				"mousemove": this.onBodyMouseMove,
				"mouseup": this.onBodyMouseUp,
				"contextmenu": this.onBodyContextMenu
			});
		}
		if(this.headerElem) {
			this.mun(this.headerElem, {
				scope: this,
				"contextmenu": this.onHeaderContextMenu
			});
		}

		// Setup the HTML structure for the whole TimelineView
		this.masterTpl.overwrite(this.el);

		// Set references to the different elements in the timeline
		this.containerElem = Ext.get(this.el.dom);
		this.headerElem = Ext.get(this.containerElem.dom.firstChild);
		this.headerBodyElem = Ext.get(this.headerElem.dom.firstChild);
		this.headerSumContainer = Ext.get(this.headerElem.dom.childNodes[1]);
		this.bodyElem = Ext.get(this.containerElem.dom.childNodes[1]);
		this.bodyBackgroundElem = Ext.get(this.bodyElem.dom.firstChild);
		this.bodyBlockContainer = Ext.get(this.bodyElem.dom.childNodes[1]);
		this.bodySelectorContainer = Ext.get(this.bodyElem.dom.childNodes[2]);

		// Set scroll event on body to fire scroll event in this TimelineView
		this.mon(this.bodyElem, {
			scope: this,
			"scroll": this.onBodyScroll,
			"mousedown": this.onBodyMouseDown,
			"mousemove": this.onBodyMouseMove,
			"mouseup": this.onBodyMouseUp,
			"contextmenu": this.onBodyContextMenu
		});
		this.mon(this.headerElem, {
			scope: this,
			"contextmenu": this.onHeaderContextMenu
		});

		// Position and set width and height for header and body elements in timeline
		this.layoutTimelineElements();

		this.fireEvent("rendertimeline", this);
	},

	/**
	 * Set the correct width for the header, body background timeline and body block container
	 * elements. It will also calculate the correct height for the header of the timeline.
	 * @private
	 */
	layoutTimelineElements: function(){
		this.headerBodyElem.setWidth(this.timelineWidth);
		this.bodyBackgroundElem.setWidth(this.timelineWidth);
		this.bodyBlockContainer.setWidth(this.timelineWidth);

		// Calculate the height settings of the header elements.
		// Header consists of three rows. The day row and the hours row have no horizontal border-spacing.
		// The header sum row is a separate table and has two border-spacings (top & bottom).
		var heightSumRowBordersIncluded = this.headerSumRowHeight + (this.borderSpacing*2);
		this.headerDayHeight = (this.headerHeight - heightSumRowBordersIncluded - this.headerHoursHeight);

		// Calculating the top style for the sumblocks container in the header that contains the
		// cumulative freebusy blocks. For this we take the full height of the header and extract
		// the height of the sum row and the border that is at the bottom.
		var sumBlockContainerTopSpace = this.headerHeight - (this.headerSumRowHeight + this.borderSpacing);
		this.headerSumContainer.setTop(sumBlockContainerTopSpace);
	},

	/**
	 * Binding the blockStore and the userStore
	 * @private
	 */
	afterRender: function(){
		Zarafa.common.freebusy.ui.TimelineView.superclass.afterRender.apply(this, arguments);

		if (this.model.getBlockStore()) {
			this.bindBlockStore(this.model.getBlockStore(), true);
		}
		if (this.model.getSumBlockStore()) {
			this.bindSumBlockStore(this.model.getSumBlockStore(), true);
		}
		if (this.model.getUserStore()) {
			this.bindUserStore(this.model.getUserStore(), true);
		}
	},

	/**
	 * Get the template target that is used to add the blocks to.
	 * @private
	 */
	getBlockTemplateTarget: function(){
		// We return the container that is used for the freebusy blocks.

		return this.bodyBlockContainer;
	},

	/**
	 * Get the template target for the background days.
	 * @private
	 */
	getBGTemplateTarget: function(){
		// In this container the background days are rendered.
		return this.bodyBackgroundElem;
	},

	/**
	 * Get the HTML Element that functions as the container for the selector.
	 * @return {HTMLElement} Selector container
	 * @private
	 */
	getSelectorContainer: function(){
		// In this container the selector can be rendered.
		return this.bodySelectorContainer;
	},

	/**
	 * Called after the component is resized, this method is empty by default but can be implemented by any
	 * subclass that needs to perform custom logic after a resize occurs.
	 * @param {Number} adjWidth The box-adjusted width that was set
	 * @param {Number} adjHeight The box-adjusted height that was set
	 * @param {Number} rawWidth The width that was originally specified
	 * @param {Number} rawHeight The height that was originally specified
	 * @protected
	 */
	onResize: function(adjWidth, adjHeight, rawWidth, rawHeight){
		this.headerElem.setHeight(this.headerHeight);

		this.bodyElem.setTop(this.headerHeight);
		var bodyElemHeight = adjHeight - this.headerHeight;
		this.bodyElem.setHeight(bodyElemHeight);

		this.sizeTimelineBackground();

		this.repaintTimeline();
	},

	/**
	 * Handles the actions that should take place when the user scrolls the body
	 * @private
	 */
	onBodyScroll: function(){
		this.repaintTimeline();

		this.fireEvent("bodyscroll", this.bodyElem.getScroll());
	},

	/**
	 * Called when the mousedown event is fired on the body.
	 * @param {Ext.EventObject} evt The {@link Ext.EventObject} encapsulating the DOM event.
	 * @param {HtmlElement} target The target of the event.
	 * @param {Object} cfg The options configuration passed to the {@link #addListener} call.
	 * @private
	 */
	onBodyMouseDown: function(evt, target, cfg){
		this.fireEvent("timelinemousedown", evt, target, cfg);
	},

	/**
	 * Called when the mousemove event is fired on the body.
	 * @param {Ext.EventObject} evt The {@link Ext.EventObject} encapsulating the DOM event.
	 * @param {HtmlElement} target The target of the event.
	 * @param {Object} cfg The options configuration passed to the {@link #addListener} call.
	 * @private
	 */
	onBodyMouseMove: function(evt, target, cfg){
		this.fireEvent("timelinemousemove", evt, target, cfg);
	},

	/**
	 * Called when the mouseup event is fired on the body.
	 * @param {Ext.EventObject} evt The {@link Ext.EventObject} encapsulating the DOM event.
	 * @param {HtmlElement} target The target of the event.
	 * @param {Object} cfg The options configuration passed to the {@link #addListener} call.
	 * @private
	 */
	onBodyMouseUp: function(evt, target, cfg){
		this.fireEvent("timelinemouseup", evt, target, cfg);
	},

	/**
	 * Called when the contextmenu event is fired on the body.
	 * @param {Ext.EventObject} evt The {@link Ext.EventObject} encapsulating the DOM event.
	 * @param {HtmlElement} target The target of the event.
	 * @param {Object} cfg The options configuration passed to the {@link #addListener} call.
	 * @private
	 */
	onBodyContextMenu: function(evt, target, cfg)
	{
		Zarafa.core.data.UIFactory.openContextMenu(Zarafa.core.data.SharedComponentType['common.contextmenu.freebusy.timelinebody'], undefined, { position: evt.getXY(), model: this.model });
	},

	/**
	 * Called when the contextmenu event is fired on the header.
	 * @param {Ext.EventObject} evt The {@link Ext.EventObject} encapsulating the DOM event.
	 * @param {HtmlElement} target The target of the event.
	 * @param {Object} cfg The options configuration passed to the {@link #addListener} call.
	 * @private
	 */
	onHeaderContextMenu: function(evt, target, cfg)
	{
		Zarafa.core.data.UIFactory.openContextMenu(Zarafa.core.data.SharedComponentType['common.contextmenu.freebusy.timelineheader'], undefined, { position: evt.getXY(), model: this.model });
	},

	/**
	 * Regenerate the background image use for the day blocks
	 * These are the horizontal lines that border the user blocks in the day blocks
	 * Basically we have a background image of a pixel in the color of the border color
	 * that we repeat in x-direction and we add it as many times as there are users in the userStore
	 * (CSS multiple backgrounds)
	 */
	restyleBodyBackground: function()
	{
		var backgroundStyle = 'url(data:image/gif;base64,R0lGODlhAQABAIABAObm5v///yH+EUNyZWF0ZWQgd2l0aCBHSU1QACwAAAAAAQABAAACAkQBADs=) repeat-x left top';
		for ( var i=0; i<=this.userStore.getCount(); i++ ){
			backgroundStyle += ', url(data:image/gif;base64,R0lGODlhAQABAIABAObm5v///yH+EUNyZWF0ZWQgd2l0aCBHSU1QACwAAAAAAQABAAACAkQBADs=) repeat-x left ' + (this.blockRowHeight*(i+1)) +'px';
		}
		var dayBlocks = this.bodyBackgroundElem.query('.x-freebusy-timeline-day');
		for ( i=0; i<dayBlocks.length; i++ ){
			Ext.get(dayBlocks[i]).setStyle('background', backgroundStyle);
		}

	},

	/**
	 * Resizes the vertical sizes based on the number of users that have been added. This needs to
	 * be recalculated because if the number of users exceeds the amount that can be shown in the
	 * viewport at one time the height of the timeline needs to be resized beyond the height of the
	 * viewport. Every time a new user is added or one is removed this function can be called to
	 * resized the timeline to the correct height.
	 * @private
	 */
	sizeTimelineBackground: function(){
		if(this.bodyElem.dom.clientHeight <= 0) {
			// if height is zero that means timelineView is hidden, so we shouldn't do resizing
			return;
		}

		// Set the height of the timeline background based on the number of users in the user store.
		var userStore = this.userStore;
		var numRows = userStore.getCount();
		var bodyHeight = numRows * this.blockRowHeight + this.extraBodyHeight;

		/**
		 * The columns of the background timeline need to be sized to fill the body viewport. If
		 * there is an active scrollbar then they need to be sized even bigger to also show behind
		 * the blocks that are in the rows outside the viewport.
		 * this.bodyElem.clientHeight => height of body element without scrollbars
		 */
		this.bodyBackgroundElem.setHeight( Math.max( this.bodyElem.dom.clientHeight, bodyHeight ) );
		// Resize the bodybackground
		this.bodySelectorContainer.setHeight( Math.max( this.bodyElem.dom.clientHeight, bodyHeight ) );

		// Always scroll to the bottom of the page...
		this.bodyElem.scrollTo("top", this.bodyElem.dom.scrollHeight);

		// Reset the background image, because they are the horizontal lines
		this.restyleBodyBackground();
	},

	/**
	 * Every time the user scrolls or when the component is resized the background needs to be
	 * redrawn. The background of the timeline only loads the days that are visible and their
	 * surrounding days. To determine what range should be loaded it looks for the
	 * bufferTimesViewportWidth to see how many times the width of the viewport should be loaded in
	 * days. If the viewport is 100px wide and the bufferTimesViewportWidth is set to 5, the range
	 * that will be loaded is 500px.
	 * It will map the range to the days that will need to be loaded. Next it will figure out what
	 * days need to be rendered and what days have to be cleaned up because they do no longer fall
	 * within the range.
	 * @private
	 */
	repaintTimeline: function(){
		// Get some basic values of the width of one day, the size of the
		// viewport and the position of the scrollbar.
		var viewportSize = Ext.get(this.bodyElem).getViewSize();
		var scrollPxls = this.bodyElem.getScroll();

		/**
		 * Next we need to decide what range needs to be buffered. We use the value of
		 * this.bufferTimesViewportWidth to calculate the number of pixels that need to be loaded
		 * outside of the visual area in the viewport. If you want to buffer five times the width of
		 * the viewport (100px) then there are 400px that are shown outside of the viewport.
		 */
		var visiblePxlsOutsideViewport = (this.bufferTimesViewportWidth-1)*viewportSize.width;
		// Calculate the offset in pixels for the start and end point of the visual range.
		var startOffsetPixels = scrollPxls.left - (visiblePxlsOutsideViewport / 2);
		var endOffsetPixels = scrollPxls.left + viewportSize.width + (visiblePxlsOutsideViewport / 2);

		// Calculate what the first and last day is that must be buffered.
		var startDayIndex = Math.floor(startOffsetPixels / this.dayWidth);
		var endDayIndex = Math.ceil(endOffsetPixels / this.dayWidth);

		// Make sure the indexes do not exceed the number of days available.
		startDayIndex = (startDayIndex < 0) ? 0: startDayIndex;
		endDayIndex = (endDayIndex < this.daysMap.length) ? endDayIndex : (this.daysMap.length - 1);

		// Determine what days need to be loaded extra and what days can be removed.
		var loadDays = [];
		var cleanupDays = [];
		for(var i=0;i<this.daysMap.length;i++){
			// If day is between the range to visualize.
			if(i >= startDayIndex && i <= endDayIndex){
				// Only add to loadDays when it is not displayed yet
				if(!this.daysMap[ i ].displayNodeBody){
					loadDays[ loadDays.length ] = i;
				}
			// If day falls outside visual range
			} else {
				// If day is marked as displayed it needs to be cleaned up
				if(this.daysMap[ i ].displayNodeBody){
					cleanupDays[ cleanupDays.length ] = i;
				}
			}
		}

		// First render days, then scroll, then cleanup. This way no empty header should be shown.
		this.renderTimelineDays( loadDays );
		this.headerElem.scrollTo("left", this.bodyElem.getScroll().left );	// Sync the header
		this.cleanUpTimelineDays( cleanupDays );

		// Reset the background image, because they are the horizontal lines
		this.restyleBodyBackground();
	},

	/**
	 * Renders the background days that have been supplied in renderDays.
	 * @param renderDays {Array} List of indexes of this.daysMap that will be rendered.
	 * @private
	 */
	renderTimelineDays: function(renderDays){
		var headerElem = Ext.get(this.headerBodyElem);
		var bodyElem = Ext.get(this.bodyBackgroundElem);

		for(var i=0;i<renderDays.length;i++){
			var dayIndex = renderDays[i];
			var currDayCls = '';
			if(this.daysMap[ dayIndex ].currentDay){
				currDayCls = ' x-freebusy-timeline-day-current';
			}

			var bodyDayElem = bodyElem.createChild({
				cls: 'x-freebusy-timeline-day' + currDayCls
			});
			bodyDayElem.dom.style.left = this.daysMap[ dayIndex ].leftPos+"px";

			this.bodyBGTemplate.overwrite(bodyDayElem, {
				dayLabel: this.daysMap[ dayIndex ].label,
				hours: this.hoursEachDayMap,
				hourWidth: this.hourWidth,
				numHours: this.hoursEachDayMap.length,
				dayWidth: this.dayWidth
			});

			var headerDayElem = headerElem.createChild({
				cls: 'x-freebusy-timeline-day' + currDayCls
			});
			headerDayElem.dom.style.left = this.daysMap[ dayIndex ].leftPos+"px";

			this.headerTemplate.overwrite(headerDayElem, {
				dayLabel: this.daysMap[ dayIndex ].label,
				hours: this.hoursEachDayMap,
				hourWidth: this.hourWidth,
				numHours: this.hoursEachDayMap.length,
				dayWidth: this.dayWidth,
				headerDayHeight: this.headerDayHeight,
				headerHoursHeight: this.headerHoursHeight,
				borderSpacing: this.borderSpacing
			});
			this.headerSumTemplate.append(headerDayElem, {
				hours: this.hoursEachDayMap,
				hourWidth: this.hourWidth,
				numHours: this.hoursEachDayMap.length,
				dayWidth: this.dayWidth,
				headerSumRowHeight: this.headerSumRowHeight,
				borderSpacing: this.borderSpacing
			});

			this.daysMap[ dayIndex ].displayNodeHeader = headerDayElem;
			this.daysMap[ dayIndex ].displayNodeBody = bodyDayElem;
		}
	},

	/**
	 * Cleans up the background days that have been supplied in cleanupDays.
	 * @param cleanupDays {Array} List of indexes of this.daysMap that will be cleaned up.
	 * @private
	 */
	cleanUpTimelineDays: function(cleanupDays){
		var elem;

		for(var i=0;i<cleanupDays.length;i++){
			var dayIndex = cleanupDays[i];

			elem = this.daysMap[ dayIndex ].displayNodeHeader;
			Ext.removeNode( elem.dom );
			elem = this.daysMap[ dayIndex ].displayNodeBody;
			Ext.removeNode( elem.dom );

			this.daysMap[ dayIndex ].displayNodeHeader = false;
			this.daysMap[ dayIndex ].displayNodeBody = false;
		}
	},

	/**
	 * Scrolls the supplied date into view. It will be centered into the view.
	 * @param date {Date|Number|Zarafa.core.DateRange.js} Date to be scrolled into view.
	 * @private
	 */
	scrollDateIntoView: function(date){
		// Make a timestamp out of a date
		if(date instanceof Date){
			date = Math.ceil(date.getTime()/1000);
		// Make a timestamp out of a Daterange
		} else if(date instanceof Zarafa.core.DateRange){
			var start = date.getStartDate().getTime()/1000;
			var end = date.getDueDate().getTime()/1000;
			var duration = end - start;
			date = start + (duration/2);
		}

		var viewport = Ext.get(this.bodyElem);
		var viewportSize = Ext.get(this.bodyElem).getViewSize();

		var pixelOffsetLeft = this.findBlockPixelOffset(date, true);

		// Make sure the date will be centered in the viewport
		pixelOffsetLeft = pixelOffsetLeft - (viewportSize.width / 2);

		viewport.scrollTo('left', pixelOffsetLeft);
	},

	/**
	 * Creates a mapping for each day and a mapping for the hours that are displayed.
	 * It also calculates and sets the widths for the days and hour cells.
	 * @private
	 */
	buildDayMapping: function()
	{
		var currDay = new Date().clearTime();

		this.daysMap = [];
		this.hoursEachDayMap = [];
		this.slotDuration = 60*60;
		var availableSlotsPerDay = (24*60*60) / this.slotDuration;

		// Dirty way of assigning the working hours
		for(var i=0;i<availableSlotsPerDay;i++) {
			// FIXME: We should support minutes as well, so we shouldn't
			// round to entire hours but to 15 minutes for example.
			var startHour = Math.floor(this.workingHoursStart / 60);
			var endHour = Math.ceil(this.workingHoursEnd / 60);
			if (!this.model.showOnlyWorkingHours() || (i >= startHour && i < endHour)) {
				this.hoursEachDayMap[ this.hoursEachDayMap.length ] = {
					// Run the hour through a formatter
					label: Date.parseDate(i, 'G').formatDefaultTime(),
					startDayOffset: i * this.slotDuration,
					workingHour: (i >= startHour && i < endHour) ? true : false
				};
			}
		}

		var numHourBlocks = this.hoursEachDayMap.length;
		this.hourWidth = this.defaultHourCellWidth;
		// Each hour has two borders (left and right) which it shares with it's neighbours.
		this.dayWidth = ( (this.hourWidth + this.borderSpacing) * numHourBlocks ) + this.borderSpacing;

		// Set clone flag in clearTime to prevent changing the time in the daterange
		// Use 12:00 as basetime, to prevent problems when the DST switch is at 00:00
		// like in Brasil.
		var startdate = this.daterange.getStartDate().clearTime(true);
		startdate.setHours(12);

		for (var i = 0; i < this.daterange.getNumDays(); i++) {
			var date = startdate.add(Date.DAY, i).clearTime();

			// Check whether this iterated day is the current day
			var currDayCheck = currDay.getTime() == date.getTime();
			var workDay = false;

			for (var j = 0, len = this.workDays.length; j < len; j++) {
				if (this.workDays[j] == date.getDay()) {
					workDay = true;
					break;
				}
			}

			if (!this.model.showOnlyWorkingHours() || workDay) {
				this.daysMap.push({
					// Run date through formatter
					// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
					label: date.format(_('l jS F Y')),
					currentDay: currDayCheck,
					timestamp: date.getTime() / 1000,
					displayNodeHeaderBody: false,
					displayNodeBody: false,
					// Left boundary are used for determining whether to show the day or not
					leftPos: (this.daysMap.length * (this.dayWidth + this.daySpacing))
				});
			}
		}

		var numDays = this.daysMap.length;
		this.timelineWidth = (this.dayWidth + this.daySpacing) * numDays - this.daySpacing;
	},

	/**
	 * Changes the data store bound to this view and refreshes it.
	 * @param {Store} store The store to bind to this view
	 * @private
	 */
	bindBlockStore: function(store, initial)
	{
		if(this.blockStore) {
			this.mun(this.blockStore, 'load', this.onBlockLoad, this);
		}
		this.blockStore = Ext.StoreMgr.lookup(store);
		if(this.blockStore){
			this.mon(this.blockStore, {
				scope: this,
				load: this.onBlockLoad
			});

			this.refresh();
		}
	},

	/**
	 * Changes the data store bound to this view and refreshes it.
	 * @param {Store} store The store to bind to this view
	 * @private
	 */
	bindSumBlockStore: function(store, initial)
	{
		if (this.sumBlockStore) {
			this.mun(this.sumBlockStore, 'load', this.onSumBlocksLoad, this);
		}
		this.sumBlockStore = Ext.StoreMgr.lookup(store);
		if(this.sumBlockStore){
			this.mon(this.sumBlockStore, {
				scope: this,
				load: this.onSumBlocksLoad
			});

			this.refresh();
		}
	},
	/**
	 * load block(s) to the UI.
	 * @param {Ext.data.Store} ds Store of the record
	 * @param {Ext.data.Record[]} records List of records that have to be loaded
	 * @param {Object} options The options used the load the blocks from the server
	 * @private
	 */
	onBlockLoad: function(ds, records, options){
		if(this.all.getCount() === 0){
			this.refresh();
			return;
		}
		var filteredRecords = this.filterRecords(records);

		var nodes = this.bufferBlockRender(filteredRecords);
		this.all.last().insertSibling(nodes, 'after', true);
		this.all.elements.push.apply(this.all.elements, nodes);
	},

	/**
	 * Called when the {@link #sumBlockStore} has loaded new data and we
	 * can update the top freebusy bar with summed blocks
	 *
	 * Renders the sum blocks that show the cumulative freebusy information for all the recipients.
	 * The data from the blocks is combined into cumulative blocks for all recipients. When this
	 * calculation is done it does it separately for the different busy statuses. So you will get a
	 * different track for tentative, busy and outofoffice. By displaying them on top of each other
	 * the outofoffice is more important than busy and busy more important than tentative.
	 *
	 * @param {Ext.data.Store} store The store which raised the event
	 * @param {Ext.data.Record[]} records The records which have been loaded
	 * @param {Object} options The options from the load event
	 * @private
	 */
	onSumBlocksLoad: function(store, records, options)
	{
		if (store.getCount() === 0) {
			// No sum records are available, simply empty the sumBlockTemplate
			this.sumBlockTemplate.overwrite(this.headerSumContainer.dom, []);
			return;
		}

		this.sumBlockTemplate.overwrite(this.headerSumContainer.dom, this.collectData(records, 0, true));
	},

	/**
	 * Remove block from the UI.
	 * @param {Ext.data.Store} ds Store of the record
	 * @param {Ext.data.Record} record Record begin removed
	 * @param {Number} index Index of the record that has to be removed
	 * @private
	 */
	onRemove: function(ds, record, index){
		var blockId = this.uniqueBlockId+"-"+record.id;
		this.all.removeElement(blockId, true);
		if (this.blockStore.getCount() === 0){
			this.refresh();
		}
	},

	/**
	 * Refreshes the view by reloading the data from the store and re-rendering the template.
	 * @private
	 */
	refresh: function(){
		// Empty the container of blocks
		this.bodyBlockContainer.update("");
		this.all.clear();

		if (this.blockStore) {
			var records = this.blockStore.getRange();

			if (records.length > 0) {
				var filteredRecords = this.filterRecords(records);
				this.blockTemplate.overwrite(this.bodyBlockContainer.dom, this.collectData(filteredRecords));
				var nodes = Ext.query(this.blockSelector, this.bodyBlockContainer.dom);
				this.all.fill(nodes);
			}
		}

		if (this.sumBlockStore) {
			var sumRecords = this.sumBlockStore.getRange();

			if (sumRecords.length > 0) {
				var filteredSumRecords = this.filterRecords(sumRecords);
				this.sumBlockTemplate.overwrite(this.headerSumContainer.dom, this.collectData(filteredSumRecords));
			}
		}
	},

	/**
	 * Render the HTML for the records and return the nodes.
	 * @param {Ext.data.Record[]} records Records
	 * @return {Array} List of HTML nodes
	 * @private
	 */
	bufferBlockRender: function(records){
		var div = document.createElement('div');
		this.blockTemplate.overwrite(div, this.collectData(records));
		var nodes = Ext.query(this.blockSelector, div);
		return nodes;
	},

	/**
	 * Call prepareData for all the records in the list and return the list of data for all records.
	 * @param {Ext.data.Record[]} records List of records
	 * @param {Number} startIndex Start index
	 * @param {Boolean} sumHeader True if the blocks will be positioned into the SumBlock header.
	 * @return {Array} List of data for all records
	 * @private
	 */
	collectData: function(records, startIndex, sumHeader){
		var r = [];
		for(var i = 0, len = records.length; i < len; i++){
			r[r.length] = this.prepareData(records[i].data, startIndex+i, records[i], sumHeader);
		}
		return r;
	},

	/**
	 * Function that provides the formatting needed for the freebusy blocks to be displayed
	 * properly. It determines what the left pixel offset should be and what the width should be.
	 * This function is used to provide custom formatting for each Record that is used by this
	 * {@link Ext.DataView}'s {@link #tpl template} to render each node.
	 * @param {Array/Object} data The raw data object that was used to create the Record.
	 * @param {Number} recordIndex the index number of the Record being prepared for rendering.
	 * @param {Record} record The Record being prepared for rendering.
	 * @param {Boolean} sumHeader True if the block will be positioned into the SumBlock header.
	 * @return {Array/Object} The formatted data in a format expected by the internal {@link #tpl template}'s overwrite() method.
	 * (either an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'}))
	 * @private
	 */
	prepareData: function(data, recordIndex, record, sumHeader){
		// Copy the data object to prevent adding properties to the Record object
		data = Ext.apply({}, data);

		data.blockId = this.uniqueBlockId + "-" + record.id;
		if(data.status != Zarafa.core.mapi.BusyStatus.UNKNOWN){
			data.busyStatusName = Zarafa.core.mapi.BusyStatus.getName(data.status) || 'busy';
			data.busyStatusName = data.busyStatusName.toLowerCase();
		} else {
			data.busyStatusName = 'blur';

			if(this.model.showOnlyWorkingHours()) {
				var day = new Date(data.start*1000).getDay();
				// if day is Saturday then add two day in current day and get time stamp of Monday
				if(day === 6) {
					data.start = new Date(data.start*1000).add(Date.DAY, 2).getTime()/1000;
				} else if(day === 0) {
					// if day is Sunday then add one day in current day and get time stamp of Monday
					data.start = new Date(data.start*1000).add(Date.DAY, 1).getTime()/1000;
				}
			}
			var dayIndex = this.findDayIndexByTimestamp(data.start,true);
			var timestamp = this.daysMap[dayIndex].timestamp;
			data.start = timestamp;
		}

		var startRowTopOffset = 1;
		var rowHeight;

		if (sumHeader === true) {
			rowHeight = this.sumBlockRowHeight;
		} else {
			rowHeight = this.blockRowHeight;

			// Find the index of the user record
			var userIndex = this.userStore.indexOfId(record.get('userid'));

			if (userIndex >= 0) {
				startRowTopOffset += userIndex * rowHeight;
			}
		}

		data.blockTop = startRowTopOffset;

		var periodStart = this.daterange.getStartDate().getTime()/1000;
		var periodEnd = this.daterange.getDueDate().getTime()/1000;
		// The start/end date from the record have to be parsed since they are in String format and
		// we will have to do some calculations with them.
		var blockStart = parseInt(data.start, 10);
		var blockEnd = parseInt(data.end, 10);

		// Filter out any records that for some reason are beyond the period range.
		if(blockStart < periodEnd && blockEnd > periodStart){

			data.blockHeight = rowHeight - 2;

			if(blockStart < periodStart){
				data.blockLeft = 0;
			} else {
				// Get the leftoffset of the start of the block
				var pixelOffset = this.findBlockPixelOffset(blockStart, true);
				data.blockLeft = pixelOffset;
			}

			if(blockEnd > periodEnd){
				// Run the block until the end of the timeline
				data.blockWidth = this.bodyBackgroundElem.getWidth() - data.blockLeft;
			} else {
				// Get the leftoffset of the end of the block
				var pixelOffset = this.findBlockPixelOffset(blockEnd, false);
				// Offset has to be transformed into a width
				data.blockWidth = pixelOffset - data.blockLeft;
			}
		}
		return data;
	},

	/**
	 * Filters the list of records by checking whether they are going to be visible on the timeline.
	 * @param {Ext.data.Record[]} records List of records
	 * @return {Ext.data.Record[]} Filtered list of records
	 * @private
	 */
	filterRecords: function(records){
		var filteredRecords = [];
		var periodStart = this.daterange.getStartDate().getTime()/1000;
		var periodEnd = this.daterange.getDueDate().getTime()/1000;

		for(var i=0;i<records.length;i++){
			var blockStart = parseInt( records[i].get("start"), 10);
			var blockEnd = parseInt( records[i].get("end"), 10);

			if(this.filterBlockData(blockStart, blockEnd, periodStart, periodEnd)){
				filteredRecords.push(records[i]);
			}
		}

		return filteredRecords;
	},

	/**
	 * Determines whether the block should be rendered or not. When based on the supplied data it
	 * should not be rendered it will return false. Otherwise it will return true.
	 * @param {Number} blockStart Timestamp of start of block (in seconds).
	 * @param {Number} blockEnd Timestamp of end of block (in seconds).
	 * @param {Number} periodStart Timestamp of start of period (in seconds).
	 * @param {Number} periodEnd Timestamp of end of period (in seconds).
	 * @return {Boolean} Returns false when block should be hidden, otherwise returns true
	 * @private
	 */
	filterBlockData: function(blockStart, blockEnd, periodStart, periodEnd){
		// Filter out any records that for some reason are beyond the period range.
		if(blockStart < periodEnd && blockEnd > periodStart){

			if (this.model.showOnlyWorkingHours()) {
				var blockDuration = blockEnd - blockStart;
				var hiddenHoursDuration = (this.workingHoursStart + ((24 * 60) - this.workingHoursEnd)) * 60;

				// Block duration is less than the hours that are hidden
				if(blockDuration <= hiddenHoursDuration){
					// Convert start and end minutes to seconds since start day
					var workStart= this.workingHoursStart * 60;
					var workEnd = this.workingHoursEnd * 60;

					// Convert the blockStart and blockEnd to seconds since start day
					var blockStartDate = new Date(blockStart*1000);
					var blockEndDate  = new Date(blockEnd*1000);
					var blockStartSecs = (blockStartDate.getHours()*60*60) +
					    (blockStartDate.getMinutes()*60) + (blockStartDate.getSeconds());
					var blockEndSecs = (blockEndDate.getHours()*60*60) +
					    (blockEndDate.getMinutes()*60) + (blockEndDate.getSeconds());

					// Check to see whether the start or end part of the block will show itself
					var startBlockShown = (blockStartSecs >= workStart && blockStartSecs < workEnd);
					var endBlockShown = (blockEndSecs > workStart && blockEndSecs <= workEnd);

					if(startBlockShown || endBlockShown){
						return true;
					}

					// Check to see whether the start or end part of the block is spanning throughout
					// the visibility hours
					if (blockStartSecs < workStart && blockEndSecs > workEnd) {
						return true;
					}

				// Block duration is higher than the hours that are hidden
				} else {
					return true;
				}
			// No hours are hidden, so all blocks within the period duration are shown
			} else {
				return true;
			}
		}
		return false;
	},

	/**
	 * Get the pixel offset from the start of the day of the timestamp.
	 * The argument inclusive is used to determine when the supplied timestamp matches the start of
	 * the day whether the timestamp belongs to the that day or to the previous day. When dealing
	 * with a start date you have to set inclusive to true and when dealing with an end date you
	 * have to set it to false. By default inclusive is set to true.
	 * @param {Number} timestamp Timestamp
	 * @param {Boolean} inclusive Is used to determine if timestamp which is set to 0:00, is set to
	 * the start of the next day (true) or at the end of the day before (false).
	 * day or the day before.
	 * @return {Number} Pixel Offset since start of the timeline
	 * @private
	 */
	findBlockPixelOffset: function(timestamp, inclusive){
		// Get the index of the day the timestamp falls on
		var dayIndex = this.findDayIndexByTimestamp(timestamp, inclusive);

		/*
		 * If there is a difference of time between timestamp and day pointed by dayIndex then probably
		 * timestamp is lying on next day, this thing occurs for 'showing working hours only' setup.
		 *
		 * if we are showing working hours only then appointments on saturday will have dayindex of
		 * friday so we need to set it's offset to end of the day so directly settings it's offset
		 * to most end of the day.
		 */
		if(timestamp - this.daysMap[dayIndex].timestamp >= 86400) {
			return this.daysMap[dayIndex].leftPos + this.dayWidth;
		}

		/* Extract the hour and minutes from the timestamp. We need this to prevent DST issues when
		 * there is a DST change during a day. If that happens the number of seconds since the start
		 * of a day is no longer correct.
		 */
		var timestampDate = new Date(timestamp*1000);
		var DSTSafeHours = timestampDate.getHours();
		var DSTSafeMinutes = timestampDate.getMinutes();

		var startDayPixelOffset;
		/* If we have a timestamp that starts at 0:00 we need to determine whether this is at the start of
		 * a day or at the end of a day. We can determine this based on the inclusive argument. If the callee
		 * wants to have an inclusive pixel offset we should give the pixel offset of the start of the day.
		 * If it is not inclusive we have to give the pixel offset of the end of the day. This is not the
		 * same as there is a space between two days.
		 */
		if(DSTSafeHours === 0 && DSTSafeMinutes === 0 && !inclusive){
			// Set the pixel offset at the end of the current day
			startDayPixelOffset = this.dayWidth;
		} else {
			var secondsSinceStartOfDay = ( ( DSTSafeHours * 60 ) + DSTSafeMinutes ) * 60;
			startDayPixelOffset = this.findPixelOffsetStartOfDayBySecs(secondsSinceStartOfDay);
		}

		// Timestamp ends after last visible hour.
		if(startDayPixelOffset == -1){
			/* When the timestamp is after the last visible hour of that day then we should add the
			 * daySpacing to the pixel offset to make it start on the next day. When this is the
			 * last day on the timeline the daySpacing should not be added. Otherwise the timeline
			 * width would be stretched and the scroll width will get out-of-sync with the header.
			 */
			var lastDayOnTimeline = (dayIndex < this.daysMap.length-1);
			startDayPixelOffset = this.dayWidth + (lastDayOnTimeline ? this.daySpacing : 0);
		}

		// Adding the number of pixels from the start of the timeline till the start of the day to
		// the pixels since the start of the day till the timestamp.
		var pixelOffset = this.daysMap[dayIndex].leftPos + startDayPixelOffset;

		return pixelOffset;
	},

	/**
	 * Returns the number of pixels since the start of the day based on the number of seconds since
	 * the start of the day. It returns -1 if the number of seconds go beyond the last displayed
	 * hour. This can happen when only working hours are shown.
	 * @param {Number} secondsSinceStartDay Seconds since the start of the day
	 * @return {Number} Pixel Offset since start of the day
	 * @private
	 */
	findPixelOffsetStartOfDayBySecs: function(secondsSinceStartDay){
		var firstHourStartDayOffsetSecs = this.hoursEachDayMap[0].startDayOffset;
		var lastHourStartDayOffsetSecs = this.hoursEachDayMap[ this.hoursEachDayMap.length-1 ].startDayOffset;

		// Timestamp takes place after last visible hour
		if(lastHourStartDayOffsetSecs + this.slotDuration < secondsSinceStartDay){
			return -1;
		}

		// Timestamp takes place before or at the start of the first hour
		if(firstHourStartDayOffsetSecs >= secondsSinceStartDay){
			return 0;
		}

		var numVisibleSeconds = this.slotDuration * this.hoursEachDayMap.length;
		var secondsSinceFirstHour = secondsSinceStartDay - firstHourStartDayOffsetSecs;
		/**
		 * Use the secondsSinceFirstHour:numVisibleSeconds ratio to turn the number of seconds since
		 * the start of the first hour into the amount of pixels since the start of the first hour.
		 */
		var pixelOffset = (secondsSinceFirstHour / numVisibleSeconds) * this.dayWidth;
		pixelOffset = Math.round(pixelOffset);
		return pixelOffset;

	},

	/**
	 * Get the dayIndex of the day in the daysMap table that the timestamp belongs to.
	 * The argument inclusive is used to determine when the supplied timestamp matches the start of
	 * the day whether the timestamp belongs to the that day or to the previous day. When dealing
	 * with a start date you have to set inclusive to true and when dealing with an end date you
	 * have to set it to false. By default inclusive is set to true.
	 * @param {Number} timestamp Timestamp
	 * @param {Boolean} inclusive Is used to determine if timestamp which is set to 0:00, is set to
	 * the start of the next day (true) or at the end of the day before (false).
	 * @return {Number} Index of the day as used in the this.daysMap.
	 * @private
	 */
	findDayIndexByTimestamp: function(timestamp, inclusive){
		if (!Ext.isDefined(inclusive)) {
			inclusive = true;
		}

		/**
		 * If date takes place before the start of the first day in the daysMap the selector will
		 * select from the first day in the daysMap.
		 */
		var dayIndex = 0;
		for (var i = 0; i < this.daysMap.length; i++){
			if (inclusive && this.daysMap[i].timestamp <= timestamp) {
				dayIndex = i;
			} else if (!inclusive && this.daysMap[i].timestamp < timestamp) {
				dayIndex = i;
			} else {
				break;
			}
		}

		return dayIndex;
	},

	/**
	 * Find the timestamp based on the supplied coordinate.
	 * @param {Number} coordX X coordinate
	 * @return {Number} Timestamp
	 * @private
	 */
	findTimestampByTimelineXCoord: function(coordX){
		var dayIndex = coordX / (this.dayWidth + this.daySpacing);
		var pixelsPastDayStart = (dayIndex % 1) * (this.dayWidth + this.daySpacing);

		// Calculate how far along the day the coordinate is (ratioDay)
		var ratioDay = pixelsPastDayStart / this.dayWidth;
		var timeSinceStartOfDay;
		if(ratioDay < 1){
			// Calculate the duration of the visible hours
			var durationVisHours = this.slotDuration * this.hoursEachDayMap.length;
			/**
			 * Calculate how many seconds past the start of the day is using the rationDay and the
			 * startDayOffset of the first hour.
			 */
			timeSinceStartOfDay = (ratioDay * durationVisHours) + this.hoursEachDayMap[0].startDayOffset;
		} else {
			// One slot duration past last shown hour slot
			timeSinceStartOfDay = this.hoursEachDayMap[ this.hoursEachDayMap.length-1 ].startDayOffset + this.slotDuration;
		}
		/* Use timestamp of the start of the day to make an date object that we can add the hour and
		 * minutes to. We divide the number of seconds since the start of the day to get the hour
		 * and minutes. This we will add to the Date object by using the setHours and setMinutes
		 * methods. This will prevent problems after Daylight Saving Time.
		 */
		var date = new Date( this.daysMap[ Math.floor(dayIndex) ].timestamp * 1000 );
		var hoursSinceStartOfDay = Math.floor(timeSinceStartOfDay/(60*60));
		var minutesSinceStartOfDay = Math.floor( (timeSinceStartOfDay%(60*60)) / 60 );
		/* NOTE: Using setHours/setMinutes will cause an issue right on the DST change. If you look
		 * at the Dutch DST change in March, the clock will move forward an hour at 02:00. Using
		 * these function it will switch 02:00 to 01:00 and 02:30 to 01:30. That might not be what
		 * the user expects, but it only happens during the DST change.
		 */
		date.setHours( hoursSinceStartOfDay );
		date.setMinutes( minutesSinceStartOfDay );
		return Math.floor( date.getTime()/1000 );
	},

	/**
	 * Binds the user store to the timeline.
	 * @param {Ext.data.Store} store Store
	 * @param {Boolean} initial Internally used to indicate that this is the first call after render.
	 * @private
	 */
	bindUserStore: function(store, initial){
		if(this.userStore){
			this.mun(this.userStore, {
				'datachanged': this.onUserRefresh,
				'add': this.onUserAdd,
				'remove': this.onUserRemove,
				'clear': this.onUserRefresh,
				scope: this
			});
		}

		this.userStore = Ext.StoreMgr.lookup(store);

		if(this.userStore){
			this.mon(this.userStore, {
				'datachanged': this.onUserRefresh,
				'add': this.onUserAdd,
				'remove': this.onUserRemove,
				'clear': this.onUserRefresh,
				scope: this
			});

			if(!initial){
				this.sizeTimelineBackground();
			}
		}
	},

	/**
	 * Called when the {@link Zarafa.common.freebusy.data.FreebusyModel model} fires the userstorechange event to indicate
	 * that another user store is set.
	 * @param {Ext.data.Store} store The new store
	 * @private
	 */
	onUserStoreChange: function(store)
	{
		this.bindUserStore(store);
		this.refresh();
		this.sizeTimelineBackground();
	},

	/**
	 * Called when the {@link Zarafa.common.freebusy.data.FreebusyModel model} fires the blockstorechange event to indicate
	 * that another block store is set.
	 * @param {Ext.data.Store} store The new store
	 * @private
	 */
	onBlockStoreChange: function(store)
	{
		this.bindBlockStore(store);
	},

	/**
	 * Called when the {@link Zarafa.common.freebusy.data.FreebusyModel model} fires the sumblockstorechange event to indicate
	 * that another sum block store is set.
	 * @private
	 */
	onSumBlockStoreChange: function(store)
	{
		this.bindSumBlockStore(store);
	},

	/**
	 * Fires when the visibility of the non-working hours has been changed
	 * @param {Boolean} hideNonWorkingHours True to hide the non-working hours
	 * @private
	 */
	onShowWorkingHoursChange: function(hideNonWorkingHours)
	{
		this.refreshTimeline();
	},

	/**
	 * Called when the userStore fires the datachanged or clear event. This TimelineView will sync
	 * the timeline background scrolling height with the user list based on the new amount of users
	 * in the userStore.
	 * @private
	 */
	onUserRefresh: function(){
		this.refresh();
		this.sizeTimelineBackground();
	},

	/**
	 * Called when the userStore fires the add event. This TimelineView will sync the timeline
	 * background scrolling height with the user list based on the new amount of users in the
	 * userStore.
	 * @private
	 */
	onUserAdd: function(){
		this.sizeTimelineBackground();
	},

	/**
	 * Called when the userStore fires the remove event. This TimelineView will sync the timeline
	 * background scrolling height with the user list based on the new amount of users in the
	 * userStore.
	 * @param {Ext.data.Store} store Store
	 * @param {Ext.data.Record} userRecord Record of the user that is removed
	 * @param {Number} index Index of the user record in the store
	 * @private
	 */
	onUserRemove: function(store, userRecord, index){
		this.refresh();
		this.sizeTimelineBackground();
	}
});

Ext.reg('zarafa.freebusytimelineview', Zarafa.common.freebusy.ui.TimelineView);
Ext.namespace('Zarafa.common.manageCc.ui');

/**
 * @class Zarafa.common.manageCc.ui.ManageCcGrid
 * @extends Ext.grid.GridPanel
 * @xtype zarafa.manageccgrid
 *
 * Grid panel will be used to display user. Which show in Cc recipient field in create mail dialog.
 */
Zarafa.common.manageCc.ui.ManageCcGrid = Ext.extend(Ext.grid.GridPanel, {
	/**
	 * @constructor
	 * @param {Object} config Configuration structure
	 */
	constructor: function (config) {
		config = config || {};

		Ext.applyIf(config, {
			xtype: 'zarafa.manageccgrid',
			border: true,
			viewConfig: {
				forceFit: true,
				deferEmptyText: false,
				emptyText: '<div class=\'emptytext\'>' + _('No Cc recipient configured') + '</div>'
			},
			loadMask: {
				msg: _('Loading Cc recipients') + '...'
			},
			store: new Zarafa.core.data.IPMRecipientStore({
				autoResolve: false,
				autoDestroy: true,
				customObjectType: Zarafa.core.data.RecordCustomObjectType.ZARAFA_CC_RECIPIENT
			}),
			columns: this.initColumnModel(),
			selModel: new Zarafa.common.ui.grid.RowSelectionModel({
				singleSelect: true
			})
		});

		Zarafa.common.manageCc.ui.ManageCcGrid.superclass.constructor.call(this, config);
	},

	/**
	 * Creates a column model object, used in {@link #colModel} config
	 * @return {Ext.grid.ColumnModel} column model object
	 * @private
	 */
	initColumnModel: function()
	{
		return [{
			dataIndex: 'icon_index',
			headerCls: 'zarafa-icon-column',
			header: '<p class="icon_index">&nbsp;<span class="title">Icon</span></p>',
			width: 25,
			sortable: false,
			menuDisabled:true,
			fixed: true,
			renderer: Zarafa.common.ui.grid.Renderers.icon
		},{
			dataIndex: 'reply_mail',
			header: _('Reply Mail'),
			renderer: this.columnRenderer,
			sortable: false,
			menuDisabled:true,
			fixed: true,
			align: "center",
			width: 150,
			listeners: {
				click: this.onCellClick
			}
		},{
			dataIndex: 'new_mail',
			header: _('New Mail'),
			renderer: this.columnRenderer,
			sortable: false,
			align: "center",
			menuDisabled:true,
			fixed: true,
			width: 150,
			listeners: {
				click: this.onCellClick,
			}
		},{
			dataIndex: 'display_name',
			header: _('Display Name'),
			renderer: Zarafa.common.ui.grid.Renderers.displayName,
			sortable: false,
			menuDisabled:true,
			width: 250
		},{
			dataIndex: 'smtp_address',
			header: _('Email Address'),
			renderer: Zarafa.common.ui.grid.Renderers.displayName,
			sortable: false,
			menuDisabled:true,
			width: 250
		}];
	},

	/**
	 * initialize events for the grid panel.
	 * @private
	 */
	initEvents: function()
	{
		Zarafa.common.manageCc.ui.ManageCcGrid.superclass.initEvents.apply(this, arguments);

		this.on({
			'viewready': this.onViewReady,
			'rowdblclick': this.onRowDblClick,
			scope: this
		});
	},

	/**
	 * Render which shows cross or green right icon in grid column if Cc recipient is configured
	 * for the 'New mail' or 'Reply mail'.
	 *
	 * @param {Object} value The data value for the cell.
	 * @param {Object} p An object with metadata
	 * @return {String} The formatted string
	 */
	columnRenderer: function (value, p) {
		p.css += 'zarafa-grid-empty-cell';
		p.css += value === true ? ' icon_flag_complete' : ' icon_cross_red';
		return '';
	},

	/**
	 * Function which delete the {@link Zarafa.common.manageCc.data.IPMCcRecipientRecord IPMCcRecipientRecord}
	 * remove the Cc recipient from the {@link Zarafa.core.data.IPMRecipientStore IPMRecipientStore}.
	 */
	removeCcRecipient: function ()
	{
		var selectionModel = this.getSelectionModel();
		var record = this.getSelectionModel().getSelected();

		if(!record) {
			Ext.Msg.alert(_('Remove'), _('Please select a Cc recipient record.'));
			return;
		}

		// before removing Cc recipient we should select next available record,
		// because deleting Cc recipient will remove selection
		if (selectionModel.hasNext()) {
			selectionModel.selectNext();
		} else if (selectionModel.hasPrevious()) {
			selectionModel.selectPrevious();
		}

		this.store.remove(record);
	},

	/**
	 * Event handler which is fired when the gridPanel is ready. This will automatically
	 * select the first row in the grid.
	 * @private
	 */
	onViewReady: function()
	{
		this.getSelectionModel().selectFirstRow();
	},

	/**
	 * Event handler which is fired when the {@link Zarafa.common.manageCc.ui.ManageCcGrid ManageCcGrid} is double clicked.
	 * it will call generic function to handle the functionality.
	 *
	 * @param {Ext.grid.GridPanel} grid the grid of which the row double clicked.
	 * @param {Number} rowIndex number of the row double clicked.
	 * @private
	 */
	onRowDblClick: function(grid, rowIndex)
	{
		var store = grid.getStore();
		var record = store.getAt(rowIndex);

		if(record.isOneOff()) {
			this.addOrEditManageCcRecipient(record, false);
		} else {
			Zarafa.common.Actions.openViewRecipientContent(record, {
				modal:true
			});
		}
	},

	/**
	 * Event handler triggered when the icon on the cell of "Reply Mail" or "New Mail" is clicked.
	 *
	 * @param {Object} item The item of {Ext.grid.ColumnModel columnModel} which was clicked.
	 * @param {Ext.grid.GridPanel} grid the grid of which the row was clicked.
	 * @param {Number} rowIndex number of the row clicked.
	 */
	onCellClick: function(item, grid, rowIndex)
	{
		var store = grid.getStore();
		var record = store.getAt(rowIndex);
		record.set(item.dataIndex, !record.get(item.dataIndex));
	},

	/**
	 * It will call {@link Zarafa.common.Actions#openEditRecipientContent} to add or edit external contact.
	 * @param {Ext.data.Record} record record to be opened
	 * @param {Boolean} removeOnCancel true to remove the record
	 * from store while pressing cancel button, false otherwise
	 */
	addOrEditManageCcRecipient: function (record, removeOnCancel)
	{
		var componentType = Zarafa.core.data.SharedComponentType['common.managecc.dialog.managecceditcontentpanel'];
		Zarafa.common.Actions.openEditRecipientContent(componentType, record, { removeOnCancel: removeOnCancel});
	}
});

Ext.reg('zarafa.manageccgrid', Zarafa.common.manageCc.ui.ManageCcGrid);
Ext.namespace('Zarafa.common.manageCc.ui');

/**
 * @class Zarafa.common.manageCc.ui.ManageCcPanel
 * @extends Ext.Panel
 * @xtype zarafa.manageccpanel
 *
 * Panel contains the {@link Zarafa.common.manageCc.ui.ManageCcGrid ManageCcGrid}.
 * which manages the default Cc recipient for new / reply mails.
 */
Zarafa.common.manageCc.ui.ManageCcPanel = Ext.extend(Ext.Panel, {

	/**
	 * @constructor
	 * @param config Configuration structure
	 */
	constructor: function(config)
	{
		config = config || {};
		Ext.applyIf(config, {
			xtype: 'zarafa.manageccpanel',
			border: false,
			autoScroll: false,
			layout: {
				type: 'vbox',
				align: 'stretch',
				pack: 'start'
			},
			items: this.createPanelItems()
		});

		Zarafa.common.manageCc.ui.ManageCcPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Function will create panel items for {@link Zarafa.common.manageCc.ui.ManageCcPanel ManageCcPanel}
	 *
	 * @return {Array} array of items that should be added to panel.
	 * @private
	 */
	createPanelItems: function()
	{
		return [{
			xtype: 'container',
			flex: 1,
			layout: {
				type: 'hbox',
				align: 'stretch',
				pack: 'start'
			},
			items: [{
				xtype: 'zarafa.manageccgrid',
				ref: '../manageCcgrid',
				flex: 1
			},{
				xtype: 'container',
				width: 160,
				layout: {
					type: 'vbox',
					align: 'center',
					pack: 'start'
				},
				items: [{
					xtype: 'button',
					text: _('Address Book') + '...',
					width: 130,
					handler: this.onClickAddressBookBtn,
					scope: this
				},{
					xtype: 'spacer',
					height: 10
				},{
					xtype: 'button',
					text: _('Add') + '...',
					width: 130,
					ref: '../../addButton',
					handler: this.onClickAdd,
					scope: this
				},{
					xtype: 'spacer',
					height: 10
				},{
					xtype: 'button',
					text: _('Remove') + '...',
					width: 130,
					disabled: true,
					ref: '../../removeButton',
					handler: this.onClickRemove,
					scope: this
				}]
			}]
		}];
	},

	/**
	 * Initialize events for the panel.
	 * @private
	 */
	initEvents: function()
	{
		Zarafa.common.manageCc.ui.ManageCcPanel.superclass.initEvents.call(this);

		this.mon(this.getStore(), {
			'remove': this.onStoreRemove,
			'update': this.onStoreUpdate,
			'add': this.onStoreAdd,
			scope: this
		});

		// register event to enable/disable buttons
		this.mon(this.manageCcgrid.getSelectionModel(), 'selectionchange', this.onGridSelectionChange, this);
	},

	/**
	 * Called by the {@link Zarafa.settings.ui.SettingsCategory Category} when
	 * it has been called with {@link zarafa.settings.ui.SettingsCategory#update}.
	 * This is used to load the latest version of the settings from the
	 *
	 * {@link Zarafa.settings.SettingsModel} into the UI of this category.
	 * @param {Zarafa.settings.SettingsModel} settingsModel The settings to load
	 */
	update: function(settingsModel)
	{
		this.model = settingsModel;

		// Convert the Cc recipients into Store data
		var recipients = settingsModel.get('zarafa/v1/contexts/mail/cc_recipients', []);

		var recipientsData = {'item': []};
		Ext.each(recipients, function(recipient, index) {
			recipientsData.item.push({props: recipient});
		});

		// Load all recipients into the Store
		this.getStore().loadData(recipientsData);
	},

	/**
	 * Called by the {@link Zarafa.settings.ui.SettingsCategory Category} when
	 * it has been called with {@link zarafa.settings.ui.SettingsCategory#updateSettings}.
	 * This is used to update the settings from the UI into the {@link Zarafa.settings.SettingsModel settings model}.
	 *
	 * @param {Zarafa.settings.SettingsModel} settingsModel The settings to update
	 */
	updateSettings: function(settingsModel)
	{
		var recipients = settingsModel.get('zarafa/v1/contexts/mail/cc_recipients', []);
		var records = Ext.pluck(this.getStore().getRange(), 'data');

		if (!recipients.equals(records)) {
			settingsModel.set('zarafa/v1/contexts/mail/cc_recipients', records);
		}
	},

	/**
	 * Event handler triggered when 'Add' button has been clicked.
	 * It will call the {@link Zarafa.common.manageCc.ui.ManageCcGrid#addOrEditManageCcRecipient}.
	 */
	onClickAdd: function()
	{
		var store = this.getStore();
		// find rowid value
		var data = Ext.pluck(store.getRange(), 'data');
		var rowId = Ext.max(Ext.pluck(data, 'rowid')) || 0;

		var record = Zarafa.core.data.RecordFactory.createRecordObjectByCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_CC_RECIPIENT, {
			rowid: rowId + 1,
			display_type: Zarafa.core.mapi.DisplayType.DT_REMOTE_MAILUSER
		});

		store.add(record);

		this.manageCcgrid.addOrEditManageCcRecipient(record, true);
	},

	/**
	 * Handler called when 'remove' button was clicked. It is used to remove the
	 * user from {@link Zarafa.common.manageCc.ui.manageCcGrid manageCcGrid}.
	 */
	onClickRemove: function()
	{
		this.manageCcgrid.removeCcRecipient();
	},

	/**
	 * Returns the {@link Zarafa.core.data.IPMRecipientStore IPMRecipientStore} associated
	 * with this panel.
	 * @return {Zarafa.core.data.IPMRecipientStore} The store
	 */
	getStore: function()
	{
		return this.manageCcgrid.getStore();
	},

	/**
	 * Event handler for the {@link Ext.data.Store#remove} event which is fired
	 * by the {@link Ext.data.Store} inside the {@link #manageCcgrid}.
	 * This will mark the {@link Zarafa.settings.SettingsContextModel} as
	 * {@link Zarafa.settings.SettingsContextModel#setDirty dirty}.
	 *
	 * @param {Ext.data.Store} store The store which fired the event
	 * @param {Ext.data.Record} record The record which was updated
	 * @private
	 */
	onStoreRemove: function(store, record)
	{
		if(!record.phantom) {
			this.settingsContext.getModel().setDirty();
		}
	},

	/**
	 * Event handler for the {@link Ext.data.Store#add} event which is fired
	 * by the {@link Ext.data.Store} inside the {@link #manageCcgrid}.
	 * This will mark the {@link Zarafa.settings.SettingsContextModel} as
	 * {@link Zarafa.settings.SettingsContextModel#setDirty dirty} while
	 * Cc recipient was added from address book only.
	 *
	 * @param {Ext.data.Store} store The store which fired the event
	 * @param {Array} records An Array of {@link Ext.data.Record record} objects which are added to store
	 * @private
	 */
	onStoreAdd: function(store, records)
	{
		if (!Ext.isEmpty(records[0].get('entryid'))) {
			// mark the model as dirty as the new send as is added
			this.settingsContext.getModel().setDirty();
		}
	},

	/**
	 * Event handler for the {@link Ext.data.Store#update} event which is fired
	 * by the {@link Ext.data.Store} inside the {@link #manageCcgrid}.
	 * This will mark the {@link Zarafa.settings.SettingsContextModel} as
	 * {@link Zarafa.settings.SettingsContextModel#setDirty dirty}.
	 *
	 * @param {Ext.data.Store} store The store which fired the event
	 * @param {Ext.data.Record} record The record which was updated
	 * @param {String} operation The update operation being performed.
	 * @private
	 */
	onStoreUpdate: function(store, record, operation)
	{
		if (operation !== Ext.data.Record.COMMIT) {
			this.settingsContext.getModel().setDirty();
		}
	},

	/**
	 * Event handler will be called when selection in {@link Zarafa.common.manageCc.ui.manageCcGrid manageCcGrid}
	 * has been changed.
	 * @param {Ext.grid.RowSelectionModel} selectionModel selection model that fired the event
	 */
	onGridSelectionChange: function(selectionModel)
	{
		this.removeButton.setDisabled(!selectionModel.hasSelection());
	},

	/**
	 * Handler function will be called when user clicks on 'Address Book' button,
	 * this will open an Address Book and one can select as a default Cc recipient
	 * from any of the address book contact list.
	 * @private
	 */
	onClickAddressBookBtn: function()
	{
		Zarafa.common.Actions.openABUserSelectionContent({
			callback: Zarafa.common.Actions.abCallBack,
			scope: this,
			singleSelect: false,
			listRestriction: {
				hide_users: ['system', 'everyone'],
				hide_companies: true
			}
		});
	}
});
Ext.reg('zarafa.manageccpanel', Zarafa.common.manageCc.ui.ManageCcPanel);
Ext.ns('Zarafa.common.plugins');

/**
 * @class Zarafa.common.plugins.FieldLabeler
 * @extends Ext.util.Observable
 * @ptype zarafa.fieldlabeler
 *
 * <p>A plugin for Field Components which renders standard Ext form wrapping and labels
 * round the Field at render time regardless of the layout of the Container.</p>
 * <p>Usage:</p>
 * <pre><code>
  {
    xtype: 'combo',
    plugins: [{
      ptype: 'zarafa.fieldlabeler'
    }],
    triggerAction: 'all',
    fieldLabel: 'Select type',
    store: typeStore
  }
 * </code></pre>
 */
Zarafa.common.plugins.FieldLabeler = Ext.extend(Ext.util.Observable, {
	/**
	 * Initialize the plugin for the given {@link Ext.form.Field field}
	 * @param {Ext.form.Field} field The field on which the plugin is placed
	 */
	init: function(field)
	{
		field.onRender = field.onRender.createSequence(this.onRender);
		field.onResize = this.onResize;
		field.adjustPosition = this.adjustPosition;
		field.onDestroy = field.onDestroy.createSequence(this.onDestroy);
	},

	/**
	 * Renders the component
	 * @private
	 */
	onRender: function()
	{
		// Do nothing if being rendered by a form layout
		if (this.ownerCt) {
			if (this.ownerCt.layout instanceof Ext.layout.FormLayout) {
				return;
			}
		}

		// Pulls a named property down from the first ancestor Container it's found in
		const getParentProperty = function(propName) {
			for (var p = this.ownerCt; p; p = p.ownerCt) {
				if (p[propName]) {
					return p[propName];
				}
			}
		};

		this.resizeEl = (this.wrap || this.el).wrap({
			cls: 'x-form-element'
		});
		this.positionEl = this.itemCt = this.resizeEl.wrap({
			cls: 'x-form-item '
		});
		if (this.nextSibling()) {
			this.margins = {
				top: 0,
				right: 0,
				bottom: this.positionEl.getMargins('b'),
				left: 0
			};
		}
		this.actionMode = 'itemCt';

		// If our Container is hiding labels, then we're done!
		if (!Ext.isDefined(this.hideLabels)) {
			this.hideLabels = getParentProperty.call(this, 'hideLabels');
		}
		if (this.hideLabels) {
			this.resizeEl.setStyle('padding-left', '0px');
			return;
		}

		// Collect the info we need to render the label from our Container.
		if (!Ext.isDefined(this.labelSeparator)) {
			this.labelSeparator = getParentProperty.call(this, 'labelSeparator');
		}
		if (!Ext.isDefined(this.labelPad)) {
			this.labelPad = getParentProperty.call(this, 'labelPad');
		}
		if (!Ext.isDefined(this.labelAlign)) {
			this.labelAlign = getParentProperty.call(this, 'labelAlign') || 'left';
		}
		this.itemCt.addClass('x-form-label-' + this.labelAlign);

		if(this.labelAlign == 'top'){
			if (!this.labelWidth) {
				this.labelWidth = 'auto';
			}
			this.resizeEl.setStyle('padding-left', '0px');
		} else {
			if (!Ext.isDefined(this.labelWidth)) {
				this.labelWidth = getParentProperty.call(this, 'labelWidth') || 100;
			}
			this.resizeEl.setStyle('padding-left', (this.labelWidth + (this.labelPad || 5)) + 'px');
			this.labelWidth += 'px';
		}

		this.label = this.itemCt.insertFirst({
			tag: 'label',
			cls: 'x-form-item-label',
			style: {
				width: this.labelWidth
			},
			html: this.fieldLabel + (this.labelSeparator || ':')
		});
	},

	/**
	 * Ensure the input field is sized to fit in the content area of the resizeEl (to the right of its padding-left)
	 * We perform all necessary sizing here. We do NOT call the current class's onResize because we need this control
	 * we skip that and go up the hierarchy to {@link Ext.form.Field Field}
	 *
	 * @param {Number} adjWidth The box-adjusted width that was set
	 * @param {Number} adjHeight The box-adjusted height that was set
	 * @param {Number} rawWidth The width that was originally specified
	 * @param {Number} rawHeight The height that was originally specified
	 * @private
	 */
	onResize: function(adjWidth, adjHeight, rawWidth, rawHeight)
	{
		var width = adjWidth;
		var height = adjHeight;
		var widthCt = adjWidth;
		var heightCt = adjHeight;

		// If this field is not placed on a FormLayout, and this
		// field has the 'flex' property set, then the padding
		// (better known as the labelWidth + padding) must be
		// removed from the width.
		if (!(this.layout instanceof Ext.layout.FormLayout)) {
			if (!Ext.isEmpty(this.flex) || !Ext.isEmpty(this.columnWidth)) {
				width -= this.resizeEl.getPadding('l');
			} else {
				widthCt += this.resizeEl.getPadding('l');
			}
		}

		// If there is an inner Container (in other words, was this a CompositeField)
		// then we must resize this accordingly. Note that the fieldLabel comes before
		// the innerContainer, so the width excluding the label must be used.
		if (this.innerCt) {
			this.innerCt.setSize(width, heightCt);
		}

		Ext.form.Field.prototype.onResize.apply(this, arguments);

		if (this.resizeEl) {
			this.resizeEl.setWidth(heightCt);
		}
		if (this.itemCt) {
			this.itemCt.setWidth(widthCt);
		}

		if (this.getTriggerWidth) {
			this.wrap.setWidth(width);
			this.el.setWidth(width - this.getTriggerWidth());
		} else {
			this.el.setWidth(width);
		}

		if (this.el.dom.tagName.toLowerCase() == 'textarea') {
			height = this.resizeEl.getHeight(true);
			if (!this.hideLabels && (this.labelAlign == 'top')) {
				height -= this.label.getHeight();
			}
			this.el.setHeight(height);
		}

		if (this instanceof Ext.form.ComboBox) {
			if (!isNaN(width) && this.isVisible() && this.list) {
				this.doResize(width);
			} else {
				this.bufferSize = width;
			}
		}
	},

	/**
	 * If the owner is a HBox, we must compensate for the
	 * resizing of the container element of the _previous_
	 * item. This if the current item is not the first item,
	 * we must change the offset to the left-offset + size of the
	 * previous item.
	 * @param {Number} x The suggested X offset for the component
	 * @param {Number} y The suggested Y offset for the component
	 * @private
	 */
	adjustPosition: function(x, y)
	{
		if (this.ownerCt.layout instanceof Ext.layout.HBoxLayout) {
			x = 0;
			this.ownerCt.items.each(function(item) {
				if (item === this) {
					return false;
				}

				x += item.getActionEl().getWidth();
			}, this);
		}

		return { x: x, y: y};
	},

	/**
	 * Remove elements created by this plugin
	 * @private
	 */
	onDestroy: function()
	{
		if(this.ownerCt) {
			Ext.destroy(this.ownerCt);
			this.ownerCt.remove();
		}
	}
});

Ext.preg('zarafa.fieldlabeler', Zarafa.common.plugins.FieldLabeler);
Ext.namespace('Zarafa.common.plugins');

/**
 * @class Zarafa.common.plugins.IconDragSelectorPlugin
 * @extends Object
 * @ptype zarafa.icondragselectorplugin
 *
 * Copied from Ext.ux.IconDragSelector, as that class is non-extentible
 * and contains some issues regarding drag & drop of items in combination
 * with a dragselection.
 *
 * This class will draw a Drag Selection block when dragging the mouse
 * over a {@link Ext.DataView}.
 */
Zarafa.common.plugins.IconDragSelectorPlugin = Ext.extend(Object, {
	/**
	 * The component which has been {@link #init initialized} on
	 * this plugin.
	 * @property
	 * @type Ext.DataView
	 */
	view: undefined,

	/**
	 * Array of {@link Ext.lib.Region} objects which belong to each {@link Ext.DataView#all}.
	 * of the {@link #view}.
	 * @property
	 * @type Array
	 */
	itemRegions: undefined,

	/**
	 * The {@link Ext.lib.Region} of the {@link #view}.
	 * @property
	 * @type Ext.lib.Region
	 */
	bodyRegion: undefined,

	/**
	 * The proxy element which marks the selected area
	 * @property
	 * @type Ext.Element
	 */
	proxy: undefined,

	/**
	 * The Drag Tracker
	 * @property
	 * @type Ext.dd.DragTracker
	 */
	tracker: undefined,

	/**
	 * The Region which is currently contains the
	 * dragselecion.
	 * @property
	 * @type Ext.lib.Region
	 */
	dragRegion: undefined,

	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		Ext.apply(this, config);

		Zarafa.common.plugins.IconDragSelectorPlugin.superclass.constructor.call(this, config);
	},

	/**
	 * Initializes the {@link Ext.DataView view} to which this plugin has been hooked.
	 * @param {Ext.DataView} view The component on which the plugin is installed
	 */
	init: function(view)
	{
		this.view = view;

		this.view.on('render', this.onRender, this);
	},

	/**
	 * Initialize the {@link #itemRegions} and {@link #bodyRegion}. By calling
	 * {@link Ext.Element#getRegion} for all involved elements.
	 * @private
	 */
	fillRegions: function()
	{
		this.itemRegions = [];

		this.view.all.each(function(el) {
			this.itemRegions.push(el.getRegion());
		}, this);

		this.bodyRegion = this.view.el.getRegion();
	},

	/**
	 * Event handler which is called when the user {@link Ext.DataView#containerclick clicks}
	 * on the {@link #view}
	 * @return {Boolean} false To cancel the event
	 * @private
	 */
	cancelClick: function()
	{
		return false;
	},

	/**
	 * Event handler for the {@link Ext.dd.DragTracker#onBeforeStart DragTracker}.
	 * @param {Ext.EventObject} e The event object
	 * @return {Boolean} False when the event must be ignored
	 * @private
	 */
	onBeforeStart: function(e)
	{
		return this.view.getEl().contains(e.target) && !Ext.dd.DragDropMgr.dragCurrent; // FIXME
	},

	/**
	 * Event handler for the {@link Ext.dd.DragTracker#onStart DragTracker}.
	 * @param {Ext.EventObject} e The event object
	 * @private
	 */
 	onStart: function(e)
	{
		this.view.on('containerclick', this.cancelClick, this, { single: true });
		if (!this.proxy) {
			this.proxy = this.view.el.createChild({
				cls:'x-view-selector'
			});
		} else {
			if (this.proxy.dom.parentNode !== this.view.el.dom) {
				this.view.el.dom.appendChild(this.proxy.dom);
			}
			this.proxy.setDisplayed('block');
		}

		this.fillRegions();
		this.view.clearSelections();
	},

	/**
	 * Event handler for the {@link Ext.dd.DragTracker#onDrag DragTracker}.
	 * @param {Ext.EventObject} e The event object
	 * @private
	 */
	onDrag: function(e)
	{
		var startXY = this.tracker.startXY;
		var xy = this.tracker.getXY();

		var x = Math.min(startXY[0], xy[0]);
		var y = Math.min(startXY[1], xy[1]);
		var w = Math.abs(startXY[0] - xy[0]);
		var h = Math.abs(startXY[1] - xy[1]);

		this.dragRegion.left = x;
		this.dragRegion.top = y;
		this.dragRegion.right = x+w;
		this.dragRegion.bottom = y+h;

		this.dragRegion.constrainTo(this.bodyRegion);
		this.proxy.setRegion(this.dragRegion);

		for (var i = 0, len = this.itemRegions.length; i < len; i++) {
			var r = this.itemRegions[i];
			var sel = this.dragRegion.intersect(r);
			if (sel && !r.selected) {
				r.selected = true;
				this.view.select(i, true);
			} else if(!sel && r.selected) {
				r.selected = false;
				this.view.deselect(i);
			}
		}
	},

	/**
	 * Event handler for the {@link Ext.dd.DragTracker#onEnd DragTracker}.
	 * @param {Ext.EventObject} e The event object
	 * @private
	 */
	onEnd: function(e)
	{
		if (!Ext.isIE) {
			this.view.un('containerclick', this.cancelClick, this, { single: true });
		}
		if (this.proxy) {
			this.proxy.setDisplayed(false);
		}
	},

	/**
	 * Event handler which is called when the {@link #view} has been rendered.
	 * @param {Ext.DataView} view The view which has been rendered
	 * @private
	 */
	onRender: function(view)
	{
		this.dragRegion = new Ext.lib.Region(0, 0, 0, 0);

		this.tracker = new Ext.dd.DragTracker({
			onBeforeStart: this.onBeforeStart.createDelegate(this),
			onStart: this.onStart.createDelegate(this),
			onDrag: this.onDrag.createDelegate(this),
			onEnd: this.onEnd.createDelegate(this)
		});

		this.tracker.initEl(this.view.getEl());
	}
});

Ext.preg('zarafa.icondragselectorplugin', Zarafa.common.plugins.IconDragSelectorPlugin);
Ext.ns('Zarafa.common.plugins');

/**
 * @class Zarafa.common.plugins.SpinnerPlugin
 * @extends Ext.ux.Spinner
 *
 * Extension to the {@link Ext.ux.Spinner Spinner} object,
 * to fix any bugs which are inside there. This plugin
 * should not be used directly, instead it should be used
 * as baseclass for any Spinner plugins.
 */
Zarafa.common.plugins.SpinnerPlugin = Ext.extend(Ext.ux.Spinner, {
	/**
	 * Automatically called when the parent {@link Zarafa.common.ui.SpinnerField SpinnerField}
	 * is being rendered. This will apply the default value to the field.
	 * @param {Container} ct The parent container into which the SpinnerField is being rendered.
	 * @param {Number} position The position where the SpinnerField is being rendered
	 * @private
	 */
	doRender: function(ct, position)
	{
		Zarafa.common.plugins.SpinnerPlugin.superclass.doRender.call(this, ct, position);
		this.field.trigger = this.trigger;

		if (!Ext.isDefined(this.field.getTriggerWidth)) {
			this.field.getTriggerWidth = Ext.form.TriggerField.prototype.getTriggerWidth;
		}
	},

	/**
	 * Handle the click event on {@link Ext.form.TriggerField TriggerField}.
	 * This function is overridden to fix problem when trigger is clicked directly without gaining
	 * focus on the {@link Zarafa.common.ui.SpinnerField SpinnerField} then we should shift the
	 * focus to {@link Zarafa.common.ui.SpinnerField SpinnerField} and then execute this event.
	 * @param {Event} event The event object for this event
	 */
	onTriggerClick: function(event)
	{
		if (this.disabled || this.el.dom.readOnly) {
			return;
		}

		var middle = this.getMiddle();
		var ud = (event.getPageY() < middle) ? 'Up' : 'Down';

		// Grab focus on the element, this will ensure that
		// the field which we are editing has the browser focus.
		this.el.focus();

		this['onSpin' + ud]();
	},

	/**
	 * checks if control is allowed to spin
	 * @return {Boolean} True if the spin action is allowed
	 * @private
	 */
	isSpinnable: function()
	{
		if (this.disabled || (this.rendered && this.el.dom.readOnly) || !Ext.isDefined(this.field.getValue())) {
			Ext.EventObject.preventDefault(); //prevent scrolling when disabled/readonly
			return false;
		}
		return true;
	}
});
Ext.ns('Zarafa.common.plugins');

/**
 * @class Zarafa.common.plugins.splitFieldLabeler
 * @extends Ext.util.Observable
 * @ptype zarafa.splitfieldlabeler
 *
 * This plugin functions similarly as the {@link Zarafa.common.plugins.FieldLabeler FieldLabeler}.
 * This plugin should be attached to an {@link Ext.Container container} or anything which
 * has an 'items' field containing components. And is therefore primarily intended for
 * splitting a label for more then one component.
 * On the {@link Ext.Container container} the fieldLabel must be set consisting
 * of a string which can be split into multiple substrings.
 * Each component should have the {@link Zarafa.common.plugins.FieldLabeler.labelSplitter labelSplitter}
 * field set to indicate which variable it represents in the fieldLabel string of the container.
 *
 * Before rendering this plugin will breakup the fieldLabel on the {@link Ext.Container container}
 * and adds {@link Ext.form.Label Label} components and the original {@link Ext.form.Field Fields}
 * into the items array of the container.
 */
Zarafa.common.plugins.SplitFieldLabeler = Ext.extend(Ext.util.Observable, {
	/**
	 * @cfg {Object} labelCfg The labelConfiguration which must be
	 * applied to all generated labels.
	 */
	labelCfg: {
		style: 'text-align: left; padding: 3px 3px 3px 3px'
	},
	/**
	 * @cfg {Object} firstLabelCfg The labelConfiguration which must be
	 * applied to the first generated label (if this is also the first
	 * displayed item inside the field!). This configuration applied after
	 * the {@link #labelCfg}.
	 */
	firstLabelCfg: {
		style: 'text-align: left; padding: 3px 3px 3px 0px'
	},
	/**
	 * @cfg {Object} lastLabelCfg The labelConfiguration which must be
	 * applied to the last generated label (if this is also the last
	 * displayed item inside the field!). This configuration applied after
	 * the {@link #labelCfg}.
	 */
	lastLabelCfg: {
		style: 'text-align: left; padding: 3px 3px 3px 3px'
	},

	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		Ext.apply(this, config);
	},

	/**
	 * Plugin initializer for the SplitFieldLabeler.
	 * @param {Ext.Component} field The field for which the fieldlabeler is initialized
	 */
	init: function(field)
	{
		// This is not a container, or no label has been assigned.
		if (!Ext.isDefined(field.items) || !Ext.isDefined(field.fieldLabel) || field.hideLabel === true) {
			return;
		}

		var labelPieces = this.createLabelDistribution(field.items, field.fieldLabel);
		var labeledItems = this.applyLabelDistribution(field.items, labelPieces);

		this.applyLabelWidths(field.labelWidth, labeledItems);

		// Overwrite the items, be careful not to change the type
		// of the items object (it could be an array or MixedCollection).
		if (Array.isArray(field.items)) {
			field.items = labeledItems;
		} else {
			field.items.clear();
			field.items.addAll(labeledItems);
			// The innerCt has already been created at this point so the items
			// need to be added to that Component as well
			field.innerCt.items.clear();
			field.innerCt.items.addAll(labeledItems);
		}

		// The main field must not display any labels
		field.hideLabel = true;
		delete field.fieldLabel;
	},

	/**
	 * Create a list of all sub-labels which can be constructed from the given label.
	 * This will return a list of all sub-labels, including the separator string which
	 * indicates the correct position of an item.
	 *
	 * @param {Array/MixedCollection} items The items for which the label distribution
	 * is required.
	 * @param {String} label The label which must be distributed over the items.
	 * @return {Array} The array of labelstrings which must be applied to the components.
	 */
	createLabelDistribution: function(items, label)
	{
		var splitters = [];
		var generateSplitters = function(item) {
			if (Ext.isDefined(item.labelSplitter)) {
				splitters.push(item.labelSplitter);
			}
		};

		// Items can be an Array or MixedCollection, depending
		// on the current state of the field for which we are generating
		// the labels.
		if (Array.isArray(items)) {
			Ext.each(items, generateSplitters, this);
		} else {
			items.each(generateSplitters, this);
		}

		return Zarafa.util.Translations.MultiSplitTranslation(label, splitters);
	},

	/**
	 * Apply the label distribution which was generated with the
	 * {@link #createLabelDistribution createLabelDistribution}
	 * function. This will construct an array of {@link Ext.Component Components} with the
	 * labels and components in the order of which they should be displayed.
	 *
	 * @param {Array/MixedCollection} items The items for which the labels must be applied.
	 * @param {Array} labels The array of labels which must be applied to the items.
	 * @return {Array} items The array of items with the labels applied, and in the
	 * order in which they must be shown.
	 */
	applyLabelDistribution: function(items, labels)
	{
		var labeledItems = [];

		for (var i = 0; i < labels.length; i++) {
			var foundItem = undefined;
			var label = labels[i];

			var findItem = function(item) {
				if (item.labelSplitter === labels[i]) {
					foundItem = item;
					return false;
				}
			};

			// Items can be an Array or MixedCollection, depending
			// on the current state of the field for which we are generating
			// the labels.
			if (Array.isArray(items)) {
				Ext.each(items, findItem, this);
			} else {
				items.each(findItem, this);
			}

			// If the label was recognized as labelSplitter,
			// we must insert the component itself, otherwise
			// generate the label.
			if (Ext.isDefined(foundItem)) {
				labeledItems.push(foundItem);
			} else {
				labeledItems.push(Ext.apply({}, this.labelCfg, {
					xtype: 'displayfield',
					value: label
				}));
			}
		}

		// Apply special configurations to the first and last items.
		if (labeledItems[0].xtype == 'displayfield') {
			Ext.apply(labeledItems[0], this.firstLabelCfg);
		}
		if (labeledItems[labeledItems.length - 1].xtype == 'displayfield') {
			Ext.apply(labeledItems[labeledItems.length - 1], this.lastLabelCfg);
		}

		for (var i = 0; i < labeledItems.length; i++) {
			labeledItems[i] = Ext.create(labeledItems[i]);
		}

		return labeledItems;
	},

	/**
	 * Calculate the {@link Ext.form.Label.width labelWidths} which
	 * must be applied to each individual component which doesn't have the
	 * {@link Ext.form.Label.width labelWidth} set explicitly.
	 *
	 * @param {Number} totalWidth The total width which can be used to
	 * for all the labels combined.
	 * @param {Ext.Component} labeledItems The components which contain the
	 * fieldLabels for which the lengths must be calculated.
	 */
	applyLabelWidths: function(totalWidth, labeledItems)
	{
		// No width configured, all labels have autoWidth
		if (!Ext.isNumber(totalWidth)) {
			return;
		}

		// The combined length of all labels
		var labelLength = 0;

		// Loop over all items to see what labels have been
		// applied, and if a width has already been specified.
		for (var i = 0; i < labeledItems.length; i++) {
			var item = labeledItems[i];

			if (item.xtype == 'displayfield') {
				// If no width is configured, but a label is provided,
				// increase the labelLength to have this label be part
				// of the labelWidth calculations.
				// If a width was provided, we reduce the totalWidth
				// because we must only calculate the labelWidth for
				// the non-fixed labels.
				if ((!Ext.isDefined(item.width) || item.width === 0) && !Ext.isEmpty(item.text)) {
					labelLength += item.text.length;
				} else {
					totalWidth -= item.width;
				}
			}
		}

		// Calculate the labelWidth
		for (var i = 0; i < labeledItems.length; i++) {
			var item = labeledItems[i];

			if (item.xtype == 'displayfield') {
				if ((!Ext.isDefined(item.width) || item.width === 0) && !Ext.isEmpty(item.text)) {
					item.width = Math.round(totalWidth * (item.text.length / labelLength));
				}
			}
		}
	}
});

Ext.preg('zarafa.splitfieldlabeler', Zarafa.common.plugins.SplitFieldLabeler);
// -*- coding: utf-8; indent-tabs-mode: nil -*-
Ext.namespace('Zarafa.common.printer.renderers');

/**
 * @class Zarafa.common.printer.renderers.BaseRenderer
 * @extends Object
 * Abstract base renderer class. Don't use this directly, use a subclass instead.
 */
Zarafa.common.printer.renderers.BaseRenderer = Ext.extend(Object, {
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		Ext.apply(this, config);
	},

	/**
	 * Prints the component
	 * @param {Zarafa.core.data.MAPIRecord|Zarafa.core.Context} objectToPrint The record(s)
	 * that will be printed, or the context for which the items in the store will be printed.
	 */
	print: function(objectToPrint) {
		// The WebKit browsers (Chrome, Safari, and Edge (which is also recognized as WebKit))
		// show a print preview, so we can use an hidden iframe to create our print.
		// IE and FireFox don't have a print preview when printing from an iframe, so
		// we will open a new window in which we will create our print. This window will
		// also function as a print preview
		if ( Ext.isWebKit ) {

			// Needed for popouts
			var activeWindow = Zarafa.core.BrowserWindowMgr.getActive();
			var activeDocument = activeWindow.document;
			var printFrame = activeDocument.createElement('iframe');
			printFrame.style.cssText = "height: 0px; width: 0px; position: absolute;";
			activeDocument.body.appendChild(printFrame);

			printFrame.onload = function () {
				// Remove the iframe after printing.
				printFrame.contentWindow.onafterprint = function() {
					activeDocument.body.removeChild(printFrame);
				};

				printFrame.contentWindow.print();
			};
			var printDocument = printFrame.contentDocument;
			printDocument.write(this.generateHTML(objectToPrint));
			printDocument.close();
			this.postRender(printDocument, objectToPrint);


		} else {
			var win = window.open('', 'name'+(new Date().getTime()));
			if (win) {
				win.document.write(this.generateHTML(objectToPrint));
				win.document.close();
				this.postRender(win.document, objectToPrint);

				// Show print dialog when window has loaded all resources.
				win.onload = function() {
					win.print();
					win.close();
				};
			}
		}
	},

	/**
	 * Generates the HTML Markup which wraps whatever this.generateBodyTemplate produces
	 * @param {Zarafa.core.data.MAPIRecord|Zarafa.core.Context} objectToPrint The record(s)
	 * that will be printed, or the context for which the items in the store will be printed.
	 * @return {String} An HTML fragment to be placed inside the print window
	 */
	generateHTML: function(objectToPrint) {
		return new Ext.XTemplate(
			this.cleanTemplate(
				// no doctype, quicks mode works better for printing, especially in chrome.
				'<html>\n' +
				'<head>\n' +
					'<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />\n' +
					this.generateHeadTemplate() +
					'<title>' + this.getTitle(objectToPrint) + '</title>\n' +
				'</head>\n' +
				'<body>\n' +
					'<div id="pagemargin">\n' +
						this.generateBodyTemplate(objectToPrint) +
					'</div>\n' +
				'</body>\n' +
				'</html>'
			)
		).apply(this.prepareData(objectToPrint));
	},

	/**
	 * Clean the template string by removing all illegal characters
	 * @param {String} template The template string to clean
	 * @return {String} The clean template
	 * @private
	 */
	cleanTemplate: function(template)
	{
		// Conversions:
		// - \r is an illegal character which cannot be present in a string
		// - \ is a valid character, but shouldn't be escaped in an HTML text.
		return template.replace(/\r/g, '').replace(/\\/g, '\\\\');
	},

	/**
	 * Returns the HTML that will be placed into the <head> part of the print window.
	 * @return {String} The HTML fragment to place inside the print window's <head> element
	 */
	generateHeadTemplate: function() {
		if ( !Ext.isString(this.customStylesheetPath) || Ext.isEmpty(this.customStylesheetPath) ) {
			return '';
		}

		return '<link href="' +this.customStylesheetPath + '?' + new Date().getTime() + '" rel="stylesheet" type="text/css" media="screen,print" />\n';
	},

	/**
	 * Returns the HTML that will be placed into the <body> part of the print window.
	 * @param {Zarafa.core.data.MAPIRecord|Zarafa.core.Context} objectToPrint The record(s)
	 * that will be printed, or the context for which the items in the store will be printed.
	 * @return {String} The HTML fragment to place inside the print window's <body> element
	 */
	generateBodyTemplate: Ext.emptyFn,

	/**
	 * Prepares data suitable for use in an XTemplate from the component
	 * @param {Zarafa.core.data.MAPIRecord} record The record(s)
	 * that will be printed, or the context for which the items in the store will be printed.
	 * @return {Array} An empty array (override this to prepare your own data)
	 */
	prepareData: Ext.emptyFn,

	/**
	 * Returns the title for the print window. Browsers use this as
	 * the default filename when printing to PDF.
	 * @param {Zarafa.core.data.MAPIRecord|Zarafa.core.Context} objectToPrint The object being printed
	 * @return {String} The title for the print document
	 */
	getTitle: function(objectToPrint)
	{
		return _('grommunio Web print');
	},

	/**
	 * Passes the newly created DOM tree to add more rendering of Ext components in.
	 * @param {Document} webappDOM original webapp DOM
	 * @param {Document} printDOM DOM containing processed print template
	 * @param {Object} obj the object for the renderer
	 */
	postRender: Ext.emptyFn
});
Ext.namespace('Zarafa.common.recipientfield.data');

/**
 * @class Zarafa.common.recipientfield.data.SuggestionListJsonWriter
 * @extends Ext.data.JsonWriter
 */
Zarafa.common.recipientfield.data.SuggestionListJsonWriter = Ext.extend(Ext.data.JsonWriter, {
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			writeAllFields: true,
			encode: false
		});

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

	/**
	 * Use the {@link Ext.data.JsonWriter#toHash toHash} function for creating the hash.
	 *
	 * @param {Ext.data.Record} record
	 * @return {Object}
	 * @override
	 * @private
	 */
	destroyRecord: function(record){
		return this.toHash(record);
	}
});
Ext.namespace('Zarafa.common.recipientfield.data');

/**
 * @class Zarafa.common.recipientfield.data.SuggestionListStore
 * @extends Ext.data.Store
 * @xtype zarafa.suggestionliststore
 *
 * The main store which holds the suggestions as shown inside the
 * {@link Zarafa.common.recipientfield.ui.RecipientField RecipientField}.
 */
Zarafa.common.recipientfield.data.SuggestionListStore = Ext.extend(Ext.data.Store, {
	/**
	 * @cfg {String} actionType type of action that should be used to send request to server,
	 * valid action types are defined in {@link Zarafa.core.Actions Actions}, default value is 'list'.
	 */
	actionType: undefined,

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

		Ext.applyIf(config, {
			batch: true,
			autoSave: true,
			remoteSort: false,
			actionType: Zarafa.core.Actions['list'],
			proxy: new Zarafa.common.recipientfield.data.SuggestionListProxy(),
			writer: new Zarafa.common.recipientfield.data.SuggestionListJsonWriter(),
			reader: new Ext.data.JsonReader({
				root: 'result',
				id: 'id'
			}, Zarafa.common.recipientfield.data.SuggestionListRecord)
		});

		Zarafa.common.recipientfield.data.SuggestionListStore.superclass.constructor.call(this, config);

		// Use multi-sorting on the suggestions,
		// we can't apply this in the configuration object
		// so we have to do it here.
		this.sort([{
			field: 'display_name',
			direction: 'ASC'
		},{
			field: 'smtp_address',
			direction: 'ASC'
		},{
			field: 'email_address',
			direction: 'ASC'
		}]);
	},

	/**
	 * Load all data from the store
	 * @param {Object} options Additional options
	 */
	load: function(options)
	{
		if (!Ext.isObject(options)) {
			options = {};
		}

		if (!Ext.isObject(options.params)) {
			options.params = {};
		}

		// By default 'load' must cancel the previous request.
		if (!Ext.isDefined(options.cancelPreviousRequest)) {
			options.cancelPreviousRequest = true;
		}

		if (!Ext.isDefined(options.actionType)) {
			options.actionType = this.actionType;
		}

		Zarafa.common.recipientfield.data.SuggestionListStore.superclass.load.call(this, options);
	}
});

Ext.reg('zarafa.suggestionliststore', Zarafa.common.recipientfield.data.SuggestionListStore);
Ext.namespace('Zarafa.common.recipientfield.ui');

/**
 * @class Zarafa.common.recipientfield.ui.RecipientHoverCardButton
 * @extends Ext.Button
 * The base class for all button that render into {@link Zarafa.common.recipientfield.ui.RecipientHoverCard}.
 * RecipientHoverCardButton provides click event handler and hide parent card view
 * @xtype zarafa.recipienthovercardbutton
 */
Zarafa.common.recipientfield.ui.RecipientHoverCardButton = Ext.extend(Ext.Button, {

	/**
	 * @cfg {Number} clickHideDelay delay time in millisecond to hide parent card after click on button.
	 */
	clickHideDelay: 1,

	/**
	 * @cfg {Zarafa.common.recipientfield.ui.RecipientHoverCard} parentCard parent card view component.
	 */
	parentCard: undefined,

	/**
	 * @cfg {Object} clickHideDelayTimer contain defer time out object.
	 */
	clickHideDelayTimer: undefined,


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

		config = Ext.applyIf(config, {
			cls: 'k-recipient-hover-card-button zarafa-normal'
		});

		Zarafa.common.recipientfield.ui.RecipientHoverCardButton.superclass.constructor.call(this, config);
	},

	/**
	 * Initialize the component.
	 * It will register click event with given handler in config.
	 * @private
	 */
	initComponent: function ()
	{
		Zarafa.common.recipientfield.ui.RecipientHoverCardButton.superclass.initComponent.call(this);

		if (this.handler) {
			this.on("click", this.handler, this.scope);
		}
	},

	/**
	 * Function called during the {@link #render rendering} of this component.
	 * This will get parent card view component
	 * @param {Ext.Container} container The container in which the component is being rendered.
	 * @param {NUmber} position The position within the container where the component will be rendered.
	 * @private
	 */
	onRender: function (container, position)
	{
		Zarafa.common.recipientfield.ui.RecipientHoverCardButton.superclass.onRender.apply(this, arguments);
		var parentCard = this.findParentByType('zarafa.recipienthovercardview');
		if (parentCard) {
			this.parentCard = parentCard;
		}
	},

	/**
	 * Sets the function that will handle click events for this item (equivalent to passing in the {@link #handler}
	 * config property). If an existing handler is already registered, it will be unregistered for you.
	 * @param {Function} handler The function that should be called on click
	 * @param {Object} scope The scope (<code>this</code> reference) in which the handler function is executed. Defaults to this menu item.
	 */
	setHandler: function (handler, scope)
	{
		if (this.handler) {
			this.un("click", this.handler, this.scope);
		}
		this.on("click", this.handler = handler, this.scope = scope);
	},

	/**
	 * Event handler handle click event it will fire click event of item and then
	 * call {@link #handelClick} to hide parent card view.
	 * @param e
	 */
	onClick: function (e)
	{
		if (!this.disabled && this.fireEvent("click", this, e) !== false) {
			this.handleClick(e);
		} else {
			e.stopEvent();
		}
	},

	/**
	 * Function which is set defer to hide parent card view.
	 */
	handleClick: function ()
	{
		this.clickHideDelayTimer = this.parentCard.hide.defer(this.clickHideDelay, this.parentCard, [true]);
	},

	/**
	 * Override to clear defer time out {@link #clickHideDelayTimer}
	 */
	beforeDestroy: function ()
	{
		clearTimeout(this.clickHideDelayTimer);
		Zarafa.common.recipientfield.ui.RecipientHoverCardButton.superclass.beforeDestroy.call(this);
	},

	/**
	 * @return {Ext.data.Record} record which was registered in {@link Zarafa.common.recipientfield.ui.RecipientHoverCard}.
	 */
	getRecords: function ()
	{
		return this.parentCard.records;
	}
});
Ext.reg('zarafa.recipienthovercardbutton', Zarafa.common.recipientfield.ui.RecipientHoverCardButton);
Ext.namespace('Zarafa.common.recipientfield.ui');

/**
 * @class Zarafa.common.recipientfield.ui.RecipientHoverCardView
 * @extends Ext.Window
 * @xtype zarafa.recipienthovercardview
 *
 * This component will show quick view of recipient as an recipient hover panel.
 * It will show/hide card view when recipient field gets mouseenter/mouseleave.
 */
Zarafa.common.recipientfield.ui.RecipientHoverCardView = Ext.extend(Ext.Window, {
	// Insertion points for this class
	/**
	 * @insert context.common.recipientfield.hovercardview.actions
	 * @param {Zarafa.common.recipientfield.ui.RecipientHoverCardView} recipienthovercardview This hover card view
	 */

	/**
	 * @cfg {Boolean} editable
	 * False to prevent any button to edit the record on which this card is shown.
	 */
	editable: true,

	/**
	 * @cfg {Boolean} hasFocus true to recipient card view has focus, false otherwise
	 */
	hasFocus: false,

	/**
	 * @cfg {Zarafa.common.ui.messagepanel.RecipientLinks | Zarafa.common.ui.messagepanel.SentInfoLinks} recipient
	 * component for which hover card will show.
	 */
	recipientView: undefined,

	/**
	 * @cfg {Number} maximum length of text allowed after truncations,
	 * truncation will be replaced with ellipsis ('...').
	 */
	ellipsisStringEndLength: 30,

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

		if (config.records) {
			if (Array.isArray(config.records)) {
				config.records = config.records[0];
			}
		}

		config = Ext.applyIf(config, {
			frame: false,
			hidden: false,
			closable: false,
			resizable: false,
			cls: 'k-recipient-card-view',
			layout: {
				type: 'hbox',
				align: 'stretch'
			},
			width: 340,
			height: 130,
			border: false,
			items: this.getHoverCardItems(config),
			buttonAlign: 'left',
			buttons: this.getHoverCardButtons(config.records)
		});

		Zarafa.common.recipientfield.ui.RecipientHoverCardView.superclass.constructor.call(this, config);
	},

	/**
	 * Initialize the component.
	 * This will register the card view window and window resize event.
	 * @private
	 */
	initComponent: function ()
	{
		Ext.EventManager.onWindowResize(this.hide, this);
		Ext.WindowMgr.register(this);
		if (this.recipientView) {
			this.recipientView.on('mouseleave', this.onRecipientMouseLeave, this, {
				buffer: 100
			});
		}
		Zarafa.common.recipientfield.ui.RecipientHoverCardView.superclass.initComponent.call(this);
	},

	/**
	 * Helper function which will return hover card items.
	 * @param {Object} config The config object contains the record which going to show in hover card.
	 * @return {Array} items The hover card items.
	 */
	getHoverCardItems: function (config)
	{
		var record = config.records;
		var emailAddress = record.get('smtp_address') || record.get('email_address');
		var toolTip = emailAddress.length > this.ellipsisStringEndLength ? emailAddress : '';
		return [{
			layout: {
				type: 'vbox'
			},
			border: false,
			width: 70,
			items: [{
				xtype: 'container',
				html: this.getImageOrInitials(record)
			}]
		}, {
			layout: {
				type: 'vbox'
			},
			border: false,
			cls: 'k-hover-card-recipient-detail-panel',
			items: [{
				xtype: 'displayfield',
				html: this.getDisplayName(record)
			}, {
				layout: {
					type: 'hbox'
				},
				width: 255,
				height: 70,
				border: false,
				items: [{
					xtype: 'displayfield',
					value: Ext.util.Format.ellipsis(emailAddress, this.ellipsisStringEndLength),
					cls: 'k-hover-card-email-text',
					tooltip: toolTip,
					plugins: 'zarafa.formfieldtooltipplugin'
				}, {
					xtype: 'zarafa.recipienthovercardbutton',
					iconCls: 'icon_copy',
					hidden: !record.isResolved(),
					handler: this.copyEmail,
					tooltip: _('Copy email address'),
					scope: this
				}, {
					xtype: 'zarafa.recipienthovercardbutton',
					iconCls: 'icon_copy_all',
					hidden: this.hideButton(config.store, record),
					handler: this.copyEmail,
					name: 'copyEmailAddresses',
					tooltip: _('Copy all email addresses'),
					scope: this
				}]
			}]
		}];
	},

	/**
	 * Helper function which used to hide the 'Copy email addresses' button from hover card if selected recipient was not resolved
	 * or only one recipient is in selected field (TO, Cc or Bcc).
	 *
	 * @param {Ext.data.Store} store The store can be {@link Zarafa.common.ui.messagepanel.RecipientLinks RecipientLinks} or
	 * {@link Zarafa.common.ui.messagepanel.SentInfoLinks SentInfoLinks}.
	 * @param {Ext.data.Record} record which will show in hover card view.
	 * @return {Boolean} return true if selected recipient is not resolved or field has only one recipient else false.
	 */
	hideButton: function(store, record)
	{
		if (!record.isResolved() || store.getCount() <= 1) {
			return true;
		}

		var recipients = Zarafa.common.Actions.getRecipientsByType(store, record.get('recipient_type'));
		return recipients.length === 1;
	},

	/**
	 * Helper function which will return array of
	 * @param {Ext.data.Record} record which will show in hover card view.
	 * @return {Array} items array of button for hover card view.
	 */
	getHoverCardButtons: function (record)
	{
		return [{
			xtype: 'zarafa.recipienthovercardbutton',
			text: _('Show Details'),
			iconCls: 'icon_contact',
			hidden: !record.isResolved(),
			handler: this.openDetailsContent,
			scope: this
		}, '->', container.populateInsertionPoint('context.common.recipientfield.hovercardview.actions', this), {
			xtype: 'zarafa.recipienthovercardbutton',
			iconCls: 'icon_new_email',
			handler: this.onEmailRecipient,
			tooltip: _('Send email'),
			scope: this
		}];
	},

	/**
	 * Displays this card at a specific xy position and fires the 'show' event if a
	 * handler for the 'beforeshow' event does not return false cancelling the operation.
	 * @param {Array} xy Contains X & Y [x, y] values for the position at which to show the menu (coordinates are page-based)
	 */
	showAt: function (xy)
	{
		if (this.fireEvent('beforeshow', this) !== false) {
			if (!this.el) {
				this.render(Ext.getBody());
			}
			//constrain to the viewport.
			xy = this.el.adjustForConstraints(xy);
			this.el.setXY(xy);
			this.el.show();
			this.focus();
			this.fireEvent('show', this);
			this.mon(this.el, 'mouseleave', this.onMouseLeave, this);
			this.mon(this.el, 'mouseenter', this.onMouseEnter, this);
		}
	},

	/**
	 * Hide the card view and unregister the card view window.
	 */
	hide: function ()
	{
		this.hasFocus = false;
		this.recipientView.un('mouseleave', this.onRecipientMouseLeave, this);
		Zarafa.common.recipientfield.ui.RecipientHoverCardView.superclass.hide.call(this);
		Ext.WindowMgr.unregister(this);
	},

	/**
	 * Event handler handle mouse leave event it will hide recipient card view
	 */
	onMouseLeave: function ()
	{
		this.hide();
	},

	/**
	 * Event handler which is call when mouse leave event occur on {@link #recipientView}
	 * It will hide recipient card view.
	 */
	onRecipientMouseLeave: function ()
	{
		if (!this.hasFocus) {
			this.hide();
		}
	},

	/**
	 * Event handler handle mouse enter event it will update {@link #hasFocus}.
	 */
	onMouseEnter: function ()
	{
		this.hasFocus = true;
	},

	/**
	 * Handler for the "Show Details" option. This will convert
	 * the {@link Zarafa.core.data.IPMRecipientRecord recipient}
	 * into a {@link Zarafa.core.data.MAPIRecord addressbook record}
	 * which can be opened in the Addressbook details content panel.
	 *
	 * @private
	 */
	openDetailsContent: function()
	{
		Zarafa.common.Actions.openViewRecipientContent(this.records);
	},

	/**
	 * Handler for the "Copy email address" and 'Copy email addresses' option. This will
	 * copy email address(es) of the resolved recipient(s).
 	 * @param {Zarafa.common.recipientfield.ui.RecipientHoverCardButton} item The item can be
	 * 'Copy email address' or 'Copy email addresses' button in hover card.
	 */
	copyEmail: function (item)
	{
		var copyAll = item.name === 'copyEmailAddresses';
		Zarafa.common.Actions.copyEmailAddress(this.records, this.store, copyAll);
	},

	/**
	 * Handler for the "Email Recipient" option. This will create
	 * a new {@link Zarafa.core.data.MAPIRecord mail record}
	 * and generate new phantom recipient for new mail. Then
	 * it will open a new MailCreate ContentPanel for the new mail
	 *
	 * @private
	 */
	onEmailRecipient: function()
	{
		Zarafa.common.Actions.onEmailRecipient(this.records);
	},

	/**
	 * Function will return html template that contain display name along with presence status.
	 * @param {Ext.data.Record} record which will show in hover card view.
	 * @returns {string} html string contains display name with presence status.
	 */
	getDisplayName: function (record)
	{
		return '<span class="zarafa-presence-status ' + Zarafa.core.data.PresenceStatus.getCssClass(record.get('presence_status')) + '">' +
			' <span class="zarafa-presence-status-icon" aria-hidden="true"></span>' + Ext.util.Format.ellipsis(record.get('display_name'), 30) + '</span>';
	},

	/**
	 * Function will return html template that contains the sender user's image or initials.
	 * @param {Ext.data.Record} record which will show in hover card view.
	 * @returns {string} html string which contains the initials of a sender.
	 */
	getImageOrInitials: function(record)
	{
		var senderUserImage = record.get('user_image');
		if (Ext.isEmpty(senderUserImage)) {
			return '<span class="preview-header-sender-initial">'+ record.getSenderInitials() +'</span>';
		}

		return '<span class="preview-header-sender-image" style ="background-image:url('+ senderUserImage +');"></span>';
	}
});

Ext.reg('zarafa.recipienthovercardview', Zarafa.common.recipientfield.ui.RecipientHoverCardView);
Ext.namespace('Zarafa.common.recurrence.data');

/**
 * @class Zarafa.common.recurrence.data.DayData
 * @singleton
 */
Zarafa.common.recurrence.data.DayData = [
	{ name: _('Day'),			value: 127 },
	{ name: _('Weekday'),		value: 62 },
	{ name: _('Weekend Day'),	value: 65 },
	// The following are initialized empty,
	// because the order of the days is
	// depending on the 'zarafa/v1/main/week_start'
	// configuration option, which we cannot use
	// until the Document has been loaded.
	{ name: null,				value: 0 },
	{ name: null,				value: 0 },
	{ name: null,				value: 0 },
	{ name: null,				value: 0 },
	{ name: null,				value: 0 },
	{ name: null,				value: 0 },
	{ name: null,				value: 0 }
];

// With the document loaded, we can now access the 'zarafa/v1/main/week_start'
// configuration option, which we need to build the last 7 items from the
// Zarafa.common.recurrence.data.DayData structure.
Zarafa.onReady(function() {
	var weekStart = container.getSettingsModel().get('zarafa/v1/main/week_start');

	for (var i = 3; i < Zarafa.common.recurrence.data.DayData.length; i++) {
		var index = (weekStart + (i - 3)) % 7;
		Zarafa.common.recurrence.data.DayData[i].name = Date.dayNames[index];
		Zarafa.common.recurrence.data.DayData[i].value = Math.pow(2, index);
	}
}, undefined, { single: true });
Ext.namespace('Zarafa.common.recurrence.data');

/**
 * @class Zarafa.common.recurrence.data.DayRankData
 * @singleton
 */
Zarafa.common.recurrence.data.DayRankData = [
	{ name: _('1st'),	value: 1 },
	{ name: _('2nd'),	value: 2 },
	{ name: _('3rd'),	value: 3 },
	{ name: _('4th'),	value: 4 },
	{ name: _('last'),	value: 5 }
];
Ext.namespace('Zarafa.common.recurrence.data');

/**
 * @class Zarafa.common.recurrence.data.MonthData
 * Month value is calculated on the bases of total minutes before that month.
 * As for example, February starts after 44640 minutes obtained by 31(days of January month) * 1440(minutes of a day).
 * @singleton
 */
Zarafa.common.recurrence.data.MonthData = [
	{ name: Date.monthNames[0],		value: 0 },
	{ name: Date.monthNames[1],		value: 44640 },
	{ name: Date.monthNames[2],		value: 84960 },
	{ name: Date.monthNames[3],		value: 129600 },
	{ name: Date.monthNames[4],		value: 172800 },
	{ name: Date.monthNames[5],		value: 217440 },
	{ name: Date.monthNames[6],		value: 260640 },
	{ name: Date.monthNames[7],		value: 305280 },
	{ name: Date.monthNames[8],		value: 349920 },
	{ name: Date.monthNames[9],		value: 393120 },
	{ name: Date.monthNames[10],	value: 437760 },
	{ name: Date.monthNames[11],	value: 480960 }
];
Ext.namespace('Zarafa.common.recurrence.dialogs');

/**
 * @class Zarafa.common.recurrence.dialogs.RecurrencePanel
 * @extends Ext.Panel
 * @xtype zarafa.recurrencepanel
 *
 * Panel that is used to create Recurrences
 */
Zarafa.common.recurrence.dialogs.RecurrencePanel = Ext.extend(Ext.Panel, {
	/**
	 * @constructor
	 * @param {Object} config configuration object.
	 */
	constructor: function(config)
	{
		config = config || {};

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

		config = Ext.applyIf(config, {
			xtype: 'zarafa.recurrencepanel',
			layout: 'form',
			border: false,
			defaults: {
				border: false,
				bodyStyle: 'padding: 8px 10px;',
				style: 'margin-bottom: 6px;'
			},
			items: [
				this.createTimePanel(),
				this.createRecurrencePanel(),
				this.createRangePanel()
			]
		});

		Zarafa.common.recurrence.dialogs.RecurrencePanel.superclass.constructor.call(this, config);
	},

	/**
	 * Creates the Time panel in which the user can select the message time.
	 * @return {Object} The configuration object for the Time
	 * @private
	 */
	createTimePanel: function()
	{
		return {
			xtype: 'panel',
			title: _('Time'),
			layout: {
				type: 'vbox',
				align: 'stretch'
			},
			anchor: '100%',
			height: 110,
			items: [{
				xtype: 'displayfield',
				ref: '../timeperiodLabel',
				htmlEncode: true,
				hideLabel: true,
				height: 20
			},{
				xtype: 'panel',
				layout: 'hbox',
				anchor: '100% 100%',
				border: false,
				bodyStyle: 'background-color: inherit;',
				items: [{
					xtype: 'zarafa.timeperiodfield',
					ref: '../../timeperiodField',
					layout: 'hbox',
					defaultPeriod: 30,
					defaultPeriodType: Date.MINUTE,
					flex: 0.7,
					spacerConfig: {
						width: 5
					},
					listeners: {
						change: this.onDurationChange,
						scope: this
					}
				},{
					xtype: 'spacer',
					width: 5
				},{
					xtype: 'container',
					flex: 0.3,
					border: false,
					style: 'background-color: inherit;',
					items: [{
						xtype: 'checkbox',
						ref: '../../../alldayCheckbox',
						name: 'alldayevent',
						boxLabel: _('All Day Event'),
						handler: this.onToggleAllDay,
						scope: this
					}]
				}]
			}]
		};
	},

	/**
	 * Creates the recurrence recurrence pattern panel, in which the user can configure
	 * the recurrence pattern (daily/weekly/monhtly/yearly) for this message.
	 * @return {Object} The configuration object for the Recurrence Pattern
	 * @private
	 */
	createRecurrencePanel: function()
	{
		return {
			xtype: 'panel',
			layout: {
				type: 'hbox',
				align: 'stretch'
			},
			title: _('Recurrence pattern'),
			anchor: '100%',
			height: 170,
			items: [{
				xtype: 'radiogroup',
				ref: '../recurrencePatternSelect',
				width: 100,
				border: false,
				columns: 1,
				items: [{
					boxLabel: _('Daily'),
					name: 'pattern',
					targetId: 'card-daily',
					patternValue: Zarafa.common.recurrence.data.RecurrenceType.DAILY,
					handler: this.onSwitchRecurrenceView,
					scope: this
				},{
					boxLabel: _('Weekly'),
					name: 'pattern',
					targetId: 'card-weekly',
					patternValue: Zarafa.common.recurrence.data.RecurrenceType.WEEKLY,
					handler: this.onSwitchRecurrenceView,
					scope: this
				},{
					boxLabel: _('Monthly'),
					name: 'pattern',
					targetId: 'card-monthly',
					patternValue: Zarafa.common.recurrence.data.RecurrenceType.MONTHLY,
					handler: this.onSwitchRecurrenceView,
					scope: this
				},{
					boxLabel: _('Yearly'),
					name: 'pattern',
					targetId: 'card-yearly',
					patternValue: Zarafa.common.recurrence.data.RecurrenceType.YEARLY,
					handler: this.onSwitchRecurrenceView,
					scope: this
				}]
			},{
				xtype: 'panel',
				ref: '../recurrencePattern',
				layout: 'card',
				flex: 1,
				border: true,
				bodyStyle: 'background-color: inherit; border-style: none none none solid; padding: 0px 10px 0px 10px',
				defaults: {
					border: false,
					bodyStyle: 'background-color: inherit;',
					autoHeight: true
				},
				items: [{
					xtype: 'zarafa.recurrencedailypanel',
					id: 'card-daily'
				},{
					xtype: 'zarafa.recurrenceweeklypanel',
					id: 'card-weekly'
				},{
					xtype: 'zarafa.recurrencemonthlypanel',
					id: 'card-monthly'
				},{
					xtype: 'zarafa.recurrenceyearlypanel',
					id: 'card-yearly'
				}]
			}]
		};
	},

	/**
	 * Creates the recurrence recurrence pattern panel, in which the user can configure
	 * the recurrence range for this message.
	 * @return {Object} The configuration object for the Recurrence Range
	 * @private
	 */
	createRangePanel: function()
	{
		return {
			xtype: 'panel',
			layout: 'column',
			title: _('Range of recurrence'),
			anchor: '100%',
			items: [{
				xtype: 'panel',
				layout: 'form',
				columnWidth: 0.4,
				border: false,
				items: [{
					xtype: 'datefield',
					ref: '../../startDateField',
					name: 'recurrence_start',
					fieldLabel: _('Start'),
					// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
					format: _('d/m/Y'),
					listeners: {
						select: this.onUTCFieldChange,
						change: this.onUTCFieldChange,
						scope: this
					}
				}]
			},{
				xtype: 'panel',
				layout: 'form',
				ref: '../endPatternPanel',
				cls: 'k-recurrence-end-pattern',
				columnWidth: 0.6,
				border: false,
				bodyStyle: 'background-color: inherit;',
				defaults: {
					anchor:'100%',
					border: false,
					bodyStyle: 'background-color: inherit;',
					defaults: {
						height: 30,
						labelWidth: 75
					}
				},
				items: [{
					xtype: 'panel',
					layout: 'hbox',
					items: [{
						xtype: 'radio',
						name: 'recurrence_term',
						endTerm: Zarafa.common.recurrence.data.RecurrenceEnd.NEVER,
						hideLabel: true,
						width: 25,
						listeners: {
							check: this.onSwitchRecurrenceTerm,
							scope: this
						}
					},{
						xtype: 'displayfield',
						value: _('No end date'),
						hideLabel: true
					}]
				},{
					xtype: 'panel',
					layout: 'hbox',
					items: [{
						xtype: 'radio',
						name: 'recurrence_term',
						endTerm: Zarafa.common.recurrence.data.RecurrenceEnd.N_OCCURRENCES,
						hideLabel: true,
						width: 25,
						listeners: {
							check: this.onSwitchRecurrenceTerm,
							scope: this
						}
					},{
						xtype: 'zarafa.compositefield',
						plugins: [ 'zarafa.splitfieldlabeler' ],
						fieldLabel: _('End after {A} occurrences'),
						labelWidth: 170,
						flex: 1,
						items: [{
							xtype: 'zarafa.spinnerfield',
							plugins: [ 'zarafa.numberspinner' ],
							ref: '../../../../endOccurrencesSpinner',
							name: 'recurrence_numoccur',
							labelSplitter: '{A}',
							allowNegative: false,
							minValue: 1,
							width: 65,
							enableKeyEvents: true,
							listeners: {
								change: this.onFieldChange,
								keypress: this.onKeyPress,
								scope: this
							}
						}]
					}]
				},{
					xtype: 'panel',
					layout: 'hbox',
					items: [{
						xtype: 'radio',
						name: 'recurrence_term',
						endTerm: Zarafa.common.recurrence.data.RecurrenceEnd.ON_DATE,
						hideLabel: true,
						width: 25,
						listeners: {
							check: this.onSwitchRecurrenceTerm,
							scope: this
						}
					},{
						xtype: 'zarafa.compositefield',
						plugins: [ 'zarafa.splitfieldlabeler' ],
						fieldLabel: _('End by {A}'),
						labelWidth: 80,
						flex: 1,
						combineErrors: false,
						items: [{
							xtype: 'datefield',
							ref: '../../../../endOnDateField',
							name: 'recurrence_end',
							width: 120,
							labelSplitter: '{A}',
							// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
							format: _('d/m/Y'),
							listeners: {
								select: this.onUTCFieldChange,
								change: this.onUTCFieldChange,
								scope: this
							}
						}]
					}]
				}]
			}]
		};
	},

	/**
	 * Event handler which is fired when either the All Day checkbox has been
	 * checked, or when the timeDuration field has been updated. In both situations
	 * the label belonging to the field will be updated to reflect the new duration.
	 * @private
	 */
	updateDurationLabel: function()
	{
		if (!this.record) {
			return;
		}

		var duration = Math.floor(this.timeperiodField.getValue().getDuration(Date.MINUTE));
		var days = Math.floor(duration / (24 * 60));
		duration %= (24 * 60);
		var hours = Math.floor(duration / 60);
		duration %= 60;
		var minutes = Math.floor(duration);

		// # TRANSLATORS: This informs the user what the exact duration of the appointment is. Where {D} represents the days, {H} the hours and {M} the minutes.
		// # For example: 'Occurrence duration: 1 day 2 hours 45 minutes', or when the appointment is shorter then 1 day: 'Occurrence duration: 1 hour'
		var label = _('Occurrence duration: {D} {H} {M}');
		if (days > 0) {
			label = label.replace('{D}', String.format(ngettext('{0} day', '{0} days', days), days));
		} else {
			label = label.replace('{D} ', '');
		}
		if (hours > 0) {
			label = label.replace('{H}', String.format(ngettext('{0} hour', '{0} hours', hours), hours));
		} else {
			label = label.replace('{H} ', '');
		}
		if (minutes > 0) {
			label = label.replace('{M}', String.format(ngettext('{0} minute', '{0} minutes', minutes), minutes));
		} else {
			label = label.replace('{M}', '');
		}

		this.timeperiodLabel.setValue(label);
	},

	/**
	 * Event handler which is fired when a field has been changed.
	 * This will update the corresponding field inside the {@link Zarafa.core.data.IPMRecord record}.
	 * @param {Ext.form.Field} field The field which has changed
	 * @param {Mixed} newValue The new value for the field
	 * @param {Mixed} oldValue The original value for the field
	 * @private
	 */
	onFieldChange: function(field, newValue, oldValue)
	{
		this.record.set(field.getName(), newValue);
	},

	/**
	 * Event handler which is fired when the recurrence start or end date has
	 * been changed. This will convert the value to the UTC start of the day,
	 * and update the record.
	 * @param {Ext.form.Field} field The field which has changed
	 * @param {Mixed} newValue The new value for the field
	 * @param {Mixed} oldValue The original value for the field
	 * @private
	 */
	onUTCFieldChange: function(field, newValue, oldValue)
	{
		// The field is represented in UTC time,
		// so convert it to local to get the time for the property
		this.record.set(field.getName(), newValue.fromUTC());
		Ext.each(this.endPatternPanel.findByType('radio'), function(radio) {
			radio.setValue(radio.endTerm == Zarafa.common.recurrence.data.RecurrenceEnd.ON_DATE);
		});
	},

	/**
	 * Event handler which is fired when the duration field has been updated.
	 * This will calculate the correct startocc and endocc values, and updates the record.
	 * @param {Ext.form.Field} field The field which has changed
	 * @param {Mixed} newValue The new value for the field
	 * @param {Mixed} oldValue The original value for the field
	 * @private
	 */
	onDurationChange: function(field, newValue, oldValue)
	{
		var startOcc = (newValue.getStartDate().getHours() * 60) + newValue.getStartDate().getMinutes();
		var endOcc = startOcc + newValue.getDuration(Date.MINUTE);

		this.record.beginEdit();
		this.record.set('recurrence_startocc', startOcc);
		this.record.set('recurrence_endocc', endOcc);
		this.record.endEdit();
	},

	/**
	 * Event handler which is fired when the Recurrence End radio button
	 * has been toggled. This will change the recurrence term status.
	 *
	 * @param {Ext.form.Radio} radio The radio which has changed
	 * @param {Boolean} checked True if the radio was checked
	 * @private
	 */
	onSwitchRecurrenceTerm: function(radio, checked)
	{
		if (checked) {
			this.record.set('recurrence_term', radio.endTerm);
		}
	},

	/**
	 * Event handler which is fired when a checkbox has been toggled
	 * to switch the recurrence pattern. This will change the recurrence
	 * pattern panel to the selected type.
	 *
	 * @param {Ext.form.CheckBox} checkbox The checkbox which was changed
	 * @param {Boolean} Checked True if the checkbox was checked
	 * @private
	 */
	onSwitchRecurrenceView: function(checkbox, checked)
	{
		if (checked) {
			this.record.set('recurrence_type', checkbox.patternValue);
			this.recurrencePattern.getLayout().setActiveItem(checkbox.targetId);
			this.recurrencePattern.doLayout();
		}
	},

	/**
	 * A function called when the checked value changes for the
	 * all day event checkbox.
	 * @param {Ext.form.Checkbox} checkbox The Checkbox being toggled.
	 * @param {Boolean} checked The new checked state of the checkbox.
	 * @private
	 */
	onToggleAllDay: function(checkbox, checked)
	{
		// When the user already has an appointment that last a more then a day, we should
		// round the end of the occurrences up to a whole number of days.
		var days = Math.ceil(this.timeperiodField.getValue().getDuration(Date.MINUTE) / 1440);

		this.record.beginEdit();
		this.record.set('alldayevent', checked);
		var busyStatus = checked ? container.getSettingsModel().get('zarafa/v1/contexts/calendar/default_allday_busy_status') : Zarafa.core.mapi.BusyStatus.BUSY;
		this.record.set('busystatus', busyStatus);
		this.record.set('recurrence_startocc', 0);
		this.record.set('recurrence_endocc', days * 1440);
		this.record.endEdit();
	},

	/**
	 * Enable/disable/hide/unhide all {@link Ext.Component Components} within the {@link Ext.Panel Panel}
	 * using the given {@link Zarafa.core.data.IPMRecord IPMRecord}.
	 * @param {Zarafa.core.data.IPMRecord} record The record to update the panel with
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 * @private
	 */
	updateUI: function(record, contentReset)
	{
		var layout = false;

		if (contentReset === true || record.isModifiedSinceLastUpdate('alldayevent')) {
			if (record.get('alldayevent')) {
				this.timeperiodField.disable();
			} else {
				this.timeperiodField.enable();
			}
		}

		if (contentReset === true || record.isModifiedSinceLastUpdate('message_class')) {
			if (record.isMessageClass('IPM.TaskRequest', true)) {
				this.alldayCheckbox.hide();
			} else {
				this.alldayCheckbox.show();
			}

			layout = true;
		}

		if (layout) {
			this.doLayout();
		}
	},

	/**
	 * Panel updater. This will initialize all UI components inside the panel with
	 * the data from the {@link Zarafa.core.data.IPMRecord record}.
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record used to update the panel
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 */
	update: function(record, contentReset)
	{
		this.record = record;
		this.updateUI(record, contentReset);

		var type = record.get('recurrence_type');
		this.recurrencePatternSelect.items.each(function(radio) {
			radio.setValue(radio.patternValue == type);
		});

		var startDate = record.get('recurrence_start');
		if (startDate) {
			// The start date is an UTC representation
			startDate = startDate.toUTC();
		} else {
			startDate = new Date().clearTime();
		}
		var endDate = record.get('recurrence_end');
		if (endDate) {
			// The end date is an UTC representation
			endDate = endDate.toUTC();
		} else {
			endDate = startDate.clone();
		}
		var startOcc = record.get('recurrence_startocc');
		var endOcc = record.get('recurrence_endocc');

		// We construct a startTime based on the first day of the year,
		// this guarantees that we are absolutely DST safe, and any time
		// can be selected.
		var startTime = new Date().clearTime();
		startTime.setDate(1);
		startTime.setMonth(0);
		startTime.setHours(startOcc / 60);
		startTime.setMinutes(startOcc % 60);

		// We construct a endTime based on the first day of the year,
		// this guarantees that we are absolutely DST safe, and any time
		// can be selected.
		var endTime = new Date().clearTime();
		endTime.setDate(1);
		endTime.setMonth(0);
		endTime.setHours(endOcc / 60);
		endTime.setMinutes(endOcc % 60);

		this.startDateField.setValue(startDate);
		this.timeperiodField.getValue().set(startTime, endTime);

		if(contentReset === true || record.isModifiedSinceLastUpdate('recurrence_startocc') || record.isModifiedSinceLastUpdate('recurrence_endocc')) {
			this.updateDurationLabel();
		}

		var endTerm = record.get('recurrence_term');
		Ext.each(this.endPatternPanel.findByType('radio'), function(radio) {
			radio.setValue(radio.endTerm == endTerm);
		});

		switch (endTerm) {
			case Zarafa.common.recurrence.data.RecurrenceEnd.NEVER:
				// Only apply default values at the first update
				if (contentReset === true) {
					this.endOccurrencesSpinner.setValue(10);
				}
				if (contentReset === true || (record.isModifiedSinceLastUpdate('recurrence_start') && startDate > this.endOnDateField.getValue())) {
					this.endOnDateField.setValue(startDate.clearTime(true));
				}
				break;
			case Zarafa.common.recurrence.data.RecurrenceEnd.N_OCCURRENCES:
				// Only apply default values at the first update
				if (contentReset === true || record.isModifiedSinceLastUpdate('recurrence_numoccur')) {
					this.endOccurrencesSpinner.setValue(record.get('recurrence_numoccur'));
				}
				if (contentReset === true) {
					this.endOnDateField.setValue(startDate.clearTime(true));
				}
				break;
			case Zarafa.common.recurrence.data.RecurrenceEnd.ON_DATE:
				// Only apply default values at the first update
				if (contentReset === true) {
					this.endOccurrencesSpinner.setValue(10);
				}
				if (contentReset === true || record.isModifiedSinceLastUpdate('recurrence_end')) {
					this.endOnDateField.setValue(endDate.clearTime(true));
				}
				break;
		}

		this.alldayCheckbox.setValue(record.get('alldayevent'));
	},

	/**
	 * Record updater. This will update the record with all data from the UI components
	 * inside the Panel.
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record to update
	 * @private
	 */
	updateRecord: function(record)
	{
		record.beginEdit();

		var pattern = this.recurrencePatternSelect.getValue();
		if (pattern) {
			record.set('recurrence_type', pattern.patternValue);
		} else {
			record.set('recurrence_type', Zarafa.common.recurrence.data.RecurrenceType.NONE);
		}

		// The start is represented in UTC time,
		// so convert it to local to get the time for the property
		record.set('recurrence_start', this.startDateField.getValue().fromUTC());

		Ext.each(this.endPatternPanel.findByType('radio'), function(radio) {
			if (radio.getValue()) {
				record.set('recurrence_term', radio.endTerm);
				return false;
			}
		});

		switch (record.get('recurrence_term')) {
			case Zarafa.common.recurrence.data.RecurrenceEnd.NEVER:
				record.set('recurrence_numoccur', undefined);
				// The end is represented in UTC time,
				// so convert it to local to get the time for the property
				record.set('recurrence_end', new Date('Jan 01 4501').fromUTC());
				break;
			case Zarafa.common.recurrence.data.RecurrenceEnd.N_OCCURRENCES:
				record.set('recurrence_numoccur', this.endOccurrencesSpinner.getValue());
				// The end is represented in UTC time,
				// so convert it to local to get the time for the property
				record.set('recurrence_end', new Date('Jan 01 4501').fromUTC());
				break;
			case Zarafa.common.recurrence.data.RecurrenceEnd.ON_DATE:
				record.set('recurrence_numoccur', undefined);
				// The end is represented in UTC time,
				// so convert it to local to get the time for the property
				record.set('recurrence_end', this.endOnDateField.getValue().fromUTC());
				break;
		}

		// The recurrence_start and the time values might have changed, time
		// to update the appointment data now as well.
		record.set('alldayevent', this.alldayCheckbox.getValue());

		var timeRange = this.timeperiodField.getValue();
		var startOcc = (timeRange.getStartDate().getHours() * 60) + timeRange.getStartDate().getMinutes();
		var endOcc = startOcc + timeRange.getDuration(Date.MINUTE);

		record.set('recurrence_startocc', startOcc);
		record.set('recurrence_endocc', endOcc);

		var startDate = this.startDateField.getValue();
		var dueDate = startDate.clone();

		// Small workaround for allday appointments, in Brasil the DST occurs at 00:00,
		// which means for allday events, that we might think the appointment should start
		// at 01:00, but that might be because that is the start of the actual day. Hence
		// we need to correct the startOcc here for that.
		if (record.get('alldayevent') && startDate.clearTime(true).getTime() === startDate.getTime()) {
			var offset = startDate.getHours() * 60;
			startOcc -= offset;
			endOcc -= offset;
		}

		startDate = startDate.add(Date.MINUTE, startOcc);
		record.set('startdate', startDate);
		record.set('commonstart', startDate);

		dueDate = dueDate.add(Date.MINUTE, endOcc);
		record.set('duedate', dueDate);
		record.set('commonend', dueDate);

		record.endEdit();
	},

	/**
	 * Event is fired when keypress event is fired on recurrence_numoccur field,
	 * e.g. the user enters the number of the occurrences manually.
	 * @param {HtmlElement} element The target of the event
	 * @param {Ext.EventObject} event The Ext.EventObject encapsulating the DOM event
	 */
	onKeyPress: function(element, event)
	{
		Ext.each(this.endPatternPanel.findByType('radio'), function(radio) {
			radio.setValue(radio.endTerm == Zarafa.common.recurrence.data.RecurrenceEnd.N_OCCURRENCES);
		});
	},
});

Ext.reg('zarafa.recurrencepanel', Zarafa.common.recurrence.dialogs.RecurrencePanel);
Ext.namespace('Zarafa.common.recurrence.dialogs');

/**
 * @class Zarafa.common.recurrence.dialogs.RecurrenceSubPanel
 * @extends Ext.Panel
 * @xtype zarafa.recurrencesubpanel
 *
 * The Panel used for configuring the {@link Zarafa.common.recurrence.data.RecurrenceSubtype subtypes}.
 * This class should be inherited by the different {@link Zarafa.common.recurrence.data.RecurrenceType types}.
 */
Zarafa.common.recurrence.dialogs.RecurrenceSubPanel = Ext.extend(Ext.Panel, {
	/**
	 * The record on which this panel is operating. This s provided in {@link #update} by
	 * the {@link Zarafa.core.plugins.RecordComponentUpdaterPlugin Record ComponentUpdater plugin}.
	 * @property
	 * @type Zarafa.core.data.MAPIRecord
	 */
	record: undefined,

	/**
	 * @cfg {Zarafa.common.recurrence.data.RecurrenceType} recurrenceType The recurrence type on
	 * which is panel is operating.
	 */
	recurrenceType: undefined,

	/**
	 * @cfg {Zarafa.common.recurrence.data.RecurrenceSubtype|Array} recurrenceSubtypes The possible
	 * subtypes which are available for the configured {@link #recurrenceType}.
	 */
	recurrenceSubtypes: undefined,

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

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

		Zarafa.common.recurrence.dialogs.RecurrenceSubPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Compare 2 {@link Zarafa.common.recurrence.data.RecurrenceSubtype} instances to see if they are equal
	 * @param {Zarafa.common.recurrence.data.RecurrenceSubtype} left The first object to compare to
	 * @param {Zarafa.common.recurrence.data.RecurrenceSubtype} right The second object to compare to
	 * @return {Boolean} True when the two {@link Zarafa.common.recurrence.data.RecurrenceSubtype subtype}
	 * objects are equal
	 * @protected
	 */
	isSubtype: function(left, right)
	{
		return (left.type === right.type && left.regen === right.regen);
	},

	/**
	 * Compare if the given {@link Zarafa.core.data.MAPIRecord} has a subtype configured which matches the given
	 * {@link Zarafa.common.recurrence.data.RecurrenceSubtype subtype}.
	 * @param {Zarafa.core.data.MAPIRecord} record The record to compare to
	 * @param {Zarafa.common.recurrence.data.RecurrenceSubtype} pattern the Subtype pattern to compare with
	 * @return {Boolean} True when the given record has the same subtype configured as the given pattern
	 * @protected
	 */
	isRecordSubtype: function(record, pattern)
	{
		return (record.get('recurrence_subtype') === pattern.type && record.get('recurrence_regen') === pattern.regen);
	},

	/**
	 * Event handler which is called when the Subtype has been changed. This must be configured by the
	 * subclass as event handler on the {@link Ext.form.Radio#change change} event. This will {@link #updateRecord update}
	 * the {@link #record}.
	 * @param {Ext.form.Radio} field The radio which was changed
	 * @param {Boolean} value True when the radio was enabled
	 * @protected
	 */
	onSubtypeChange: function(field, value)
	{
		if (value) {
			var pattern = field.patternValue;

			// The recurrence properties use 'forceProtocol' on the field definition, this means
			// that they will always be markked as changed when we set them. So to be safe,
			// we manually check if we are about to set a different value or not.
			//
			// We also apply a full update of all properties, because when the subtype changes,
			// the meaning of the recurrence properties like 'everyn', 'nday', 'weekdays', etc. also changes.
			if (!this.isRecordSubtype(this.record, pattern)) {
				this.updateRecord(this.record);
			}
		}
	},

	/**
	 * Event handler which is called when a property for a specific subtype has been changed.
	 * This must be called by the subclass with the subtype object to indicate for which subtype
	 * the given field must be changed.
	 * @param {Zarafa.common.recurrence.data.RecurrenceSubtype} pattern The subtype pattern to which
	 * the given field belongs. The property will only be updated when this subtype is currently enabled.
	 * @param {Ext.form.Field} field The field which was changed by the user
	 * @param {Mixed} value The value which was entered into the field
	 * @protected
	 */
	onSubtypePropertyChange: function(pattern, field, value)
	{
		if (this.isRecordSubtype(this.record, pattern)) {
			this.record.set(field.name, value);
		}
	},

	/**
	 * Enable/disable/hide/unhide all {@link Ext.Component Components} within the {@link Ext.Panel Panel}
	 * using the given {@link Zarafa.core.data.MAPIRecord record}.
	 * @param {Zarafa.core.data.MAPIRecord} record The record to update the panel with
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 * @protected
	 */
	updateUI: Ext.emptyFn,

	/**
	 * Update the {@link Ext.Component components} which belong to the given
	 * {@link Zarafa.common.recurrence.data.RecurrenceSubtype subtype}.
	 * @param {Zarafa.core.data.MAPIRecord} record The record from where the values must be read
	 * @param {Zarafa.common.recurrence.data.RecurrenceSubtype} subtype The subtype for which the UI
	 * components must be updated
	 * @param {Boolean} useDefault True if default values should be used rather then the data from
	 * the given record.
	 * @protected
	 */
	updateSubtype: Ext.emptyFn,

	/**
	 * Panel updater. This will initialize all UI components inside the panel with
	 * the data from the {@link Zarafa.core.data.MAPIRecord record}.
	 *
	 * @param {Zarafa.core.data.MAPIRecord} record The record used to update the panel
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 */
	update: function(record, contentReset)
	{
		this.record = record;
		this.updateUI(record, contentReset);

		// The complexity of this panel lies in the fact that we have a recurrence type,
		// and subtype. The properties inside the record have different meanings based on
		// the selected subtype. So when the recurrence type doesn't match this panel's type,
		// we must load default values for everything.
		//
		// However when this panel's type does match the recurrence type, then we must load
		// the correct values for the UI components belonging to the subtype, and default
		// values for all other components.
		//
		// Only do this during initializing as the user will probably be switching around
		// types and subtypes a couple of times.
		if (contentReset) {
			var subTypeRadios = this.findByType('radio');

			if (record.get('recurrence_type') !== this.recurrenceType) {

				// Mark the first radio as the default subtype.
				subTypeRadios[0].setValue(true);

				// Load the default information for all subtypes
				for (var key in this.recurrenceSubtypes) {
					var pattern = this.recurrenceSubtypes[key];

					this.updateSubtype(record, pattern, true);
				}
			} else {
				// Search which radio represents the desired subtype
				Ext.each(subTypeRadios, function(radio) {
					radio.setValue(this.isRecordSubtype(record, radio.patternValue));
				}, this);

				// Load the correct settings for the enabled subtype, and the
				// default values for all others.
				for (var key in this.recurrenceSubtypes) {
					var pattern = this.recurrenceSubtypes[key];

					this.updateSubtype(record, pattern, !this.isRecordSubtype(record, pattern));
				}
			}
		}
	},

	/**
	 * Called by {@link #updateRecordSubType} to indicate that the record must be updated for the
	 * given {@link Zarafa.common.recurrence.data.RecurrenceSubtype recurrence subtype}. The record
	 * itself is already in {@link Zarafa.core.data.MAPIRecord#editing editing} mode.
	 * @param {Zarafa.core.data.MAPIRecord} record The record which must be updated from the UI
	 * @param {Zarafa.common.recurrence.data.RecurrenceSubtype} pattern The Subtype which is
	 * currently enabled. Only the components for this subtype must be used to update the record.
	 * @protected
	 */
	updateRecordSubType: Ext.emptyFn,

	/**
	 * Record updater. This will update the record with all data from the UI components
	 * inside the Panel.
	 *
	 * @param {Zarafa.core.data.MAPIRecord} record The record to update
	 * @private
	 */
	updateRecord: function(record)
	{
		// We are not the active Panel, we have nothing to configure on the record.
		if (record.get('recurrence_type') !== this.recurrenceType) {
			return;
		}

		// Search which subtype has been enabled, we didn't use
		// a RadioGroup, which would remove the requirements for
		// findByType and the looping.
		Ext.each(this.findByType('radio'), function(radio) {
			if (radio.getValue()) {
				var pattern = radio.patternValue;

				record.beginEdit();

				// Update the subtype which was selected
				record.set('recurrence_subtype', pattern.type);
				record.set('recurrence_regen', pattern.regen);

				// Load the corresponding properties
				this.updateRecordSubType(record, pattern);

				record.endEdit();

				return false;
			}
		}, this);

	}
});

Ext.reg('zarafa.recurrencesubpanel', Zarafa.common.recurrence.dialogs.RecurrenceSubPanel);
Ext.namespace('Zarafa.common.reminder.dialogs');

/**
 * @class Zarafa.common.reminder.dialogs.ReminderPanel
 * @extends Ext.Panel
 * @xtype zarafa.reminderpanel
 */
Zarafa.common.reminder.dialogs.ReminderPanel = Ext.extend(Ext.Panel, {
	/**
	 * @cfg {Zarafa.common.reminder.data.ReminderStore} store store that will be used to get reminder information.
	 */
	store: undefined,

	/**
	 * @cfg {Ext.Template/String} activeReminderTemplate The template or template string which
	 * must be applied to the {@link Zarafa.common.reminder.dialogs.ReminderPanel #activeReminder} to build selected reminder info when the
	 * {@link Zarafa.common.reminder.data.ReminderRecord record} has been {@link #update updated}.
	 * The arguments of this template will be the {@link Zarafa.common.reminder.data.ReminderRecord#data record.data} field.
	 */
	activeReminderTemplate:
		'<tpl>' +
			'<div>'+
				'<span class="zarafa-reminder-dialog-active-reminder-icon {[Zarafa.common.ui.IconClass.getIconClass(values.record)]}"></span>'+
				'<span class="zarafa-reminder-dialog-active-reminder-subject">' +
					'<tpl if="!Ext.isEmpty(values.subject)">' +
						// @FIXME this should be changed to truncate strings based on dialog width
						'{values.subject:htmlEncodeEllipsis(60)}'+
					'</tpl>' +
					'<tpl if="Ext.isEmpty(values.subject)">' +
						'&nbsp;'+
					'</tpl>' +
				'</span>'+
			'</div>'+
			'<div>'+
				'<span>' +
					// Use &quot; instead of " or \" as neither will be accepted by the XTemplate for creating a string
					'<tpl if="Zarafa.core.ContainerClass.isClass(values.message_class, &quot;IPM.Task&quot;, true)">' +
						'<tpl if="Ext.isDefined(values.task_duedate)">' +
							_('Due') + ': {values.task_duedate:date("' +
							// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
							_('l jS F Y') + '")}' +
						'</tpl>' +
						'<tpl if="Ext.isDefined(values.reminder_time) && !Ext.isDefined(values.task_duedate)">' +
							_('Reminder time') + ': {values.reminder_time:formatDefaultTimeString("' + _("l jS F Y {0}") + '")}' +
							// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
						'</tpl>' +
					'</tpl>' +
					// Use &quot; instead of " or \" as neither will be accepted by the XTemplate for creating a string
					'<tpl if="!Zarafa.core.ContainerClass.isClass(values.message_class, &quot;IPM.Task&quot;, true)">' +
						'<tpl if="Ext.isDefined(values.reminder_time)">' +
							_('Start time') + ': {values.reminder_time:formatDefaultTimeString("' + _("l jS F Y {0}") + '")}' +
							// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
						'</tpl>' +
					'</tpl>' +
				'</span>'+
			'</div>'+
			// @TODO show location data
		'</tpl>',

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

		Ext.applyIf(config, {
			xtype: 'zarafa.reminderpanel',
			cls: 'k-reminderpanel',
			layout: {
				type: 'vbox',
				align: 'stretch',
				pack: 'start'
			},
			header: false,
			border: false,
			defaults: {
				border: false
			},
			items: [
				this.activeReminderPanel(),
				this.reminderGrid(config.store),
				this.reminderAction(),
				this.reminderSnooze()
			]
		});

		Zarafa.common.reminder.dialogs.ReminderPanel.superclass.constructor.call(this, config);

		if (Ext.isString(this.activeReminderTemplate)) {
			this.activeReminderTemplate = new Ext.XTemplate(this.activeReminderTemplate, {
				compiled: true
			});
		}

		// Set the focus to the dismissAllButton when the parent window is shown
		// This is already done in the afterrender handler of the button itself, but
		// Ext will focus the focus element of the dialog when it is shown. (We need
		// the focus action also on the afterrender event of the button because the
		// show event is not fired for the popped out reminder panel)
		// defer the action 10ms because Ext does the same to focus the focus element
		// of the dialog in its show event handler.
		this.ownerCt.on('show', this.dismissAllButton.focus, this.dismissAllButton, {delay: 10});
	},

	/**
	 * Create the {@link Ext.Panel panel} containing the information about active/selected reminder
	 * and some extra information about it.
	 * @return {Object} Configuration object for the panel containing the fields
	 * @private
	 */
	activeReminderPanel: function()
	{
		return {
			xtype: 'panel',
			cls:'zarafa-reminder-dialog-active-reminder',
			layout: 'fit',
			ref: 'activeReminder',
			height: 52
		};
	},

	/**
	 * Create the {@link Zarafa.common.reminder.dialogs.ReminderGrid ReminderGrid} object.
	 * @param {Zarafa.common.reminder.ReminderStore} store reminder store.
	 * @return {Object} The configuration object for the reminder grid
	 * @private
	 */
	reminderGrid: function(store)
	{
		return{
			xtype: 'zarafa.remindergrid',
			ref: 'reminderGridView',
			store: store,
			border: true,
			flex: 2
		};
	},

	/**
	 * Create the {@link Ext.Panel Panel} containing the form button fields
	 * which performs different action on selected reminders
	 * @return {Object} Configuration object for the panel with reminder action fields
	 * @private
	 */
	reminderAction: function()
	{
		return{
			xtype: 'panel',
			layout: 'fit',
			height: 40,
			ref: 'reminderAction',
			minButtonWidth: 90,
			buttons: [{
				text	: _('Dismiss All'),
				handler	: this.onDismissAll,
				ref: '../../dismissAllButton',
				scope	: this,
				listeners: {
					// A listener to focus the button in a popped out panel
					afterrender: function(btn){ btn.focus(); }
				}
			},{
				text	: _('Open Item'),
				handler	: this.onOpenItem,
				ref: '../../openButton',
				scope	: this
			},{
				text	: _('Dismiss'),
				handler	: this.onDismiss,
				ref: '../../dismissButton',
				scope	: this
			}]
		};
	},

	/**
	 * Create the {@link Ext.Panel Panel} containing the form snooze button fields
	 * which will set the snoozing time for selected reminders
	 * @return {Object} Configuration object for the panel with snooze field
	 * @private
	 */
	reminderSnooze: function()
	{
		var reminderStore = {
			xtype: 'jsonstore',
			fields: ['name', 'value'],
			// Remove the first value of the ReminderPeriods array because
			// we don't need to snooze with a period of 0 minutes.
			data: Zarafa.calendar.data.ReminderPeriods.slice(1)
		};

		return{
			xtype: 'panel',
			cls: 'zarafa-reminder-dialog-snooze',
			layout: {
				type: 'vbox',
				align: 'stretch',
				pack: 'start'
			},
			height: 80,
			ref: 'reminderAction',
			items:[{
				xtype: 'displayfield',
				value: _('Click Snooze to be reminded again in') + ': ',
				hideLabel: true,
				height: 25,
				ref: '../snoozeInfoText'
			},{
				xtype: 'panel',
				layout: 'hbox',
				border: false,
				flex: 1,
				items: [{
					xtype: 'combo',
					ref: '../../snoozeTimeCombo',
					name: 'snooze',
					flex: 1,
					store: reminderStore,
					mode: 'local',
					triggerAction: 'all',
					displayField: 'name',
					valueField: 'value',
					value: container.getSettingsModel().get('zarafa/v1/main/reminder/default_snooze_time'),
					lazyInit: false,
					forceSelection: true,
					editable: false
				},{
					xtype: 'spacer',
					width: 10
				},{
					xtype	: 'button',
					width: 90,
					text	: _('Snooze'),
					handler	: this.onSnooze,
					ref: '../../snoozeButton',
					scope	: this
				}]
			}]
		};
	},

	/**
	 * initialize events for the {@link Zarafa.common.reminder.dialogs.ReminderPanel ReminderPanel}
	 * @private
	 */
	initEvents: function()
	{
		Zarafa.common.reminder.dialogs.ReminderPanel.superclass.initEvents.call(this);

		this.mon(this.reminderGridView, 'viewready', this.onViewReady, this);
		this.mon(this.reminderGridView.getSelectionModel(), 'selectionchange', this.onSelectionChange, this);
		this.mon(this.reminderGridView.getSelectionModel(), 'selectionchange', this.toggleFields, this);
	},

	/**
	 * Event handler which is fired when the {@link Zarafa.common.reminder.dialogs.ReminderGrid ReminderGrid}
	 * is ready. This will automatically select the first row in the grid.
	 * @private
	 */
	onViewReady: function()
	{
		this.reminderGridView.getSelectionModel().selectFirstRow();
	},

	/**
	 * Event handler which is triggered when the {@link Zarafa.common.reminder.dialogs.ReminderGrid ReminderGrid}
	 * {@link Zarafa.core.data.IPMRecord record} selection is changed. This will inform
	 * the {@link Zarafa.common.reminder.dialogs.ReminderPanel} about the change.
	 *
	 * @param {Ext.grid.RowSelectionModel} selectionModel The selection model used by the grid.
	 * @private
	 */
	onSelectionChange: function()
	{
		var selections = this.reminderGridView.getSelectionModel().getSelections();

		// clear the previous contents
		var el = this.activeReminder.getEl();
		if(Ext.isDefined(el.dom)) {
			el.dom.innerHTML = '';
		}

		if (selections.length == 1) {
			var data = selections[0].data;
			data.record = selections[0];
			this.activeReminderTemplate.overwrite(el.dom , data);
		} else {
			el.createChild({
				tag: 'div',
				html: String.format(
					npgettext('reminder.dialog', '{0} reminder is selected.', '{0} reminders are selected.', selections.length),
					selections.length
				)
			});
		}

		this.doLayout();
	},

	/**
	 * Event handler which is triggered when the {@link Zarafa.common.reminder.dialogs.ReminderGrid grid}
	 * {@link Zarafa.core.data.IPMRecord record} selection is changed.
	 *
	 * Function will disable buttons if there isn't any reminder selected.
	 * @private
	 */
	toggleFields: function()
	{
		var reminderCount = this.reminderGridView.getStore().getCount();

		var hasSelection = false;
		if(reminderCount > 0) {
			hasSelection = this.reminderGridView.getSelectionModel().hasSelection();
		}

		// If any reminder is not selected then disable buttons and fields.
		this.dismissButton.setDisabled(!hasSelection);
		this.openButton.setDisabled(!hasSelection);
		this.snoozeTimeCombo.setDisabled(!hasSelection);
		this.snoozeButton.setDisabled(!hasSelection);
		this.snoozeInfoText.setDisabled(!hasSelection);

		// if any reminder is present then show dismiss all button
		// @FIXME this should be done on store events instead of here
		this.dismissAllButton.setDisabled(reminderCount <= 0);
	},

	/**
	 * Function will send request to dismiss all reminder present at the given time.
	 * @param {Ext.Button} button button object.
	 * @param {EventObject} eventObject The click event object.
	 * @private
	 */
	onDismissAll: function()
	{
		this.store.dismissReminders(this.store.getRange());

		// close the dialog, if error occurs in dismiss all then reminder dialog will popup again after reminder interval
		this.dialog.close();
	},

	/**
	 * Function will open appointment/task/mail item for the selected reminder item.
	 * if multiple reminders are selected then it will open only the first item.
	 * @param {Ext.Button} button button object.
	 * @param {EventObject} eventObject The click event object.
	 * @private
	 */
	onOpenItem: function()
	{
		var record = this.reminderGridView.getSelectionModel().getSelected();

		if(record) {
			Zarafa.common.Actions.openReminderRecord(record);
			this.dialog.close();
		}
	},

	/**
	 * Function will send request to dismiss selected reminder
	 * @param {Ext.Button} button button object.
	 * @param {EventObject} eventObject The click event object.
	 * @private
	 */
	onDismiss: function()
	{
		var selectedRecords = this.reminderGridView.getSelectionModel().getSelections();

		this.store.dismissReminders(selectedRecords);

		// we have selected all reminders to dismiss, so a virtual dismiss all
		if(this.store.getCount() === 0) {
			// close the dialog, if error occurs in dismiss then reminder dialog will popup again after reminder interval
			this.dialog.close();
		}
	},

	/**
	 * Function will set the snooze timing for selected reminders
	 * @param {Ext.Button} button button object.
	 * @param {EventObject} eventObject The click event object.
	 * @private
	 */
	onSnooze: function()
	{
		var selectedRecords = this.reminderGridView.getSelectionModel().getSelections();
		this.store.snoozeReminders(selectedRecords, this.snoozeTimeCombo.getValue());
	}
});

Ext.reg('zarafa.reminderpanel', Zarafa.common.reminder.dialogs.ReminderPanel);
Ext.namespace('Zarafa.common.rules.dialogs');

/**
 * @class Zarafa.common.rules.dialogs.BaseContainer
 * @extends Ext.Container
 * @xtype zarafa.basecontainer
 *
 * The base container is base class of {@link Zarafa.common.rules.dialogs.RulesConditionContainer RulesConditionContainer}
 * and {@link Zarafa.common.rules.dialogs.RulesExceptionContainer RulesExceptionContainer}.
 * This container manages adding, removing and creating combo box containers.
 * And includes common functionality of {@link Zarafa.common.rules.dialogs.RulesConditionContainer RulesConditionContainer}
 * and {@link Zarafa.common.rules.dialogs.RulesExceptionContainer RulesExceptionContainer}.
 * This class can be expanded to create containers like conditions container and exception container, and is able to parse
 * the rules_condition property of a {@link Zarafa.common.rules.data.RulesRecord rule}.
 */
Zarafa.common.rules.dialogs.BaseContainer = Ext.extend(Ext.Container, {
  /**
   * The current number of combo boxes containers which are present in the container.
   * This number is changed by {@link #addComboBoxContainer} and {@link #removeComboBoxContainer}.
   * @property
   * @type Number
   * @private
   */
  boxContainerCount: 0,

  /**
   * @constructor
   * @param {Object} config Configuration object
   */
  constructor: function(config)
  {
    var addBtnTxt = config.addBtnName;
    var removeBtnTxt = config.removeBtnName;

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

    Ext.applyIf(config, {
      layout: 'form',
      id: 'rule-condition-container',
      autoHeight: true,
      items: [{
        xtype: 'zarafa.compositefield',
        hideLabel: true,
        items: [{
          xtype: 'button',
          ref: '../addBtn',
          text: addBtnTxt,
          handler: this.addComboBoxContainer,
          scope: this
        },{
          xtype: 'button',
          ref: '../removeBtn',
          text: removeBtnTxt,
          handler: this.removeComboBoxContainer,
          scope: this
        }]
      }]
    });

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

  /**
   * Generic function to create containers in which a condition or exception is represented. This consists of
   * two components, the first one is the combobox in which the condition type or exception type is selected, and the
   * second in which special option for the given condition/exception can be configured.
   * @param {String} id for the new container.
   * @param {Ext.data.JsonStore} profileStore store of the Conditions or Exceptions for combo box.
   * @return {Object} config object to create a {@link Ext.Container Container}.
   * @private
   */
  createComboBoxContainer: function(id, profileStore)
  {
    return {
      xtype: 'container',
      id: id,
      flex: 1,
      height: 25,
      layout: {
        type: 'hbox',
        align: 'stretch',
        defaultMargins: '0 5 0 0'
      },
      items: [{
        xtype: 'combo',
        fieldLabel: _('Condition'),
        width: 300,
        store: profileStore,
        mode: 'local',
        triggerAction: 'all',
        displayField: 'name',
        valueField: 'value',
        lazyInit: false,
        forceSelection: true,
        editable: false,
        value: _('Select one…'),
        listeners: {
          'select': this.onComboSelect,
          'scope': this
        }
      }, {
        xtype: 'container',
        flex: 1,
        layout: 'card',
        activeItem: 0,
        items: this.createContentPanels(id)
      }]
    };
  },

  /**
   * Create a set of ContentPanels which are used to configure the various condition/exception 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/exception 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
   */
  createContentPanels: 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 + '-recipientwords'
    }, {
      xtype: 'zarafa.wordselectionlink',
      id: baseId + '-words'
    }, {
      xtype: 'zarafa.wordselectionlink',
      id: baseId + '-bodywords'
    }, {
      xtype: 'zarafa.wordselectionlink',
      id: baseId + '-headerwords'
    }, {
      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.atleatsizelink',
      id: baseId + '-atleastsize'
    }, {
      xtype: 'zarafa.atmostsizelink',
      id: baseId + '-atmostsize'
    }, {
      xtype: 'zarafa.sentccmelink',
      id: baseId + '-cc-me'
    }, {
      xtype: 'zarafa.namebcclink',
      id: baseId + '-name-to-bcc'
    }, {
      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 combobox containers in a rule container.
   * @return {Ext.Container} The combobox container which was inserted
   * @private
   */
  addComboBoxContainer: function()
  {
    this.boxContainerCount++;

    var container = this.createComboBoxContainer(this.boxContainerCount);
    container = this.insert(this.items.getCount() - 1, container);

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

    this.doLayout();

    return container;
  },

  /**
   * Function that can be used to remove a combobox containers from a rule container.
   * This will always remove the last combobox container.
   * @private
   */
  removeComboBoxContainer: function()
  {
    if (this.boxContainerCount > 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.boxContainerCount--;

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

      this.doLayout();
    }
  },

  /**
   * {@link #addComboBoxContainer add} or {@link #removeComboBoxContainer remove}
   * combobox containers for a rule, until the {@link #boxContainerCount} reaches
   * the given count.
   * @param {Number} count The desired number of combobox containers
   * @private
   */
  setBoxContainerCount: function(count)
  {
    while (count < this.boxContainerCount) {
      this.removeComboBoxContainer();
    }
    while (count > this.boxContainerCount) {
      this.addComboBoxContainer();
    }
  },

  /**
   * This function will update 'rule_condition' property of {@link Zarafa.core.data.IPMRecord record}.
     * This function will handle adding, removing exceptions/conditions in conditions from conditions.
     * This is common function to update Exception/Condition in Record.
     * @param {Zarafa.core.data.IPMRecord} record which needs to be updated.
     * @param {Array} conditionsToSet array of conditions which needs to be set in record.
     * @param {Boolean} isException True if given conditionsToSet should be treated as Exceptions else false.
     * @private
     */
    updateConditionsInRecord: function(record, conditionsToSet, isException)
    {
        // If Exceptions are needed to be set in record.
        if (isException) {
            // Get conditions and add exceptions after it.
            var conditions = record.getConditions(this);

            conditionsToSet.forEach(function(exception) {
                conditions.push(exception);
            });

            if (conditions.length > 1) {
                conditions = Zarafa.core.data.RestrictionFactory.createResAnd(conditions);
            } else {
                conditions = conditions[0];
            }

            record.set('rule_condition', conditions);

        } else {
            // If Conditions are needed to be set in record.
            var exceptions = record.getExceptions(this);

            // Adding Exceptions after conditions, if exceptions are already exist in record.
            if (!Ext.isEmpty(exceptions)) {
                exceptions.forEach(function(exception) {
                    conditionsToSet.push(exception);
                });
            }

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

            record.set('rule_condition', conditionsToSet);
        }
    },

    /**
     * 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 (innerCondition[1][Zarafa.core.mapi.Restrictions.ULPROPTAG] === 'PR_DISPLAY_TO') {
                                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 #addComboBoxContainer}.
     * @param {Ext.Container} panel The container on which the condition will be loaded
     * @param {Object} condition The condition which should be loaded
     * @param {Zarafa.common.data.SizeUnits} sizeUnit selected size unit for the at least and at most size condition components.
     * @private
     */
    applyCondition: function(panel, condition, sizeUnit)
    {
        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.onComboSelect(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.ATLEAST_SIZE:
            case Zarafa.common.rules.data.ConditionFlags.ATMOST_SIZE:
                layout.activeItem.setCondition(conditionFlag, condition, sizeUnit);
                break;
            case Zarafa.common.rules.data.ConditionFlags.SUBJECT_WORDS:
            case Zarafa.common.rules.data.ConditionFlags.BODY_WORDS:
            case Zarafa.common.rules.data.ConditionFlags.TRANSPORTHEADER_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.RECIPIENT_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.NAME_BCC:
            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;
                }
            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_TRANSPORT_MESSAGE_HEADERS':
                        return Zarafa.common.rules.data.ConditionFlags.TRANSPORTHEADER_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':
                        // If there exists sub restriction.
                        if (condition[0] === Restrictions.RES_SUBRESTRICTION) {
                            var isRecepientWordsRes = condition[1][Restrictions.RESTRICTION][1][Restrictions.ULPROPTAG] === 'PR_SMTP_ADDRESS';
                            if (isRecepientWordsRes) {
                                return Zarafa.common.rules.data.ConditionFlags.RECIPIENT_WORDS;
                            }
                            return Zarafa.common.rules.data.ConditionFlags.SENT_TO;
                        }
                        break;
                    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_MESSAGE_SIZE':
                        if (condition[1][1] === Restrictions.RELOP_LE) {
                            return Zarafa.common.rules.data.ConditionFlags.ATMOST_SIZE;
                        } else if (condition[1][1] === Restrictions.RELOP_GE) {
                            return Zarafa.common.rules.data.ConditionFlags.ATLEAST_SIZE;
                        }
                        break;
                    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:
                var bccLinkFlag = 0;
                var currCoreVersion = container.getVersion().getZCP();
                var versionCompare = container.versionRecord.versionCompare(currCoreVersion,"8.7.2");

                for (var i = 0, len = condition[1].length; i < len; i++) {
                    var sub = condition[1][i];

                    // Check if the RES_AND contains the restriction for PR_MESSAGE_RECIP_ME,
                    // And its value is false. So, increase bccLinkFLag value by one.
                    if (sub[0] === Restrictions.RES_PROPERTY &&
                        sub[1][Restrictions.ULPROPTAG] === 'PR_MESSAGE_RECIP_ME') {
                        var isOlderVersion = versionCompare === -1 ? true : false;

                        if (isOlderVersion && sub[1][Restrictions.VALUE]['PR_MESSAGE_RECIP_ME'] === false) {
                            bccLinkFlag++;
                            continue;
                        } else if (!isOlderVersion && sub[1][Restrictions.VALUE]['PR_MESSAGE_RECIP_ME'] === true) {
                            bccLinkFlag++;
                            continue;
                        }
                    }

                    // Check if the RES_AND contains the restriction for PR_MESSAGE_CC_ME,
                    // And its value is false. So, increase bccLinkFlag value by one.
                    if (sub[0] === Restrictions.RES_PROPERTY &&
                        sub[1][Restrictions.ULPROPTAG] === 'PR_MESSAGE_CC_ME' &&
                        sub[1][Restrictions.VALUE]['PR_MESSAGE_CC_ME'] === false) {
                        bccLinkFlag++;
                        continue;
                    }

                    // Check if the RES_AND contains the restriction for PR_MESSAGE_TO_ME,
                    // And its value is false. So, increase bccLinkFlag value by one.
                    if (sub[0] === Restrictions.RES_PROPERTY &&
                        sub[1][Restrictions.ULPROPTAG] === 'PR_MESSAGE_TO_ME' &&
                        sub[1][Restrictions.VALUE]['PR_MESSAGE_TO_ME'] === false) {
                        bccLinkFlag++;
                        continue;
                    }

                    // 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'&&
                        sub[1][Restrictions.VALUE]['PR_MESSAGE_CC_ME'] === true) {
                        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' &&
                        sub[1][Restrictions.VALUE]['PR_MESSAGE_TO_ME'] === true) {
                        return Zarafa.common.rules.data.ConditionFlags.SENT_TO_ME_ONLY;
                    }
                }

                // if all three conditions is matched with bcc rule's restriction then only return BCC flag.
                if (bccLinkFlag === 3) {
                    return Zarafa.common.rules.data.ConditionFlags.NAME_BCC;
                }
                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 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
     */
    onComboSelect: 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.RECIPIENT_WORDS:
                layout.setActiveItem(panel.id + '-recipientwords');
                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.TRANSPORTHEADER_WORDS:
                layout.setActiveItem(panel.id + '-headerwords');
                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.NAME_BCC:
                layout.setActiveItem(panel.id + '-name-to-bcc');
                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.ATLEAST_SIZE:
                layout.setActiveItem(panel.id + '-atleastsize');
                layout.activeItem.setCondition(value);
                break;
            case Zarafa.common.rules.data.ConditionFlags.ATMOST_SIZE:
                layout.setActiveItem(panel.id + '-atmostsize');
                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.basecontainer', Zarafa.common.rules.dialogs.BaseContainer);
Ext.namespace('Zarafa.common.rules.dialogs');

/**
 * @class Zarafa.common.rules.dialogs.BaseLink
 * @extends Ext.Container
 * @xtype zarafa.baselink
 *
 * Base class for RuleLinks.
 */
Zarafa.common.rules.dialogs.BaseLink = Ext.extend(Ext.Container, {
	/**
	 * The Condition type which is handled by this view
	 * This is set during {@link #setCondition}.
	 * @property
	 * @type Zarafa.common.rules.data.ConditionFlags
	 */
	conditionFlag: undefined,

	/**
	 * The condition property which was configured during
	 * {@link #setCondition}.
	 * @property
	 * @type Object
	 */
	condition: undefined,

	/**
	 * True if the condition was modified by the user, if this is false,
	 * then {@link #getCondition} will return {@link #condition} instead
	 * of returning a new object.
	 * @property
	 * @type Boolean
	 */
	isModified: false,

	/**
	 * Apply an action onto the DataView, this will parse the condition and show
	 * the contents in a user-friendly way to the user.
	 * @param {Zarafa.common.rules.data.ConditionFlags} conditionFlag The condition type
	 * which identifies the exact type of the condition.
	 * @param {Object} condition The condition to apply
	 */
	setCondition: function(conditionFlag, condition)
	{
		this.conditionFlag = conditionFlag;
		this.condition = condition;
		this.isModified = !Ext.isDefined(condition);
	}
});

Ext.reg('zarafa.baselink', Zarafa.common.rules.dialogs.BaseLink);
Ext.namespace('Zarafa.common.rules.dialogs');

/**
 * @class Zarafa.common.rules.dialogs.FolderSelectionLink
 * @extends Ext.BoxComponent
 * @xtype zarafa.folderselectionlink
 *
 * Action component for the {@link Zarafa.common.rules.data.ActionFlags#MOVE MOVE}
 * and {@link Zarafa.common.rules.data.ActionFlags#COPY COPY} Action Flags. This
 * will show the currently selected {@link #folder} to the user, and allows
 * the user to select the desired destination folder.
 */
Zarafa.common.rules.dialogs.FolderSelectionLink = Ext.extend(Ext.BoxComponent, {
	/**
	 * @cfg {String} fieldLabel The label which must be applied to template
	 * as a prefix to the list of attachments.
	 */
	emptyText:_('Select one…'),

	/**
	 * The folder which was selected by the user
	 * @property
	 * @type Zarafa.hierarchy.data.MAPIFolderRecord
	 */
	folder: undefined,

	/**
	 * The Action type which is handled by this view
	 * This is set during {@link #setAction}.
	 * @property
	 * @type Zarafa.common.rules.data.ActionFlags
	 */
	actionFlag: undefined,

	/**
	 * The action property which was configured during
	 * {@link #setAction}.
	 * @property
	 * @type Object
	 */
	action: undefined,

	/**
	 * True if the action was modified by the user, if this is false,
	 * then {@link #getAction} will return {@link #action} instead
	 * of returning a new object.
	 * @property
	 * @type Boolean
	 */
	isModified: false,

	/**
	 * True if the action/condition is complete and valid,
	 * False will denote that action/condition is invalid or incomplete
	 * if this is true, then {@link #getCondition} will return {@link #condition} instead
	 * of returning a new object and {@link #getAction} will return {@link #action}
	 * instead of returning a new Object.
	 * @property
	 * @type Boolean
	 */
	isValid: true,

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

		Ext.applyIf(config,{
			xtype: 'zarafa.folderselectionlink',
			border: false,
			autoScroll:true,
			anchor: '100%',
			tpl: new Ext.XTemplate(
				'<div class="zarafa-folder-link">' +
					'<tpl if="!Ext.isEmpty(values.display_name)">' +
						'&quot;{display_name:htmlEncode}&quot;' +
					'</tpl>' +
					'<tpl if="Ext.isEmpty(values.display_name)">' +
						'&quot;' + _('Unnamed folder') + '&quot;' +
					'</tpl>' +
				'</div>',
				{
					compiled: true
				}
			)
		});

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

	/**
	 * This function is called after the component has been rendered.
	 * This will register the {@link #onActivate} and {@link #onClick} event handlers.
	 * @private
	 */
	afterRender: function()
	{
		Zarafa.common.rules.dialogs.FolderSelectionLink.superclass.afterRender.apply(this, arguments);

		this.mon(this.getActionEl(), 'click', this.onClick, this);
	},

	/**
	 * Called when user clicks on a {@link Zarafa.common.rules.dialogs.FolderSelectionLink}
	 * It opens hierarchy folder selection dialog
	 * @param {Ext.DataView} dataView Reference to this object
	 * @param {Number} index The index of the target node
	 * @param {HTMLElement} node The target node
	 * @param {Ext.EventObject} evt The mouse event
 	 * @protected
	 */
	onClick: function(dataView, index, node, evt)
	{
		Zarafa.hierarchy.Actions.openFolderSelectionContent({
			folder: this.folder,
			hideTodoList: true,
			// IPMSubTreeFilter: this.storeEntryId, // do not filter shared private stores
			callback: function(folder) {
				this.folder = folder;
				this.isModified = true;
				this.update(folder);
			},
			scope: this,
			modal: true
		});
	},

	/**
	 * Apply an action onto the DataView, this will parse the action and show
	 * the contents in a user-friendly way to the user.
	 * @param {Zarafa.common.rules.data.ActionFlags} actionFlag The action type
	 * which identifies the exact type of the action.
	 * @param {Object} action The action to apply
	 */
	setAction: function(actionFlag, action)
	{
		this.folder = undefined;
		this.isValid = false;

		if (action) {
			var store = container.getHierarchyStore().getById(action.storeentryid);
			if (store) {
				this.folder = store.getSubStore('folders').getById(action.folderentryid);
				if(Ext.isDefined(this.folder)) {
					this.isValid = true;
				}
			}
		}

		this.actionFlag = actionFlag;
		this.action = action;
		this.isModified = !Ext.isDefined(action);
		this.update(this.folder);
	},

	/**
	 * Obtain the action as configured by the user
	 * @return {Object} The action
	 */
	getAction: function()
	{
		if (this.isModified !== true && this.isValid === true) {
			return this.action;
		}

		var actionFactory = container.getRulesFactoryByType(Zarafa.common.data.RulesFactoryType.ACTION);
		var actionDefinition = actionFactory.getActionById(this.actionFlag);
		return actionDefinition({folder: this.folder});
	},

	/**
	 * Update the contents of this dataview, this will apply the {@link #tpl} for
	 * the {@link #folder}.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder The folder to show
	 */
	update: function(folder)
	{
 		var data = folder ? folder.data : { display_name: this.emptyText };
		Zarafa.common.rules.dialogs.FolderSelectionLink.superclass.update.call(this, this.tpl.apply(data));
	}
});

Ext.reg('zarafa.folderselectionlink', Zarafa.common.rules.dialogs.FolderSelectionLink);
Ext.namespace('Zarafa.common.rules.dialogs');

/**
 * @class Zarafa.common.rules.dialogs.MarkAsReadLink
 * @extends Ext.Container
 * @xtype zarafa.markasreadlink
 *
 * Extension of the {@link Ext.Container Container}
 * especially for the {@link Zarafa.common.rules.data.ActionFlags#MARK_AS_READ MARK_AS_READ ActionFlag}.
 * This component handles MARK_AS_READ action.
 * This will not show anything to the user, but during {@link #setAction} this will force
 * the {@link #actionFlag} to be {@link Zarafa.common.rules.data.ActionFlags#MARK_AS_READ MARK_AS_READ}
 */
Zarafa.common.rules.dialogs.MarkAsReadLink = Ext.extend(Ext.Container,{

	/**
	 * The Action type which is handled by this view
	 * This is set during {@link #setAction}.
	 * @property
	 * @type Zarafa.common.rules.data.ActionFlags
	 */
	actionFlag: undefined,

	/**
	 * The action property which was configured during
	 * {@link #setAction}.
	 * @property
	 * @type Object
	 */
	action: undefined,

	/**
	 * True if the action was modified by the user, if this is false,
	 * then {@link #getAction} will return {@link #action} instead
	 * of returning a new object.
	 * @property
	 * @type Boolean
	 */
	isModified: false,

	/**
	 * True if the action/condition is complete and valid,
	 * False will denote that action/condition is invalid or incomplete
	 * if this is true, then {@link #getCondition} will return {@link #condition} instead
	 * of returning a new object and {@link #getAction} will return {@link #action}
	 * instead of returning a new Object.
	 * @property
	 * @type Boolean
	 */
	isValid: true,

	/**
   * Apply an action onto the DataView, this will parse the action and show
   * the contents in a user-friendly way to the user.
   * @param {Zarafa.common.rules.data.ActionFlags} actionFlag The action type
   * which identifies the exact type of the action.
   * @param {Object} action The action to apply
   */
  setAction: function(actionFlag, action)
	{
		this.isValid = action ? true : false;
		this.actionFlag = actionFlag;
		this.action = action;
		this.isModified = !Ext.isDefined(action);
	},

	/**
	 * Obtain the action as configured by the user
	 * @return {Object} The action
	 */
	getAction: function()
	{
		if (this.isModified !== true && this.isValid === true) {
			return this.action;
		}

		var actionFactory = container.getRulesFactoryByType(Zarafa.common.data.RulesFactoryType.ACTION);
		var actionDefinition = actionFactory.getActionById(this.actionFlag);
		return actionDefinition(this.actionFlag);
	}

});

Ext.reg('zarafa.markasreadlink', Zarafa.common.rules.dialogs.MarkAsReadLink);Ext.namespace('Zarafa.common.rules.dialogs');

/**
 * @class Zarafa.common.rules.dialogs.NoneLink
 * @extends Zarfa.common.rules.BaseLink
 * @xtype zarafa.nonelink
 *
 * Condition component for the {@link Zarafa.common.rules.data.ConditionFlags#NONE NONE}
 * This will match every incoming message.
 */
Zarafa.common.rules.dialogs.NoneLink = Ext.extend(Zarafa.common.rules.dialogs.BaseLink, {
	/**
	 * Obtain the condition as configured by the user
	 * @return {Object} The condition
	 */
	getCondition: function()
	{
		if (this.isModified !== true) {
			return this.condition;
		}

		// Invalid conditionFlag
		if (this.conditionFlag !== Zarafa.common.rules.data.ConditionFlags.NONE) {
			return false;
		}

		var conditionFactory = container.getRulesFactoryByType(Zarafa.common.data.RulesFactoryType.CONDITION);
		var conditionDefinition = conditionFactory.getConditionById(this.conditionFlag);
		return conditionDefinition();
	}
});

Ext.reg('zarafa.nonelink', Zarafa.common.rules.dialogs.NoneLink);
Ext.namespace('Zarafa.common.rules.dialogs');

/**
 * @class Zarafa.common.rules.dialogs.RulesActionsContainer
 * @extends Ext.Container
 * @xtype zarafa.rulesactionscontainer
 *
 * The container in which all actions can be edited. This container
 * can be expanded to include multiple actions, and is able to parse
 * the rules_actions property of a {@link Zarafa.common.rules.data.RulesRecord rule}.
 */
Zarafa.common.rules.dialogs.RulesActionsContainer = Ext.extend(Ext.Container, {
	/**
	 * The current number of action boxes which are present in the container.
	 * This number is changed by {@link #addActionBox} and {@link #removeActionBox}.
	 * @property
	 * @type Number
	 * @private
	 */
	actionCount: 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: '../addActionBtn',
					text: _('Add action'),
					handler: this.addActionBox,
					scope: this
				},{
					xtype: 'button',
					ref: '../removeActionBtn',
					text: _('Remove action'),
					handler: this.removeActionBox,
					scope: this
				}]
			}]
		});

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

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

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

	/**
	 * Create a set of ContentPanels which are used to configure the various action 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
	 * action 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
	 */
	createActionContentPanels: function(baseId)
	{
		return [{
			xtype: 'container',
			id: baseId + '-empty'
		},{
			xtype: 'zarafa.folderselectionlink',
			id: baseId + '-folder',
			storeEntryId: this.storeEntryId,
		},{
			xtype: 'zarafa.deletelink',
			id: baseId + '-delete',
			storeEntryId: this.storeEntryId
		},{
			xtype: 'zarafa.userselectionlink',
			id: baseId + '-to'
		},{
			xtype: 'zarafa.markasreadlink',
			id: baseId + '-markasread',
		}];
	},

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

		var container = this.createActionBox(this.actionCount);
		container = this.insert(this.items.getCount() - 1, container);

		// Toggle the removeActionBtn
		this.removeActionBtn.setDisabled(this.actionCount <= 1);

		this.doLayout();

		return container;
	},

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

			// Toggle the removeActionBtn
			this.removeActionBtn.setDisabled(this.actionCount <= 1);

			this.doLayout();
		}
	},

	/**
	 * {@link #addActionBox add} or {@link #removeActionBox remove}
	 * Action Boxes for a rule, until the {@link #actionCount} reaches
	 * the given count.
	 * @param {Number} count The desired number of action boxes
	 * @private
	 */
	setActionBoxCount: function(count)
	{
		while (count < this.actionCount) {
			this.removeActionBox();
		}
		while (count > this.actionCount) {
			this.addActionBox();
		}
	},

	/**
	 * 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_actions')) {
			var actions = record.get('rule_actions');
			if (Ext.isEmpty(actions)) {
				this.setActionBoxCount(1);
				return;
			}

			// Force actions to be an array
			actions = [].concat(actions);

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

			for (var i = 0, len = actions.length; i < len; i++) {
				// Apply the action to the corresponding container
				if (actions[i]) {
					this.applyAction(this.get(i), actions[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 actions = [];
		var actionsValid = true;

		for (var i = 0; i < this.actionCount; i++) {
			var panel = this.get(i);
			var activeItem = panel.get(1).layout.activeItem;
			var action = null;

			if (Ext.isFunction(activeItem.getAction)) {
				action = activeItem.getAction();
			}

			// If no valid action was found, then
			// we have a problem and we can't save
			// the action. Break out of the loop
			// and invalidate the rule_actions property.
			if (!action) {
				panel.get(0).markInvalid();
				actionsValid = false;
				break;
			}

			// If the Action is 'Mark as read', We need to shift it first in actions list.
			// Because it will not work if we keep it later after other actions.
			// i.e. for actions like Move/copy , delete the entry id of the message changes,
			// So if we add action after these actions it will not find the required message.
 			if (action.action === Zarafa.core.mapi.RuleActions.OP_MARK_AS_READ) {
				actions.unshift(action);
			} else {
				actions.push(action);
			}

		}

		record.set('rule_actions', actions);
		record.setActionsValid(actionsValid);
	},

	/**
	 * Load an Action from a {@Link Zarafa.common.rules.data.RulesRecord} and apply it
	 * onto the {@link Ext.Container} which was created by {@link #addActionBox}.
	 * @param {Ext.Container} panel The container on which the action will be loaded
	 * @param {Object} action The action which should be loaded
	 * @private
	 */
	applyAction: function(panel, action)
	{
		var actionFlag = this.getActionFlagFromAction(action);
		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, actionFlag);
		if (index >= 0) {
			var record = store.getAt(index);
			combo.setValue(actionFlag);
			this.onActionComboSelect(combo, record, index);
		} else {
			actionFlag = Zarafa.common.rules.data.ActionFlags.UNKNOWN;
			combo.setValue(_('Unknown action'));
			combo.markInvalid(_('This action for the current rule is unknown'));
		}

		// Fill the content with the data from the action
		var layout = content.getLayout();
		switch (actionFlag) {
			case Zarafa.common.rules.data.ActionFlags.UNKNOWN:
			/* falls through */
			default:
				break;
			case Zarafa.common.rules.data.ActionFlags.MOVE:
			case Zarafa.common.rules.data.ActionFlags.COPY:
			case Zarafa.common.rules.data.ActionFlags.DELETE:
			case Zarafa.common.rules.data.ActionFlags.REDIRECT:
			case Zarafa.common.rules.data.ActionFlags.FORWARD:
			case Zarafa.common.rules.data.ActionFlags.FORWARD_ATTACH:
			case Zarafa.common.rules.data.ActionFlags.MARK_AS_READ:
				layout.activeItem.setAction(actionFlag, action);
				break;
		}
	},

	/**
	 * Read a Action object as located in the {@link Zarafa.common.rules.data.RulesRecord Rule}
	 * and convert it to the corresponding ActionFlag which properly represents the action
	 * to be taken.
	 * @param {Object} action The action which should be converted to a Action Flag
	 * @return {Zarafa.common.rules.data.ActionFlags} The Action Flag
	 * @private
	 */
	getActionFlagFromAction: function(action)
	{
		switch (action.action) {
			case Zarafa.core.mapi.RuleActions.OP_MOVE:
				// The MOVE action can be used for either
				// DELETE or MOVE action. The decision for this
				// depends on if the selected folder is set the
				// the "Deleted Items" folder, and if the rule_state
				// property has the ST_EXIT_LEVEL flag.
				var RulesStates = Zarafa.core.mapi.RuleStates;
				if ((this.record.get('rule_state') & RulesStates.ST_EXIT_LEVEL) === RulesStates.ST_EXIT_LEVEL) {
					var deletedItems = container.getHierarchyStore().getDefaultFolder('wastebasket');
					if (deletedItems && Zarafa.core.EntryId.compareEntryIds(deletedItems.get('entryid'), action.folderentryid)) {
						return Zarafa.common.rules.data.ActionFlags.DELETE;
					}
				}
				return Zarafa.common.rules.data.ActionFlags.MOVE;
			case Zarafa.core.mapi.RuleActions.OP_COPY:
				// Normal copy, nothing fancy.
				return Zarafa.common.rules.data.ActionFlags.COPY;
			case Zarafa.core.mapi.RuleActions.OP_FORWARD:
				// The exact forward action depends on the 'flavor' property.
				var FlavorFlags = Zarafa.core.mapi.FlavorFlags;
				switch (action.flavor) {
					case 0: // Forward
						return Zarafa.common.rules.data.ActionFlags.FORWARD;
					case FlavorFlags.FWD_PRESERVE_SENDER | FlavorFlags.FWD_DO_NOT_MUNGE_MSG:
						return Zarafa.common.rules.data.ActionFlags.REDIRECT;
					case FlavorFlags.FWD_AS_ATTACHMENT:
						return Zarafa.common.rules.data.ActionFlags.FORWARD_ATTACH;
					default:
						return Zarafa.common.rules.data.ActionFlags.UNKNOWN;
				}
			case Zarafa.core.mapi.RuleActions.OP_MARK_AS_READ:
				return Zarafa.common.rules.data.ActionFlags.MARK_AS_READ;
				/* falls through */
			default:
				// Any other RuleAction is not supported
				return Zarafa.common.rules.data.ActionFlags.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
	 */
	onActionComboSelect: 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.ActionFlags.UNKNOWN:
			/* falls through */
			default:
				layout.setActiveItem(panel.id + '-empty');
				break;
			case Zarafa.common.rules.data.ActionFlags.MOVE:
			case Zarafa.common.rules.data.ActionFlags.COPY:
				layout.setActiveItem(panel.id + '-folder');
				layout.activeItem.setAction(value);
				break;
			case Zarafa.common.rules.data.ActionFlags.DELETE:
				layout.setActiveItem(panel.id + '-delete');
				layout.activeItem.setAction(value);
				// For the DELETE action, the rule_state
				// must contain the ST_EXIT_LEVEL flag.
				this.record.set('rule_state', this.record.get('rule_state') | Zarafa.core.mapi.RuleStates.ST_EXIT_LEVEL);
				break;
			case Zarafa.common.rules.data.ActionFlags.REDIRECT:
			case Zarafa.common.rules.data.ActionFlags.FORWARD:
			case Zarafa.common.rules.data.ActionFlags.FORWARD_ATTACH:
				layout.setActiveItem(panel.id + '-to');
				layout.activeItem.setAction(value);
				break;
			case Zarafa.common.rules.data.ActionFlags.MARK_AS_READ:
				layout.setActiveItem(panel.id + '-markasread');
				layout.activeItem.setAction(value);
				break;
		}
	}
});

Ext.reg('zarafa.rulesactionscontainer', Zarafa.common.rules.dialogs.RulesActionsContainer);
Ext.namespace('Zarafa.common.rules.dialogs');

/**
 * @class Zarafa.common.rules.dialogs.RulesEditPanel
 * @extends Ext.form.FormPanel
 * @xtype zarafa.ruleeditpanel
 *
 * Will generate UI for {@link Zarafa.common.rules.dialogs.RulesEditContentPanel RulesEditContentPanel}.
 */
Zarafa.common.rules.dialogs.RulesEditPanel = Ext.extend(Ext.form.FormPanel, {
	/**
	 * @constructor
	 * @param config Configuration structure
	 */
	constructor: function(config)
	{
		config = config || {};

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

		Ext.applyIf(config, {
			// Override from Ext.Component
			xtype: 'zarafa.ruleseditpanel',
			layout: 'anchor',
			autoScroll: true,
			items: this.createPanelItems(config)
		});

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

	/**
	 * Function will create panel items for {@link Zarafa.common.rules.dialogs.RulesEditPanel RulesEditPanel}
	 * @param {Object} config the Configuration structure from the constructor.
	 * @return {Array} array of items that should be added to panel.
	 * @private
	 */
	createPanelItems: function(config)
	{
		return [{
			xtype: 'displayfield',
			value: _('Rule name'),
			height: 20
		}, {
			xtype: 'textfield',
			name: 'rule_name',
			fieldLabel: _('Rule name'),
			width: 250,
			listeners: {
				change: this.onChange,
				scope: this
			}
		}, {
			xtype: 'spacer',
			height: 15
		}, {
			xtype: 'displayfield',
			value: _('When the message') + '...',
			height: 20
		}, {
			xtype: 'zarafa.rulesconditioncontainer',
			anchor: '100%'
		}, {
			xtype: 'spacer',
			height: 15
		}, {
			xtype: 'displayfield',
			value: _('Do the following') + '...',
			height: 20
		}, {
			xtype: 'zarafa.rulesactionscontainer',
			anchor: '100%',
			storeEntryId: config.storeEntryId
		}, {
			xtype: 'spacer',
			height: 15
		},	{
			xtype: 'displayfield',
			value: _('Exceptions'),
			height: 20
		},	{
			xtype: 'zarafa.rulesexceptionscontainer',
			anchor: '100%'
		},	{
			xtype: 'spacer',
			height: 15
		}, {
			xtype: 'checkbox',
			ref: 'onlyIfOOFCheckbox',
			hidden: this.hideCheckBox(),
			boxLabel: _('Apply only when Out of Office is active'),
			handler: this.onToggleIfOOF,
			scope: this
		}, {
			xtype: 'checkbox',
			ref: 'stopProcessingCheckbox',
			boxLabel: _('Stop processing more rules'),
			handler: this.onToggleStopProcessing,
			scope: this
		}];
	},

	/**
	 * Save the changes in record when change event is fired on fields.
	 * @param {Object} field The field updated field
	 * @param {Object} value The value of the field updated
	 * @private
	 */
	onChange: function(field, value)
	{
		this.record.set(field.name, value);
	},

	/**
	 * Function will hide the "Apply only when..." check box when
	 * ZCP version is less than 5.6.
	 */
	hideCheckBox: function(){
		var version = container.getVersion();
		var mapiVersion = version.getZCP();
		return version.versionCompare(mapiVersion, '5.6') === -1 ? true : false;
	},

	/**
	 * Function will be called when user toggles state of checkbox to indicate that
	 * this will be last rule to be executed after that no rules should be executed.
	 * @param {Ext.form.Checkbox} checkbox The Checkbox being toggled.
	 * @param {Boolean} checked The new checked state of the checkbox.
	 * @private
	 */
	onToggleStopProcessing: function(checkbox, checked)
	{
		var state = this.record.get('rule_state');

		if (checked) {
			state |= Zarafa.core.mapi.RuleStates.ST_EXIT_LEVEL;
		} else {
			state &= ~Zarafa.core.mapi.RuleStates.ST_EXIT_LEVEL;
		}

		this.record.set('rule_state', state);
	},

	/**
	 * Function will be called when user toggles "Apply only when Out of Office.." of checkbox
	 * to indicate that this will be the rule to be executed only when user is out of office.
	 *
	 * @param {Ext.form.Checkbox} checkbox The Checkbox being toggled.
	 * @param {Boolean} checked The new checked state of the checkbox.
	 * @private
	 */
	onToggleIfOOF: function(checkbox, checked)
	{
		var state = this.record.get('rule_state');
		if (checked) {
			state |= Zarafa.core.mapi.RuleStates.ST_ONLY_WHEN_OOF;
		} else {
			state &= ~Zarafa.core.mapi.RuleStates.ST_ONLY_WHEN_OOF;
		}
		this.record.set('rule_state', state);
	},

	/**
	 * Updates the panel by loading data from the record into the form panel.
	 * @param {Zarafa.rules.delegates.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;

		this.getForm().loadRecord(record);

		var RuleStates = Zarafa.core.mapi.RuleStates;
		var state = record.get('rule_state');

		this.stopProcessingCheckbox.setValue((state & RuleStates.ST_EXIT_LEVEL) === RuleStates.ST_EXIT_LEVEL);
		this.onlyIfOOFCheckbox.setValue((state & RuleStates.ST_ONLY_WHEN_OOF) === RuleStates.ST_ONLY_WHEN_OOF);
	},

	/**
	 * 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)
	{
		record.beginEdit();

		this.getForm().updateRecord(record);

		var state = this.record.get('rule_state');
		if (this.stopProcessingCheckbox.getValue()) {
			state |= Zarafa.core.mapi.RuleStates.ST_EXIT_LEVEL;
		} else {
			state &= ~Zarafa.core.mapi.RuleStates.ST_EXIT_LEVEL;
		}

		if (this.onlyIfOOFCheckbox.getValue()) {
			state |= Zarafa.core.mapi.RuleStates.ST_ONLY_WHEN_OOF;
		} else {
			state &= ~Zarafa.core.mapi.RuleStates.ST_ONLY_WHEN_OOF;
		}

		this.record.set('rule_state', state);

		record.endEdit();
	}
});

Ext.reg('zarafa.ruleseditpanel', Zarafa.common.rules.dialogs.RulesEditPanel);
Ext.namespace('Zarafa.common.rules.dialogs');

/**
 * @class Zarafa.common.rules.dialogs.RulesWordsEditPanel
 * @extends Ext.form.FormPanel
 * @xtype zarafa.ruleswordseditpanel
 *
 * Panel to edit the {@link Zarafa.common.rules.data.ConditionFlags#SUBJECT_WORDS} condition
 * for a {@link Zarafa.common.rules.data.RulesRecord Rule}.
 */
Zarafa.common.rules.dialogs.RulesWordsEditPanel = Ext.extend(Ext.form.FormPanel, {
	/**
	 * @cfg {Ext.data.Store} store The store which contains the words on which
	 * is being filtered.
	 */
	store: undefined,

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

		Ext.applyIf(config, {
			// Override from Ext.Component
			xtype: 'zarafa.ruleswordseditpanel',
			layout: {
				type: 'vbox',
				align: 'stretch'
			},
			padding: 5,
			items: [{
				xtype: 'zarafa.compositefield',
				items: [{
					xtype: 'button',
					ref: '../editWordBtn',
					text: _('Edit'),
					width: 70,
					disabled: true,
					handler: this.onWordEdit,
					scope: this
				},{
					xtype: 'spacer',
					width: 5
				},{
					xtype: 'button',
					ref: '../deleteWordBtn',
					text: _('Delete'),
					width: 70,
					disabled: true,
					handler: this.onWordDelete,
					scope: this
				}]
			},{
				xtype: 'spacer',
				height: 5
			},{
				xtype: 'zarafa.compositefield',
				items: [{
					xtype: 'textfield',
					ref: '../wordField',
					fieldLabel: _('Word'),
					hideLabel: true,
					flex: 1,
					listeners: {
						'specialkey': this.onInputSpecialKey,
						'scope': this
					}
				},{
					xtype: 'button',
					autoWidth: true,
					iconCls: 'zarafa-rules-add',
					tooltip: _('Add'),
					handler: this.onWordAdd,
					scope: this
				}]
			},{
				xtype: 'spacer',
				height: 5
			},{
				xtype: 'grid',
				ref: 'gridPanel',
				flex: 1,
				border: true,
				enableHdMenu: false,
				enableColumnMove: false,
				store: config.store,
				viewConfig: {
					headersDisabled: true,
					forceFit: true,
					autoExpandColumn: true,
					markDirty: false,
					scrollOffset: 0
				},
				colModel: new Ext.grid.ColumnModel({
					columns: [{
						dataIndex: 'words',
						sortable: false,
						renderer: Ext.util.Format.htmlEncode
					}]
				}),
				sm: new Ext.grid.RowSelectionModel({
					singleSelect: true,
					listeners: {
						'selectionchange': this.onSelectionChange,
						'scope': this
					}
				})
			}]
		});

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

	/**
	 * Event handler which is fired when the user pressed the {@link Ext.EventObject#ENTER ENTER} key.
	 * This will call {@link #onWordAdd}.
	 * @param {Ext.form.Field} field The field which fired the event
	 * @param {Ext.EventObject} e The event object
	 * @private
	 */
	onInputSpecialKey: function(field, e)
	{
		if (e.getKey() == e.ENTER) {
			this.onWordAdd();
		}
	},

	/**
	 * Event handler which is fired when the 'Add' button has been clicked. This
	 * will read the content of the input field, and add it to the {@link #store}.
	 * After that the wordField is cleared, and the focus is reapplied to the
	 * input field.
	 * @return {Boolean} False when user tries to add another word.
	 * @private
	 */
	onWordAdd: function()
	{
		var value = this.wordField.getValue().trim();
		if (!Ext.isEmpty(value)) {
			this.store.add(new Ext.data.Record({ words: value }));
			this.wordField.reset();
		}
		this.wordField.focus();
	},

	/**
	 * Event handler which is fired when the 'Edit' button has been clicked. This
	 * will obtain the currently selected record, remove it from the {@link #store}
	 * and place the contents into the input field for further editing.
	 * @private
	 */
	onWordEdit: function()
	{
		var record = this.gridPanel.getSelectionModel().getSelected();
		if (record) {
			this.store.remove(record);
			this.wordField.setValue(record.get('words'));
			this.wordField.focus();
		}
	},

	/**
	 * Event handler which is fired when the 'Delete' button has been clicked. This
	 * will remove the selected record from the {@link #store}.
	 * @private
	 */
	onWordDelete: function()
	{
		var record = this.gridPanel.getSelectionModel().getSelected();
		if (record) {
			this.store.remove(record);
		}
	},

	/**
	 * Event handler which is fired when the {@link Ext.grid.RowSelectionModel} fires the
	 * {@link Ext.grid.RowSelectionModel#selectionchange 'selectionchange'} event. This will
	 * update the "Edit" and "Delete" buttons.
	 * @param {Ext.grid.RowSelectionModel} selModel The selection model which fired the event
	 * @private
	 */
	onSelectionChange: function(selModel)
	{
		var hasSelection = selModel.hasSelection();
		this.editWordBtn.setDisabled(!hasSelection);
		this.deleteWordBtn.setDisabled(!hasSelection);
	}
});

Ext.reg('zarafa.ruleswordseditpanel', Zarafa.common.rules.dialogs.RulesWordsEditPanel);
Ext.namespace('Zarafa.common.rules.dialogs');

/**
 * @class Zarafa.common.rules.dialogs.UserSelectionLink
 * @extends Ext.BoxComponent
 * @xtype zarafa.userselectionlink
 */
Zarafa.common.rules.dialogs.UserSelectionLink = Ext.extend(Ext.BoxComponent, {
	/**
	 * @cfg {String} fieldLabel The label which must be applied to template
	 * as a prefix to the list of attachments.
	 */
	emptyText:_('Select one…'),

	/**
	 * @cfg {String} userStringSeparator The separator which is used to separate list
	 * of user while displaying them as string.
	 */
	userStringSeparator: _('and'),

	/**
	 * @cfg {Zarafa.core.data.IPMRecipientStore} store The store in which
	 * the recipients are stored
	 */
	store: undefined,

	/**
	 * The Action type which is handled by this view
	 * This is set during {@link #setAction}.
	 * @property
	 * @type Zarafa.common.rules.data.ActionFlags
	 */
	actionFlag: undefined,

	/**
	 * The action property which was configured during
	 * {@link #setAction}.
	 * @property
	 * @type Object
	 */
	action: undefined,

	/**
	 * The Condition type which is handled by this view
	 * This is set during {@link #setCondition}.
	 * @property
	 * @type Zarafa.common.rules.data.ConditionFlags
	 */
	conditionFlag: undefined,

	/**
	 * The condition property which was configured during
	 * {@link #setCondition}.
	 * @property
	 * @type Object
	 */
	condition: undefined,

	/**
	 * True if the action/condition was modified by the user, if this is false,
	 * then {@link #getCondition} will return {@link #condition} instead
	 * of returning a new object and {@link #getAction} will return {@link #action}
	 * instead of returning a new Object.
	 * @property
	 * @type Boolean
	 */
	isModified: false,

	/**
	 * True if the action/condition is complete and valid,
	 * False will denote that action/condition is invalid or incomplete
	 * if this is true, then {@link #getCondition} will return {@link #condition} instead
	 * of returning a new object and {@link #getAction} will return {@link #action}
	 * instead of returning a new Object.
	 * @property
	 * @type Boolean
	 */
	isValid: true,

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

		Ext.applyIf(config,{
			xtype: 'zarafa.userselectionlink',
			border: false,
			autoScroll:true,
			anchor: '100%',
			multiSelect: false,
			store: new Zarafa.core.data.IPMRecipientStore(),
			tpl: new Ext.XTemplate(
				'<div class="zarafa-user-link">' +
					'<tpl for="list">' +
						'<tpl if="!Ext.isEmpty(values.display_name)">' +
							'&quot;{display_name:htmlEncode}&quot;' +
						'</tpl>' +
						'<tpl if="Ext.isEmpty(values.display_name) && !Ext.isEmpty(values.smtp_address)">' +
							'&quot;{smtp_address:htmlEncode}&quot;' +
						'</tpl>' +
						'<tpl if="xcount &gt; 0 && xindex != xcount">' +
							'<span>&nbsp;{parent.separator}&nbsp;</span>' +
						'</tpl>' +
					'</tpl>' +
				'</div>',
				{
					compiled: true
				}
			)
		});

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

	/**
	 * This function is called after the component has been rendered.
	 * This will register the {@link #onActivate} and {@link #onClick} event handlers.
	 * @private
	 */
	afterRender: function()
	{
		Zarafa.common.rules.dialogs.UserSelectionLink.superclass.initComponent.apply(this, arguments);

		this.mon(this.getActionEl(), 'click', this.onClick, this);
		this.mon(this.store, 'update', this.onRecipientUpdate, this);
		this.mon(this.store, 'add', this.onRecipientAdd, this);
		this.mon(this.store, 'resolved', this.onRecipientAdd, this);
	},

	/**
	 * Handler will be called when recipient in {@link Zarafa.core.data.IPMRecipientStore IPMRecipientStore}.
	 * It could happen that recipient is updated after we have closed addressbook dialog (mainly in case of resolving)
	 * so we need to update the ui also
	 * @param {Zarafa.core.data.IPMRecipientStore} store The store which fired the event
	 * @param {Zarafa.core.data.IPMRecipientRecord} record The record which was updated
	 * @param {String} operation The update operation being performed.
	 * @private
	 */
	onRecipientUpdate: function(store, records, operation)
	{
		if (operation !== Ext.data.Record.COMMIT) {
			// update ui, after store is updated
			this.update(this.store);
		}
	},

	/**
	 * Handler will be called when recipient is added to {@link Zarafa.core.data.IPMRecipientStore IPMRecipientStore}.
	 * @param {Zarafa.core.data.IPMRecipientStore} store The store which fired the event
	 * @param {Zarafa.core.data.IPMRecipientRecord} records The records which have been added
	 * @private
	 */
	onRecipientAdd: function(store, records)
	{
		var record = records[0];
		// Only force expanding the distlist when it's a local distlist.
		if (record.get('object_type') === Zarafa.core.mapi.ObjectType.MAPI_DISTLIST &&
			record.get('address_type') === 'MAPIPDL') {
			Ext.MessageBox.show({
				title: _('Expand distribution list'),
				msg: _('Distribution lists are not supported in rules, would you like to replace the distribution list with its members?'),
				buttons: Ext.MessageBox.YESNO,
				fn: this.onExpandDistList.createDelegate(this, [record], 1),
				scope: this
			});
		}
	},

	/**
	 * Handler for messagebox which asks the user to expand the distribution list of remove it.
	 * @param {String} btn string containing either 'yes' or 'no
	 * @param {Zarafa.core.data.IPMRecipientRecord} recip The records which should been expanded
	 */
	onExpandDistList: function(btn, recip)
	{
		if(btn === 'yes') {
			this.store.expand(recip, true);
		}
		this.store.remove(recip);
	},

	/**
	 * Called when user clicks on a {@link Zarafa.common.rules.dialogs.UserSelectionLink}
	 * It opens user selection dialog dialog
	 * @param {Ext.DataView} dataView Reference to this object
	 * @param {Number} index The index of the target node
	 * @param {HTMLElement} node The target node
	 * @param {Ext.EventObject} evt The mouse event
	 * @protected
	 */
	onClick: function(dataView, index, node, evt)
	{
		var hideGroups = [];
		if(this.conditionFlag === Zarafa.common.rules.data.ConditionFlags.RECEIVED_FROM) {
			hideGroups.push('distribution_list');
		}

		var label;
		if (this.conditionFlag === Zarafa.common.rules.data.ConditionFlags.RECEIVED_FROM) {
			label = _('From');
		} else {
			label = _('To');
		}

		// Open addressbook dialog for selection of users
		Zarafa.common.Actions.openABUserMultiSelectionContent({
			listRestriction: {
				hide_groups: hideGroups
			},
			callback: function(record) {
				this.isModified = true;
				this.update(this.store);
			},
			convert: function(r) {
				return r.convertToRecipient();
			},
			scope: this,
			store: this.store,
			modal: true,
			selectionCfg: [{
				xtype: 'zarafa.recipientfield',
				fieldLabel: label + ':',
				height: 50,
				boxStore: this.store,
				flex: 1
			}]
		});
	},

	/**
	 * Apply an action onto the DataView, this will parse the condition and show
	 * the contents in a user-friendly way to the user.
	 * @param {Zarafa.common.rules.data.ConditionFlags} conditionFlag The condition type
	 * which identifies the exact type of the condition.
	 * @param {Object} condition The condition to apply
	 */
	setCondition: function(conditionFlag, condition)
	{
		this.store.removeAll();
		this.isValid = false;
		if (condition) {
			var Restrictions = Zarafa.core.mapi.Restrictions;
			var subs;

			switch (conditionFlag) {
				case Zarafa.common.rules.data.ConditionFlags.SENT_TO:

					// Check if a RES_OR restriction was provided, if
					// so we need to loop over all recipients from the list.
					if (condition[0] === Restrictions.RES_OR) {
						subs = condition[1];
					} else {
						subs = [ condition ];
					}

					for (var i = 0, len = subs.length; i < len; i++) {
						var value = subs[i][1][Restrictions.RESTRICTION];

						// Do not add empty recipient, it is possible that restriction might not have recipient.
						if(value[1] && value[1][Restrictions.PROPS] && value[1][Restrictions.PROPS]['0x0001001E']) {
							var recipient = this.store.parseRecipient(value[1][Restrictions.PROPS]['0x0001001E']);
							recipient.set('display_type', value[1][Restrictions.PROPS]['PR_DISPLAY_TYPE']);
							recipient.set('search_key', value[1][Restrictions.RESTRICTION][1][Restrictions.VALUE]['0x00010102']);

							this.store.add(recipient);
							this.isValid = true;
						}
					}
					break;
				case Zarafa.common.rules.data.ConditionFlags.RECEIVED_FROM:

					// Check if a RES_OR restriction was provided, if
					// so we need to loop over all recipients from the list.
					if (condition[0] === Restrictions.RES_OR) {
						subs = condition[1];
					} else {
						subs = [ condition ];
					}

					for (var i = 0, len = subs.length; i < len; i++) {
						var value = subs[i][1];

						// Do not add empty recipient, it is possible that restriction might not have recipient.
						if(value[Restrictions.PROPS] && value[Restrictions.PROPS]['0x0001001E']) {
							var recipient = this.store.parseRecipient(value[Restrictions.PROPS]['0x0001001E']);
							recipient.set('display_type', value[Restrictions.PROPS]['PR_DISPLAY_TYPE']);
							recipient.set('search_key', value[Restrictions.RESTRICTION][1][Restrictions.VALUE]['0x00010102']);

							this.store.add(recipient);
							this.isValid = true;
						}
					}
					break;
				default:
					break;
			}
		}

		this.conditionFlag = conditionFlag;
		this.condition = condition;
		this.userStringSeparator = _('or');
		this.isModified = !Ext.isDefined(condition);
		this.update(this.store);
	},

	/**
	 * Obtain the condition as configured by the user
	 * @return {Object} The condition
	 */
	getCondition: function()
	{
		if (this.isModified !== true && this.isValid === true) {
			return this.condition;
		}

		// No recipients selected, this means
		// we can't create a condition.
		if (this.store.getCount() === 0) {
			return false;
		}

		var conditionFactory = container.getRulesFactoryByType(Zarafa.common.data.RulesFactoryType.CONDITION);
		var conditionDefinition = conditionFactory.getConditionById(this.conditionFlag);
		return conditionDefinition({store: this.store});
	},

	/**
	 * Apply an action onto the DataView, this will parse the action and show
	 * the contents in a user-friendly way to the user.
	 * @param {Zarafa.common.rules.data.ActionFlags} actionFlag The action type
	 * which identifies the exact type of the action.
	 * @param {Object} action The action to apply
	 */
	setAction: function(actionFlag, action)
	{
		this.store.removeAll();
		this.isValid = false;
		if (action) {
			var RecordFactory = Zarafa.core.data.RecordFactory;
			var CustomObjectType = Zarafa.core.data.RecordCustomObjectType;

			for (var i = 0, len = action.adrlist.length; i < len; i++) {
				var address = action.adrlist[i];
				this.isValid = true;

				var recipient = RecordFactory.createRecordObjectByCustomType(CustomObjectType.ZARAFA_RECIPIENT, {
					'entryid': address['PR_ENTRYID'],
					'object_type': address['PR_OBJECT_TYPE'],
					'display_name': address['PR_DISPLAY_NAME'],
					'display_type': address['PR_DISPLAY_TYPE'],
					'email_address': address['PR_EMAIL_ADDRESS'],
					'smtp_address': address['PR_SMTP_ADDRESS'],
					'address_type': address['PR_ADDRTYPE'],
					'recipient_type': address['PR_RECIPIENT_TYPE'],
					'search_key': address['PR_SEARCH_KEY']
				});
				this.store.add(recipient);
			}
		}

		this.actionFlag = actionFlag;
		this.action = action;
		this.userStringSeparator = _('and');
		this.isModified = !Ext.isDefined(action);
		this.update(this.store);
	},

	/**
	 * Obtain the action as configured by the user
	 * @return {Object} The action
	 */
	getAction: function()
	{
		if (this.isModified !== true && this.isValid === true) {
			return this.action;
		}

		var actionFactory = container.getRulesFactoryByType(Zarafa.common.data.RulesFactoryType.ACTION);
		var actionDefinition = actionFactory.getActionById(this.actionFlag);
		return actionDefinition({store: this.store});
	},

	/**
	 * Update the contents of this dataview, this will apply the {@link #tpl} for
	 * the {@link #store}.
	 * @param {Zarafa.core.data.IPMRecipientStore} store The store to show
	 */
	update: function(store)
	{
		var data = {
			separator: this.userStringSeparator,
			list: Ext.pluck(store.getRange(), 'data')
		};

		if (Ext.isEmpty(data.list)) {
			data.list = [{
				display_name: this.emptyText
			}];
		}

		Zarafa.common.rules.dialogs.UserSelectionLink.superclass.update.call(this, this.tpl.apply(data));
	}
});

Ext.reg('zarafa.userselectionlink', Zarafa.common.rules.dialogs.UserSelectionLink);
Ext.namespace('Zarafa.common.rules.dialogs');

/**
 * @class Zarafa.common.rules.dialogs.WordSelectionLink
 * @extends Ext.BoxComponent
 * @xtype zarafa.wordselectionlink
 */
Zarafa.common.rules.dialogs.WordSelectionLink = Ext.extend(Ext.BoxComponent, {
	/**
	 * @cfg {String} fieldLabel The label which must be applied to template
	 * as a prefix to the list of attachments.
	 */
	emptyText:_('Select one…'),

	/**
	 * @cfg {String} wordStringSeparator The separator which is used to separate list
	 * of words while displaying them as string.
	 */
	wordStringSeparator:_('or'),

	/**
	 * @cfg {Ext.data.Store} store The store in which
	 * the words are stored
	 */
	store: undefined,

	/**
	 * The Condition type which is handled by this view
	 * This is set during {@link #setCondition}.
	 * @property
	 * @type Zarafa.common.rules.data.ConditionFlags
	 */
	conditionFlag: undefined,

	/**
	 * The condition property which was configured during
	 * {@link #setCondition}.
	 * @property
	 * @type Object
	 */
	condition: undefined,

	/**
	 * True if the action/condition was modified by the user, if this is false,
	 * then {@link #getCondition} will return {@link #condition} instead
	 * of returning a new object and {@link #getAction} will return {@link #action}
	 * instead of returning a new Object.
	 * @property
	 * @type Boolean
	 */
	isModified: false,

	/**
	 * True if the action/condition is complete and valid,
	 * False will denote that action/condition is invalid or incomplete
	 * if this is true, then {@link #getCondition} will return {@link #condition} instead
	 * of returning a new object and {@link #getAction} will return {@link #action}
	 * instead of returning a new Object.
	 * @property
	 * @type Boolean
	 */
	isValid: true,

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

		Ext.applyIf(config,{
			xtype: 'zarafa.wordselectionlink',
			border: false,
			autoScroll:true,
			anchor: '100%',
			multiSelect: false,
			store: new Ext.data.Store({ fields: [ 'words' ] }),
			tpl: new Ext.XTemplate(
				'<div class="zarafa-word-link">' +
					'<tpl for=".">' +
						'<tpl if="!Ext.isEmpty(values.words)">' +
							'&quot;{words:htmlEncode}&quot;'+
						'</tpl>' +
						'<tpl if="xcount &gt; 0 && xindex != xcount">' +
							'<span>&nbsp;' + this.wordStringSeparator + '&nbsp;</span>' +
						'</tpl>' +
					'</tpl>' +
				'</div>',
				{
					compiled: true,
					wordStringSeparator: config.wordStringSeparator || this.wordStringSeparator
				}
			)
		});

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

	/**
	 * This function is called after the component has been rendered.
	 * This will register the {@link #onActivate} and {@link #onClick} event handlers.
	 * @private
	 */
	afterRender: function()
	{
		Zarafa.common.rules.dialogs.WordSelectionLink.superclass.initComponent.apply(this, arguments);

		this.mon(this.getActionEl(), 'click', this.onClick, this);
	},

	/**
	 * Called when user clicks on a {@link Zarafa.common.rules.dialogs.WordSelectionLink}
	 * It opens words edit dialog.
	 * @param {Ext.DataView} dataView Reference to this object
	 * @param {Number} index The index of the target node
	 * @param {HTMLElement} node The target node
	 * @param {Ext.EventObject} evt The mouse event
 	 * @protected
	 */
	onClick: function(dataView, index, node, evt)
	{
		var tmpStore = new Ext.data.Store({ fields: [ 'words' ] });
		tmpStore.add(this.store.getRange());

		// Open RulesWordsEditDialog
		Zarafa.common.Actions.openRulesWordsEditContent({
			store: tmpStore,
			callback: this.rulesWordsEditDialogCallback,
			scope: this,
			modal: true
		});
	},

	/**
	 * Callback function for {@link Zarafa.common.rules.dialogs.RulesWordsEditDialog}
	 * @param {Ext.data.Store} store the store which contains list of words
	 * @private
	 */
	rulesWordsEditDialogCallback: function(store)
	{
		// Remove old words and add words which we have got
		// from callback function of RulesWordsEditDialog.
		this.store.removeAll();
		this.store.add(store.getRange());
		this.isModified = true;
		this.update(this.store);
	},

	/**
	 * Apply an action onto the DataView, this will parse the condition and show
	 * the contents in a user-friendly way to the user.
	 * @param {Zarafa.common.rules.data.ConditionFlags} conditionFlag The condition type
	 * which identifies the exact type of the condition.
	 * @param {Object} condition The condition to apply
	 */
	setCondition: function(conditionFlag, condition)
	{
		this.store.removeAll();
		this.isValid = false;

		// Parse the condition and add words in the store.
		if (condition) {
			var Restrictions = Zarafa.core.mapi.Restrictions;
			switch (conditionFlag) {
				case Zarafa.common.rules.data.ConditionFlags.SENDER_WORDS:
				case Zarafa.common.rules.data.ConditionFlags.RECIPIENT_WORDS:
					var subs;

					// Check if a RES_OR restriction was provided, if
					// so we need to loop over all words from the list.
					if (condition[0] == Restrictions.RES_OR) {
						subs = condition[1];
					} else {
						subs = [ condition ];
					}

					for (var i = 0, len = subs.length; i < len; i++) {
						var value;
						var subResVal = subs[i][1][Restrictions.RESTRICTION];
						//if condition has sub Restriction (i.e restriction of Recepient's words rule )
						// then take value of restriction available in sub restriction.
						if (subResVal) {
							value = subResVal[1][Restrictions.VALUE];
						} else {
							value = subs[i][1][Restrictions.VALUE];
						}

						Ext.iterate(value, function(key, value) {
							if(!Ext.isEmpty(value)) {
								this.isValid = true;
								if (conditionFlag === Zarafa.common.rules.data.ConditionFlags.SENDER_WORDS) {
									value = Zarafa.core.Util.hexToString(value);
								}
								this.store.add(new Ext.data.Record({ words: value }));
							}
						}, this);
					}
					break;

				case Zarafa.common.rules.data.ConditionFlags.SUBJECT_WORDS:
				case Zarafa.common.rules.data.ConditionFlags.BODY_WORDS:
				/* falls through */
				default:
					var subs;

					// Check if a RES_OR restriction was provided, if
					// so we need to loop over all words from the list.
					if (condition[0] == Restrictions.RES_OR) {
						subs = condition[1];
					} else {
						subs = [ condition ];
					}

					for (var i = 0, len = subs.length; i < len; i++) {
						var value = subs[i][1][Restrictions.VALUE];
						Ext.iterate(value, function(key, value) {
							if(!Ext.isEmpty(value)) {
								this.isValid = true;
								this.store.add(new Ext.data.Record({ words: value }));
							}
						}, this);
					}
					break;
			}
		}

		this.conditionFlag = conditionFlag;
		this.condition = condition;
		this.isModified = !Ext.isDefined(condition);
		this.update(this.store);

	},

	/**
	 * Obtain the condition as configured by the user
	 * @return {Object} The condition
	 */
	getCondition: function()
	{
		if (this.isModified !== true && this.isValid === true) {
			return this.condition;
		}

		// No words are added, so we can't create a condition.
		if (this.store.getCount() === 0) {
			return false;
		}

		var conditionFactory = container.getRulesFactoryByType(Zarafa.common.data.RulesFactoryType.CONDITION);
		var conditionDefinition = conditionFactory.getConditionById(this.conditionFlag);
		return conditionDefinition({store: this.store});
	},

	/**
	 * Update the contents of this dataview, this will apply the {@link #tpl} for
	 * the {@link #store}.
	 * @param {Zarafa.core.data.IPMRecipientStore} store The store to show
	 */
	update: function(store)
	{
		var data = Ext.pluck(store.getRange(), 'data');
		if (Ext.isEmpty(data)) {
			data = [{
				words: this.emptyText
			}];
		}

		Zarafa.common.rules.dialogs.WordSelectionLink.superclass.update.call(this, this.tpl.apply(data));
	}
});

Ext.reg('zarafa.wordselectionlink', Zarafa.common.rules.dialogs.WordSelectionLink);
Ext.namespace('Zarafa.common.rules.ui');

/**
 * @class Zarafa.common.rules.ui.RulesPanel
 * @extends Ext.Container
 * @xtype zarafa.rulespanel
 * Will generate UI for the {@link Zarafa.common.settings.SettingsRuleWidget SettingsRuleWidget}.
 */
Zarafa.common.rules.ui.RulesPanel = Ext.extend(Ext.Container, {
	/**
	 * @cfg {Zarafa.common.rules.data.RulesStore} store store to use for loading rules
	 */
	store: undefined,

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

		if(Ext.isEmpty(config.store)) {
			config.store = new Zarafa.common.rules.data.RulesStore({
				'storeEntryId': container.getHierarchyStore().getDefaultStore().get('store_entryid')
			});
		}

		Ext.applyIf(config, {
			// Override from Ext.Component
			xtype: 'zarafa.rulespanel',
			border: false,
			layout: 'fit',
			items: this.createPanelItems(config)
		});

		Zarafa.common.rules.ui.RulesPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Creates an jsonstore for the combobox which contains the users of which store is fully opened
	 * and the Inbox 'folder permissions' are set.
	 * @return {Object} array which contains the jsonstore.
	 * @private
	 */
	createComboboxStore: function()
	{
		var hierarchyStore = container.getHierarchyStore();
		var myStoreEntryId = hierarchyStore.getDefaultStore().get('store_entryid');
		var data = [{name: _('myself'), value: myStoreEntryId }];
		var processedStores = [];

		hierarchyStore.getStores().forEach(function(store) {
			var subtree = store.getSubtreeFolder();
			var hasSufficientPermission = (subtree.get('rights') & Zarafa.core.mapi.Rights.RIGHTS_OWNER) === Zarafa.core.mapi.Rights.RIGHTS_OWNER;
			var storeEntryId = store.get('store_entryid');
			if (storeEntryId != myStoreEntryId && hasSufficientPermission && store.get('mailbox_owner_name')) {
				data = data.concat({ name: store.get('mailbox_owner_name'), value: storeEntryId });
				processedStores.push(Zarafa.core.Util.bin2hex(store.get('user_name')));
			}
		});

		return new Ext.data.JsonStore({
			fields: ['name', 'value'],
			data: data
		});
	},

	/**
	 * Function will create panel items for {@link Zarafa.common.rules.ui.RulesPanel RulesPanel}
	 * @param {Object} config config passed to the constructor
	 * @return {Array} array of items that should be added to panel.
	 * @private
	 */
	createPanelItems: function(config)
	{
		var items = [];
		// Only create the combolist when the setting is enabled in config.php
		if(container.getServerConfig().isSharedRulesEnabled()) {
			var comboStore = this.createComboboxStore();
			if (comboStore.getCount() > 1) {
	
				items.push({
					xtype: 'container',
					cls: 'k-store-picker',
					border: false,
					layout: 'form',
					labelWidth: '-', // Anything but a number to make sure Ext does not set a width
					items: {
						xtype: 'combo',
						mode: 'local',
						store: comboStore,
						fieldLabel: _('Update rules for'),
						triggerAction: 'all',
						displayField: 'name',
						valueField: 'value',
						lazyInit: false,
						forceSelection: true,
						value: comboStore.getAt(0).get('value'),
						editable: false,
						listeners: {
							beforeselect: this.onBeforeUserSelect,
							select: this.onUserSelect,
							scope: this
						}
					}
				});
			}
		}

		items.push({
			xtype: 'zarafa.rulesgrid',
			ref: '../rulesGrid',
			flex: 1,
			store: config.store
		});

		return [{
			xtype: 'container',
			flex: 1,
			layout: {
				type: 'vbox',
				align: 'stretch',
				pack: 'start'
			},
			items: items
		}];
	},

	/**
	 * Event handler for the beforeselect event, when the user selects a different
	 * user to update the mail filters settings.
	 *
	 * Checks if the model is dirty and shows the user a dialog if the user wants to
	 * save any changes.
	 *
	 * @param {Ext.form.ComboBox} field The combobox which was selected
	 * @param {Ext.Record} record The record that was selected
	 * @param {Number} index The index of the selected record
	 *
	 * @return {Mixed} False if there are pending changes. The selecting will then be
	 * handled by {#applyChanges}. Undefined otherwise.
	 */
	onBeforeUserSelect: function(field, nextRecord, index)
	{
		var context = container.getContextByName('settings');
		var model = context.getModel();
		if (model.hasChanges()) {
			Ext.MessageBox.show({
				title: _('Apply changes'),
				msg: _('Do you wish to apply the changes?'),
				fn: this.applyChanges.createDelegate(this, [ model, field, nextRecord ], 1),
				buttons: Ext.MessageBox.YESNOCANCEL
			});

			return false;
		}
	},

	/**
	 * Event handler for the select event, when the user selects a different
	 * user to update the mail filters settings.
	 *
	 * Loads the selected users store.
	 *
	 * @param {Ext.form.ComboBox} field The combobox which was selected
	 */
	onUserSelect: function(field)
	{
		this.loadUserStore(field.getValue());
	},

	/**
	 * Applies changes according to the answer from the user. And then switches
	 * the store to the user provided selection.
	 *
	 * @param {Ext.button} btn The messagebox button
	 * @param {Zarafa.settings.SettingsContextModel} model the settings model
	 * @param {Ext.form.ComboBox} field the user selection combobox.
	 */
	applyChanges: function(btn, model, field, record)
	{
		// The user cancels the switch to a different category
		if (btn === 'cancel') {
			return;
		}

		// Check if the user wishes to save or discard all changes
		if (btn === 'yes') {
			model.applyChanges();
		} else {
			model.discardChanges();
		}

		// Select the user in the dropdown
		field.setValue(record.get('value'));

		// Load the store of the selected user
		this.loadUserStore(field.getValue());
	},

	/**
	 * Loads the rules from the store with the given entryId
	 *
	 * @param {String} entryId the netryid of the store to be used for reading and writing the rules.
	 */
	loadUserStore: function(entryId)
	{
		this.store.storeEntryId = entryId;
		this.store.load();
	},

	/**
	 * Function will be used to reload data in the {@link Zarafa.common.rules.data.RulesStore RulesStore}.
	 */
	discardChanges: function()
	{
		this.store.load();
	},

	/**
	 * Function will be used to save changes in the {@link Zarafa.common.rules.data.RulesStore RulesStore}.
	 */
	saveChanges: function()
	{
		this.store.save();
	}
});

Ext.reg('zarafa.rulespanel', Zarafa.common.rules.ui.RulesPanel);
Ext.ns('Zarafa.common.searchfield.ui');

/**
 * @class Zarafa.common.searchfield.ui.SearchDropdownPanel
 * @extends Ext.Panel
 *
 * A dropdown panel shown below the search field on focus. Provides quick access
 * to recent search history, KQL filter fields and folder scope selection.
 */
Zarafa.common.searchfield.ui.SearchDropdownPanel = Ext.extend(Ext.Panel, {

	/**
	 * @cfg {Zarafa.common.searchfield.ui.SearchTextField} searchTextField
	 * The parent search text field that owns this dropdown.
	 */
	searchTextField: undefined,

	/**
	 * @cfg {Zarafa.common.searchfield.ui.SearchFolderCombo} searchFolderCombo
	 * The folder combo for folder scope selection.
	 */
	searchFolderCombo: undefined,

	/**
	 * The currently highlighted history row index (-1 = none).
	 * @property
	 * @type Number
	 */
	historyIndex: -1,

	/**
	 * The localStorage key for search history.
	 * @property
	 * @type String
	 */
	historyStorageKey: 'grommunio_search_history',

	/**
	 * Maximum number of history entries to keep.
	 * @property
	 * @type Number
	 */
	historyMax: 25,

	/**
	 * The available KQL filter definitions.
	 * @property
	 * @type Array
	 */
	filterFields: [
		{ key: 'subject', label: _('Subject') },
		{ key: 'from', label: _('From') },
		{ key: 'to', label: _('To') },
		{ key: 'cc', label: _('Cc') },
		{ key: 'bcc', label: _('Bcc') },
		{ key: 'body', label: _('Body') },
		{ key: 'attachment', label: _('Attachment') },
		{ key: 'category', label: _('Category') },
		{ key: 'unread', label: _('Unread'), value: 'true' }
	],

	/**
	 * Message type options for the dropdown.
	 * @property
	 * @type Array
	 */
	typeOptions: [
		{ value: 'mail', label: _('Mails') },
		{ value: 'appointment', label: _('Appointments') },
		{ value: 'contact', label: _('Contacts') },
		{ value: 'task', label: _('Tasks') },
		{ value: 'note', label: _('Notes') }
	],

	/**
	 * Date range options for the dropdown.
	 * @property
	 * @type Array
	 */
	dateOptions: [
		{ value: 'past_week', label: _('Past week') },
		{ value: 'past_two_weeks', label: _('Past 2 weeks') },
		{ value: 'past_month', label: _('Past month') },
		{ value: 'past_six_month', label: _('Past 6 month') },
		{ value: 'past_year', label: _('Past year') }
	],

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

		Ext.applyIf(config, {
			floating: true,
			shadow: false,
			border: false,
			cls: 'k-search-dropdown',
			renderTo: Ext.getBody(),
			hidden: true,
			autoHeight: true,
			width: 340
		});

		Zarafa.common.searchfield.ui.SearchDropdownPanel.superclass.constructor.call(this, config);
	},

	/**
	 * @override
	 */
	onRender: function(ct, position)
	{
		Zarafa.common.searchfield.ui.SearchDropdownPanel.superclass.onRender.call(this, ct, position);
		this.buildDropdownContent();
	},

	// =================================================================
	// Search history (localStorage)
	// =================================================================

	/**
	 * Loads search history from localStorage.
	 * Each entry: { query: String, tokens: Array, virtualTokens: Array, time: Number }
	 * @return {Array}
	 */
	getHistory: function()
	{
		try {
			var raw = window.localStorage.getItem(this.historyStorageKey);
			if (raw) {
				var entries = JSON.parse(raw);
				if (Ext.isArray(entries)) {
					return entries;
				}
			}
		} catch (e) {}
		return [];
	},

	/**
	 * Saves search history to localStorage.
	 * @param {Array} entries
	 * @private
	 */
	saveHistory: function(entries)
	{
		try {
			window.localStorage.setItem(this.historyStorageKey,
				JSON.stringify(entries.slice(0, this.historyMax)));
		} catch (e) {}
	},

	/**
	 * Adds a search to the history. Deduplicates by query string.
	 * @param {String} query The KQL query string
	 * @param {Array} tokens All token objects
	 * @param {Array} virtualTokens Virtual-only tokens
	 */
	addToHistory: function(query, tokens, virtualTokens)
	{
		if (!query && (!tokens || !tokens.length)) {
			return;
		}

		// Build a display string from all tokens for history rendering
		var display = this.buildDisplayString(tokens);
		if (!display) {
			return;
		}

		var entries = this.getHistory();

		// Remove existing entry with same display string (case-insensitive)
		var lowerDisplay = display.toLowerCase();
		entries = entries.filter(function(e) {
			return (e.display || e.query || '').toLowerCase() !== lowerDisplay;
		});

		// Add to front
		entries.unshift({
			query: query || '',
			display: display,
			tokens: tokens || [],
			virtualTokens: virtualTokens || [],
			time: Date.now()
		});

		// Cap at max
		this.saveHistory(entries);
	},

	/**
	 * Builds a human-readable display string from tokens.
	 * @param {Array} tokens
	 * @return {String}
	 * @private
	 */
	buildDisplayString: function(tokens)
	{
		if (!tokens || !tokens.length) {
			return '';
		}
		var tf = this.searchTextField;
		var parts = [];
		for (var i = 0; i < tokens.length; i++) {
			var t = tokens[i];
			if (t.type === 'filter') {
				var label = (tf && tf.filterLabels && tf.filterLabels[t.key]) || t.key;
				if (t.value) {
					var valLabels = tf && tf.valueLabels && tf.valueLabels[t.key];
					var valLabel = (valLabels && valLabels[t.value]) || t.value;
					parts.push(label + ': ' + valLabel);
				} else {
					parts.push(label);
				}
			} else if (t.type === 'operator') {
				parts.push(t.key);
			} else if (t.type === 'text') {
				parts.push(t.value);
			}
		}
		return parts.join(' ');
	},

	/**
	 * Removes a history entry by index.
	 * @param {Number} index
	 */
	removeHistoryEntry: function(index)
	{
		var entries = this.getHistory();
		if (index >= 0 && index < entries.length) {
			entries.splice(index, 1);
			this.saveHistory(entries);
			this.updateHistory();
		}
	},

	// =================================================================
	// Dropdown content building
	// =================================================================

	/**
	 * Builds the HTML content for the dropdown panel.
	 * @private
	 */
	buildDropdownContent: function()
	{
		var html = '<div class="k-search-dropdown-inner">';
		var enc = Ext.util.Format.htmlEncode;
		var i;

		// Recent searches section (populated dynamically)
		html += '<div class="k-search-dropdown-section k-search-history-section" id="' + this.getId() + '-history" style="display:none">';
		html += '<div class="k-search-dropdown-header">' + enc(_('Recent searches')) + '</div>';
		html += '<div class="k-search-history-list" role="listbox" aria-label="' + enc(_('Recent searches')) + '" id="' + this.getId() + '-history-list"></div>';
		html += '</div>';

		// Filter fields section
		html += '<div class="k-search-dropdown-section">';
		html += '<div class="k-search-dropdown-header">' + enc(_('Filter by')) + '</div>';
		html += '<div class="k-search-dropdown-filters">';
		for (i = 0; i < this.filterFields.length; i++) {
			var f = this.filterFields[i];
			html += '<span class="k-search-filter-option" role="option" tabindex="-1" data-filter-key="' + f.key + '"';
			if (f.value) {
				html += ' data-filter-value="' + enc(f.value) + '"';
			}
			html += '>';
			html += '<span class="k-search-filter-label">' + enc(f.label) + '</span>';
			html += '</span>';
		}
		html += '</div>';
		html += '</div>';

		// Message type + operators section (combined)
		html += '<div class="k-search-dropdown-section">';
		html += '<div class="k-search-dropdown-header">' + enc(_('Show')) + ' / ' + enc(_('Operators')) + '</div>';
		html += '<div class="k-search-dropdown-filters">';
		for (i = 0; i < this.typeOptions.length; i++) {
			var t = this.typeOptions[i];
			html += '<span class="k-search-filter-option k-search-type-option" role="option" tabindex="-1" data-filter-key="type" data-filter-value="' + t.value + '">';
			html += '<span class="k-search-filter-label">' + enc(t.label) + '</span>';
			html += '</span>';
		}
		// Inline separator + operators
		html += '<span class="k-search-dropdown-sep"></span>';
		html += '<span class="k-search-operator-option" role="option" tabindex="-1" data-operator="AND">' + enc(_('AND')) + '</span>';
		html += '<span class="k-search-operator-option" role="option" tabindex="-1" data-operator="OR">' + enc(_('OR')) + '</span>';
		html += '<span class="k-search-operator-option" role="option" tabindex="-1" data-operator="NOT">' + enc(_('NOT')) + '</span>';
		html += '</div>';
		html += '</div>';

		// Date range section
		html += '<div class="k-search-dropdown-section">';
		html += '<div class="k-search-dropdown-header">' + enc(_('Date')) + '</div>';
		html += '<div class="k-search-dropdown-filters">';
		for (i = 0; i < this.dateOptions.length; i++) {
			var d = this.dateOptions[i];
			html += '<span class="k-search-filter-option k-search-date-option" role="option" tabindex="-1" data-filter-key="date" data-filter-value="' + d.value + '">';
			html += '<span class="k-search-filter-label">' + enc(d.label) + '</span>';
			html += '</span>';
		}
		html += '</div>';
		html += '</div>';

		// Folder scope section
		html += '<div class="k-search-dropdown-section k-search-dropdown-folder-section">';
		html += '<div class="k-search-dropdown-header">' + enc(_('Search in')) + '</div>';
		html += '<div class="k-search-dropdown-folders" role="listbox" aria-label="' + enc(_('Search in')) + '" id="' + this.getId() + '-folders"></div>';
		html += '</div>';

		html += '</div>';

		this.body.update(html);

		// Bind click handlers
		this.body.on('click', this.onFilterClick, this, { delegate: '.k-search-filter-option' });
		this.body.on('click', this.onOperatorClick, this, { delegate: '.k-search-operator-option' });
		this.body.on('click', this.onFolderClick, this, { delegate: '.k-search-folder-option' });
		this.body.on('click', this.onHistoryClick, this, { delegate: '.k-search-history-row' });
		this.body.on('click', this.onHistoryRemoveClick, this, { delegate: '.k-search-history-remove' });
	},

	/**
	 * Updates the recent searches section in the dropdown.
	 */
	updateHistory: function()
	{
		var sectionEl = Ext.get(this.getId() + '-history');
		var listEl = Ext.get(this.getId() + '-history-list');
		if (!sectionEl || !listEl) {
			return;
		}

		var entries = this.getHistory();
		this.historyIndex = -1;

		if (!entries.length) {
			sectionEl.setDisplayed(false);
			return;
		}

		var enc = Ext.util.Format.htmlEncode;
		var html = '';
		for (var i = 0; i < entries.length; i++) {
			var entry = entries[i];
			var display = entry.display || entry.query || '';
			html += '<div class="k-search-history-row" role="option" tabindex="-1" data-history-index="' + i + '">';
			html += '<span class="k-search-history-icon" aria-hidden="true"></span>';
			html += '<span class="k-search-history-text">' + enc(display) + '</span>';
			html += '<span class="k-search-history-remove" role="button" tabindex="-1" aria-label="' + enc(_('Remove')) + '" data-history-index="' + i + '">&times;</span>';
			html += '</div>';
		}

		listEl.update(html);
		sectionEl.setDisplayed(true);
	},

	/**
	 * Moves the keyboard highlight in the history list.
	 * @param {Number} direction 1 for down, -1 for up
	 * @return {Boolean} true if a row was highlighted
	 */
	moveHistoryHighlight: function(direction)
	{
		var listEl = Ext.get(this.getId() + '-history-list');
		if (!listEl) {
			return false;
		}
		var rows = listEl.dom.querySelectorAll('.k-search-history-row');
		if (!rows.length) {
			return false;
		}

		// Clear current highlight
		if (this.historyIndex >= 0 && this.historyIndex < rows.length) {
			Ext.fly(rows[this.historyIndex]).removeClass('k-search-history-active');
		}

		// Compute new index
		var newIndex = this.historyIndex + direction;
		if (newIndex < -1) {
			newIndex = rows.length - 1; // wrap to bottom
		} else if (newIndex >= rows.length) {
			newIndex = -1; // wrap past top = deselect
		}

		this.historyIndex = newIndex;

		if (this.historyIndex >= 0 && this.historyIndex < rows.length) {
			var row = rows[this.historyIndex];
			Ext.fly(row).addClass('k-search-history-active');
			// Scroll into view if needed
			row.scrollIntoView({ block: 'nearest' });
			return true;
		}
		return false;
	},

	/**
	 * Returns the currently highlighted history entry, or null.
	 * @return {Object|null}
	 */
	getHighlightedEntry: function()
	{
		if (this.historyIndex < 0) {
			return null;
		}
		var entries = this.getHistory();
		return entries[this.historyIndex] || null;
	},

	/**
	 * Resets the history highlight to none.
	 */
	resetHistoryHighlight: function()
	{
		if (this.historyIndex >= 0) {
			var listEl = Ext.get(this.getId() + '-history-list');
			if (listEl) {
				var rows = listEl.dom.querySelectorAll('.k-search-history-row');
				if (this.historyIndex < rows.length) {
					Ext.fly(rows[this.historyIndex]).removeClass('k-search-history-active');
				}
			}
		}
		this.historyIndex = -1;
	},

	/**
	 * Handler for clicking a history row.
	 * @param {Ext.EventObject} e
	 * @param {HTMLElement} target
	 * @private
	 */
	onHistoryClick: function(e, target)
	{
		// Don't trigger if the remove button was clicked
		if (Ext.fly(e.getTarget()).hasClass('k-search-history-remove')) {
			return;
		}
		var el = Ext.fly(target);
		if (!el.hasClass('k-search-history-row')) {
			el = el.findParent('.k-search-history-row', 3, true);
		}
		if (!el) {
			return;
		}
		var idx = parseInt(el.getAttribute('data-history-index'), 10);
		var entries = this.getHistory();
		if (idx >= 0 && idx < entries.length) {
			this.applyHistoryEntry(entries[idx]);
		}
	},

	/**
	 * Handler for clicking the remove button on a history row.
	 * @param {Ext.EventObject} e
	 * @param {HTMLElement} target
	 * @private
	 */
	onHistoryRemoveClick: function(e, target)
	{
		e.stopEvent();
		var el = Ext.fly(target);
		if (!el.hasClass('k-search-history-remove')) {
			return;
		}
		var idx = parseInt(el.getAttribute('data-history-index'), 10);
		this.removeHistoryEntry(idx);
	},

	/**
	 * Applies a history entry to the search field and triggers search.
	 * @param {Object} entry
	 */
	applyHistoryEntry: function(entry)
	{
		if (!entry || !this.searchTextField) {
			return;
		}

		var tf = this.searchTextField;

		// Restore tokens directly
		tf.tokens = [];
		if (entry.tokens && entry.tokens.length) {
			for (var i = 0; i < entry.tokens.length; i++) {
				tf.tokens.push({
					type: entry.tokens[i].type,
					key: entry.tokens[i].key || '',
					value: entry.tokens[i].value || ''
				});
			}
		}

		// Clear the tail input
		if (tf.tailInputEl) {
			tf.tailInputEl.dom.value = '';
		}

		tf.renderTokens();
		tf.syncHiddenInput();
		tf.fireEvent('chipchange', tf, tf.getFilterChips());

		this.hide();

		// Trigger search
		tf.onTriggerClick();
	},

	/**
	 * Updates the folder list in the dropdown based on the SearchFolderCombo store.
	 */
	updateFolders: function()
	{
		var foldersEl = Ext.get(this.getId() + '-folders');
		if (!foldersEl || !this.searchFolderCombo) {
			return;
		}

		var store = this.searchFolderCombo.getStore();
		var currentValue = this.searchFolderCombo.getValue();
		var html = '';

		store.each(function(record) {
			var value = record.get('value');
			var name = Ext.util.Format.htmlEncode(record.get('name'));
			var isActive = (value === currentValue) ? ' k-active' : '';

			if (value === 'other') {
				html += '<div class="k-search-folder-option k-search-folder-other' + isActive + '" role="option" tabindex="-1"' + (isActive ? ' aria-selected="true"' : '') + ' data-folder-value="other">';
				html += '<span class="k-search-folder-icon icon_folder_browse" aria-hidden="true"></span>';
				html += '<span>' + name + '</span>';
				html += '</div>';
			} else {
				html += '<div class="k-search-folder-option' + isActive + '" role="option" tabindex="-1"' + (isActive ? ' aria-selected="true"' : '') + ' data-folder-value="' + Ext.util.Format.htmlEncode(value) + '">';
				html += '<span class="k-search-folder-icon icon_folder_small" aria-hidden="true"></span>';
				html += '<span>' + name + '</span>';
				if (isActive) {
					html += '<span class="k-search-folder-check" aria-hidden="true">&#10003;</span>';
				}
				html += '</div>';
			}
		}, this);

		foldersEl.update(html);
	},

	/**
	 * Handler for clicking a filter option in the dropdown.
	 * @param {Ext.EventObject} e The event
	 * @param {HTMLElement} target The target element
	 * @private
	 */
	onFilterClick: function(e, target)
	{
		var el = Ext.fly(target);
		if (!el.hasClass('k-search-filter-option')) {
			el = el.findParent('.k-search-filter-option', 3, true);
		}
		if (el) {
			var filterKey = el.getAttribute('data-filter-key');
			var filterValue = el.getAttribute('data-filter-value');
			if (filterKey) {
				this.searchTextField.insertFilter(filterKey, filterValue || '');
			}
		}
	},

	/**
	 * Handler for clicking an operator option (AND/OR) in the dropdown.
	 * @param {Ext.EventObject} e The event
	 * @param {HTMLElement} target The target element
	 * @private
	 */
	onOperatorClick: function(e, target)
	{
		var el = Ext.fly(target);
		if (!el.hasClass('k-search-operator-option')) {
			el = el.findParent('.k-search-operator-option', 3, true);
		}
		if (el) {
			var operator = el.getAttribute('data-operator');
			if (operator) {
				this.searchTextField.insertOperator(operator);
			}
		}
	},

	/**
	 * Handler for clicking a folder option in the dropdown.
	 * @param {Ext.EventObject} e The event
	 * @param {HTMLElement} target The target element
	 * @private
	 */
	onFolderClick: function(e, target)
	{
		var el = Ext.fly(target);
		if (!el.hasClass('k-search-folder-option')) {
			el = el.findParent('.k-search-folder-option', 3, true);
		}
		if (!el) {
			return;
		}

		var folderValue = el.getAttribute('data-folder-value');
		if (!folderValue) {
			return;
		}

		if (folderValue === 'other') {
			this.hide();
			Zarafa.advancesearch.Actions.openSelectSearchFolderDialog({
				searchFolderCombo: this.searchFolderCombo,
				model: this.searchFolderCombo.model
			});
		} else {
			var store = this.searchFolderCombo.getStore();
			var recordIndex = store.find('value', folderValue);
			if (recordIndex !== -1) {
				var record = store.getAt(recordIndex);
				this.searchFolderCombo.setValue(folderValue);
				this.searchFolderCombo.fireEvent('select', this.searchFolderCombo, record, recordIndex);
			}
			this.updateFolders();
		}

		this.searchTextField.focus();
	},

	/**
	 * Shows the dropdown aligned below the search field.
	 * @param {Ext.Element} alignEl The element to align below
	 */
	showBelow: function(alignEl)
	{
		if (!this.rendered) {
			this.render(Ext.getBody());
		}

		this.alignEl = alignEl;
		this.updateHistory();
		this.updateFolders();
		this.syncSize();

		this.show();
		this.el.alignTo(alignEl, 'tl-bl', [-4, 1]);
		this.el.setStyle('z-index', '20000');

		if (!this.resizeBound) {
			this.resizeBound = true;
			if (window.ResizeObserver) {
				this.resizeObserver = new ResizeObserver(this.onAlignElResize.createDelegate(this));
				this.resizeObserver.observe(alignEl.dom);
			} else {
				Ext.EventManager.onWindowResize(this.onAlignElResize, this);
			}
		}
	},

	/**
	 * Syncs dropdown width with the alignment element.
	 * @private
	 */
	syncSize: function()
	{
		if (!this.alignEl) {
			return;
		}
		var width = this.alignEl.getWidth() + 3;
		if (width > 280) {
			this.setWidth(width);
		}
	},

	/**
	 * Handler called when the alignment element or window resizes.
	 * Re-syncs dropdown width and position.
	 * @private
	 */
	onAlignElResize: function()
	{
		if (!this.isVisible() || !this.alignEl) {
			return;
		}
		this.syncSize();
		this.el.alignTo(this.alignEl, 'tl-bl', [-4, 1]);
	},

	/**
	 * @override
	 */
	beforeDestroy: function()
	{
		if (this.resizeObserver) {
			this.resizeObserver.disconnect();
			this.resizeObserver = null;
		}
		Zarafa.common.searchfield.ui.SearchDropdownPanel.superclass.beforeDestroy.call(this);
	}
});
Ext.ns('Zarafa.common.searchfield.ui');

/**
 * @class Zarafa.common.searchfield.ui.SearchFieldContainer
 * @extends Ext.Container
 * @xtype zarafa.searchfieldcontainer
 *
 * Container for the modernized search field. Holds the
 * {@link Zarafa.common.searchfield.ui.SearchTextField SearchTextField} and a hidden
 * {@link Zarafa.common.searchfield.ui.SearchFolderCombo SearchFolderCombo}
 * (used internally for folder scope management).
 */
Zarafa.common.searchfield.ui.SearchFieldContainer = Ext.extend(Ext.Container, {

	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		Ext.apply(config, {
			xtype: 'zarafa.searchfieldcontainer',
			cls: 'k-search-container',
			items:[{
				xtype: 'zarafa.searchtextfield',
				searchContainer: this
			},{
				xtype: 'zarafa.searchfoldercombo',
				model: config.model,
				searchFieldContainer: this,
				hidden: true,
				width: 0
			}]
		});

		Zarafa.common.searchfield.ui.SearchFieldContainer.superclass.constructor.call(this, config);
	}
});
Ext.reg('zarafa.searchfieldcontainer', Zarafa.common.searchfield.ui.SearchFieldContainer);
Ext.ns('Zarafa.common.searchfield.ui');

/**
 * @class Zarafa.common.searchfield.ui.SearchFolderCombo
 * @extends Ext.form.ComboBox
 * @xtype zarafa.searchfoldercombo
 *
 * This class can be used to construct a search folder combo box which
 * manly contains 'All folders', 'Current folder', 'Imported folder' and 'Other…' options.
 * which represent IPM_SUBTREE, currently selected folder in hierarchy, folder which imported
 * in search folder combo box using {@link Zarafa.advancesearch.dialogs.SelectFolderContentPanel SelectFolderContentPanel}
 * respectively. while 'Other…' option used to open the {@link Zarafa.advancesearch.dialogs.SelectFolderContentPanel SelectFolderContentPanel}
 */
Zarafa.common.searchfield.ui.SearchFolderCombo = Ext.extend(Ext.form.ComboBox, {

	/**
	 * The {@link Zarafa.core.ContextModel} which is obtained from the {@link #context}.
	 * @property
	 * @type Zarafa.core.ContextModel
	 */
	model: undefined,

	/**
	 * @cfg {Number} minListWidth The minimum width of the dropdown list in pixels (defaults to <tt>70</tt>, will
	 * be ignored if <tt>{@link #listWidth}</tt> has a higher value)
	 */
	minListWidth: 160,

	/**
	 * @cfg {Number} ellipsisStringStartLength maximum length of text allowed before truncations,
	 * truncation will be replaced with ellipsis ('...').
	 */
	ellipsisStringStartLength: 15,

	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		if (!this.tpl) {
			var tplString =
				'<tpl for=".">' +
					'<tpl if="Ext.isEmpty(values.flag)">' +
						'<div class="k-search-combobox-menu-sep-parent">'+
							'<span class="x-menu-sep k-search-combobox-menu-sep"></span>' +
						'</div>'+
					'</tpl>' +
					'<div class="x-combo-list-item" ext:qtip="{[this.getQtip(values)]}" ext:qwidth="100%">' +
						'<tpl if="this.isOtherStoreFolder(values.value)">' +
							'<span>{name:htmlEncodeElide(' + this.ellipsisStringStartLength + ', 0, true)}</span>' +
								'<span class="k-search-node-owner">' +
									' - {this.ownerName:htmlEncodeElide(' + this.ellipsisStringStartLength + ', 0, true)}' +
								'</span>' +
						'</tpl>' +
						'<tpl if="!this.isOtherStoreFolder(values.value)">' +
							'<span>{name:htmlEncodeElide(' + this.ellipsisStringStartLength + ', 0, true)}</span>' +
						'</tpl>' +
					'</div>' +
				'</tpl>';
			this.tpl = new Ext.XTemplate(tplString, {
				ownerName: '',
				compiled: true,
				ellipsisStringLength: this.ellipsisStringStartLength,
				isOtherStoreFolder: function (entryid) {
					var hierarchyStore = container.getHierarchyStore();
					var folder = hierarchyStore.getFolder(entryid);
					if (folder) {
						var store = hierarchyStore.getById(folder.get('store_entryid'));
						if (store.isSharedStore()) {
							this.ownerName = store.get('mailbox_owner_name');
							return true;
						}
					}
					return false;
				},
				getQtip: function (values) {
					var folderNameQtip, ownerNameQtip;
					if (values.name.length > this.ellipsisStringLength) {
						folderNameQtip = values.name;
					}
					if (this.isOtherStoreFolder(values.value)) {
						if (this.ownerName.length > this.ellipsisStringLength) {
							ownerNameQtip = this.ownerName;
						}
					}
					if (folderNameQtip || ownerNameQtip) {
						if (folderNameQtip && ownerNameQtip) {
							return folderNameQtip + " - " + ownerNameQtip;
						} else {
							return folderNameQtip ? folderNameQtip : ownerNameQtip;
						}
					} else {
						return '';
					}
				}
			});
		}

		var searchFolderStore = new Ext.data.JsonStore({
			idIndex: 0,
			idProperty: 'value',
			fields: ['name', 'value', 'flag', 'include_subfolder'],
			data: [],
			autoDestroy: true
		});

		Ext.apply(config, {
			xtype: 'zarafa.searchfoldercombo',
			mode: 'local',
			ref: 'searchFolderCombo',
			valueField: 'value',
			displayField: 'name',
			store: searchFolderStore,
			triggerAction: 'all',
			editable: false,
			width: 200,
			listWidth: 250,
			listeners: {
				expand: this.onExpandComboBox,
				beforeRender: this.onBeforeRender,
				scope: this
			}
		});

		Zarafa.common.searchfield.ui.SearchFolderCombo.superclass.constructor.call(this, config);
	},

	/**
	 * Event handler which is raised just before the {@link Zarafa.common.searchfield.ui.SearchFolderCombo SearchFolderCombo}
	 * is being rendered. At this moment data for store will be prepared based on default selected folder
	 * and {@link Ext.data.JsonStore searchFolderStore} will be loaded. Also default combobox value will be set.
	 * @private
	 */
	onBeforeRender: function()
	{
		var index = 0;
		var defaultFolder = this.model.getDefaultFolder();
		if (defaultFolder.getDefaultFolderKey() !== 'inbox' && defaultFolder.getDefaultFolderKey() !== 'publicfolders') {
			index = 1;
    }

		var data = [{
			'name': _('All folders'),
			'value': defaultFolder.getMAPIStore().get("subtree_entryid"),
			'include_subfolder': true,
			'flag': Zarafa.advancesearch.data.SearchComboBoxFieldsFlags.ALL_FOLDERS
		}, {
			'name': Ext.util.Format.htmlEncode(defaultFolder.getDisplayName()),
			'value': defaultFolder.get('entryid'),
			'include_subfolder': false,
			'flag': Zarafa.advancesearch.data.SearchComboBoxFieldsFlags.CURRENT_SELECTED_FOLDER
		}, {
			'name': _('Other…'),
			'value': 'other'
		}];

		this.store.loadData(data);
		this.setValue(this.store.getAt(index).get('value'));
	},

	/**
	 * initialize events for the grid panel
	 * @private
	 */
	initEvents: function()
	{
		Zarafa.common.searchfield.ui.SearchFolderCombo.superclass.initEvents.apply(this, arguments);

		this.on('beforeselect', this.onBeforeSelectSearchFolder, this);
    this.on('select', this.onSelectSearchFolder, this);
		this.mon(this.model, 'folderchange', this.onFolderChange, this);
		this.mon(this.model, 'activate', this.onActiveFolder, this);
		this.mon(container, 'aftercontextswitch', this.onAfterContextSwitch, this);
	},

	/**
	 * Event handler triggered before selection performs in search folder combo
	 * Will open {@link Zarafa.advancesearch.dialogs.SelectFolderContentPanel SelectFolderContentPanel}, if
	 * "Other…" option was selected.
	 *
	 * @param {Zarafa.common.searchfield.ui.SearchFolderCombo} combo The combo which fired the event.
	 * @param {Ext.data.Record} record The data record returned from the underlying store
	 * @param {number} index The index of the selected item in the dropdown list
	 * @return {boolean} true if selected record is not 'Other…' else false.
	 */
	onBeforeSelectSearchFolder: function (combo, record, index)
	{
		if(record.get('value') === 'other') {
			combo.collapse();
			Zarafa.advancesearch.Actions.openSelectSearchFolderDialog({
				searchFolderCombo: combo,
				model: this.model
			});
			return false;
		} else if (!container.getHierarchyStore().getFolder(combo.getValue())) {
			// Check if current selected folder is available in hierarchy tree,
			// If not then remove that folder from search combo box.
			var folderRecord = this.findRecord('value', combo.getValue());
			this.getStore().remove(folderRecord);
		}

		return true;
	},

	/**
	 * Event handler triggered when {@link Zarafa.calendar.ui.CalendarMultiView CalendarMultiView}
	 * was activated and user select the calendar by using clicking on tab of calendar.
	 * it was used to set the selected calendar folder as current folder in search folder combo box.
	 *
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder which will be current folder in search folder combo box.
	 */
	onActiveFolder: function(folder)
	{
		this.setFolder(folder, true);
	},

	/**
	 * Event handler which is triggered when the folder is changed.
	 *
	 * @param {Zarafa.core.ContextModel} model The model which fired the event.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord[]} folders selected folders as an array of
	 * {@link Zarafa.hierarchy.data.MAPIFolderRecord MAPIFolder} objects.
	 * @private
	 */
	onFolderChange: function (model, folders)
	{
		var folder = model.getDefaultFolder();
		this.setFolder(folder, true);
	},

	/**
	 * Event handler triggers when context was switch successfully.
	 *
	 * @param {Object} folders contains folder details
	 * @param {Context} oldContext previously selected context
	 * @param {Context} context selected context
	 */
	onAfterContextSwitch: function (folders, oldContext, context)
	{
		var folder = context.getModel().getDefaultFolder();
		this.setFolder(folder, false);
	},

	/**
	 * Function is used to set the folder in {@link Zarafa.common.searchfield.ui.SearchFolderCombo SearchFolderCombo}
	 * according to folder type.
	 *
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder folder that should be shown by the selected context.
	 * @param {Boolean} changeCurrentFolder true to change the current folder in search folder combo box.
	 */
	setFolder: function(folder, changeCurrentFolder)
	{
		// Don't update search folder combo box which belongs to search panel toolbar or we
		// not found folder.
		if (this.findParentByType("zarafa.searchpaneltoolbar") || !Ext.isDefined(folder)) {
			return;
		} else if (Ext.isArray(folder)) {
			folder = folder[0];
		}

		var store = this.getStore();
		if (changeCurrentFolder) {
			this.doChangeCurrentFolder(store, folder);
		}

		// Check if closed or deleted folder is available in hierarchy tree,
		// If yes then remove that folder from search combo box.
		this.getStore().each(function (record) {
			var value = record.get('value');
			if (value !== 'other' && !container.getHierarchyStore().getFolder(value)) {
				this.getStore().remove(record);
				return false;
			}
		}, this);

		// Select 'All folders' if select folder is 'Inbox' folder of shared/own store or 'Public Folders' of public store.
		if (folder.getDefaultFolderKey() === 'inbox' || folder.getDefaultFolderKey() === 'publicfolders') {
			var allFolderRecord = store.getAt(store.find('flag', Zarafa.advancesearch.data.SearchComboBoxFieldsFlags.ALL_FOLDERS));
			this.setValue(allFolderRecord.get('value'));
		} else {
			this.setValue(folder.get('entryid'));
		}
	},

	/**
	 * Function is used to change the current folder record with selected folder and 'All folders' record
	 * with selected folder's IPM_SUBTREE from hierarchy in search folder combo.
	 *
	 * @param {Ext.data.JsonStore} store of search folder combo box.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder folder that should be shown by the selected context.
	 */
	doChangeCurrentFolder: function(store, folder)
	{
		var currentFolderRecord = store.getAt(store.find('flag', Zarafa.advancesearch.data.SearchComboBoxFieldsFlags.CURRENT_SELECTED_FOLDER));

		currentFolderRecord.beginEdit();
		currentFolderRecord.set("name", folder.getDisplayName());
		currentFolderRecord.set("value", folder.get('entryid'));
		currentFolderRecord.set("include_subfolder", false);
		currentFolderRecord.id = folder.get('entryid');
		currentFolderRecord.endEdit();
		currentFolderRecord.commit();

		var subTreeEntryid = folder.getMAPIStore().get("subtree_entryid");
		var allFolderRecord = store.getAt(store.find('flag', Zarafa.advancesearch.data.SearchComboBoxFieldsFlags.ALL_FOLDERS));

		if (!Zarafa.core.EntryId.compareEntryIds(allFolderRecord.get('value'), subTreeEntryid)) {
			allFolderRecord.set("value", subTreeEntryid);
			allFolderRecord.id = subTreeEntryid;
		}
	},

	/**
	 * Helper function which is use to resize the search combobox list according to the widest list item content
	 * @private
	 */
	onExpandComboBox: function ()
	{
		var store = this.store;
		var listWidth = 0;
		var textMetrics = Ext.util.TextMetrics.createInstance(this.view.el);

		// Get the width of the maximum text in list.
		store.each(function (record) {
			// Get the folder name and append the ellipsis if require
			var name = Ext.util.Format.htmlEncodeElide(record.get('name'), this.ellipsisStringStartLength, 0, true);
			var hierarchyStore = container.getHierarchyStore();
			var folder = hierarchyStore.getFolder(record.get('value'));
			if (folder) {
				var store = hierarchyStore.getById(folder.get('store_entryid'));
				// In case of shared folder, post-fix the owning store name.
				if (store.isSharedStore()) {
					name += " - " + Ext.util.Format.htmlEncodeElide(store.get('mailbox_owner_name'), this.ellipsisStringStartLength, 0, true);
				}
			}
			var curWidth = textMetrics.getWidth(name);
			if (curWidth > listWidth) {
				listWidth = curWidth;
			}
		}, this);
		if (listWidth > 0) {

			// Set maximum width in between minimum list width and text width,
			// Where as text width also have actual list width with padding.
			listWidth = Math.max(this.minListWidth, listWidth + 100);
			this.list.setWidth(listWidth);
			this.innerList.setWidth(listWidth - this.list.getFrameWidth('lr'));
		}
	},

  /**
	 * Event handler triggered when a search combo list item is selected.
	 * It will fire click event of search button to automatically triggers a search
	 * when user selects a different folder from the search-tab drop-down.
   *
   */
  onSelectSearchFolder: function ()
	{
    var searchTextField = this.searchFieldContainer.searchTextField;
    if (searchTextField.searchPanelRendered) {
      searchTextField.onTriggerClick();
    }
  }
});

Ext.reg('zarafa.searchfoldercombo', Zarafa.common.searchfield.ui.SearchFolderCombo);
Ext.namespace('Zarafa.common.sendas.ui');

/**
 * @class Zarafa.common.sendas.ui.SendAsGrid
 * @extends Ext.grid.GridPanel
 * @xtype zarafa.sendasgrid
 *
 * {@link Zarafa.common.sendas.ui.SendAsGrid SendAsGrid} will be used to display
 * sendas of the current user.
 */
Zarafa.common.sendas.ui.SendAsGrid = Ext.extend(Ext.grid.GridPanel, {
	/**
	 * @constructor
	 * @param {Object} config Configuration structure
	 */
	constructor: function(config)
	{
		config = config || {};

		var store = new Zarafa.core.data.IPMRecipientStore({
			autoResolve: false,
			autoDestroy: true,
			customObjectType: Zarafa.core.data.RecordCustomObjectType.ZARAFA_FROM_RECIPIENT
		});

		Ext.applyIf(config, {
			xtype: 'zarafa.sendasgrid',
			store: store,
			border: true,
			viewConfig: {
				forceFit: true,
				deferEmptyText: false,
				emptyText: '<div class=\'emptytext\'>' + _('No from addresses configured') + '</div>'
			},
			loadMask: {
				msg: _('Loading from addresses') + '...'
			},
			columns: this.initColumnModel(),
			selModel: new Zarafa.common.ui.grid.RowSelectionModel({
				singleSelect: true
			}),
			listeners: {
				viewready: this.onViewReady,
				rowdblclick: this.onRowDblClick,
				scope: this
			}
		});

		Zarafa.common.sendas.ui.SendAsGrid.superclass.constructor.call(this, config);
	},

	/**
	 * Creates a column model object, used in {@link #colModel} config
	 * @return {Ext.grid.ColumnModel} column model object
	 * @private
	 */
	initColumnModel: function()
	{
		return [{
			dataIndex: 'icon_index',
			headerCls: 'zarafa-icon-column',
			header: '<p class="icon_index">&nbsp;<span class="title">Icon</span></p>',
			width: 24,
			fixed: true,
			renderer: Zarafa.common.ui.grid.Renderers.icon
		},{
			dataIndex: 'reply_mail',
			header: _('Reply Mail'),
			renderer: this.columnRenderer,
			sortable: false,
			menuDisabled:true,
			fixed: true,
			align: "center",
			width: 150,
			listeners: {
				click: this.onCellClick,
				scope: this
			}
		},{
			dataIndex: 'new_mail',
			header: _('New Mail'),
			renderer: this.columnRenderer,
			sortable: false,
			align: "center",
			menuDisabled:true,
			fixed: true,
			width: 150,
			listeners: {
				click: this.onCellClick,
				scope: this
			}
		},{
			dataIndex: 'forward_mail',
			header: _('Forward Mail'),
			renderer: this.columnRenderer,
			sortable: false,
			align: "center",
			menuDisabled:true,
			fixed: true,
			width: 150,
			listeners: {
				click: this.onCellClick,
				scope: this
			}
		},{
			dataIndex: 'display_name',
			header: _('Name'),
			renderer: Zarafa.common.ui.grid.Renderers.text,
			sortable: false,
			menuDisabled:true,
			width: 250
		},{
			dataIndex: 'smtp_address',
			header: _('Email Address'),
			renderer: Zarafa.common.ui.grid.Renderers.text,
			sortable: false,
			menuDisabled:true,
			width: 250
		}];
	},

	/**
	 * Render which shows cross or green right icon in grid column if From recipient is configured
	 * for the 'New mail', 'Reply mail' or 'Forward mail'.
	 *
	 * @param {Object} value The data value for the cell.
	 * @param {Object} p An object with metadata
	 * @return {String} The formatted string
	 */
	columnRenderer: function (value, p)
	{
		p.css += 'zarafa-grid-empty-cell';
		p.css += value === true ? ' icon_flag_complete' : ' icon_cross_red';
		return '';
	},

	/**
	 * Event handler triggered when the icon on the cell of "Reply Mail" or "New Mail" or "Forward mail" is clicked.
	 *
	 * @param {Object} item The item of {Ext.grid.ColumnModel columnModel} which was clicked.
	 * @param {Ext.grid.GridPanel} grid the grid of which the row was clicked.
	 * @param {Number} rowIndex number of the row clicked.
	 */
	onCellClick: function(item, grid, rowIndex)
	{
		var store = grid.getStore();
		var record = store.getAt(rowIndex);
		var recordProp = item.dataIndex;
		var value = !record.get(recordProp);

		// Uncheck other true values while setting clicked cell's value as true.
		if (value) {
			store.getRange().forEach(function(record) {
				if (record.get(recordProp)) {
					record.set(recordProp, false);
				}
			});
		}
		record.set(item.dataIndex, value);
	},

	/**
	 * Event handler which is fired when the gridPanel is ready. This will automatically
	 * select the first row in the grid.
	 * @private
	 */
	onViewReady: function()
	{
		this.getSelectionModel().selectFirstRow();
	},

	/**
	 * Function will be called to remove a sendas address.
	 * It will raise an alert if there is no selection made by user,
	 * but user is trying to remove record from the grid.
	 */
	removeSendAs: function()
	{
		var selectionModel = this.getSelectionModel();
		var sendasRecord = this.getSelectionModel().getSelected();
		var rowToSelect;

		if(!sendasRecord) {
			Ext.Msg.alert(_('Alert'), _('Please select a from address record.'));
			return;
		}

		// before removing send as we should store row index of next available send as,
		// because deleting send as will remove selection
		if (selectionModel.hasNext()) {
			rowToSelect = selectionModel.last;
		} else if (selectionModel.hasPrevious()) {
			rowToSelect = selectionModel.last-1;
		}

		this.store.remove(sendasRecord);

		if (Ext.isDefined(rowToSelect)) {
			selectionModel.selectRow(rowToSelect);
		}
	},

	/**
	 * Event handler which is fired when the
	 * {@link Zarafa.common.sendas.ui.SendAsGrid SendAsGrid} is double clicked.
	 * it will call generic function to handle the functionality.
	 * @param {Ext.grid.GridPanel} grid the grid of which the row double clicked.
	 * @param {Number} rowIndex number of the row double clicked.
	 * @private
	 */
	onRowDblClick: function(grid, rowIndex)
	{
		var record = grid.getStore().getAt(rowIndex);

		if(record.isOneOff()) {
			this.editSendAsRecipient(record, false);
		} else {
			this.viewSendAsRecipient(record);
		}
	},

	/**
	 * It will call {@link Zarafa.common.Actions#openSendAsRecipientContent} to add or edit external contact.
	 * @param {Ext.data.Record} record record to be opened
	 * @param {Boolean} removeOnCancel true to remove the record
	 * from store while pressing cancel button, false otherwise
	 */
	editSendAsRecipient: function(record, removeOnCancel)
	{
		Zarafa.common.Actions.openSendAsRecipientContent(record, {
			title: _('Add/Edit From recipient'),
			removeOnCancel: removeOnCancel
		});
	},

	/**
	 * It will call {@link Zarafa.common.Actions#openViewRecipientContent} to open the address book contact.
	 * @param {Ext.data.Record} record record to be opened
	 */
	viewSendAsRecipient: function(record)
	{
		Zarafa.common.Actions.openViewRecipientContent(record, {modal:true});
	}
});

Ext.reg('zarafa.sendasgrid', Zarafa.common.sendas.ui.SendAsGrid);
Ext.namespace('Zarafa.common.sendas.ui');

/**
 * @class Zarafa.common.sendas.ui.SendAsPanel
 * @extends Ext.Panel
 * @xtype zarafa.sendaspanel
 * Will generate UI for the {@link Zarafa.common.settings.SettingsSendAsWidget SettingsSendAsWidget}.
 */
Zarafa.common.sendas.ui.SendAsPanel = Ext.extend(Ext.Panel, {
	/**
	 * @constructor
	 * @param config Configuration structure
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			// Override from Ext.Component
			xtype: 'zarafa.sendaspanel',
			border: false,
			layout: {
				type: 'vbox',
				align: 'stretch',
				pack: 'start'
			},
			items: this.createPanelItems()
		});

		Zarafa.common.sendas.ui.SendAsPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Function will create panel items for {@link Zarafa.common.sendas.ui.SendAsPanel SendAsPanel}
	 * @return {Array} array of items that should be added to panel.
	 * @private
	 */
	createPanelItems: function()
	{
		return [{
			xtype: 'displayfield',
			value: _("Manage the recipients used in the 'From' field"),
			fieldClass: 'x-form-display-field zarafa-settings-widget-extrainfo'
		}, {
			xtype: 'container',
			flex: 1,
			layout: {
				type: 'hbox',
				align: 'stretch',
				pack: 'start'
			},
			items: [{
				xtype: 'zarafa.sendasgrid',
				ref: '../sendasGrid',
				flex: 1
			}, {
				xtype: 'container',
				width: 160,
				layout: {
					type: 'vbox',
					align: 'center',
					pack: 'start'
				},
				items: [{
					xtype: 'button',
					text: _('Address Book') + '...',
					handler: this.onSendAsAddressBook,
					scope: this
				},{
					xtype: 'spacer',
					height: 20
				},{
					xtype: 'button',
					text: _('Add') + '...',
					handler: this.onSendAsAdd,
					scope: this
				},{
					xtype: 'spacer',
					height: 20
				},{
					xtype: 'button',
					text: _('Edit') + '...',
					disabled: true,
					ref: '../../editButton',
					handler: this.onSendAsEdit,
					scope: this
				},{
					xtype: 'spacer',
					height: 20
				},{
					xtype: 'button',
					text: _('View') + '...',
					disabled: true,
					ref: '../../viewButton',
					handler: this.onSendAsView,
					scope: this
				},{
					xtype: 'spacer',
					height: 20
				},{
					xtype: 'button',
					text: _('Remove') + '...',
					disabled: true,
					ref: '../../removeButton',
					handler: this.onSendAsRemove,
					scope: this
				}]
			}]
		}];
	},

	/**
	 * initialize events for the panel.
	 * @private
	 */
	initEvents: function()
	{
		Zarafa.common.sendas.ui.SendAsPanel.superclass.initEvents.call(this);

		// register event to enable/disable buttons
		this.mon(this.sendasGrid.getSelectionModel(), 'selectionchange', this.onGridSelectionChange, this);
	},

	/**
	 * Returns the {@link Zarafa.core.data.IPMRecipientStore IPMRecipientStore} associated
	 * with this panel.
	 * @return {Zarafa.core.data.IPMRecipientStore} The store
	 */
	getStore: function()
	{
		return this.sendasGrid.getStore();
	},

	/**
	 * Handler function will be called when user clicks on 'Add' button,
	 * this will show addressbook dialog to select sendas user.
	 * @private
	 */
	onSendAsAdd: function()
	{
		// find rowid value
		var store = this.getStore();
		var data = Ext.pluck(store.getRange(), 'data');
		var rowId = Ext.max(Ext.pluck(data, 'rowid')) || 0;

		var record = Zarafa.core.data.RecordFactory.createRecordObjectByCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_FROM_RECIPIENT, {
			// rowid is the {@link Ext.data.JsonReader#idProperty} in {@link Zarafa.core.data.IPMRecipientStore IPMRecipientStore}
			// so we must have to configure the rowid properly.
			rowid: rowId + 1,
			display_type: Zarafa.core.mapi.DisplayType.DT_REMOTE_MAILUSER
		});

		store.add(record);

		this.sendasGrid.editSendAsRecipient(record, true);
	},

	/**
	 * Event handler will be called when selection in {@link Zarafa.common.ui.SendAsGrid SendAsGrid}
	 * has been changed.
	 * @param {Ext.grid.RowSelectionModel} selectionModel selection model that fired the event
	 */
	onGridSelectionChange: function(selectionModel)
	{
		var record = selectionModel.getSelected();

		var isOneOff = record && record.isOneOff();
		var hasSelection = selectionModel.hasSelection();

		// +----------------------------------------------------+
		// |       | Enable / Disable (Edit, View, Remove)|
		// +----------------------------------------------------+
		// |       | Selection | Operation | isOneOff   |
		// +----------------------------------------------------+
		// |Edit Button | !(true)  | OR    | !(true)   |
		// |View Button | !(true)  | OR    | true     |
		// |Remove Button| !(true)  | Null   | Null     |
		// +----------------------------------------------------+
		// Here is toggle the view and edit button as per the contact type
		// and if there is no selection in sendAsGrid, all three (Edit, View, Remove) buttons are disable.
		this.editButton.setDisabled(!hasSelection || !isOneOff);
		this.viewButton.setDisabled(!hasSelection || isOneOff);
		this.removeButton.setDisabled(!hasSelection);
	},

	/**
	 * Handler function will be called when user clicks on 'Remove' button,
	 * this will remove currently selected sendas from sendass list.
	 * @private
	 */
	onSendAsRemove: function()
	{
		this.sendasGrid.removeSendAs();
	},

	/**
	 * Function will be call to get the selected contact from {@link Zarafa.common.ui.SendAsGrid SendAsGrid}
	 * @return {Ext.data.Record} The record which is selected from {@link Zarafa.common.ui.SendAsGrid SendAsGrid}.
	 * @private
	 */
	getSendAsRecord: function()
	{
		var sendasRecord = this.sendasGrid.getSelectionModel().getSelected();

		if(!sendasRecord) {
			Ext.Msg.alert(_('Alert'), _('Please select a sendas record.'));
			return;
		}

		return sendasRecord;
	},

	/**
	 * Handler function will be called when user click on 'Edit' button,
	 * this will edit currently selected contact from sendas list.
	 * @private
	 */
	onSendAsEdit: function()
	{
		var sendasRecord = this.getSendAsRecord();

		this.sendasGrid.editSendAsRecipient(sendasRecord, false);
	},

	/**
	 * Handler function will be called when user click on 'View' button,
	 * this will open currently selected address book contact from sendas list.
	 * @private
	 */
	onSendAsView: function()
	{
		var sendasRecord = this.getSendAsRecord();

		this.sendasGrid.viewSendAsRecipient(sendasRecord);
	},

	/**
	 * Handler function will be called when user clicks on 'Address Book' button,
	 * this will open an Address Book and one can select sendas from any of the address book contact list.
	 * @private
	 */
	onSendAsAddressBook: function()
	{
		Zarafa.common.Actions.openABUserSelectionContent({
			callback: Zarafa.common.Actions.abCallBack,
			scope: this,
			singleSelect: false,
			listRestriction: {
				hide_users: ['system', 'everyone'],
				hide_companies: true
			}
		});
	}
});

Ext.reg('zarafa.sendaspanel', Zarafa.common.sendas.ui.SendAsPanel);
Ext.namespace('Zarafa.common.ui');

/**
 * @class Zarafa.common.ui.Box
 * @extends Ext.BoxComponent
 * @xtype zarafa.box
 *
 * The Box which is used by {@link Zarafa.common.ui.BoxField} components
 * to display the boxes inside itself. This box is dediciated to the BoxField
 * and cannot be used separately.
 */
Zarafa.common.ui.Box = Ext.extend(Ext.BoxComponent, {
	/**
	 * @cfg {Ext.data.Record} record The record which is bound to this box.
	 */
	record: undefined,

	/**
	 * @cfg {Zarafa.common.ui.BoxField} parent The boxfield
	 * in which this box is being displayed.
	 */
	parent: undefined,

	/**
	 * The {@link Ext.Element} which contains the icon for this box,
	 * this will is filled during the {@link #updateIcons} function.
	 * @property
	 * @type Ext.Element
	 */
	iconEl: undefined,

	/**
	 * The {@link Ext.Element} which contains the text contents for this box,
	 * this is filled with the {@link #textTpl} template.
	 * @property
	 * @type Ext.Element
	 */
	textEl: undefined,

	/**
	 * The {@link Ext.Element} which contains the delete button for this box.
	 * @property
	 * @type Ext.Element
	 */
	delBtnEl: undefined,

	/**
	 * @cfg {Boolean} editable False when none of the buttons or user actions is
	 * allowed to change the record inside this box.
	 */
	editable: true,

	/**
	 * @cfg {Boolean} enableButtons True if {@link #renderButtons} should be called
	 * which will create the buttons inside the box.
	 */
	enableButtons: true,

	/**
	 * @cfg {Boolean} enableIcons True if {@link #renderIcons} should be called
	 * which will create the icon inside the box.
	 */
	enableIcons: true,

	/**
	 * @cfg {String} hoverCls The CSS class which will be applied to {@link #el} when
	 * the cursor is hovering over this component.
	 */
	hoverCls: 'x-zarafa-boxfield-item-hover',

	/**
	 * @cfg {String} focusCls The CSS class which will be applied to {@link #el} when
	 * the component has been selected.
	 */
	focusCls: 'x-zarafa-boxfield-item-focus',

	/**
	 * @cfg {String} textCls The CSS class which will be applied to the {@link #textEl} when
	 * the component is rendered.
	 */
	textCls: 'x-zarafa-boxfield-item-text',

	/**
	 * @cfg {String} iconCls The CSS class which will be applied to the {@link #iconEl} when
	 * the component is rendered.
	 */
	iconCls: 'x-zarafa-boxfield-item-icon',

	/**
	 * @cfg {String} btnCls The CSS class which will be applied to the {@link #btnEl} when
	 * the component is rendered.
	 */
	btnCls: 'x-zarafa-boxfield-item-close',

	/**
	 * @cfg {String} btnHoverCls The CSS class which will be applied to the {@link #btnEl} when
	 * the cursor is hovering over the button.
	 */
	btnHoverCls: 'x-zarafa-boxfield-item-close-hover',

	/**
	 * @cfg {String/Ext.XTemplate} textTpl <b>Required.</b> The template or template string
	 * which must be applied to the {@link #textEl inner span} of the box.
	 * The template arguments are have been returned by {@link #collectData}.
	 */
	textTpl: undefined,

	/**
	 * Similar to {@link #isDestroying} only this is only set when
	 * {@link #doDestroy} has been called with animation enabled.
	 * Because of the animation, the actual {@link #isDestroying}
	 * will be set a bit later. During this period we should still block
	 * all events.
	 * @property
	 * @type Boolean
	 */
	isAnimDestroying: false,

	/**
	 * @cfg {Boolean} enableTextSelection Enable text selection in the {@link #el},
	 * this will prevent {@link Ext.Element#unselectable} to be called on {@link #el}.
	 */
	enableTextSelection: false,

	/**
	 * @cfg {Number} maximum length of text allowed before truncations,
	 * truncation will be replaced with ellipsis ('...').
	 */
	ellipsisStringStartLength: 30,

	/**
	 * @cfg {Number} maximum length of text allowed after truncations,
	 * truncation will be replaced with ellipsis ('...').
	 */
	ellipsisStringEndLength: 30,

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

		Ext.applyIf(config, {
			autoEl: {
				tag: 'li',
				cls: 'x-zarafa-boxfield-item',
				role: 'option'
			}
		});

		Zarafa.common.ui.Box.superclass.constructor.call(this, config);

		if (Ext.isString(this.textTpl)) {
			this.textTpl = new Ext.XTemplate(this.textTpl, {
				compiled: true,
				ellipsisStringStartLength: this.ellipsisStringStartLength,
				ellipsisStringEndLength: this.ellipsisStringEndLength
			});
		}
	},

	/**
	 * Function called during the {@link #render rendering} of this component. This
	 * will call {@link #renderBox} and {@link #renderButtons} to generate the two
	 * basic elements of which the box consists of.
	 * @param {Ext.Container} ct The container in which the component is being rendered.
	 * @param {NUmber} position The position within the container where the component will be rendered.
	 * @private
	 */
	onRender: function(ct, position)
	{
		Zarafa.common.ui.Box.superclass.onRender.call(this, ct, position);

		if (this.enableTextSelection !== true) {
			this.el.unselectable();
		}

		if (this.enableIcons === true) {
			this.renderIcons();
		}

		this.renderBox();

		if (this.enableButtons === true) {
			this.renderButtons();
		}

		if (Ext.isDefined(this.record)) {
			this.update(this.record);
		}

		this.el.addClassOnOver(this.hoverCls);

		this.mon(this.el, 'click', this.onClick, this);
		this.mon(this.el, 'contextmenu', this.onContextMenu, this);
		this.mon(this.el, 'dblclick', this.onDblClick, this);
	},

	/**
	 * Function called after the {@link #render rendering} of this component.
	 * This will hide the {@link #delBtnEl} when the {@link #editable} flag is false
	 * @param {Ext.Container} ct The container in which the component is being rendered.
	 * @private.
	 */
	afterRender: function(ct)
	{
		Zarafa.common.ui.Box.superclass.afterRender.call(this, ct);
		this.delBtnEl.setVisible(this.editable);
	},

	/**
	 * Set the {@link #editable} flag, making the box non-editable.
	 * @param {Boolean} value The new editable status.
	 */
	setEditable: function(value)
	{
		if (this.editable !== value) {
			this.editable = value;
			this.delBtnEl.setVisible(this.editable);
		}
	},

	/**
	 * Renders the {@link #iconEl} into the main {@link #el} element.
	 * This element will be used to render the icon into the box. This function
	 * will only be called when {@link #enableIcons} is true.
	 * @private
	 */
	renderIcons: function()
	{
		this.iconEl = this.el.createChild({
			tag: 'span',
			cls: this.iconCls
		});
	},

	/**
	 * Renders the {@link #textEl} into the main {@link #el} element.
	 * This element will contain the actual contents which is being updated
	 * during {@link #update}.
	 * @Private
	 */
	renderBox: function()
	{
		this.textEl = this.el.createChild({
			tag: 'span',
			cls: this.textCls
		});
	},

	/**
	 * Setup the buttons of the box. This will be done after the template is done rendering. At that
	 * point we can start adding event listeners to the DOM elements. This function will only be
	 * called when {@link #enableButtons} is true.
	 * @private
	 */
	renderButtons: function()
	{
		this.renderRemoveButton();
	},

	/**
	 * Sets event listeners on the remove button of the box. This is done in a separate function
	 * than in {@link #initButtons initButtons} so it can easily be overwritten if needed.
	 * @private
	 */
	renderRemoveButton: function()
	{
		this.delBtnEl = this.el.createChild({
			tag: 'span',
			cls: this.btnCls
		});
		this.delBtnEl.set({
			'role': 'button',
			'tabindex': '0',
			'aria-label': _('Remove')
		});

		this.delBtnEl.addClassOnOver(this.btnHoverCls);
		this.mon(this.delBtnEl, 'click', this.onClickRemove, this);
		this.mon(this.delBtnEl, 'keydown', function(e) {
			if (e.getKey() === e.ENTER || e.getKey() === e.SPACE) {
				e.stopEvent();
				this.onClickRemove();
			}
		}, this);
	},

	/**
	 * Enables the focus on this component.
	 */
	focus: function()
	{
		Zarafa.common.ui.Box.superclass.focus.call(this);
		this.el.addClass(this.focusCls);
		this.parent.doBoxFocus(this);
	},

	/**
	 * Removes the focus on this component.
	 */
	blur: function()
	{
		Zarafa.common.ui.Box.superclass.blur.call(this);
		this.el.removeClass(this.focusCls);
		this.parent.doBoxBlur(this);
	},

	/**
	 * @return {Boolean} Returns true if this box currently has focus.
	 */
	hasFocus: function()
	{
		return this.el.hasClass(this.focusCls);
	},

	/**
	 * Called when the user has clicked on the remove button.
	 * @private
	 */
	onClickRemove: function()
	{
		if (!this.isAnimDestroying && !this.isDestroying) {
			// Set focus on parent element.
			this.parent.focus();
			this.parent.doBoxRemove(this);
		}
	},

	/**
	 * Called when the user has clicked on the element.
	 * @param {Ext.EventObject} e The event object
	 * @private
	 */
	onClick: function(e)
	{
		e.stopEvent();
		if (!this.isAnimDestroying && !this.isDestroying) {
			this.parent.doBoxClick(this);
		}
	},

	/**
	 * Called when the user requested the contextmenu of the element.
	 * @param {Ext.EventObject} e The event object
	 * @private
	 */
	onContextMenu: function(e)
	{
		e.stopEvent();
		if (!this.isAnimDestroying && !this.isDestroying) {
			this.parent.doBoxContextMenu(this);
		}
	},

	/**
	 * Called when the user has doubleclicked on the element.
	 * @param {Ext.EventObject} e The event object
	 * @private
	 */
	onDblClick: function(e)
	{
		e.stopEvent();
		if (!this.isAnimDestroying && !this.isDestroying) {
			this.parent.doBoxDblClick(this);
		}
	},

	/**
	 * Function which can be overridden to provide custom formatting for the given {@link Ext.data.Record}
	 * to the {@link #update} function. The data object returned here is used by the {@link #textTpl template}
	 * to render the contents of the box.
	 * @param {Ext.data.Record} record The record which is going to be rendered
	 * @return {Object} The data object which can be passed to {@link #textTpl}.
	 * @private
	 */
	prepareData: function(record)
	{
		return record.data;
	},

	/**
	 * Function which can be overridden to provide custom icon rendering for the given {@link Ext.data.Record}
	 * to the {@link #iconEl} element. The string returned here is the CSS class which will be set on the
	 * {@link #iconEl}.
	 * @param {Ext.data.Record} record The record which is going to be rendered
	 * @return {String} The CSS class which must be applied to the {@link #iconEl}.
	 * @private
	 */
	prepareIcon: function(record)
	{
		return Zarafa.common.ui.IconClass.getIconClass(record);
	},

	/**
	 * Update the {@link #textEl inner HTML} of this component using the {@link #textTpl template}.
	 * @param {Ext.data.Record} record The Ext.data.Record which data must be applied to the template
	 */
	update: function(record)
	{
		this.textTpl.overwrite(this.textEl, this.prepareData(record));

		if (this.enableIcons === true) {
			var icon_class = this.prepareIcon(record);
			if (!Ext.isEmpty(icon_class)) {
				this.iconEl.addClass(icon_class);
			}
		}

		this.parent.sizeContainer();
	},

	/**
	 * Event handler which is fired when the box is being resized. This will determine
	 * what the desired with of the text should be, to ensure that the buttons (if available)
	 * will be positioned completely at the right.
	 * @param {Number} adjWidth The box-adjusted width that was set
	 * @param {Number} adjHeight The box-adjusted height that was set
	 * @param {Number} rawWidth The width that was originally specified
	 * @param {Number} rawHeight The height that was originally specified
	 * @private
	 */
	onResize: function(adjWidth, adjHeight, rawWidth, rawHeight)
	{
		if (Ext.isNumber(rawWidth)) {
			if (!Ext.isNumber(adjWidth)) {
				adjWidth = rawWidth;
			}
			adjWidth -= (this.el.getMargins('lr') +
						 this.iconEl.getWidth() + this.iconEl.getFrameWidth('lr') + this.iconEl.getMargins('lr') +
						 this.delBtnEl.getWidth() + this.delBtnEl.getFrameWidth('lr') + this.delBtnEl.getMargins('lr') +
						 this.getResizeEl().getFrameWidth('lr') + this.getResizeEl().getMargins('lr'));

			// Adjust the text width according to expand button as well, if it is defined.
			if(Ext.isDefined(this.expandBtnEl)){
				adjWidth -= (this.expandBtnEl.getWidth() + this.expandBtnEl.getFrameWidth('lr') + this.expandBtnEl.getMargins('lr'));
			}

			this.textEl.setWidth(adjWidth);
		}
	},

	/**
	 * Wrapper around the {@link #destroy} function which supports
	 * the animating of the event.
	 *
	 * @param {Boolean} animate True to enable the animation of the
	 * box removal.
	 */
	doDestroy: function(animate)
	{
		if (animate === true) {
			this.isAnimDestroying = true;
			this.getEl().hide({
				duration: 0.2,
				callback: function() {
					this.destroy();
					this.parent.sizeContainer();
				},
				scope: this
			});
		} else {
			this.destroy();
			this.parent.sizeContainer();
		}
	}
});

Ext.reg('zarafa.box', Zarafa.common.ui.Box);
Ext.namespace('Zarafa.common.ui');

/**
 * @class Zarafa.common.ui.BoxField
 * @extends Ext.form.ComboBox
 * @xtype zarafa.boxfield
 *
 * A special input field which displays all contents inside special {@link Zarafa.common.ui.Box}
 * components which are rendered inside this {@link #el}. This makes the user interface nicer,
 * and allows the user to perform extra actions on each box.
 *
 * The contents of the field is based on the contents of the {@link #boxStore} which contains
 * the {@link Ext.data.Record records} which must be rendered as {@link Zarafa.common.ui.Box box}.
 */
Zarafa.common.ui.BoxField = Ext.extend(Ext.form.ComboBox, {
	/**
	 * @cfg {String} boxType The xtype of the component which is used as box.
	 * This defaults to {@link Zarafa.common.ui.Box 'zarafa.box'}.
	 */
	boxType: 'zarafa.box',
	/**
	 * @cfg {Ext.data.Store} boxStore The store which contains all {@link Ext.data.Record records}
	 * which must be rendered as {@link Zarafa.common.ui.Box}. Before rendering, the contents
	 * will be filtered by {@link #filterRecords}.
	 */
	boxStore: undefined,
	/**
	 * @cfg {Object} boxConfig A configuration object which must be applied to all
	 * {@link Zarafa.common.ui.Box box} components which are rendered into this component
	 * by default.
	 */
	boxConfig: undefined,
	/**
	 * The collection of all {@link Zarafa.common.ui.Box Box} components which have been
	 * rendered inside the component.
	 * @property
	 * @type Ext.util.MixedCollection
	 */
	items: undefined,
	/**
	 * @cfg {Number|Char} handleInputKey The input key which must be pressed before the
	 * {@link #handleInput} function is being called. This is normally used to indicate
	 * that the current input is ready to be converted into {@link #items boxes}.
	 * This can be given as a charCode directly, or as a character,
	 * in which case it will be converted to the corresponding code using charCodeAt().
	 */
	handleInputKey: ';',
	/***
	 * @cfg {Boolean} enableComboBox True to enable all combobox functionality on this field.
	 * When this is true, all required fields from the {@link Ext.from.ComboBox Ext.form.ComboBox}
	 * must be configured.
	 */
	enableComboBox: true,
	/**
	 * The {@link Zarafa.common.ui.Box box} which currently has the userfocus.
	 * @property
	 * @type Zarafa.common.ui.Box
	 */
	currentFocus: false,
	/**
	 * A dummy 'a' element which is created as child of the {@link #wrap}. This element is
	 * not hidden, but instead has the dimensions 0x0, otherwise the browser is not able to
	 * put the focus on the element. By using this focus element, we can capture {@link #keyMap} events
	 * and control the different {@link #items boxes}.
	 * @property
	 * @type Ext.Element
	 */
	boxFocusEl: undefined,
	/**
	 * The 'ul' element which contains all rendered {@link #items} and the {@link #inputEl}.
	 * @property
	 * @type Ext.Element
	 */
	wrapBoxesEl: undefined,
	/**
	 * The 'li' element which has been wrapped around {@link #el}, and is to make sure the
	 * input element is positioned correctly within the list. This should always be the
	 * last element inside the {@link #wrapBoxesEl}.
	 * @property
	 * @type Ext.Element
	 */
	inputEl: undefined,
	/**
	 * The {@link Ext.KeyMap} which is used on the {@link #boxFocusEl} to add keyboard
	 * control over the current {@link #items box} selection. This will only be enabled
	 * when this field is {@link #editable editable}.
	 *
	 * @property
	 * @type Ext.KeyMap
	 */
	boxKeyMap: undefined,

	/**
	 * The {@link Ext.KeyMap} which is used on the {@link #el} to add keyboard
	 * control over the input field. This will only be enabled
	 * when this field is {@link #editable editable}.
	 * This KeyMap is currently only used for the handleInputKey,
	 * which causes the BoxField to evaluate its input and convert it to boxes.
	 * This is separated from {@link #specialInputKeyMap} because it needs reliable
	 * character code detection, which can only happen on a 'keypress' event.
	 *
	 * @property
	 * @type Ext.KeyMap
	 */
	inputKeyMap: undefined,

	/**
	 * The {@link Ext.KeyMap} which is used on the {@link #el} to add keyboard
	 * control over the input field. This will only be enabled
	 * when this field is {@link #editable editable}.
	 * This KeyMap is used only for special keys
	 * that do not insert characters (e.g. arrow keys, page up/down, enter, etc.).
	 * These keys are separated from {@link #inputKeyMap} because they have to be caught on
	 * a 'keydown' event.
	 *
	 * @property
	 * @type Ext.KeyMap
	 */
	specialInputKeyMap: undefined,

	/**
	 * The {@link Ext.KeyMap} which is used on the {@link #el} to add keyboard control
	 * for DELETE key. This will only be enabled when {@link #enableComboBox} is configured true.
	 *
	 * @property
	 * @type Ext.KeyMap
	 */
	listKeyMap: undefined,

	/**
	 * @cfg {Number} minInputFieldWidth The minimum number of pixels which must be available
	 * for the {@link #el input field}, to type in the text. The number of pixels should be
	 * sufficient to at least contain 1 character (in any characterset).
	 */
	minInputFieldWidth: 25,

	/**
	 * @cfg {Number} inputFieldHeight The default height for the {@link #el input field}.
	 */
	inputFieldHeight: 24,

	/**
	 * @cfg {Boolean} enableAnim Enable special {@link Ext.Fx FX} effects for
	 * this this field and all container {@link #items boxes}.
	 */
	enableAnim: true,

	/**
	 * @cfg {String} wrapCls The CSS classes which must be applied to the {@link #wrap} element.
	 */
	wrapCls: 'x-form-text x-zarafa-boxfield',

	/**
	 * Instance of the {@link Ext.util.TextMetrics TextMetrics} which is bound to the {@link #innerList}.
	 * This is used to calculate the desired width of text inside the {@link #innerList}. This field
	 * should only be obtained by the {@link #getListTextMetric} function.
	 * @property
	 * @type Ext.util.TextMetrics
	 */
	listTextMetric: undefined,

	/**
	 * Instance of the {@link Ext.util.TextMetrics TextMetrics} which is bound to the {@link #el}.
	 * This is used to calculate the desired width of text inside the {@link #el}. This field
	 * should only be obtained by the {@link #getInputTextMetric} function.
	 * @property
	 * @type Ext.util.TextMetrics
	 */
	inputTextMetric: undefined,

	/**
	 * @cfg {Boolean} listMode True to show all boxes in a list rather then side-by-side.
	 * When enabled, all boxes (and input field) will get the full width of the component.
	 */
	listMode: false,

	/**
	 * @cfg {Number} boxLimit If set, this number will limit the number of boxes which will
	 * be rendered into the field. It will also disable the {@link #inputEl} when the limit
	 * has been reached.
	 */
	boxLimit: undefined,

	/**
	 * @cfg {String}
	 * A simple CSS selector (e.g. div.some-class or span:first-child) that will be used to
	 * determine what extra nodes are required to be added into each of the dropdown-list item.
	 */
	extraItemSelector: undefined,

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

		if (config.readOnly === true) {
			config.editable = false;
		}

		Ext.applyIf(config, {
			// Override from Ext.Component
			xtype: 'zarafa.boxfield',
			cls: 'x-zarafa-boxfield-input',
			hideTrigger: true,

			autoHeight: true,
			autoScroll: true,

			/*
			 * Override value of Ext.form.TriggerField
			 * Because we don't want TAB-key to blur the element
			 */
			monitorTab: false
		});
		config.wrapCls = config.wrapCls ? config.wrapCls + ' ' + this.wrapCls : this.wrapCls;

		this.addEvents(
			/**
			 * @event boxfocus
			 * Fired when a box receives focus
			 * @param {Zarafa.common.ui.BoxField} boxField Parent of the box
			 * @param {Zarafa.common.ui.Box} box The box that has been focussed
			 * @param {Ext.data.Record} record The record that belongs to the box
			 */
			'boxfocus',
			/**
			 * @event boxblur
			 * Fired when a box looses focus
			 * @param {Zarafa.common.ui.BoxField} boxField Parent of the box
			 * @param {Zarafa.common.ui.Box} box The box that has been blurred
			 * @param {Ext.data.Record} record The record that belongs to the box
			 */
			'boxblur',
			/**
			 * @event boxclick
			 * Fires when the user clicked on a box
			 * @param {Zarafa.common.ui.BoxField} boxField Parent of the box
			 * @param {Zarafa.common.ui.Box} box The box that has been clicked
			 * @param {Ext.data.Record} record The record that belongs to the box
			 */
			'boxclick',
			/**
			 * @event boxdblclick
			 * Fires when the user doubleclicked on a box
			 * @param {Zarafa.common.ui.BoxField} boxField Parent of the box
			 * @param {Zarafa.common.ui.Box} box The box that has been doubleclicked
			 * @param {Ext.data.Record} record The record that belongs to the box
			 */
			'boxdblclick',
			/**
			 * @event boxcontextmenu
			 * Fires when the user requested the contextmenu for a box
			 * @param {Zarafa.common.ui.BoxField} boxField Parent of the box
			 * @param {Zarafa.common.ui.Box} box The box for which the contextmenu was requested
			 * @param {Ext.data.Record} record The record that belongs to the box
			 */
			'boxcontextmenu',
			/**
			 * @event boxadd
			 * Fires when a box has been added
			 * @param {Zarafa.common.ui.BoxField} boxField Parent of the to be added box
			 * @param {Zarafa.common.ui.Box} box The box that has been added
			 * @param {Ext.data.Record} record The record that belongs to the box
			 */
			'boxadd',
			/**
			 * @event boxremove
			 * Fires when a box will be removed.
			 * @param {Zarafa.common.ui.BoxField} boxField Parent of the to be removed box
			 * @param {Zarafa.common.ui.Box} box The box that will be removed
			 * @param {Ext.data.Record} record The record that belongs to the box
			 */
			'boxremove'
		);

		Zarafa.common.ui.BoxField.superclass.constructor.call(this, config);

		this.on('boxremove', this.onBoxRemove, this);
		this.on('boxadd', this.onBoxAdd, this);
	},

	/**
	 * Called by the superclass to initialize the component
	 * @private
	 */
	initComponent: function()
	{
		Zarafa.common.ui.BoxField.superclass.initComponent.call(this);
		this.items = new Ext.util.MixedCollection();

		// Convert input key to character code if necessary
		if (Ext.isString(this.handleInputKey)) {
			this.handleInputKey = this.handleInputKey.charCodeAt(0);
		}

		this.previousHeight = this.height;
	},

	/**
	 * Called by the superclass to initialize all events for this component.
	 * This is called on {@link #afterrender}.
	 * @private
	 */
	initEvents: function()
	{
		this.mon(this.el, {
			keydown: this.onKeyDownHandler,
			scope : this
		});

		this.mon(this.getContentTarget(), 'click', this.onContainerClick, this);

		// Initialize keyboard control
		if (this.editable !== false) {
			this.boxKeyMap = this.createBoxKeyMap(this.boxFocusEl);
			this.specialInputKeyMap = this.createSpecialInputKeyMap(this.el);
			this.inputKeyMap = this.createInputKeyMap(this.el);

			// Initialize keyboard control for suggestion list, only
			// if {@link #enableComboBox} configured to true.
			if (this.enableComboBox === true) {
				this.listKeyMap = this.createListKeyMap(this.el);
			}

			/*
			 * Register the event handler for paste events. This will ensure
			 * the input element will be resized when pasting.
			 * Use the Zarafa.core.Event function to ensure compatibility
			 * with all browsers and support for drag & dropping text.
			 */
			Zarafa.core.Events.addPasteEventHandler(this, this.el, this.onPaste, this);
		}

		// Register event handler when listMode is enabled,
		// this will force the boxes to be resized when the parent
		// has been laid out.
		if (this.listMode === true) {
			this.mon(this.ownerCt, 'afterlayout', this.onParentLayout, this);
		}

		// Listen to select events in order to convert them to boxes.
		this.on('select', this.onSelect, this);

		Zarafa.common.ui.BoxField.superclass.initEvents.call(this);
	},

	/**
	 * Overridden to relay the value of {@link #extraItemSelector} to respective {@link Ext.DataView#extraItemSelector}
	 * and start listening to the {@link Ext.DataView#extraitemclick}, {@link Ext.DataView#mouseenter}
	 * and {@link Ext.DataView#mouseleave} event.
	 */
	initList: function()
	{
		Zarafa.common.ui.BoxField.superclass.initList.call(this);

		if(Ext.isDefined(this.extraItemSelector)) {
			this.view.extraItemSelector = this.extraItemSelector;

			this.view.on('extraitemclick', this.onExtraItemClick, this);
		}
	},

	/**
	 * Sets a new {@link boxStore boxStore}. It will first unregister all events from the old one.
	 * Then the new store will be set, the recipient field will be refreshed and after that the
	 * event listeners will be registered again.
	 * @param {Ext.data.Store} boxStore The store that will be applied to this field.
	 * @param {Boolean} initial True if this function is called from the constructor.
	 */
	setBoxStore: function(boxStore, initial)
	{
		if (this.boxStore === boxStore && initial !== true) {
			return;
		}

		if (this.boxStore) {
			this.mun(this.boxStore, {
				'datachanged': this.onBoxStoreDataChanged,
				'add': this.onBoxStoreAdd,
				'remove': this.onBoxStoreRemove,
				'update': this.onBoxStoreUpdate,
				'clear': this.onBoxStoreClear,
				scope: this
			});
			this.clearBoxes();
		}

		this.boxStore = Ext.StoreMgr.lookup(boxStore);

		if (this.boxStore) {
			this.loadBoxes(this.boxStore);
			this.mon(this.boxStore, {
				'datachanged': this.onBoxStoreDataChanged,
				'add': this.onBoxStoreAdd,
				'remove': this.onBoxStoreRemove,
				'update': this.onBoxStoreUpdate,
				'clear': this.onBoxStoreClear,
				scope: this
			});
		}
	},

	/**
	 * Returns the {@link boxStore boxStore}.
	 * @return {Ext.data.Store} The set {@link boxStore boxStore}.
	 */
	getBoxStore: function()
	{
		return this.boxStore;
	},

	/**
	 * Obtain (and create if required) the {@link #listTextMetric} instance.
	 * @return {Ext.util.TextMetrics} The textmetrics for {@link #innerList}.
	 * @private
	 */
	getListTextMetric: function()
	{
		if (!this.listTextMetric) {
			this.listTextMetric = Ext.util.TextMetrics.createInstance(this.innerList);
		}

		return this.listTextMetric;
	},

	/**
	 * Obtain (and create if required) the {@link #listTextMetric} instance.
	 * @return {Ext.util.TextMetrics} The textmetrics for {@link #el}.
	 * @private
	 */
	getInputTextMetric: function()
	{
		if (!this.inputTextMetric) {
			this.inputTextMetric = Ext.util.TextMetrics.createInstance(this.el);
		}

		return this.inputTextMetric;
	},

	/**
	 * The function that should handle the trigger's click event.
	 *
	 * This method is disabled if {@link #enableComboBox} is set to false.
	 *
	 * @param {EventObject} e
	 * @private
	 */
	onTriggerClick: function()
	{
		if (this.enableComboBox === true) {
			Zarafa.common.ui.BoxField.superclass.onTriggerClick.apply(this, arguments);
		}
	},

	/**
	 * Execute a query to filter the dropdown list. Fires the {@link #beforequery} event prior to performing the
	 * query allowing the query action to be canceled if needed.
	 *
	 * This method is disabled if {@link #enableComboBox} is set to false.
	 *
	 * @param {String} query The SQL query to execute
	 * @param {Boolean} forceAll <tt>true</tt> to force the query to execute even if there are currently fewer
	 * characters in the field than the minimum specified by the <tt>{@link #minChars}</tt> config option. It
	 * also clears any filter previously saved in the current store (defaults to <tt>false</tt>)
	 * @private
	 */
	doQuery: function()
	{
		if (this.enableComboBox === true) {
			Zarafa.common.ui.BoxField.superclass.doQuery.apply(this, arguments);
		}
	},

	/**
	 * Check if the value typed into the field matches a record from the store.
	 *
	 * This method is disabled if {@link #enableComboBox} is set to false.
	 *
	 * @private
	 */
	assertValue: function()
	{
		if (this.enableComboBox === true) {
			Zarafa.common.ui.BoxField.superclass.assertValue.apply(this, arguments);
		}
	},

	/**
	 * Event handler which is called when any key related to navigation (arrows, tab, enter, esc,
	 * etc.) is pressed.
	 * @param {Ext.data.Record} record
	 * @private
	 */
	onSelect: function(record)
	{
		this.hideSuggestionList();
		this.handleSelection(record);
		this.lastQuery = '';
	},

	/**
	 * Function which is use to hide suggestion list and
	 * It will also remove all records from suggestion list store.
	 */
	hideSuggestionList: function ()
	{
		if (this.isExpanded()) {
			this.collapse();
			this.el.dom.value = '';
			this.sizeInputfield();
			this.store.removeAll(true);
		}
	},

	/**
	 * Calculate the desired width of the dropdown {@link #list}.
	 * This should only be called when {@link #list} has been filled with the
	 * data which is going to be shown, as we calculate the desired width
	 * based on the current contents of the {@link #list}.
	 *
	 * This means that if a subclass overrides {@link #tpl} and adds all sort
	 * of extra text, this function should still be relatively accurate in the
	 * required width.
	 *
	 * @private
	 */
	getDesiredListWidth: function()
	{
		var metric = this.getListTextMetric();
		var desiredWidth = 0;

		if (this.innerList.dom.children.length > 0) {
			for (var i = 0, len = this.innerList.dom.children.length; i < len; i++) {
				var child = Ext.fly(this.innerList.dom.children[i]);
				var width = child.getPadding('lr') + metric.getWidth(child.dom.innerHTML);

				if (width > desiredWidth) {
					desiredWidth = width;
				}
			}
		} else {
			desiredWidth = metric.getWidth(this.innerList.dom.innerHTML);
		}

		return desiredWidth;
	},

	/**
	 * Called just before the {@link #store} is going to call 'list' to the server.
	 * This will show a {@link #loadingText} in the {@link #list.
	 *
	 * We make sure that we resize our {@link #list} here using {@link #getDesiredListWidth}
	 * to guarantee that our {@link #loadingText} and loading image receive sufficient
	 * space.
	 *
	 * @private
	 */
	onBeforeLoad: function()
	{
		Zarafa.common.ui.BoxField.superclass.onBeforeLoad.apply(this, arguments);

		var width = this.getDesiredListWidth();
		this.restrictWidth(width);
	},

	/**
	 * Called when the {@link #store} has loaded and we can fill the dropdown list
	 * with the suggestionlist. Based on the records in the store we will also
	 * determine the {@link #getDesiredListWidth} desired width of the list.
	 *
	 * Note that this has been called after the dropdown box has been filled with
	 * the contents. Hence it is safe to call {@link #getDesiredListWidth} here.
	 *
	 * @param {Ext.data.Store} store The store which fired the event
	 * @param {Ext.data.Record[]} records The records which were loaded from the server
	 * @param {Object} options The options object which were provided during load
	 * @private
	 */
	onLoad: function(store, records, options)
	{
		if (this.hasFocus && (this.store.getCount() > 0 || this.listEmptyText)) {
			var desiredWidth = this.getDesiredListWidth();

			// getDesiredListWidth obtains the desired width for
			// the 'innerList'. The 'list' however is slightly larger.
			desiredWidth += this.list.getFrameWidth('lr');

			// When there are sufficient results, a scrollbar is shown.
			// Add the required space.
			desiredWidth += Ext.getScrollBarWidth();

			// Resize the dropdownbox.
			this.restrictWidth(desiredWidth);
		}

		Zarafa.common.ui.BoxField.superclass.onLoad.apply(this, arguments);
	},

	/**
	 * Change the width of the {@link #list} and {@link #innerList}. We never
	 * fill out the dropdown list to the complete width of the field, and the
	 * list itself is aligned to the {@link #el} element (which can be positioned
	 * on various locations in the field depending on the number of {@link #items boxes}
	 * which have been rendered.
	 *
	 * The width must fall within the the {@link #minListWidth} and the {@link #container}
	 * width range. Depending on the width, and the position of the input element, we
	 * also have to force the list a few pixels to the left of the {@link #el} if by
	 * that we can prevent the list to exceed the limits of the field itself.
	 *
	 * @param {Number} width The desired width of the list
	 * @private
	 */
	restrictWidth: function(width)
	{
		var target = this.getEl();
		var container = this.getResizeEl();

		var offset = target.getOffsetsTo(container);
		var fieldWidth = container.getWidth();

		var desiredOffsetLeft = 0;
		var desiredOffsetBottom = target.getFrameWidth('b');
		var desiredWidth = width;

		// Establish the actual width we can apply for this list.
		if (this.minListWidth > desiredWidth) {
			desiredWidth = this.minListWidth;
		} else if (fieldWidth < desiredWidth) {
			// Don't restrict width of suggestion-list in case of zarafa.userlistbox
			// because container is smaller then the width of content of suggestion list.
			// And we don't want the ellipsis.
			if (this.boxType !== "zarafa.userlistbox"){
				desiredWidth = fieldWidth;
			}
		}

		// We now have the width we need. Since we are going to align
		// the list to the input field, we must check the available width,
		// from the start of the input field to the end of the box field.
		var availableWidth = container.getWidth() - offset[0];
		if (availableWidth < desiredWidth) {
			// Don't adjust left-offset of suggestion-list in case of zarafa.userlistbox.
			// it will result into negative value because container is smaller then
			// the desired-width.
			if (this.boxType !== "zarafa.userlistbox") {
				desiredOffsetLeft = availableWidth - desiredWidth;
			}
		}

		// But what if so much size was needed, that the offset would cause
		// the dropdown box to appear to the left of the boxfield...
		// Lets limit that case as well, and make sure we decrease the
		// width.
		if (desiredOffsetLeft > offset[0]) {
			desiredWidth -= desiredOffsetLeft - offset[0];
			desiredOffsetLeft = -offset[0];
		}

		this.list.setWidth(desiredWidth);
		this.innerList.setWidth(desiredWidth - this.list.getFrameWidth('lr'));
		this.listAlign = [ 'tl-bl', [ desiredOffsetLeft, desiredOffsetBottom ] ];
	},

	/**
	 * Function called during the {@link #render rendering} of this component. This will
	 * wrap {@link #el} into a 'li' element which is saved as {@link #inputEl}. This element
	 * will then be wrapped into a 'ul' element which is saved as {@link #wrapBoxesEl}, and
	 * will later contain all {@link #items} as well.
	 * @param {Ext.Container} ct The container in which the component is being rendered.
	 * @param {NUmber} position The position within the container where the component will be rendered.
	 * @private
	 */
	onRender: function(ct, position)
	{
		Zarafa.common.ui.BoxField.superclass.onRender.call(this, ct, position);

		// Apply some extra CSS classes to the wrap element.
		if (!Ext.isEmpty(this.wrapCls)) {
			this.wrap.addClass(this.wrapCls);
		}

		// If autoHeight is set to false we need to set our own height. Otherwise the CSS
		// class x-form-text will set it to a default height.
		if(this.autoHeight === false){
			if(Ext.isDefined(this.height)) {
				this.wrap.setHeight(this.height);
			}
		} else {
			 this.wrap.applyStyles('height: auto;');
		}

		// Create a focus element which is used for focussing a box.
		this.boxFocusEl = this.wrap.createChild({
			tag: 'a',
			href: '#',
			// Disable tab-index, and position it somewhere where it cannot be seen
			// by the user. This will make the element completely invisible for the
			// user while we can benefit from the focus capabilities.
			tabindex: -1,
			style: 'position: absolute; left:-10000px; top:-10000px;'
		});

		// Wraps the items (boxes) in an UL-tag
		this.wrapBoxesEl = this.el.wrap({
			tag: 'ul',
			role: 'listbox'
		});
		if (this.fieldLabel) {
			this.wrapBoxesEl.set({ 'aria-label': this.fieldLabel });
		}

		this.inputEl = this.el.wrap({
			tag: 'li',
			cls: 'x-zarafa-boxfield-input-item'
		});

		if (this.border === false) {
			this.el.addClass('x-zarafa-boxfield-input-noborder');
		}

		// If a boxStore was previously configured, we
		// can now set it (this will cause the boxes to
		// be rendered).
		if (Ext.isDefined(this.boxStore)) {
			this.setBoxStore(this.boxStore, true);
		}
	},

	/**
	 * Callback function from {@link #setEditable} when the {@link #editable} state
	 * has been changed. This will go over all {@link #items} and change the
	 * {@link Zarafa.common.ui.Box#editable} state on there.
	 * @private
	 */
	updateEditState: function()
	{
		Zarafa.common.ui.BoxField.superclass.updateEditState.apply(this, arguments);

		this.inputEl.setVisible(this.editable);
		this.items.each(function(box) {
			box.setEditable(this.editable);
		});
	},

	/**
	 * Return the {@link Ext.Element} which acts as as the content element. Normally
	 * this is {@link #el}, but as this combobox is more a container, we return {@link #wrap}.
	 *
	 * @return {Ext.Element} The content element
	 * @private
	 */
	getContentTarget: function()
	{
		return this.wrap;
	},

	/**
	 * Called after the component is resized. This will automatically update
	 * the sizing of the {@link #sizeInputfield input} and {@link #sizeContainer container}.
	 *
	 * @param {Number} adjWidth The box-adjusted width that was set
	 * @param {Number} adjHeight The box-adjusted height that was set
	 * @param {Number} rawWidth The width that was originally specified
	 * @param {Number} rawHeight The height that was originally specified
	 * @private
	 */
	onResize: function(w, h, rw, rh)
	{
		Zarafa.common.ui.BoxField.superclass.onResize.call(this, w, h, rw, rh);

		this.sizeInputfield();
		this.sizeContainer();
	},

	/**
	 * Apply auto-sizing to the component. When {@link #autoHeight} is true,
	 * we apply automatic height calculations to make sure the component
	 * is always correctly sized. When {@link #boxMaxHeight} is also provided,
	 * we check if the limit has been exceeded and display the
	 * {@link #setAutoScroll scrollbar} if needed.
	 *
	 * @private
	 */
	sizeContainer: function()
	{
		if (!this.rendered || this.autoHeight === false) {
			return false;
		}

		var target = this.getResizeEl();
		if ( !Ext.isDefined(target) || !Ext.isDefined(target.dom) ){
			// The element has been removed already. This is possible
			// if a box was removed with animation at the same time that
			// this element was removed. (See Zarafa.common.ui.Box.doDestroy())
			return;
		}

		var outerHeight = target.getHeight();
		var innerHeight = target.dom.scrollHeight;
		var doLayout = false;

		if ( this.previousOuterHeight !== outerHeight ) {
			doLayout = true;
		}

		if (outerHeight > this.boxMaxHeight) {
			// The height of the box exceeds the maximum height,
			// enable the scrollbar and scroll to the bottom.
			target.setHeight(this.boxMaxHeight);
			if (this.initialConfig.autoScroll !== false) {
				this.setAutoScroll(true);
				target.scrollTo('top', outerHeight);
			}
			doLayout = true;
		} else if ( outerHeight === this.boxMaxHeight && innerHeight !== this.previousInnerHeight && innerHeight <= outerHeight -2 ) { // subtract 2 for the border
			// The scroll height is smaller then the height of the
			// box, this means we do not need the scrollbar anymore.
			target.setHeight('auto');
			if (this.initialConfig.autoScroll !== false) {
				this.setAutoScroll(false);
			}
			doLayout = true;
		}

		this.previousOuterHeight = outerHeight;
		this.previousInnerHeight = innerHeight;

		// Fire a resizeheight event so parent components can listen to it and
		// do a layout if they want to
		if (doLayout === true ) {
			this.fireEvent('resizeheight');
		}
	},

	/**
	 * Called when the {@link #ownerCt owner} has been laid out, this
	 * will obtain the new desired {@link #getDesiredBoxWidth boxWidth}
	 * and applies them to all available {@link #items boxes}.
	 * @private
	 */
	onParentLayout: function()
	{
		var width = this.getDesiredBoxWidth();
		if (Ext.isDefined(width)) {
			this.items.each(function(box) { box.setWidth(width); });
		}
	},

	/**
	 * Returns the desired width for the {@link #items boxes} which should be applied
	 * whenever the container resizes, or a new box is created. When {@link #listMode}
	 * is disabled, this function returns undefined, to ensure dynamic sizing of the box.
	 * @return {Number} The desired width for all boxes
	 * @private
	 */
	getDesiredBoxWidth: function()
	{
		if (this.listMode === true) {
			var target = this.getResizeEl();
			return target.getWidth() - target.getFrameWidth('lr') - this.el.getFrameWidth('lr');
		} else {
			return undefined;
		}
	},

	/**
	 * Apply auto-sizing to the {@link #el input element}.
	 * Whenever the contents of the input field has changed, or when
	 * the width of the entire component has changed, we must recalculate
	 * the desired width of the input element. Normally it should be only
	 * a few pixels wider then the typed text requires, with a maximum
	 * of the current width of the entire component (to prevent a
	 * horizontal scrollbar to appear while typing).
	 *
	 * @private
	 */
	sizeInputfield: function()
	{
		if (!this.rendered) {
			return false;
		}

		var target = this.getResizeEl();
		var width;

		if (this.listMode === true) {
			width = target.getWidth() - target.getFrameWidth('lr') - this.el.getFrameWidth('lr');
		} else {
			var metric = this.getInputTextMetric();

			// Calculate the desired width of the component based on the
			// required width for the input text.
			var value = Ext.util.Format.htmlEncode(this.el.dom.value);
			width = metric.getWidth(value);

			// Add spacing to the input field. This will ensure the
			// the input box will always have the correct minimum size.
			width += this.minInputFieldWidth;

			// Ensure the width does not exceed the component width.
			width = Math.min(width, target.getWidth());
		}

		// Store the current height, this way we can detect if the input
		// field has been wrapped to the next line after the resize.
		var oldHeight = target.getHeight();

		// Set the inputfield to the calculated width
		this.el.setSize(width, this.inputFieldHeight - this.el.getBorderWidth('tb'));

		// Check if linewrapping has occurred, and update the container accordingly.
		var newHeight = target.getHeight();
		if (this.listMode === true || oldHeight !== newHeight) {
			this.sizeContainer();
		}

		// When scrolling is enabled, we must always scroll to the
		// bottom of the container to ensure the input field is
		// fully visible.
		target.scrollTo('top', newHeight);
	},

	/**
	 * Called to handle the input when the user presses the handleInputKey or another trigger makes
	 * this component need to handle the input. Has to be overwritten to implement the desired
	 * behavior and the creation of the correct type of record.
	 * @param {String} value The value from the input field
	 * @protected
	 */
	handleInput: Ext.emptyFn,

	/**
	 * Called to handle a selection from the dropdown list. This function needs to
	 * convert the selected record into a record for the {@link #boxStore}.
	 * Has to be overwritten to implement the desired behavior of creation of the correct type
	 * of record.
	 * @param {Ext.data.Record} record The record which was selected from {@link #store}
	 * @protected
	 */
	handleSelection: Ext.emptyFn,

	/**
	 * Check {@link #el} for input and call {@link #handleInput} to convert
	 * the input into a new {@link Zarafa.common.ui.Box Box}.
	 * @private
	 */
	convertInputToBox: function()
	{
		var value = this.el.getValue();

		if (!Ext.isEmpty(value)) {
			this.el.dom.value = '';
			this.sizeInputfield();
			this.store.removeAll(true);
			this.handleInput(value);
		}
	},

	/**
	 * Called to filter out records before they are added to this field.
	 * This will loop over all records and call {@link #filterRecord} to
	 * check if the record should be visible or not.
	 * @param {Ext.data.Store} store The store
	 * @param {Ext.data.Record[]} records The records to filter
	 * @return {Ext.data.Record[]} The records which are visible
	 * @protected
	 */
	filterRecords: function(store, records)
	{
		var ret = [];

		for (var i = 0, len = records.length; i < len; i++) {
			var record = records[i];
			if (this.filterRecord(record)) {
				ret.push(record);
			}
		}

		return ret;
	},

	/**
	 * Called by {@link #filterRecords} to check if the given record
	 * must be visible in the field or not.
	 * @param {Ext.data.Record} record The record to filter
	 * @return {Boolean} True if the record should be visible, false otherwise
	 * @protected
	 */
	filterRecord: function(record)
	{
		return true;
	},

	/**
	 * Find the {@link Zarafa.common.ui.Box} which belongs to the given record.
	 * @param {Ext.data.Record} record The record for which the box is searched
	 * @return {Zarafa.common.ui.Box} The box which belongs to the given record
	 */
	getBoxForRecord: function(record)
	{
		return this.items.find(function(box) { return box.record === record; });
	},

	/**
	 * Call before the field is being blurred. When we still have an
	 * {@link #currentFocus boxfocus} it must be removed now.
	 *
	 * @private
	 */
	beforeBlur: function()
	{
		Zarafa.common.ui.BoxField.superclass.beforeBlur.apply(this, arguments);
		this.convertInputToBox();
		this.boxBlur();
	},

	/**
	 * Put the focus on a particular box. This will call {@link Zarafa.common.ui.Box#blur}
	 * on the {@link #currentFocus currently focussed} element. And then call
	 * {@link Zarafa.common.ui.Box#focus} on the new box which will be set in {@link #currentFocus}.
	 *
	 * @param {Zarafa.common.ui.Box} box The box to put the focus on
	 * @private
	 */
	boxFocus: function(box)
	{
		if (this.currentFocus) {
			this.currentFocus.blur();
		}

		this.currentFocus = box;

		if (this.currentFocus) {
			this.currentFocus.focus();
		}
	},

	/**
	 * Remove the focus from the {@link #currentFocus currently focussed} box.
	 * This will call {@link Zarafa.common.ui.Box#blur} on the box, and then
	 * reset {@link #currentFocus}.
	 * @private
	 */
	boxBlur: function()
	{
		if (this.currentFocus) {
			this.currentFocus.blur();
		}

		this.currentFocus = false;
	},

	/**
	 * Try to focus this component.
	 * @param {Boolean} selectText (optional) If applicable, true to also select the text in this component
	 * @param {Boolean/Number} delay (optional) Delay the focus this number of milliseconds (true for 10 milliseconds)
	 * @return {Ext.Component} this
	 */
	focus: function(selectText, delay)
	{
		if (delay) {
			this.focusTask = new Ext.util.DelayedTask(this.focus, this, [selectText, false]);
			this.focusTask.delay(Ext.isNumber(delay) ? delay : 10);
			return;
		}
		if (this.rendered && !this.isDestroyed) {
			this.inputFocus(undefined, selectText);
		}
		return this;
	},

	/**
	 * Put the focus on the {@link #el Input element}, when a box is already
	 * {@link #currentFocus focussed} it will be {@link #boxBlur blurred} after
	 * which the focus is put on the {@link #el Input element}.
	 *
	 * When provided, the new caret position for the input field can also be provided.
	 * @param {Number} caretPos (optional) The desired caret position
	 * @private
	 */
	inputFocus: function(caretPos, selectText)
	{
		this.boxBlur();
		this.el.focus();
		if (!Ext.isEmpty(caretPos)) {
			Zarafa.core.Util.setCaretPosition(this.el, caretPos);
		}
		if (selectText === true) {
			this.el.dom.select();
		}
	},

	/**
	 * Remove the focus from the {@link #el} element.
	 * @private
	 */
	inputBlur: function()
	{
		this.el.blur();
	},

	/**
	 * Event handler which is fired when the user clicked anywhere
	 * on the {@link #container}, but not on a specific {@link #items box}.
	 * In this case, we redirect our focus to the input element.
	 * @private
	 */
	onContainerClick: function(e, element)
	{
		this.collapse();
		if (this.editable) {
			if (element === this.el.dom) {
				this.inputFocus();
			} else {
				// If element is visible then and only we can set focus on the element.
				if(this.el.isVisible()) {
					// Stop event propagation.
					e.stopEvent();
					this.inputFocus(this.el.dom.value.length);
				}
			}
		} else if (!this.currentFocus) {
			this.boxFocus(this.items.last());
		}
	},

	/**
	 * Create the {@link Ext.KeyMap} for an {@link Ext.Element} which
	 * serves as the focus element for the {@link #items boxes}.
	 * @param {Ext.Element} focusEl The element which acts as the focusElement for the boxes.
	 * @return {Ext.KeyMap} The keymap for the focus element.
	 * @private
	 */
	createBoxKeyMap: function(focusEl)
	{
		var bindings = [{
			key: [
				Ext.EventObject.ENTER
			],
			handler: this.onBoxKeyEnter,
			scope: this
		},{
			key: [
				Ext.EventObject.UP,
				Ext.EventObject.PAGE_UP
			],
			handler: this.onBoxKeyUp,
			scope: this
		},{
			key: [
				Ext.EventObject.DOWN,
				Ext.EventObject.PAGE_DOWN
			],
			handler: this.onBoxKeyDown,
			scope: this
		},{
			key: [
				Ext.EventObject.LEFT
			],
			handler: this.onBoxKeyLeft,
			scope: this
		},{
			key: [
				Ext.EventObject.RIGHT
			],
			handler: this.onBoxKeyRight,
			scope: this
		},{
			key: [
				Ext.EventObject.HOME
			],
			handler: this.onBoxKeyHome,
			scope: this
		},{
			key: [
				Ext.EventObject.END
			],
			handler: this.onBoxKeyEnd,
			scope: this
		},{
			key: [
				Ext.EventObject.BACKSPACE
			],
			handler: this.onBoxKeyBackspace,
			scope: this
		},{
			key: [
				Ext.EventObject.DELETE
			],
			handler: this.onBoxKeyDelete,
			scope: this
		},{
			key: [
				Ext.EventObject.C
			],
			ctrl: true,
			handler: this.onBoxKeyCopy,
			scope: this
		}];

		// disable modifier keys in the bindings
		for(var i = 0, len = bindings.length; i < len; i++) {
			var binding = bindings[i];

			Ext.applyIf(binding, {
				shift: false,
				alt: false,
				ctrl: false
			});
		}
		return new Ext.KeyMap(focusEl, bindings);
	},

	/**
	 * Event handler for the keydown event of the {@link Ext.KeyMap KeyMap}
	 * when the user wants to copy email address of resolved recipient.
	 */
	onBoxKeyCopy: function ()
	{
		Zarafa.common.Actions.copyEmailAddress(this.currentFocus.record);
	},

	/**
	 * Key handler for the {@link Ext.EventObject.ENTER ENTER} event
	 * for the {@link #boxFocusEl Box focus element}.
	 *
	 * This will perform the same action as {@link #doBoxDblClick}.
	 *
	 * @param {Number} key The key which was pressed
	 * @param {Ext.EventObject} e The event object which fired the event
	 * @private
	 */
	onBoxKeyEnter: function(key, e)
	{
		e.stopEvent();

		this.doBoxDblClick(this.currentFocus);
	},

	/**
	 * Key handler for the {@link Ext.EventObject.UP} event
	 * for the {@link #boxFocusEl Box focus element}.
	 *
	 * This will select the box left to the currently selected box.
	 * If this is the first box of the {@link #items} array, the focus
	 * on the items is lost, and the {@link #el input element} is focussed.
	 *
	 * @param {Number} key The key which was pressed
	 * @param {Ext.EventObject} e The event object which fired the event
	 * @private
	 */
	onBoxKeyUp: function(key, e)
	{
		this.onBoxKeyLeft(key, e);
	},

	/**
	 * Key handler for the {@link Ext.EventObject.DOWN} event
	 * for the {@link #boxFocusEl Box focus element}.
	 *
	 * This will select the box right to the currently selected box.
	 * If this is the last box of the {@link #items} array, the focus
	 * on the items is lost, and the {@link #el input element} is focussed.
	 *
	 * @param {Number} key The key which was pressed
	 * @param {Ext.EventObject} e The event object which fired the event
	 * @private
	 */
	onBoxKeyDown: function(key, e)
	{
		this.onBoxKeyRight(key, e);
	},

	/**
	 * Key handler for the {@link Ext.EventObject.LEFT} event
	 * for the {@link #boxFocusEl Box focus element}.
	 *
	 * This will select the box left to the currently selected box.
	 * If this is the first box of the {@link #items} array, the focus
	 * remains on the current box.
	 *
	 * @param {Number} key The key which was pressed
	 * @param {Ext.EventObject} e The event object which fired the event
	 * @private
	 */
	onBoxKeyLeft: function(key, e)
	{
		e.stopEvent();

		var index = this.items.indexOf(this.currentFocus);
		if (index !== 0) {
			this.boxFocus(this.items.itemAt(index - 1));
		}
	},

	/**
	 * Key handler for the {@link Ext.EventObject.RIGHT} event
	 * for the {@link #boxFocusEl Box focus element}.
	 *
	 * This will select the box right to the currently selected box.
	 * If this is the last box of the {@link #items} array, the focus
	 * on the items is lost, and the {@link #el input element} is focussed.
	 *
	 * @param {Number} key The key which was pressed
	 * @param {Ext.EventObject} e The event object which fired the event
	 * @private
	 */
	onBoxKeyRight: function(key, e)
	{
		e.stopEvent();

		var index = this.items.indexOf(this.currentFocus);
		if (index !== this.items.getCount() - 1) {
			this.boxFocus(this.items.itemAt(index + 1));
		} else if (this.editable) {
			this.inputFocus(0);
		}
	},

	/**
	 * Key handler for the {@link Ext.EventObject.HOME} event
	 * for the {@link #boxFocusEl Box focus element}.
	 *
	 * This will select the first item in the {@link #items} array.
	 *
	 * @param {Number} key The key which was pressed
	 * @param {Ext.EventObject} e The event object which fired the event
	 * @private
	 */
	onBoxKeyHome: function(key, e)
	{
		e.stopEvent();

		var item = this.items.first();
		if (!item.hasFocus()) {
			this.boxFocus(item);
		}
	},

	/**
	 * Key handler for the {@link Ext.EventObject.END} event
	 * for the {@link #boxFocusEl Box focus element}.
	 *
	 * This will select the last item in the {@link #items} array.
	 *
	 * @param {Number} key The key which was pressed
	 * @param {Ext.EventObject} e The event object which fired the event
	 * @private
	 */
	onBoxKeyEnd: function(key, e)
	{
		e.stopEvent();

		var item = this.items.last();
		if (!item.hasFocus()) {
			this.boxFocus(item);
		} else if (this.editable) {
			this.inputFocus(this.el.dom.value.length);
		}
	},

	/**
	 * Key handler for the {@link Ext.EventObject.BACKSPACE} event
	 * for the {@link #boxFocusEl Box focus element}.
	 *
	 * This will delete the currently selected item from the field,
	 * and will select the item to the left of the deleted item.
	 *
	 * @param {Number} key The key which was pressed
	 * @param {Ext.EventObject} e The event object which fired the event
	 * @private
	 */
	onBoxKeyBackspace: function(key, e)
	{
		e.stopEvent();

		if (this.editable !== false) {
			var index = this.items.indexOf(this.currentFocus);

			this.doBoxRemove(this.currentFocus);

			if (index > 0) {
				this.boxFocus(this.items.itemAt(index - 1));
			} else if (this.items.getCount() > 0) {
				this.boxFocus(this.items.itemAt(0));
			} else {
				this.inputFocus();
			}
		}
	},

	/**
	 * Key handler for the {@link Ext.EventObject.DELETE} event
	 * for the {@link #boxFocusEl Box focus element}.
	 *
	 * This.will delete the currently selected item from the field,
	 * and will select the item to the right of the deleted item.
	 *
	 * @param {Number} key The key which was pressed
	 * @param {Ext.EventObject} e The event object which fired the event
	 * @private
	 */
	onBoxKeyDelete: function(key, e)
	{
		e.stopEvent();

		if (this.editable !== false) {
			var index = this.items.indexOf(this.currentFocus);

			this.doBoxRemove(this.currentFocus);

			if (index < this.items.getCount()) {
				this.boxFocus(this.items.itemAt(index));
			} else {
				this.inputFocus(0);
			}
		}
	},

	/**
	 * Create the {@link Ext.KeyMap} for the {@link #el}.
	 * This KeyMap is only used for the {@link handleInputKey}, which needs its character code detected.
	 * That is why the 'keypress' event is used instead of the default 'keydown',
	 * which does not report character codes.
	 *
	 * @param {Ext.Element} focusEl The element which acts as the focusElement for the input element.
	 * @return {Ext.KeyMap} The keymap for the focus element.
	 * @private
	 */
	createInputKeyMap: function(focusEl)
	{
		return new Ext.KeyMap(focusEl, [{
			key: [
				this.handleInputKey
			],
			shift: false,
			alt: false,
			ctrl: false,
			handler: this.onInputKey,
			scope: this
		}], 'keypress');
	},

	/**
	 * Create the {@link Ext.KeyMap} for the {@link #el} to handle
	 * various key board controls for suggestion {@link #list}.
	 *
	 * @param {Ext.Element} element The {@link Zarafa.common.ui.BoxField#el combo box} element.
	 * @return {Ext.KeyMap} The keymap for the {@link Zarafa.common.ui.BoxField#el combo box} element.
	 * @private
	 */
	createListKeyMap: function(element)
	{
		// Listen to DELETE key event which remove record from suggestion list.
		return new Ext.KeyMap(element, {
			key: [
				Ext.EventObject.DELETE
			],
			handler: this.onListKeyDelete,
			scope: this,
			shift: false,
			alt: false,
			ctrl: false
		});
	},

	/**
	 * Create the {@link Ext.KeyMap} for the {@link #el}.
	 * This KeyMap is used for special keys, such as enter, page up/down, arrows, etc.
	 *
	 * @param {Ext.Element} focusEl The element which acts as the focusElement for the input element.
	 * @return {Ext.KeyMap} The keymap for the focus element.
	 * @private
	 */
	createSpecialInputKeyMap: function(focusEl)
	{
		var bindings = [{
			key: [
				Ext.EventObject.ENTER
			],
			handler: this.onInputKeyEnter,
			scope: this
		},{
			key: [
				Ext.EventObject.TAB
			],
			handler: this.onInputKeyTab,
			scope: this
		},{
			key: [
				Ext.EventObject.UP,
				Ext.EventObject.PAGE_UP
			],
			handler: this.onInputKeyUp,
			scope: this
		},{
			key: [
				Ext.EventObject.LEFT
			],
			handler: this.onInputKeyLeft,
			scope: this
		},{
			key: [
				Ext.EventObject.HOME
			],
			handler: this.onInputKeyHome,
			scope: this
		},{
			key: [
				Ext.EventObject.BACKSPACE
			],
			handler: this.onInputKeyBackspace,
			scope: this
		}];

		// disable modifier keys in the bindings
		for(var i = 0, len = bindings.length; i < len; i++) {
			var binding = bindings[i];

			Ext.apply(binding, {
				shift: false,
				alt: false,
				ctrl: false
			});
		}

		// Add combination of TAB with shift as well to trigger blur and handle input text
		bindings.push({
			key: [
				Ext.EventObject.TAB
			],
			shift: true,
			alt: false,
			ctrl: false,
			handler: this.onInputKeyTab,
			scope: this
		});

		return new Ext.KeyMap(focusEl, bindings);
	},

	/**
	 * Generic {@link Ext.Element#keydown} event handler for the {@link #el input element}.
	 * This handler is run for all keys which were not handled by the {@link #inputKeyMap}.
	 * This will simply {@link #sizeInputfield resize} the {@link #el input element}.
	 *
	 * @param {Number} key The key which was pressed
	 * @param {Ext.EventObject} e The event object which fired the event
	 * @private
	 */
	onKeyDownHandler: function(e)
	{
		// Don't resize the inputfield on special keys (arrow-keys, backspace, etc)
		// for some weird reason these keys would trigger the input field to become
		// larger, while no input is being added...
		if (!e.isSpecialKey()) {
			this.collapse();
			this.sizeInputfield();
		}
	},

	/**
	 * Key handler for the {@link #handleInputKey} event
	 * for the {@link #el input element}.
	 *
	 * This will clear the {@link #el} element, and send the
	 * old value to {@link #handleInput} to create a new {@link #items box}.
	 *
	 * @param {Number} key The key which was pressed
	 * @param {Ext.EventObject} e The event object which fired the event
	 * @private
	 */
	onInputKey: function(key, e)
	{
		e.stopEvent();
		this.convertInputToBox();
	},

	/**
	 * Key handler for the {@link Ext.EventObject.ENTER} event
	 * for the {@link #el input element}.
	 *
	 * This will clear the {@link #el} element, and send the
	 * old value to {@link #handleInput} to create a new {@link #items box}.
	 *
	 * @param {Number} key The key which was pressed
	 * @param {Ext.EventObject} e The event object which fired the event
	 * @private
	 */
	onInputKeyEnter: function(key, e)
	{
		// Make sure we are not expanded, because in that
		// case the ENTER command is reserved for selecting
		// an item from the dropdown list.
		if (!(this.isExpanded() && this.store.getCount() > 0)) {
			e.stopEvent();
			this.convertInputToBox();
		}
	},

	/**
	 * Key handler for the {@link Ext.EventObject.TAB} event
	 * for the {@link #el input element}.
	 *
	 * @param {Number} key The key which was pressed
	 * @param {Ext.EventObject} e The event object which fired the event
	 * @private
	 */
	onInputKeyTab: function(key, e)
	{
		//If the user entered something, we should prevent default tab behavior
		if (!Ext.isEmpty(this.getValue())) {
			e.stopEvent();

			if (!(this.isExpanded() && this.store.getCount() > 0)){
			  this.convertInputToBox();
			}
		} else {
			//Trigger a blur to remove the focus class from the element
			this.triggerBlur();
		}
	},

	/**
	 * Key handler for the {@link Ext.EventObject.UP} event
	 * for the {@link #el input element}.
	 *
	 * When the cursor is at the most-left position of the {@link #el input element}
	 * then the box to the left of the input element is selected.
	 *
	 * @param {Number} key The key which was pressed
	 * @param {Ext.EventObject} e The event object which fired the event
	 * @private
	 */
	onInputKeyUp: function(key, e)
	{
		this.onInputKeyLeft(key, e);
	},

	/**
	 * Key handler for the {@link Ext.EventObject.LEFT} event
	 * for the {@link #el input element}.
	 *
	 * When the cursor is at the most-left position of the {@link #el input element}
	 * then the box to the left of the input element is selected.
	 *
	 * @param {Number} key The key which was pressed
	 * @param {Ext.EventObject} e The event object which fired the event
	 * @private
	 */
	onInputKeyLeft: function(key, e)
	{
		var caret = Zarafa.core.Util.getSelectionRange(this.el);
		if (caret.start === 0 && caret.end === 0 && this.items.getCount() > 0) {
			e.stopEvent();
			this.boxFocus(this.items.last());
		}
	},

	/**
	 * Key handler for the {@link Ext.EventObject.HOME} event
	 * for the {@link #el input element}.
	 *
	 * When the cursor is at the most-left position of the {@link #el input element}
	 * then the first box of {@link #items} is selected.
	 *
	 * @param {Number} key The key which was pressed
	 * @param {Ext.EventObject} e The event object which fired the event
	 * @private
	 */
	onInputKeyHome: function(key, e)
	{
		var caret = Zarafa.core.Util.getSelectionRange(this.el);
		if (caret.start === 0 && caret.end === 0 && this.items.getCount() > 0) {
			e.stopEvent();
			this.boxFocus(this.items.first());
		}
	},

	/**
	 * Key handler for the {@link Ext.EventObject.BACKSPACE event
	 * for the {@link #el input element}.
	 *
	 * When the cursor is at the most-left position of the {@link #el input element}
	 * then the box to the left of the input element is selected (so that pressing
	 * 'backspace' again will delete it.
	 *
	 * @param {Number} key The key which was pressed
	 * @param {Ext.EventObject} e The event object which fired the event
	 * @private
	 */
	onInputKeyBackspace: function(key, e)
	{
		var caret = Zarafa.core.Util.getSelectionRange(this.el);
		if (caret.start === 0 && caret.end === 0 && this.items.getCount() > 0) {
			e.stopEvent();
			this.boxFocus(this.items.last());
		}
	},

	/**
	 * Key handler for the {@link Ext.EventObject.DELETE} event
	 * for the {@link #el element} defined by {@link Ext.KeyMap}.
	 *
	 * This will remove current record identified by {@link #selectedIndex} from
	 * {@link Zarafa.common.recipientfield.data.SuggestionListStore SuggestionListStore}.
	 *
	 * @param {Number} key The key which was pressed
	 * @param {Ext.EventObject} e The event object which fired the event
	 * @private
	 */
	onListKeyDelete: function(key, e)
	{
		var store = this.getStore();

		if (store.getCount() !== 0) {
			// Prevent default browser behaviour of deleting characters from input text
			// as we are going to use this event to delete records from list.
			e.stopEvent();
			store.removeAt(this.selectedIndex);

			// If deleted item is the last one in the list than previous item of the deleted item will be selected.
			if (store.getCount() == this.selectedIndex) {
				this.selectPrev();
			} else {
				this.select(this.selectedIndex);
			}
			this.restrictHeight();
		}
	},

	/**
	 * Event handler for the {@link Ext.DataView#extraitemclick} event.
	 * This is fired when {@link #extraItemSelector} node from dropdown-list is clicked.
	 * Just remove selected node by calling {@link #onListKeyDelete} function.
	 * @param {Ext.DataView} The underlying {@link Ext.DataView} instance responsible to create dropdown-list.
	 * @param {Number} index The index of the target node
	 * @param {HTMLElement} extraItem The target node
	 * @param {Ext.EventObject} e The raw event object
	 * @private
	 */
	onExtraItemClick: function(dataView, index, extraItem, e)
	{
		this.onListKeyDelete(false, e);
	},

	/**
	 * Event handler for the 'paste' event on the {@link #el input element}.
	 * This is fired by the special {@link Zarafa.core.Event#addPasteEventHandler}
	 * event handler. This will resize the input field, to fit the field to the
	 * new contents.
	 * @private
	 */
	onPaste: function()
	{
		this.sizeInputfield();
	},

	/**
	 * Event handler for the {@link Ext.data.Store#datachanged} event from the {@link #boxStore}.
	 * This will {@link #clearBoxes clear all boxes} and then {@link #loadBoxes rebuild them}.
	 * @param {Ext.data.Store} store The store which fired the event
	 * @private
	 */
	onBoxStoreDataChanged: function(store)
	{
		this.clearBoxes();
		this.loadBoxes(store);
	},

	/**
	 * Event handler for the {@link Ext.data.Store#clear} event from the {@link #boxStore}.
	 * This will {@link #clearBoxes clear all boxes}.
	 * @private
	 */
	onBoxStoreClear: function()
	{
		this.clearBoxes();
	},

	/**
	 * Event handler for the {@link Ext.data.Store#add} event from the {@link #boxStore}.
	 * This will check if the provided records should be {@link #filterRecords shown} inside
	 * this field, and those that are not filtered will be {@link #addBox added}.
	 * @param {Ext.data.Store} store The store which fired the event
	 * @param {Ext.data.Record[]} records The records which were added
	 * @param {Number} index The index position at which the records where added
	 * @private
	 */
	onBoxStoreAdd: function(store, records, index)
	{
		if (!Array.isArray(records)) {
			records = [ records ];
		}

		records = this.filterRecords(store, records);

		for (var i = 0; i < records.length; i++) {
			this.addBox(records[i]);
		}
	},

	/**
	 * Event handler for the {@link Ext.data.Store#remove} event from the {@link #boxStore}.
	 * This will {@link #removeBox remove} the box which is attached to the given record.
	 * @param {Ext.data.Store} store The store which fired the event
	 * @param {Ext.data.Record} record The record which was removed
	 * @param {Number} index The position from where the record was removed
	 * @private
	 */
	onBoxStoreRemove: function(store, record, index)
	{
		this.removeBox(record);
	},

	/**
	 * Event handler for the {@link Ext.data.Store#update} event from the {@link #boxStore}.
	 * This will {@link Zarafa.common.ui.Box#update update} the box with the new
	 * {@link Ext.data.Record#data record data}.
	 * When the field is updated, the modified record may have changed whether it passes the field's filter or not.
	 * Therefore, the function adds or removes this record's box.
	 * @param {Ext.data.Store} store The store which fired the event
	 * @param {Ext.data.Record} record THhe record which was updated
	 * @private
	 */
	onBoxStoreUpdate: function(store, record)
	{
		if (this.filterRecord(record)) {
			var box = this.getBoxForRecord(record);
			if (box) {
				// record passes filter and has a box - just update
				box.update(record);
			} else {
				// record passes the filter and does not have a box in this field yet - add it
				this.addBox(record);
			}
		} else {
			// if this record does not pass the filter, remove its box
			this.removeBox(record);
		}
	},

	/**
	 * Callback function from {@link Zarafa.common.ui.Box} which indicates
	 * that the box has been clicked. This will {@link #boxFocus focus the
	 * box}, and fire the {@link #boxclick} event.
	 * @param {Zarafa.common.ui.Box} box The box which called this function
	 */
	doBoxClick: function(box)
	{
		this.boxFocus(box);

		this.fireEvent('boxclick', this, box, box.record);
	},

	/**
	 * Callback function from {@link Zarafa.common.ui.Box} which indicates
	 * that the box has been doubleclicked. This will fire the
	 * {@link #boxdblclick} event.
	 * @param {Zarafa.common.ui.Box} box The box which called this function
	 */
	doBoxDblClick: function(box)
	{
		this.fireEvent('boxdblclick', this, box, box.record);
	},

	/**
	 * Callback function from {@link Zarafa.common.ui.Box} which indicates
	 * that the contextmenu has been requested for the given box. This will
	 * {@link #boxFocus focus the box}, and fire the {@link #boxcontextmenu} event.
	 * @param {Zarafa.common.ui.Box} box The box which called this function
	 */
	doBoxContextMenu: function(box)
	{
		this.boxFocus(box);

		this.fireEvent('boxcontextmenu', this, box, box.record);
	},

	/**
	 * Callback function from {@link Zarafa.common.ui.Box} which indicates that
	 * the box has received the focus. This will focus the {@link #boxFocusEl}
	 * and {@link Ext.Element#scrollIntoView scroll the box into view}. Finally
	 * the {@link #boxfocus} event.
	 * @param {Zarafa.common.ui.Box} box The box which called this function
	 */
	doBoxFocus: function(box)
	{
		// It could happen a box is being focussed without
		// the field being focussed.
		if (this.hasFocus !== true) {
			this.onFocus();
		}

		this.boxFocusEl.focus();
		box.getPositionEl().scrollIntoView(this.getContentTarget());

		this.fireEvent('boxfocus', this, box, box.record);
	},

	/**
	 * Callback function from {@link Zarafa.common.ui.Box} which indicates
	 * that the box has been blurred. This will blur the {@link #boxFocuEl}
	 * and will fire the {@link #boxblur} event.
	 * @param {Zarafa.common.ui.Box} box The box which called this function
	 */
	doBoxBlur: function(box)
	{
		this.boxFocusEl.blur();

		this.fireEvent('boxblur', this, box, box.record);
	},

	/**
	 * Callback function from {@link Zarafa.common.ui.Box} which indicates that
	 * the box is being removed by the user. This will fire the {@link #boxremove}
	 * event.
	 * @param {Zarafa.common.ui.Box} box The box which called this function
	 */
	doBoxRemove: function(box)
	{
		this.fireEvent('boxremove', this, box, box.record);
	},

	/**
	 * Event handler for the {@link #boxremove} event. This will remove
	 * the record belonging to the box from the {@link #boxStore}. Then
	 * {@link #sizeContainer} is called. This is needed because the
	 * removal of the box might have triggered a resize of the {@link #wrap}
	 * and thus we might no longer need the scrollbars.
	 * @param {Zarafa.common.ui.BoxField} field The field which has fired the event
	 * @param {Zarafa.common.ui.Box} box The box which has been removed
	 * @param {Ext.data.Record} record The record which belongs to the given box
	 * @private
	 */
	onBoxRemove: function(field, box, record)
	{
		this.boxStore.remove(record);
		if (this.boxStore.getCount() < this.boxLimit && this.initialConfig.readOnly !== true) {
			this.setReadOnly(false);
			this.inputFocus();
		}
	},

	/**
	 * Event handler for the {@link #boxadd} event. This will
	 * {@link #sizeContainer resize the container}. This is needed because
	 * the new box, might have triggered a resize of the {@link #wrap}
	 * and thus we might need scrollbars.
	 * @param {Zarafa.common.ui.BoxField} field The field which has fired the event
	 * @param {Zarafa.common.ui.Box} box The box which has been added
	 * @param {Ext.data.Record} record The record which belongs to the given box
	 * @private
	 */
	onBoxAdd: function(field, box, record)
	{
		if (this.boxStore.getCount() >= this.boxLimit) {
			this.setReadOnly(true);
		}
	},

	/**
	 * Adds a box to the field. It will instantiate the {@link Zarafa.common.ui.Box Box} component that will
	 * render the box according to the data in the supplied record. It will render the box inside
	 * the wrapBoxesEl. After rendering the box it will add the box to the {@link #items items} and
	 * fire the {@link #boxadd} event.
	 * @param {Ext.data.Record} record The record to add as a box into the field
	 * @private
	 */
	addBox: function(record)
	{
		/*
		 * Create the configuration object for the Box. The this.boxConfig is used as default and we
		 * apply the record and renderTo properties onto that object.
		 */
		var configObj = {};
		Ext.apply(configObj, {
			xtype: this.boxType,
			parent: this,
			record: record,
			editable: this.editable,
			width: this.getDesiredBoxWidth()
		}, this.boxConfig);

		var box = Ext.create(configObj);
		box.render(this.wrapBoxesEl, this.items.length);

		this.items.add(box);

		this.sizeContainer();

		this.fireEvent('boxadd', this, box, record);
	},

	/**
	 * Removes a {@link Zarafa.common.ui.Box Box} from the field.
	 * @param {Ext.data.Record} record The record which belongs to the box which must be removed.
	 * @private
	 */
	removeBox: function(record)
	{
		var box = this.getBoxForRecord(record);
		if (!box) {
			return;
		}

		var index = this.items.indexOf(box);
		box.doDestroy(this.enableAnim);
		this.items.remove(box);

		if (this.items.getCount() < this.boxLimit && this.initialConfig.readOnly !== true) {
			this.setReadOnly(false);
		}

		if (!this.currentFocus || this.currentFocus === box) {
			if (this.items.getCount() > index) {
				this.boxFocus(this.items.itemAt(index));
			} else {
				this.inputFocus();
			}
		}
	},

	/**
	 * Load all new {@link Zarafa.common.ui.Box boxes} based on the records from
	 * the {@link Ext.data.Store store}. This will first {@link #filterRecords filter}
	 * the records from the store before {@link #addBox adding} them.
	 * @param {Ext.data.Store} store
	 * @private
	 */
	loadBoxes: function(store)
	{
		var records = this.filterRecords(store, store.getRange());

		for (var i = 0; i < records.length; i++) {
			this.addBox(records[i]);
		}
	},

	/**
	 * This will clear all {@link #items boxes}. Each box will be
	 * {@link Zarafa.common.ui.Box#destroy destroyed}, and then removed from {@link #items}
	 * and will reset size of this BoxField.
	 * @private
	 */
	clearBoxes: function()
	{
		this.items.each(function(item) {
			item.destroy();
		});

		this.items.clear();
		this.sizeContainer();
	},

	/**
	 * Called when the component is being destroyed
	 * @private
	 */
	onDestroy: function()
	{
		Zarafa.common.ui.BoxField.superclass.onDestroy.apply(this, arguments);

		if (this.editable !== false) {
			Zarafa.core.Events.removePasteEventHandler(this, this.el, this.onPaste, this);
		}
	}
});

Ext.reg('zarafa.boxfield', Zarafa.common.ui.BoxField);
Ext.namespace('Zarafa.common.ui');

/**
 * @class Zarafa.common.ui.BoxFieldDragZone
 * @extends Ext.dd.DragZone
 * @xtype zarafa.boxfielddragzone
 *
 * A {@link Ext.dd.DragZone DragZone} installed on a {@link Zarafa.common.ui.BoxField BoxField}
 * Ensures cooperation between different BoxFields' drag and drop zones in order to enable the dragging
 * of boxes
 */
Zarafa.common.ui.BoxFieldDragZone = Ext.extend(Ext.dd.DragZone, {
	/**
	 * @cfg {Zarafa.common.ui.BoxFieldField} field The field on which this drag zone is installed
	 */
	field: undefined,

	/**
	 * Returns the data object associated with this drag source
	 * Here it contains:
	 * 	- ddel - a drag proxy that follows the mouse
	 * - sourceEl - original dragged element
	 * - repairXY - {Number[]} an array of [x, y] coordinates for repairing on illegal drop
	 * - draggedRecord - the {Zarafa.core.data.IPMRecord} record for the box that is being dragged
	 *
	 * @param {Ext.EventObject} e The mouse event
	 * @return {Object} data An object containing arbitrary data
	 */
	getDragData: function(e)
	{
		var sourceEl = e.getTarget('.x-zarafa-boxfield-item');
		if (sourceEl) {

			// create box element but in a div instead of li
			var d = Ext.DomHelper.createDom({
				tag: 'div',
				html: sourceEl.innerHTML,
				id: Ext.id(),
				cls: sourceEl.className,
				style: 'display: inline-block'
			});

			// ensure that child nodes have unique ids
			for (var i=0; i < d.childNodes.length; i++) {
				if (d.childNodes[i].id) {
					d.childNodes[i].id = Ext.id();
				}
			}

			return {
				ddel: d,
				sourceEl: sourceEl,
				repairXY: Ext.fly(sourceEl).getXY(),
				draggedRecord: this.field.items.get(sourceEl.id).record
			};
		}
	},

	/**
	 * Called before a repair of an invalid drop to get the XY to animate to. By default returns
	 * the XY of this.dragData.ddel
	 * @param {EventObject} e The mouse up event
	 * @return {Array} The xy location (e.g. [100, 200])
	 */
	getRepairXY: function()
	{
		return this.dragData.repairXY;
	}
});

Ext.reg('zarafa.boxfielddragzone', Zarafa.common.ui.BoxFieldDragZone);
Ext.namespace('Zarafa.common.ui');

/**
 * @class Zarafa.common.ui.BoxFieldDropZone
 * @extends Ext.dd.DropZone
 * @xtype zarafa.boxfielddropzone
 *
 * A {@link Ext.dd.DropZone DropZone} installed on a {@link Zarafa.common.ui.BoxField BoxField}
 * Ensures cooperation between different BoxFields' drag and drop zones in order to enable the dragging
 * of boxes
 */
Zarafa.common.ui.BoxFieldDropZone = Ext.extend(Ext.dd.DropZone, {
	/**
	 * @cfg {Zarafa.common.ui.BoxField} field The field on which this drag zone is installed
	 */
	field: undefined,

	/**
	 * Returns a custom data object associated with the DOM node that is the target of the event.
	 * @param {Event} e The event
	 * @return {Object} data The custom data
	 */
	getTargetFromEvent: function(e)
	{
		return e.getTarget('.x-zarafa-boxfield');
	},

	/**
	 * Called when the DropZone determines that a {@link Ext.dd.DragSource} has entered a drop node
	 * that has either been registered or detected by a configured implementation of {@link #getTargetFromEvent}.
	 * Override highlights the drop field, adding wrapFocusClass to it.
	 *
	 * @param {Object} nodeData The custom data associated with the drop node (this is the same value returned from
	 * {@link #getTargetFromEvent} for this node)
	 * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
	 * @param {Event} e The event
	 * @param {Object} data An object containing arbitrary data supplied by the drag source
	 */
	onNodeEnter: function(target, dd, e, data)
	{
		Ext.get(target).addClass(this.wrapFocusClass);
	},

	/**
	 * Called when the DropZone determines that a {@link Ext.dd.DragSource} has been dragged out of
	 * the drop node without dropping.
	 * Override removes the focus class, 'unhighlighting' the field.
	 *
	 * @param {Object} nodeData The custom data associated with the drop node (this is the same value returned from
	 * {@link #getTargetFromEvent} for this node)
	 * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
	 * @param {Event} e The event
	 * @param {Object} data An object containing arbitrary data supplied by the drag source
	 */
	onNodeOut: function(target , dd, e, data)
	{
		Ext.get(target).removeClass(this.wrapFocusClass);
	},

	/**
	 * Called while the DropZone determines that a {@link Ext.dd.DragSource} is over a drop node
	 * that has either been registered or detected by a configured implementation of {@link #getTargetFromEvent}.
	 * The default implementation returns this.dropNotAllowed, so it should be
	 * overridden to provide the proper feedback.
	 * @param {Object} nodeData The custom data associated with the drop node (this is the same value returned from
	 * {@link #getTargetFromEvent} for this node)
	 * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
	 * @param {Event} e The event
	 * @param {Object} data An object containing arbitrary data supplied by the drag source
	 * @return {String} status The CSS class that communicates the drop status back to the source so that the
	 * underlying {@link Ext.dd.StatusProxy} can be updated
	 */
	onNodeOver: function(target, dd, e, data)
	{
		return Ext.dd.DropZone.prototype.dropAllowed;
	}
});

Ext.reg('zarafa.boxfielddropzone', Zarafa.common.ui.BoxFieldDropZone);
Ext.namespace('Zarafa.common.ui');

/**
 * @class Zarafa.common.ui.CharacterStrip
 * @extends Ext.Container
 * @xtype zarafa.characterstrip
 *
 * This class will add a vertical character strip to any container which is specified
 * in renderTo config option, it will maintain selection for character, view can register on selectionchanged
 * event to change data if selection is changed in this strip.
 */
Zarafa.common.ui.CharacterStrip = Ext.extend(Ext.Container, {
	/**
	 * @cfg {Array} characterData array of characters that will be used to render view
	 */
	characterData: ['...', '123', 'a', 'b', 'c', 'd', 'e', 'f',
						'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
						'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
						'y', 'z'],

	/**
	 * @cfg {String} selectedChar character that is currently selected.
	 * defaults to null.
	 */
	selectedChar: null,

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

		Ext.applyIf(config, {
			xtype: 'zarafa.characterstrip',
			layout: {
				type: 'vbox',
				align: 'stretchmax'
			},
			border: false,
			autoWidth: true
		});

		this.addEvents(
			/**
			 * @event selectionchanged
			 * Event handler will be called when selection of selected character will be changed.
			 * @param {Zarafa.common.ui.CharacterStrip} CharacterStrip object of {@link Zarafa.common.ui.CharacterStrip}
			 * @param {String} character character that is selected currently
			 */
			'selectionchanged',
			/**
			 * @event selectioncleared
			 * Event handler will be called when selection of selected character will be cleared.
			 * @param {Zarafa.common.ui.CharacterStrip} CharacterStrip object of {@link Zarafa.common.ui.CharacterStrip}
			 */
			'selectioncleared'
		);

		Zarafa.common.ui.CharacterStrip.superclass.constructor.call(this, config);
	},

	/**
	 * Initializes the component.
	 */
	initComponent: function() {
		// create buttons for all characters
		this.items = [];

		Ext.each(this.characterData, function(character) {
			this.items.push(
				new Ext.Button({
					text: character,
					enableToggle: true,
					scale: 'small',
					toggleGroup: 'chargroup',
					handler: this.onButtonClick,
					scope: this,
					flex: 1,
					pressed: this.selectedChar == character,
					allowDepress: false,
					ref: 'char_' + character
				})
			);
		}, this);

		Zarafa.common.ui.CharacterStrip.superclass.initComponent.call(this, arguments);
	},

	/**
	 * Event handler will be called on toggle vent of any button.
	 * @param {Ext.Button} button button component
	 * @param {Boolean} pressed state of the button which is toggled
	 * @private
	 */
	onButtonClick: function(button, eventObj)
	{
		if(button.pressed) {
			this.selectedChar = button.getText();
			this.fireEvent('selectionchanged', this, this.selectedChar);
		}
	},

	/**
	 * Function will be used to clear selection of any character.
	 */
	clearSelection: function()
	{
		// find currently selected button and remove selection
		var button = this['char_' + this.selectedChar];
		button.toggle(false);

		this.selectedChar = null;

		this.fireEvent('selectioncleared', this);
	}
});

Ext.reg('zarafa.characterstrip', Zarafa.common.ui.CharacterStrip);
Ext.namespace('Zarafa.common.ui');

/**
 * @class Zarafa.common.ui.ColorPicker
 * @extends Ext.form.Field
 * @xtype zarafa.colorpicker
 *
 * The ColorPicker is a component that can be used to have the user pick a color. It consists
 * of an squared area in the color of the currently selected color that can be clicked on to
 * open a {@link Ext.menu.ColorMenu} with predefined colors. The user can then pick one of these
 * colors by clicking on it.
 */
Zarafa.common.ui.ColorPicker = Ext.extend(Ext.form.Field, {
	/**
   * @cfg {String} value The default value of the color picker.
	 */
	value: 'BDC3C7',

	/**
   * @cfg {Boolean} border True to display a border around the colorpicker box
   * Can be useful if you want to use white.
	 */
	border: true,

  /**
   * @cfg {String} fieldClass The default CSS class for the field (defaults to 'x-form-field k-colorpicker')
   */
  fieldClass: 'x-form-field k-colorpicker',

	/**
   * @cfg {String[]} colors The colors that will be used for the colorpicker. Colors
   * should be in RGB hex format without the #. E.g. 'AA0000' for red. Defaults to
   * the default calendar colors used by grommunio Web.
	 */
	colors: [
		'E30022', // cadmium red
		'F89406', // california
		'F7CA18', // yellow
		'F7B884', // apricot
		'5AB557', // green
		'1FA480', // mint
		'88D8C0', // pearl aqua
		'D3E28B', // soft green
		'912787', // purple
		'9A8BBC', // mauve
		'FF0099', // pink
		'F17DAA', // charm pink
		'0F70BD', // blue
		'00B3F0', // grommunio blue
		'86CFF5', // baby blue
		'BDC3C7' // silver sand
	],

	/**
	 * The element that is rendered as a colored box on which a user
	 * can click to show {#menu the color picker menu}
	 * @type Ext.Element
	 * @property
	 */
	box: null,

	/**
	 * The contextmenu with the palette
   * @property
   * @type {Ext.menu.ColorMenu}
	 */
  menu: null,

	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
  constructor: function(config)
  {
  	this.addEvents(
			/**
			 * @event show
			 * Fired when the menu is shown.
			 *
			 * @param {Zarafa.common.ui.ColorPicker} this The color picker.
			 * @param {Ext.menu.ColorMenu} colorMenu The color menu of this color picker.
			 */
			'show',

			/**
			 * @event hideu
			 * Fired when the menu is hidden.
			 *
			 * @param {Zarafa.common.ui.ColorPicker} this The color picker.
			 * @param {Ext.menu.ColorMenu} colorMenu The color menu of this color picker.
			 */
			'hide',

			/**
			 * @event select
			 * Fired when a color was picked.
			 *
			 * @param {Zarafa.common.ui.ColorPicker} this The color picker.
			 * @param {String} color The color that was picked (hex rgb code)
			 */
			'select'
  	);

  	config = config || {};
  	Ext.applyIf(config, {
  		// setting defaultAutoCreate to true to have a simple div as element
  		// and not have Ext.form.Field create an input.
  			defaultAutoCreate: true
  	});

    Zarafa.common.ui.ColorPicker.superclass.constructor.call(this, config);
  },

	/**
	 * Renders the color picker.
	 */
	onRender: function()
	{
    if(!this.el){
      var cfg = this.getAutoCreate();

      if(!cfg.name){
        cfg.name = this.name || this.id;
      }
      this.autoEl = cfg;
    }
    Zarafa.common.ui.ColorPicker.superclass.onRender.apply(this, arguments);

    if ( this.border ){
    	this.cls = (this.cls || '') + ' zarafa-border';
    }

    this.el.addClass([this.fieldClass, this.cls]);

		// Create the clickable box. It is created inside the colorpicker div
		// to be able to use padding for better positioning
    this.box = Ext.get(Ext.DomHelper.createDom({
		tag: 'div',
		cls: 'k-colorpicker-box',
		role: 'button',
		tabindex: '0',
		'aria-label': this.fieldLabel || _('Select color'),
		'aria-haspopup': 'true'
	}));
    this.el.appendChild(this.box);

		// Create the menu with the color palette
    this.createMenu();
	},

	/**
	 * Creates the menu with the color palette.
	 */
	createMenu: function()
	{
		this.menu = new Ext.menu.ColorMenu({
			cls: 'k-colorpicker-menu',
			handler: function(palette, color){
				this.setValue(color);
				this.fireEvent('select', this, color);
			},
			scope: this
		});

		this.menu.palette.colors = this.colors;

		this.relayEvents(this.menu, ['show', 'hide']);
	},

  /**
   * Initializes event handlers. Adds a handler for the click event
   * on {#box the colored box}
   */
  initEvents: function(){
    Zarafa.common.ui.ColorPicker.superclass.initEvents.call(this);

    this.mon(this.box, 'click', this.onClick, this);
    this.mon(this.box, 'keydown', function(e) {
      if (e.getKey() === e.ENTER || e.getKey() === e.SPACE) {
        e.preventDefault();
        this.onClick(e);
      }
    }, this);
  },

	/**
	 * Sets a value for this field
	 * @param {String} value The value to set. Should be an RGB
	 * hex color without the '#'. E.g. 'FF0000' for red.
	 * (# will be stripped if included)
	 * @return {Zarafa.common.ui.ColorPicker} this
	 */
	setValue: function(value) {
    if ( !Ext.isEmpty(value) ){
    	this.value = value.startsWith('#') ? value.replace('#', '') : value;
    }

    if(this.rendered){
      this.box.setStyle('background-color', '#' + this.value);
    }
    return this;
	},

	/**
	 * Returns the value of the field.
	 * @return {String} The RGB code of the color that has been picked
	 */
	getValue: function() {
		return this.value;
	},

	/**
	 * Handler for the click event of the box. Will trigger the menu
	 * with the color palette.
	 * @param {Ext.EventObject} event The normalized click event object
	 */
	onClick: function(event) {
		this.menu.showAt(event.getXY());

		var value = this.value.toUpperCase();
		if ( this.colors.indexOf(value) > -1 ){
			this.menu.palette.select(value, true);
		}
	}
});

Ext.reg('zarafa.colorpicker', Zarafa.common.ui.ColorPicker);
Ext.namespace('Zarafa.common.ui');

/**
 * @class Zarafa.common.ui.CompositeField
 * @extends Ext.form.CompositeField
 * @xtype zarafa.compositefield
 */
Zarafa.common.ui.CompositeField = Ext.extend(Ext.form.CompositeField, {

	/**
	 * @cfg {Boolean} isSingleValued True when this class contains a valid {@link #getValue} function
	 * which returns a valid value and the {@link Ext.form.BasicForm}#{@link Ext.form.BasicForm#updateRecord}
	 * doesn't need to loop over {@link #eachItem} to construct an array of values to be stored into the
	 * {@link Ext.data.Record} property for the {@link #name}.
	 */
	isSingleValued: false,

	/**
	 * @cfg {String} focusClass The CSS class to use when the checkbox receives focus (defaults to undefined)
	 * this will not apply any focus class from the child elements of composite fields
	 */
	focusClass: undefined,

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

		Ext.applyIf(config, {
			// Overridden from Ext.Component
			xtype: 'zarafa.compositefield',
			// Unfortunately we cannot set this margin in the css, because Ext uses absolute positions
			// for the items in a composite field and doesn't handle margins using css margins
			defaultMargins: '0 6 0 0'
		});
		config.cls = config.cls ? config.cls + ' zarafa-compositefield' : 'zarafa-compositefield';

		Ext.apply(this, config);

		Zarafa.common.ui.CompositeField.superclass.constructor.call(this, config);
	},

	/**
	 * Initialize component
	 * We perform a Ext.create() on each item within the
	 * {@link Zarafa.common.ui.CompositeField CompositeField} to make sure it
	 * is allocated. This fixes any issues with find{By,ById,ByType} which
	 * could be called before the field is rendered. Only during rendering
	 * will a inner {@link Ext.Container container} be allocated which allocates
	 * the items within this field. This would break the find functions, so
	 * allocating them here we can be sure that this field really behaves
	 * as a container.
	 */
	initComponent: function()
	{
		Zarafa.common.ui.CompositeField.superclass.initComponent.call(this);

		// Ext.form.CompositeField removes references to components which are not form fields
		// but we should support adding non form fields to composite field also
		 var fields = this.innerCt.findBy(function(c) {
			return c.isXType('box');
		}, this);

		this.items.addAll(fields);

		// Correct the ownerCt reference of the container, so we can use findParentBy
		// on the items in the innerCt to reach the compositefield (and its parents).
		this.innerCt.ownerCt = this;
	},

	/**
	 * Cascades down the component/container hierarchy from this component (called first), calling the specified function with
	 * each component. The scope (<i>this</i>) of
	 * function call will be the scope provided or the current component. The arguments to the function
	 * will be the args provided or the current component. If the function returns false at any point,
	 * the cascade is stopped on that branch.
	 * @param {Function} fn The function to call
	 * @param {Object} scope (optional) The scope of the function (defaults to current component)
	 * @param {Array} args (optional) The args to call the function with (defaults to passing the current component)
	 * @return {Ext.Container} this
	 */
	cascade: function(fn, scope, args)
	{
		if (fn.apply(scope || this, args || [this]) !== false) {
			if (this.items) {
				var cs = (this.items instanceof Ext.util.MixedCollection) ? this.items.items : this.items;
				for (var i = 0, len = cs.length; i < len; i++) {
					if (cs[i].cascade) {
						cs[i].cascade(fn, scope, args);
					} else {
						fn.apply(scope || cs[i], args || [cs[i]]);
					}
				}
			}
		}
		return this;
	},

	/**
	 * Performs validation checks on each subfield and returns false if any of them fail validation.
	 * We override this function to be able to have non-form-field elements in the composite.
	 * @return {Boolean} False if any subfield failed validation
	 */
	validateValue: function(value, preventMark) {
		var valid = true;

		this.eachItem(function(field) {
			if ( field.isXType('field') && !field.isValid(preventMark)) {
				valid = false;
			}
		});

		return valid;
	},

	/**
	 * Find a component under this container at any level by property
	 *
	 * @param {String} prop
	 * @param {String} value
	 * @return {Array} Array of Ext.Components
	 */
	find: Ext.Container.prototype.find,

	/**
	 * Find a component under this container at any level by a custom function.
	 * If the passed function returns true, the component will be included in the results.
	 * The passed function is called with the arguments (component, this container).
	 *
	 * @param {Function} fn The function to call
	 * @param {Object} scope (optional) The scope for the function
	 * @return {Array} Array of Ext.Components
	 */
	findBy: Ext.Container.prototype.findBy,

	/**
	 * Find a component under this container at any level by xtype or class
	 *
	 * @param {String/Class} xtype The xtype string for a component, or the class of the component directly
	 * @param {Boolean} shallow (optional) False to check whether this Component is descended from the xtype
	 * (this is the default), or true to check whether this Component is directly of the specified xtype
	 * @return {Array} Array of Ext.Components
	 */
	findByType: Ext.Container.prototype.findByType
});

Ext.reg('zarafa.compositefield', Zarafa.common.ui.CompositeField);
Ext.namespace('Zarafa.common.ui');

/**
 * @class Zarafa.common.ui.ContextMainPanel
 * @extends Ext.Panel
 * @xtype zarafa.contextmainpanel
 */
Zarafa.common.ui.ContextMainPanel = Ext.extend(Ext.Panel, {
	/**
	 * @cfg {Zarafa.mail.MailContext} context The context to which this panel belongs
	 */
	context: undefined,

	/**
	 * The {@link Zarafa.mail.MailContextModel} which is obtained from the {@link #context}.
	 * @property
	 * @type Zarafa.mail.MailContextModel
	 */
	model: undefined,

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

		if (!Ext.isDefined(config.model) && Ext.isDefined(config.context)) {
			config.model = config.context.getModel();
		}

		Ext.applyIf(config, {
			xtype: 'zarafa.contextmainpanel',
			border: false,
			cls: 'zarafa-panel'
		});

		Zarafa.common.ui.ContextMainPanel.superclass.constructor.call(this, config);
	}
});

Ext.reg('zarafa.contextmainpanel', Zarafa.common.ui.ContextMainPanel);
Ext.namespace('Zarafa.common.ui');

/**
 * @class Zarafa.common.ui.ContextMainPanelToolbar
 * @extends Ext.Toolbar
 * @xtype zarafa.contextmainpaneltoolbar
 *
 * Main toolbar for all the contexts which contains paging toolbar, foldertitle
 * and some context related menu buttons e.g. copy/move, delete
 */
Zarafa.common.ui.ContextMainPanelToolbar = Ext.extend(Ext.Toolbar, {
	/**
	 * @cfg {Zarafa.core.Context} context The context to which this toolbar belongs
	 */
	context: undefined,

	/**
	 * The {@link Zarafa.core.ContextModel} which is obtained from the {@link #context}.
	 * @property
	 * @type Zarafa.mail.MailContextModel
	 */
	model: undefined,

	/**
	 * @cfg {Array} paging Configuration objects of pagination toolbars which should
	 * be added. This can be used if the default pagination toolbar is not sufficient,
	 * and in some situations should be swapped with a different paging toolbar.
	 */
	paging: undefined,

	/**
	 * @cfg {String} defaultTitle The title for the {@link Ext.Panel} Panel
	 */
	defaultTitle: _('grommunio Web'),

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

		if (!Ext.isDefined(config.model) && Ext.isDefined(config.context)) {
			config.model = config.context.getModel();
		}

		// Built up the items array
		var items = [];

		items = items.concat({
			xtype: 'zarafa.searchfieldcontainer',
			boxMinWidth: 100,
			ref: 'searchFieldContainer',
			model: config.model
		});

		if (!Ext.isEmpty(config.paging)) {
			items = items.concat(config.paging);
		}

		// We add the default buttons
		items = items.concat([
			'->',
			container.populateInsertionPoint('context.mainpaneltoolbar.item', config.context),
		{
			xtype: 'zarafa.toolbarbutton',
			overflowText: _('Copy/Move'),
			tooltip: _('Copy/Move') + Zarafa.core.KeyMapMgr.formatShortcutHint('Ctrl + M', false),
			iconCls: 'icon_copy',
			ref: 'copyButton',
			nonEmptySelectOnly: true,
			handler: this.onCopyMove,
			model: config.model,
			scope: this
		},{
			xtype: 'zarafa.toolbarbutton',
			overflowText: _('Delete'),
			tooltip: _('Delete'),
			ref: 'deleteButton',
			iconCls: 'icon_delete',
			nonEmptySelectOnly: true,
			handler: this.onDelete,
			model: config.model,
			scope: this
		}]);

		// Add the extra items
		if (!Ext.isEmpty(config.items)) {
			items = items.concat(config.items);
		}

		// Delete config.items as we will override it
		// during Ext.applyIf.
		delete config.items;

		// Update configuration
		Ext.applyIf(config, {
			xtype: 'zarafa.contextmainpaneltoolbar',
			ref: 'contextMainPanelToolbar',
			items: items,
			enableOverflow: true
		});

		Zarafa.common.ui.ContextMainPanelToolbar.superclass.constructor.call(this, config);
		this.initEvent();
	},

	/**
	 * Initialize Events
	 */
	initEvent: function()
	{
		this.on('afterlayout', this.resizeSearchField, this, {delay: 2, single: true});
	},

	/**
	 * Open the {@link Zarafa.common.dialogs.CopyMoveContentPanel CopyMoveContentPanel} for copying
	 * or moving the currently selected folders.
	 * @private
	 */
	onCopyMove: function()
	{
		Zarafa.common.Actions.openCopyMoveContent(this.model.getSelectedRecords());
	},

	/**
	 * Delete the currently selected messages. If any of the records is a recurring item,
	 * then the {@link #Zarafa.common.dialogs.MessageBox.select MessageBox} will be used
	 * to select between the recurring and single appointment.
	 * @private
	 */
	onDelete: function()
	{
		Zarafa.common.Actions.deleteRecords(this.model.getSelectedRecords());
	},

	/**
	 * Open the Print dialog
	 * @private
	 */
	onPrint: function()
	{
		Zarafa.common.Actions.openPrintDialog(this.model.getSelectedRecords());
	},

	/**
	 * Called when the grid is being resized. This will calculate the new width for the internal
	 * {@link #searchTextField}.
	 * @param {Number} adjWidth The box-adjusted width that was set
	 * @param {Number} adjHeight The box-adjusted height that was set
	 * @param {Number} rawWidth The width that was originally specified
	 * @param {Number} rawHeight The height that was originally specified
	 * @private
	 **/
	onResize: function(adjWidth, adjHeight, rawWidth, rawHeight)
	{
		Zarafa.common.ui.ContextMainPanelToolbar.superclass.onResize.apply(this, arguments);

		// Only resize the searchTextField when the width
		// of this component has been changed.
		var allowResize = this.searchFieldContainer.rendered && this.copyButton.rendered && this.deleteButton.rendered;
		if (Ext.isDefined(adjWidth) && allowResize) {
			this.resizeSearchField();
		}
	},

	/**
	 * Function is used to resize the {@link #searchTextField}. also it will show the
	 * hidden tool bar items if tool bar container has enough size to show.
	 */
	resizeSearchField: function()
	{
		// Get the width of the container without the padding
		var containerWidth = this.el.getStyleSize().width;
		var copyButtonWidth = 0;
		var deleteButtonWidth = 0;
		var filterButtonWidth = 0;

		if(Ext.isDefined(this.filterBtn)) {
			if(this.filterBtn.xtbHidden) {
				filterButtonWidth = this.filterBtn.xtbWidth;
				if (containerWidth > this.searchFieldContainer.getWidth() + filterButtonWidth) {
					this.layout.unhideItem(this.filterBtn);
					// Update the selection of filter split button.
					if (!Ext.isEmpty(this.model)) {
						if (this.model.getStore().hasFilterApplied) {
							this.filterBtn.btnEl.addClass('k-selection');
						}
					}
					this.doLayout();
				}
			} else {
				filterButtonWidth = this.filterBtn.getWidth();
			}
		}

		if(this.copyButton.xtbHidden) {
			copyButtonWidth = this.copyButton.xtbWidth;
			if (containerWidth > this.searchFieldContainer.getWidth() + copyButtonWidth) {
				this.layout.unhideItem(this.copyButton);
				this.doLayout();
			}
		} else {
			copyButtonWidth = this.copyButton.getWidth();
		}

		if(this.deleteButton.xtbHidden) {
			deleteButtonWidth = this.deleteButton.xtbWidth;
			if (containerWidth > this.searchFieldContainer.getWidth() + copyButtonWidth + deleteButtonWidth) {
				this.layout.unhideItem(this.deleteButton);
				this.doLayout();
			}
		} else {
			deleteButtonWidth = this.deleteButton.getWidth();
		}

		var adjWidth = containerWidth - copyButtonWidth - deleteButtonWidth - filterButtonWidth;

		var searchFieldContainer = this.searchFieldContainer;
		var searchField = searchFieldContainer.searchTextField;

		// In non-mail/non-search contexts (calendar, contacts, tasks, notes), cap
		// the search field width so it doesn't stretch across the entire toolbar.
		var contextName = this.context && this.context.getName ? this.context.getName() : '';
		var isFullWidthContext = (contextName === 'mail' || contextName === 'advancesearch');
		if (!isFullWidthContext && adjWidth > 600) {
			adjWidth = 600;
		}

		searchField.setWidth(adjWidth);
	}
});

Ext.reg('zarafa.contextmainpaneltoolbar', Zarafa.common.ui.ContextMainPanelToolbar);
Ext.namespace('Zarafa.common.ui');

/**
 * @class Zarafa.common.ui.DataDragZone
 * @extends Ext.dd.DragZone
 *
 * A Special DragZone implementation which
 * can be used on a {@link Ext.DataView DataView}.
 * This is used for example in the
 * {@link Zarafa.common.ui.DraggableDataView}
 */
Zarafa.common.ui.DataDragZone = Ext.extend(Ext.dd.DragZone, {
	/**
	 * The view on which this DragZone is installed
	 * @property
	 * @type Ext.DataView
	 * @private
	 */
	view: undefined,

	/**
	 * The special Element which is displayed when dragging data
	 * over the DragZone.
	 * @property
	 * @type Ext.Element
	 * @private
	 */
	ddel: undefined,

	/**
	 * @constructor
	 * @param {Ext.DataView} view The view on which the DragZone is installed
	 * @param {Object} config Configuration object
	 */
	constructor: function(view, config)
	{
		config = config || {};

		this.view = view;

		Ext.applyIf(config, {
			ddGroup: 'DataDD'
		});

		Zarafa.common.ui.DataDragZone.superclass.constructor.call(this, view.getEl(), config);

		this.ddel = document.createElement('div');
		this.ddel.className = 'x-data-dd-wrap';
		this.ddel.setAttribute('aria-hidden', 'true');
	},

	/**
	 * <p>The provided implementation of the getDragData method which collects the data to be dragged from the DataView on mousedown.</p>
	 * <p>This data is available for processing in the {@link Ext.dd.DropZone#onNodeEnter onNodeEnter}, {@link Ext.dd.DropZone#onNodeOver onNodeOver},
	 * {@link Ext.dd.DropZone#onNodeOut onNodeOut} and {@link Ext.dd.DropZone#onNodeDrop onNodeDrop} methods of a cooperating {@link Ext.dd.DropZone DropZone}.</p>
	 * <p>The data object contains the following properties:<ul>
	 * <li><b>view</b>: Ext.DataView<div class="sub-desc">The DataView from which the data is being dragged.</div></li>
	 * <li><b>ddel</b>: htmlElement<div class="sub-desc">An htmlElement which provides the "picture" of the data being dragged.</div></li>
	 * <li><b>rowIndex</b>: Number<div class="sub-desc">The index of the row which received the mousedown gesture which triggered the drag.</div></li>
	 * <li><b>selections</b>: Array<div class="sub-desc">An Array of the selected Records which are being dragged from the DataView.</div></li>
	 * </ul></p>
	 * @private
	 */
	getDragData: function(e)
	{
		var node = this.view.findItemFromChild(Ext.lib.Event.getTarget(e));
		if (!node) {
			return;
		}

		var index = this.view.indexOf(node);

		if (index >= 0) {
			// Create a copy of the event object,
			// we need this later in the onInitDrag function
			var origEvent = new Ext.EventObjectImpl(e);

			return {
				view: this.view,
				ddel: this.ddel,
				index: index,
				selections: this.view.getSelectedRecords(),
				origEvent: origEvent
			};
		}

		return false;
	},

	/**
	 * <p>The provided implementation of the onInitDrag method. Sets the <tt>innerHTML</tt> of the drag proxy which provides the "picture"
	 * of the data being dragged.</p>
	 * <p>The <tt>innerHTML</tt> data is found by calling the owning GridPanel's {@link Ext.grid.GridPanel#getDragDropText getDragDropText}.</p>
	 * @param {Number} x The x coordinate from where the dragging started
	 * @param {Number} y The y coordinate from where the dragging started
	 * @private
	 */
	onInitDrag: function(x, e)
	{
		// Check if the dragData contains a origEvent (as initialized by getDragData).
		// If this is present, we have to manually call the onItemClick event handler
		// on the view to ensure that the item we are about to drag is marked as selected.
		//
		// We can't do that in getDragData because of the ordering of the event handlers
		// of both this class as well as the DataView.
		if (this.dragData && this.dragData.origEvent) {
			var index = this.dragData.index;
			var node = this.view.getNode(index);

			if (!this.view.isSelected(index)) {
				this.view.onItemClick(node, index, this.dragData.origEvent);

				// The selection has been changed, update the selections array
				this.dragData.selections = this.view.getSelectedRecords();
			}
			delete this.dragData.origEvent;
		}

		this.ddel.innerHTML = this.view.getDragDropText();
		this.proxy.update(this.ddel);
		// fire start drag?
	},

	/**
	 * An empty immplementation. Implement this to provide behaviour after a repair of an invalid drop. An implementation might highlight
	 * the selected rows to show that they have not been dragged.
	 * @private
	 */
	afterRepair: function()
	{
		this.dragging = false;
	},

	/**
	 * <p>An empty implementation. Implement this to provide coordinates for the drag proxy to slide back to after an invalid drop.</p>
	 * <p>Called before a repair of an invalid drop to get the XY to animate to.</p>
	 * @param {EventObject} e The mouse up event
	 * @return {Array} The xy location (e.g. [100, 200])
	 * @private
	 */
	getRepairXY: function(e, data)
	{
		return false;
	},

	/**
	 * Called when the dragging has ended
	 * @param {Object} data The data which was used during the drag
	 * @param {Ext.EventObject} e The event object
	 * @private
	 */
	onEndDrag: function(data, e)
	{
		// fire end drag?
	},

	/**
	 * Called when an item has been dropped
	 * @param {Ext.dd.DropZone} dd The zone in which the item was dropped
	 * @param {Ext.EventObject} e The event object
	 * @param {String} id The id of the Ext.Element where the item was dropped
	 * @private
	 */
	onValidDrop: function(dd, e, id)
	{
		// fire drag drop?
		this.hideProxy();
	},

	/**
	 * Called when an item has been dropped on an invalid location
	 * @param {Ext.EventObject} e The event object
	 * @param {String} id The id of the Ext.Element where the item was dropped
	 * @private
	 */
	beforeInvalidDrop: function(e, id)
	{
	}
});
Ext.ns('Zarafa.common.ui');

/**
 * @class Zarafa.common.ui.DateRangeField
 * @extends Ext.Container
 * @xtype zarafa.daterangefield
 *
 * This class can be used to combine two {@link Ext.form.Field fields}
 * objects together to configure a {@link Zarafa.core.DateRange DateRange}.
 * The first {@link Ext.form.Field field} will never be allowed to be set
 * to a later {@link Date Date} then the second {@link Ext.form.Field field}
 * or vice versa. By default there will be a default range between the start
 * and end {@link Ext.Date Date} which is applied when either one of the two
 * {@link Ext.form.Field fields} become invalid.
 */
Zarafa.common.ui.DateRangeField = Ext.extend(Ext.Container, {
	/**
	 * @cfg {Object} startFieldConfig The configuration which must be
	 * applied to the start {@link Ext.form.Field field}.
	 * This {@link Ext.form.Field field} must accept a
	 * {@link Ext.Date Date} object in the {@link Ext.form.Field.setValue setValue}
	 * function and return {@link Ext.Date Date} in the {@link Ext.form.Field.getValue getValue}
	 * function.
	 */
	startFieldConfig: {},
	/**
	 * @cfg {Object} endFieldConfig The configuration which must be
	 * applied to the end date {@link Ext.form.Field field}.
	 * This {@link Ext.form.Field field} must accept a
	 * {@link Ext.Date Date} object in the {@link Ext.form.Field.setValue setValue}
	 * function and return {@link Ext.Date Date} in the {@link Ext.form.Field.getValue getValue}
	 * function.
	 */
	endFieldConfig: {},
	/**
	 * @cfg {Object} spacerConfig If this spacerConfig is provided, a {@link Ext.Spacer}
	 * will be created between the startField and endField. The spacerConfig object will
	 * be applied to the spacer object.
	 */
	spacerConfig: undefined,
	/**
	 * @cfg {Zarafa.core.DateRange} defaultValue The default {@link Zarafa.core.DateRange daterange}
	 * object which must be set on this field.
	 */
	defaultValue: undefined,
	/**
	 * @cfg {Number} defaultPeriod The default value which must be assumed for
	 * the {@link Zarafa.core.DateRange daterange}. This value can be in minutes, hours, days, etc...
	 * depending on the {@link #defaultPeriodType} field.
	 */
	defaultPeriod: undefined,
	/**
	 * @cfg {String} defaultPeriodType The value type of the {@link #defaultPeriod}.
	 * This can be Date.MINUTE, Date.HOUR, etc...
	 */
	defaultPeriodType: Date.MINUTE,

	/**
	 * @cfg {Boolean} allowEqualValue True to indicate that the start and due values
	 * are allowed to be equal. This will prevent the startdate to be adjusted when the duedate is being
	 * changed by the user
	 */
	allowEqualValue: false,

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

		config = Ext.applyIf(config, {
			xtype: 'zarafa.daterangefield',
			border: false,
			bodyStyle: 'background-color: inherit;',
			layout: 'form'
		});

		this.addEvents(
			/**
			 * @event change
			 * Fires when the start or enddate has been changed
			 * @param {Zarafa.common.ui.DateRangeField} field The field which has been changed
			 * @param {Zarafa.core.DateRange} newValue The new DateRange
			 * @param {Zarafa.core.DateRange} oldValue the old DateRange
			 */
			'change'
		);

		if (!Ext.isDefined(config.defaultValue)) {
			// If no defaultValue is given, construct it using the current date
			// when the defaultPeriod was provided.
			if (Ext.isDefined(config.defaultPeriod)) {
				var start = new Date();
				var end = start.clone().add(config.defaultPeriodType, config.defaultPeriod);

				config.defaultValue = new Zarafa.core.DateRange({ startDate: start, dueDate: end });
			}
		} else if (!Ext.isDefined(config.defaultPeriod)) {
			// A default value was provided, but no defaultPeriod,
			// calculate the new period based on the default value.
			// _however_ this means we reset defaultPeriodType to minutes
			// to simplify our calculation for the period.
			config.defaultPeriodType = Date.MINUTE;
			config.defaultPeriod = config.defaultValue.getDuration(Date.MINUTE);
		}

		Zarafa.common.ui.DateRangeField.superclass.constructor.call(this, config);

		this.addFieldItems();
		this.bindDateRange(this.defaultValue);
	},

	/**
	 * Add the two {@link Ext.form.Field fields} which are used
	 * to display the start and end {@link Ext.Date Date} to this container.
	 * @private
	 */
	addFieldItems: function()
	{
		if (Ext.isDefined(this.startFieldConfig)) {
			if (this.layout !== 'form') {
				this.startFieldConfig.plugins = Ext.value(this.startFieldConfig.plugins, []).concat('zarafa.fieldlabeler');
			}

			this.startFieldConfig.listeners = Zarafa.core.Util.mergeListeners(this.startFieldConfig.listeners, {
				change: this.onStartChange,
				scope: this
			});

			this.add(Ext.applyIf(this.startFieldConfig, {
				ref: 'startField',
				flex: 0.5,
				// custom made components uses defaultValue config to set its value when field is rendered
				// but extjs default components uses value config instead so added value in both configs
				defaultValue: this.defaultValue ? this.defaultValue.getStartDate() : undefined,
				value: this.defaultValue ? this.defaultValue.getStartDate() : undefined
			}));
		}

		if (Ext.isDefined(this.spacerConfig)) {
			this.add(Ext.applyIf(this.spacerConfig, {
				xtype: 'spacer',
				height: 5,
				width: 5
			}));
		}

		if (Ext.isDefined(this.endFieldConfig)) {
			if (this.layout !== 'form') {
				this.endFieldConfig.plugins = Ext.value(this.endFieldConfig.plugins, []).concat('zarafa.fieldlabeler');
			}

			this.endFieldConfig.listeners = Zarafa.core.Util.mergeListeners(this.endFieldConfig.listeners, {
				change: this.onEndChange,
				scope: this
			});

			this.add(Ext.applyIf(this.endFieldConfig, {
				ref: 'endField',
				flex: 0.5,
				// custom made components uses defaultValue config to set its value when field is rendered
				// but extjs default components uses value config instead so added value in both configs
				defaultValue: this.defaultValue ? this.defaultValue.getDueDate() : undefined,
				value: this.defaultValue ? this.defaultValue.getDueDate() : undefined
			}));
		}
	},

	/**
	 * Set the {@link Zarafa.core.DateRange DateRange} object, this will replace
	 * the currently set object and attaches event listeners to it.
	 * @param {Zarafa.core.DateRange} daterange The new DateRange object
	 * @private
	 */
	bindDateRange: function(daterange)
	{
		if (this.defaultValue) {
			this.mun(this.defaulValue, 'update', this.onDateRangeUpdate, this);
		}

		var oldDateRange = this.defaultValue;
		this.defaultValue = daterange;

		if (this.defaultValue) {
			this.mon(this.defaultValue, 'update', this.onDateRangeUpdate, this);
		}

		this.onDateRangeUpdate(daterange, oldDateRange);
	},

	/**
	 * Event handler which is raised when the start date has been changed,
	 * this will update the end date to maintain the currently active period settings.
	 *
	 * @param {Ext.form.Field} field The field which has changed
	 * @param {Mixed} newValue The new value for the field
	 * @param {Mixed} oldValue The old value for the field
	 * @private
	 */
	onStartChange: function(field, newValue, oldValue)
	{
		var range = this.defaultValue;
		var oldRange = this.defaultValue.clone();

		if (range.getStartDate() != newValue) {
			var duration = range.getDuration();
			var oldDueValue = range.getDueDate().clone();
			var newDueValue = newValue.add(Date.MILLI, duration);

			// When working around the Daylight Saving time, it is
			// possible that adding an hour to a date, will be canceled
			// if the new date falls in the hour which has disappeared.
			// If that happens, we need to add an extra hour to move the
			// due date to exceed the DST time.
			if (newDueValue < newValue) {
				newDueValue = newDueValue.add(Date.HOUR, 2);
			}

			if (this.endField.maxValue && newDueValue > this.endField.maxValue) {
				newDueValue = this.endField.maxValue;
			}

			range.set(newValue.clone(), newDueValue.clone());
			if (newDueValue != oldDueValue) {
				this.endField.setValue(newDueValue);
			}

			this.fireEvent('change', this, range.clone(), oldRange);
		}
	},

	/**
	 * Event handler which is raised when the end date has been changed,
	 * this will update the start date when the end date is earlier then
	 * the start date.
	 *
	 * @param {Ext.form.Field} field The field which has changed
	 * @param {Mixed} newValue The new value for the field
	 * @param {Mixed} oldValue The old value for the field
	 * @private
	 */
	onEndChange: function(field, newValue, oldValue)
	{
		var range = this.defaultValue;
		var oldRange = this.defaultValue.clone();

		if (range.getDueDate() != newValue) {
			var newTime = newValue.getTime();
			var oldTime = range.getStartTime();

			if (newTime < oldTime || (this.allowEqualValue === false && newTime === oldTime)) {
				var oldStartValue = range.getStartDate().clone();
				var newStartValue = newValue.add(this.defaultPeriodType, -this.defaultPeriod);

				if (this.startField.minValue && newStartValue < this.startField.minValue) {
					newStartValue = this.startField.minValue;
				}

				range.set(newStartValue.clone(), newValue.clone());

				if (newStartValue != oldStartValue) {
					this.startField.setValue(newStartValue);
				}
			} else {
				range.setDueDate(newValue.clone());
			}

			this.fireEvent('change', this, range.clone(), oldRange);
		}
	},

	/**
	 * Sets a data value into the field and validates it. To set the value directly
	 * without validation see {@link #setStartValue}.
	 * @param {Zarafa.core.DateRange} The value to set
	 */
	setValue: function(value)
	{
		this.bindDateRange(value);
	},

	/**
	 * Sets the underlying DOM field's value directly, bypassing validation.
	 * To set the value with validation see {@link #setValue}.
	 * @param {Zarafa.core.DateRange} The value to set
	 */
	setRawValue: function(value)
	{
		this.bindDateRange(value);
	},

	/*
	 * Returns the normalized data value (undefined or emptyText will be returned as '').
	 * To return the raw value see {@link #getRawValue}.
	 * @return {Date} The date object
	 */
	getValue: function()
	{
		return this.defaultValue;
	},

	/**
	 * Returns the raw data value which may or may not be a valid, defined value.
	 * To return a normalized value see {@link #getValue}.
	 * @return {Date} The date object
	 */
	getRawValue: function()
	{
		return this.defaultValue;
	},

	/**
	 * Enable this component and fire the 'enable' event.
	 */
	enable: function()
	{
		Zarafa.common.ui.DateRangeField.superclass.enable.call(this);
		this.startField.enable();
		this.endField.enable();
	},

	/**
	 * Disable this component and fire the 'disable' event.
	 */
	disable: function()
	{
		Zarafa.common.ui.DateRangeField.superclass.disable.call(this);
		this.startField.disable();
		this.endField.disable();
	},

	/**
	 * Event handler which is triggered when the {@link Zarafa.core.DateRange daterange}
	 * has been changed. This will write the new value into the {@link Zarafa.core.ui.DateTimeField DateTimeField}
	 * @param {Zarafa.core.DateRange} daterange The daterange which has been update
	 * @param {Zarafa.core.DateRange} olddaterange The copy of the daterange object containing the old values
	 */
	onDateRangeUpdate: function(daterange, olddaterange)
	{
		if (daterange) {
			if (this.rendered) {
				this.startField.setValue(daterange.getStartDate());
				this.endField.setValue(daterange.getDueDate());
			} else {
				this.items.get(0).defaultValue = daterange.getStartDate();
				this.items.get(1).defaultValue = daterange.getDueDate();

				this.items.get(0).value = daterange.getStartDate();
				this.items.get(1).value = daterange.getDueDate();
			}
		}
	}
});

Ext.reg('zarafa.daterangefield', Zarafa.common.ui.DateRangeField);
Ext.namespace('Zarafa.common.ui');

/**
 * @class Zarafa.common.ui.DisplayField
 * @extends Ext.form.DisplayField
 * @xtype zarafa.displayfield
 *
 * Special extension to the default {@link Ext.form.DisplayField}
 * which adds a {@link #renderer} configuration option which can
 * be used to render the apply value correctly.
 */
Zarafa.common.ui.DisplayField = Ext.extend(Ext.form.DisplayField, {
	/**
	 * @cfg {Function} renderer A special renderer functions which
	 * can be used to apply special formatting to the displayed value
	 */
	renderer: function(value) { return value; },

	/**
	 * The raw value which was set to {@link #setRawValue}, this is
	 * the non-{@link #htmlEncode encoded} and non-{@link #rendered}
	 * value.
	 * @property
	 * @type Mixed
	 */
	rawValue: undefined,

	/**
	 * Sets the underlying DOM field's value directly, bypassing validation. To set the value with validation see {@link #setValue}.
	 * This will apply the {@link #renderer} to the applied value.
	 * @param {Mixed} The value to set
	 * @return {Mixed} The field value that is set
	 */
	setRawValue: function(value)
	{
		this.rawValue = value;
		return Zarafa.common.ui.DisplayField.superclass.setRawValue.call(this, this.renderer(value));
	},

	/**
	 * Returns the raw data value which may or may not be a valid, defined value. To return a normalized value see {@link #getValue}.
	 * @return {Mixed} The field value
	 */
	getRawValue: function()
	{
		return this.rawValue;
	}
});

Ext.reg('zarafa.displayfield', Zarafa.common.ui.DisplayField);
Ext.namespace('Zarafa.common.ui');

/**
 * @class Zarafa.common.ui.DraggableDataView
 * @extends Ext.DataView
 * @xtype zarafa.draggabledataview
 */
Zarafa.common.ui.DraggableDataView = Ext.extend(Ext.DataView, {
	/**
	 * @cfg {String} ddGroup The DD group this DataView belongs to. Defaults to 'DataDD' if not specified.
	 */
	ddGroup: undefined,

	/**
	 * @cfg {Boolean} enableDragDrop Enables dragging of the selected utems of the DataView. Defaults to false.
	 */
	enableDragDrop: false,

	/**
	 * @cfg {Boolean} enableDrop true to enable just drag
	 */
	enableDrop: false,

	/**
	 * The dropZone used by this tree if drop is enabled (see {@link #enableDrop})
	 * @property
	 * @type Ext.dd.DropZone
	 */
	dropZone: undefined,

	/**
	 * @cfg {Object} dropConfig Custom config to pass to the {@link Ext.dd.DropZone} instance
	 */
	dropConfig: undefined,

	/**
	 * @cfg {Boolean} enableDrop true to enable just drag
	 */
	enableDrag: false,

	/**
	 * The dragZone used by this tree if drag is enabled (see {@link #enableDrag})
	 * @property
	 * @type Ext.dd.DragZone
	 */
	dragZone: undefined,

	/**
	 * @cfg {Object} dragConfig Custom config to pass to the {@link Ext.dd.DragZone} instance
	 */
	dragConfig: undefined,

	/**
	 * We are creating this element as div element, to render template within this element.
	 * @property
	 * @type Ext.Element
	 * @private
	 */
	templateEl: undefined,

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

		config.plugins = Ext.value(config.plugins, []);
		config.plugins.push({
			ptype: 'zarafa.enablefocusplugin'
		});

		Zarafa.common.ui.DraggableDataView.superclass.constructor.call(this, config);
	},

	/**
	 * Called during rendering of the DataView. When {@link #enableDD Drag & Drop} has
	 * been enabled. This will initialize the {@link #dropZone} and {@link #dragZone}
	 * to enable drag & drop for this view.
	 * @private
	 */
	onRender: function()
	{
		Zarafa.common.ui.DraggableDataView.superclass.onRender.apply(this, arguments);

		/*
		 * We are using 'zarafa.enablefocusplugin' plugin with this view, the plugin adds
		 * anchor element within component's {@link #el element}, Dataview also renders its
		 * template inside {@link #el element}. So whenever data is changed, dataview
		 * recreates the {@link #el element} so anchor tag added by the plugin will be removed,
		 * so here we are creating new templateEl inside {@link #el element} as a child in which
		 * data view will render its template.
		 */
		var element = this.getEl();
		this.templateEl = element.createChild({ tag: this.autoEl });

		if ((this.enableDragDrop || this.enableDrop) && !this.dropZone) {
			this.dropZone = new Ext.dd.DropZone(this.getEl(), this.dropConfig || {
				ddGroup: this.ddGroup || 'DataDD'
			});
		}

		// Initialize a special DragZone which has support for dragging MAPIRecord objects
		// from a DataView into the hierarchy.
		if ((this.enableDragDrop || this.enableDrag) && !this.dragZone) {
			this.dragZone = new Zarafa.common.ui.DataDragZone(this, this.dragConfig || {
				ddGroup: this.ddGroup || 'DataDD'
			});
		}
	},

	/**
	 * Called when the data-view has been rendered.
	 * This activate the keymap on the element of this component after the normal operations of
	 * afterRender have been completed.
	 * @private
	 */
	afterRender: function()
	{
		Zarafa.common.ui.DraggableDataView.superclass.afterRender.apply(this, arguments);
		// This will activate keymaps for mapID 'view.mapimessage'
		Zarafa.core.KeyMapMgr.activate(this, 'view.mapimessage');
	},

	/**
	 * Override the method {@link Ext.DataView#getTemplateTarget} to change target element
	 * for template to render, we have create new templateEl otherwise when template is
	 * rendered it will remove our focus {@link #focusEl element}.
	 * @private
	 */
	getTemplateTarget: function()
	{
		if(this.templateEl) {
			return this.templateEl;
		} else {
			return Zarafa.common.ui.DraggableDataView.superclass.getTemplateTarget.apply(this);
		}
	},

	/**
	 * Called to get grid's drag proxy text.
	 * This will use 'ngettext' to return a properly formatted plural sentence.
	 * @return {String} The text
	 */
	getDragDropText: function()
	{
		var count = this.getSelectionCount();
		return String.format(ngettext('{0} selected item', '{0} selected items', count), count);
	},

	/**
	 * Event handler which triggered when mouse enter/over the {@link Ext.DataView}.
	 * It will also shows the notification/slider at bottom-center of {@link Ext.DataView}.
	 * which contains the {@link Zarafa.common.ui.PagingToolbar PagingToolbar}.
	 * @param {Ext.EventObjectImpl} EventObjImpl Event object
	 * @param {HTMLElement} node The target node
	 * @param {Ext.EventObject} eventObj The raw event object
	 * @param {Ext.Element} parentCt The element on which paging slider should show.
	 */
	onMouseEnter: function(EventObjImpl, node, eventObj ,parentCt)
	{
		container.getNotifier().notify('pagination.paging', undefined, undefined,{
			parentEl: !Ext.isEmpty(parentCt) ? parentCt : this.getEl(),
			model: this.model,
			store: this.store
		});
	},

	/**
	 * Event handler which triggered when mouse leaves the {@link Ext.DataView}
	 * It will destroy the slider from bottom-center of {@link Ext.DataView}.
	 */
	onMouseLeave: function()
	{
		container.getNotifier().notify('pagination.paging', undefined, undefined,{
			destroy: true
		});
	}
});

Ext.reg('zarafa.draggabledataview', Zarafa.common.ui.DraggableDataView);
Ext.namespace('Zarafa.common.ui');

/**
 * @class Zarafa.common.ui.DurationField
 * @extend Ext.form.NumberField
 * @xtype zarafa.durationfield
 *
 * The duration field is a special case of the {@link Ext.form.NumberField}.
 * When the user types in a numeric value, the value is converted into a
 * duration (by postfixing the value with 'hours').
 */
Zarafa.common.ui.DurationField = Ext.extend(Ext.form.NumberField, {
	/**
	 * @cfg {RegExp} stripCharsRe
	 * @hide
	 */
	stripCharsRe: /[^0-9]+/,

	/**
	 * @cfg {Boolean} allowDecimals
	 * @hide
	 */
	allowDecimals: false,

	/**
	 * @cfg {Boolean} allowNegative
	 * @hide
	 */
	allowNegative: false,

	/**
	 * Prepare a raw value or validation (see {@link #validate} and {@link #isValid}).
	 * This method will strip any non-numeric value from the value.
	 * @param {Mixed} value
	 * @return {Mixed} The string containing only number
	 */
	processValue: function(value){
		if (Ext.isString(value) && this.stripCharsRe) {
			return value.replace(this.stripCharsRe, '');
		}
		return value;
	},

	/**
	 * Sets a data value into the field and validates it. This will append the
	 * given numeric value with 'hour' or 'hours' to visualize the meaning
	 * of the value.
	 * @param {Mixed} value The value to set
	 * @return {Ext.form.Field} this
	 */
	setValue: function(value)
	{
		value = this.parseValue(value);

		// Apply a translated string which indicates the hours
		value = String.format(ngettext('{0} hour', '{0} hours', value), value);

		Ext.form.NumberField.superclass.setValue.call(this, value);
	}
});

Ext.reg('zarafa.durationfield', Zarafa.common.ui.DurationField);
Ext.namespace('Zarafa.common.ui');

/**
 * @class Zarafa.common.ui.EditorField
 * @extends Ext.Container
 * @xtype zarafa.editorfield
 *
 * The grommunio Web EditorField is intended as Input area for data. This field
 * offers the possibility for editing using the {@link Zarafa.common.ui.HtmlEditor HtmlEditor}
 * or a simple {@link Ext.form.TextArea textarea}.
 */
Zarafa.common.ui.EditorField = Ext.extend(Ext.Container, {
	/**
	 * @cfg {Boolean} useHtml Use the {@link Zarafa.common.ui.HtmlEditor HTML editor}
	 * by default. Otherwise the {@link Ext.form.TextArea PlainTextEditor}.
	 */
	useHtml: true,
	/**
	 * @cfg {String} htmlXtype The Xtype of the component which is used for the Html editor
	 * By default this is {@link Zarafa.common.ui.HtmlEditor zarafa.htmleditor}.
	 */
	htmlXtype: 'zarafa.htmleditor',
	/**
	 * @cfg {String} plaintextXtype The Xtype of the component which is used for the Plain
	 * text editor. By default this is {@link Zarafa.common.form.TextArea textarea}.
	 */
	plaintextXtype: 'zarafa.textarea',
	/**
	 * @cfg {String} htmlName The {@link Ext.form.Field#name name} property of the
	 * {@link Zarafa.common.ui.HtmlEditor zarafa.htmleditor}
	 */
	htmlName: '',
	/**
	 * @cfg {String} plaintextName The {@link Ext.form.Field#name name} property of the
	 * {@link Zarafa.common.form.TextArea textarea}
	 */
	plaintextName: '',
	/**
	 * @cfg {Boolean} readOnly Start the editor in read only mode. When enabled the
	 * editor will switch to edit mode on double click.
	 */
	readOnly: false,
	/**
	 * @cfg {Object} componentConfig (optional) The configuration object which must be
	 * used for the Editor component. This can be the configuration object for the
	 * {@link Zarafa.common.ui.HtmlEditor HtmlEditor} and {@link Zarafa.common.ui.TextArea textarea}.
	 * If this object is not set, the configuration object used to create this
	 * {@link Zarafa.common.ui.EditorField component} will be used.
	 */
	componentConfig: undefined,
	/**
	 * @cfg {Array} relayedEvents (optional) the array of event names which must
	 * be relayed from the Editor component to this {@link Zarafa.common.ui.EditorField field}.
	 * This allows the user to listen to events directly coming from the Editor.
	 */
	relayedEvents: ['change', 'valuecorrection','keypress', 'initialized'],
	/**
	 * @cfg {Boolean} allowEdit Allow switching from read only to edit mode by
	 * double clicking the editor. When disabled the editor will remain
	 * read only.
	 */
	allowEdit: true,
	/**
	 * @cfg {Boolean} enableSystemContextMenu Enable the browser's default contextmenu
	 * to be opened on the {@link #items element}.
	 */
	enableSystemContextMenu: true,
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			xtype: 'zarafa.editorfield',
			layout: 'card',
			items: this.getEditors(config)
		});

		Ext.apply(this, config);

		// Determine the default item
		config.activeItem = this.useHtml ? 0: 1;

		// Register all relayed events as possible events from this component
		this.addEvents.apply(this, this.relayedEvents);

		this.addEvents(
			/**
			 * @event setAutoFocusCursor
			 * Fires when text area gets the focus.
			 */
			'setAutoFocusCursor'
		);

		Zarafa.common.ui.EditorField.superclass.constructor.call(this, config);

		this.on('beforerender', this.onBeforeRender, this);
		this.on('setAutoFocusCursor', this.onSetAutoFocusCursor, this);
		this.on('initialized', this.onEditorInitialized, this);
		this.on('show', this.onShow, this);
	},

	/**
	 * Obtain the editors according to the configuration settings.
	 *
	 * This will generate {@link #htmlXtype HTML} and {@link #plaintextXtype plain-text}
	 * type editors.
	 *
	 * The Editor configuration is provided by componentConfig, if this
	 * was not configured by the caller, then we use the default configuration
	 * which was used to construct this {@link Ext.Container container}.
	 * @private
	 */
	getEditors: function(config)
	{
		var componentConfig = config.componentConfig;

		// If componentConfig is not given, we use the config object itself,
		// but we must make sure to remove the config options we use only within
		// this main panel (this includes any layout options).
		if (!Ext.isDefined(componentConfig)) {
			componentConfig = Ext.apply({}, config);
			delete componentConfig.xtype;
			delete componentConfig.layout;
			delete componentConfig.layoutConfig;
			delete componentConfig.useHtml;
			delete componentConfig.htmlXtype;
			delete componentConfig.plaintextXtype;
			delete componentConfig.relayedEvents;
			delete componentConfig.listeners;
			delete componentConfig.ownerCt;
			delete componentConfig.ref;
		}

		// Apply some defaults
		Ext.applyIf(componentConfig, {
			// By default the HtmlEditor has hideMode: 'offsets',
			// which doesn't work with with the CardLayout as that
			// hides the Form element, but preserves the height
			// causing the other components to displayed at a weird
			// offset.
			hideMode: 'display',
			// Update reference
			ownerCt: this,
			// Toggle the system contextmenu
			enableSystemContextMenu: this.enableSystemContextMenu
		});

		// Now create the 2 editor configuration objects,
		// ensure that the xtypes are always configured to
		// the right type.
		return [
			Ext.apply({}, {
				xtype: config.htmlXtype || this.getHTMLEditorXtype(),
				name: config.htmlName || this.htmlName,
				enableKeyEvents: config.enableKeyEvents || false
			}, componentConfig),

			Ext.apply({}, {
				xtype: config.plaintextXtype || this.plaintextXtype,
				name: config.plaintextName || this.plaintextName,
				fieldLabel: _('Message body'),
				enableKeyEvents: config.enableKeyEvents || false
			}, componentConfig)
		];
	},

	/**
	 * Event handler which is raised just before the {@link Ext.Container container}
	 * is being rendered. At this moment the Editor component is being initialized,
	 * and if available the configured events will be relayed from the Editor to
	 * this {@link Ext.Container container}.
	 * @param {Ext.Container} container The container being rendered
	 * @private
	 */
	onBeforeRender: function(container)
	{
		if (!Ext.isEmpty(this.relayedEvents)) {
			this.items.each(function(item) {
				this.relayEvents(item, this.relayedEvents);
			}, this);
		}
	},

	/**
	 * Called when the underlying editor finished initializing. This will set the
	 * configured readOnly state and register a double click handler to switch
	 * to edit mode.
	 */
	onEditorInitialized: function()
	{
		this.setReadOnly(this.readOnly);

		if (this.readOnly && this.allowEdit) {
			var editor = this.getEditor();
			this.dblClickListener = function() {
				this.setReadOnly(false);
			};
			editor.on('dblclick', this.dblClickListener, this);
		}
	},

	/**
	 * Toggle read-only state on the current editor.
	 * @param {Boolean} readOnly True to make the editor read-only
	 */
	setReadOnly: function(readOnly)
	{
		var wasReadOnly = this.readOnly;
		this.readOnly = readOnly;
		var editor = this.getEditor();
		editor.readOnly = readOnly;
		if (Ext.isFunction(editor.setReadOnly)) {
			editor.setReadOnly(readOnly);
		} else if (readOnly && Ext.isFunction(editor.disable)) {
			editor.disable();
		} else if (!readOnly && Ext.isFunction(editor.enable)) {
			editor.enable();
		}

		if (wasReadOnly && !readOnly && Ext.isFunction(editor.focus)) {
			editor.focus(false, 50);
		}

		if (editor) {
			if (!readOnly && this.dblClickListener) {
				editor.un('dblclick', this.dblClickListener, this);
				this.dblClickListener = null;
			} else if (readOnly && this.allowEdit && !this.dblClickListener) {
				this.dblClickListener = function() {
					this.setReadOnly(false);
				};
				editor.on('dblclick', this.dblClickListener, this);
			}
		}
	},

	/**
	 * Enable or disable switching to edit mode by double click.
	 * @param {Boolean} allow True to allow editing
	 */
	setAllowEdit: function(allow)
	{
		this.allowEdit = allow;
		var editor = this.getEditor();
		if (editor) {
			if (this.dblClickListener) {
				editor.un('dblclick', this.dblClickListener, this);
				this.dblClickListener = null;
			}
			if (allow && this.readOnly) {
				this.dblClickListener = function() {
					this.setReadOnly(false);
				};
				editor.on('dblclick', this.dblClickListener, this);
			}
		}
	},

	/**
	 * Ensure the HTML editor is active when showing this field so HTML is rendered
	 * instead of displayed as plain text after switching tabs.
	 */
	onShow: function()
	{
		var layout = this.getLayout();

		// Ensure the HTML editor component is active so HTML is rendered
		if (this.useHtml && layout.activeItem !== this.items.get(0)) {
			var value = this.getValue();
			this.setHtmlEditor(true, false);
			this.setValue(value);
		}

		// When returning to this field, TinyMCE might have been hidden; reload it
		var editor = this.getEditor();
		if (editor && Ext.isFunction(editor.withEd)) {
			editor.withEd(function() {
				var ed = editor.getEditor();
				if (ed && ed.isHidden()) {
					ed.load();
				}
			});
		}
	},

	/**
	 * Sets the underlying DOM field's value directly, bypassing validation.
	 * To set the value with validation see {@link #setValue}.
	 * @param {Mixed} value The value to set
	 * @return {Ext.form.Field} this
	 */
	setRawValue: function(value)
	{
		var component = this.getLayout().activeItem;
		return component.setRawValue(value);
	},

	/**
	 * Sets a data value into the field and validates it.
	 * To set the value directly without validation see {@link #setRawValue}
	 * @param {Mixed} value The value to set
	 * @return {Ext.form.Field} this
	 */
	setValue: function(value)
	{
		var component = this.getLayout().activeItem;
		return component.setValue(value);
	},

	/**
	 * Returns the raw data value which may or may not be a valid, defined value.
	 * To return a normalized value see {@link #getValue}.
	 * @return {Mixed} value The field value
	 */
	getRawValue: function()
	{
		var component = this.getLayout().activeItem;
		return component.getRawValue();
	},

	/**
	 * Returns the normalized data value (undefined or emptyText will be returned as '').
	 * To return the raw value see {@link #getRawValue}.
	 * @return {Mixed} value The field value
	 */
	getValue: function()
	{
		var component = this.getLayout().activeItem;
		return component.getValue();
	},

	/**
	 * Try to focus this component.
	 * @param {Boolean} selectText (optional) If applicable, true to also select the text in this component
	 * @param {Boolean/Number} delay (optional) Delay the focus this number of milliseconds (true for 10 milliseconds)
	 * @return {Ext.Component} this
	 */
	focus: function(select, delay)
	{
		var component = this.getLayout().activeItem;
		component.focus(select, delay);
	},

	/**
	 * Resets the current field value to the originally loaded value and clears any validation messages.
	 * See {@link Ext.form.BasicForm}.{@link Ext.form.BasicForm#trackResetOnLoad trackResetOnLoad}
	 */
	reset: function()
	{
		var component = this.getLayout().activeItem;
		component.reset();
	},

	/**
	 * Enables all components using this action.
	 */
	enable: function()
	{
		this.items.each(function(item) {
			item.enable();
		});
	},

	/**
	 * Disables all components using this action.
	 */
	disable: function()
	{
		this.items.each(function(item) {
			item.disable();
		});
	},

	/**
	 * Obtain the currently active Editor Component.
	 * @return {Ext.form.Field} The currently active Component in the editor
	 */
	getEditor: function()
	{
		return this.getLayout().activeItem;
	},

	/**
	 * Returns the {@link #name} or {@link #hiddenName} attribute of the field if available.
	 * @return {String} name The field {@link Ext.form.Field#name name} or {@link Ext.form.ComboBox#hiddenName hiddenName}
	 */
	getName: function()
	{
		var component = this.getLayout().activeItem;
		return component.getName();
	},

	/**
	 * Updates the {@link #useHtml} flag and {@link Ext.layout.CardLayout#setActiveItem activates}
	 * the corresponding {@link Ext.form.Field editor} from {@link #items}.
	 * @param {Boolean} useHtml True to enable the HTML editor
	 * @param {Boolean} convert (optional) False to prevent the current {@link #getValue value}
	 * to be converted to the new editor.
	 */
	setHtmlEditor: function(useHtml, convert)
	{
		if (this.useHtml !== useHtml) {
			var layout = this.getLayout();
			var value = layout.activeItem.getValue();

			this.useHtml = useHtml;
			layout.setActiveItem(useHtml ? 0: 1);

			// Convert the old value
			if (convert !== false && !Ext.isEmpty(value)) {
				var newValue;
				if (useHtml) {
					newValue = Zarafa.core.HTMLParser.convertPlainToHTML(value);
				} else {
					newValue = Zarafa.core.HTMLParser.convertHTMLToPlain(value);
				}

				this.fireEvent('change', this, newValue, value);
				layout.activeItem.setValue(newValue);
			}
		}
	},

	/**
	 * Indicates if the Editor is currently working in HTML mode or not.
	 * @return {Boolean} True if the editor is currently in HTML mode.
	 */
	isHtmlEditor: function()
	{
		return this.useHtml;
	},

	/**
	 * Function is used to set the cursor position in {@link Zarafa.common.form.TextArea}.
	 */
	onSetAutoFocusCursor: function()
	{
		var component = this.getLayout().activeItem;
		component.setCursorLocation();
	},

	/**
	 * Function is used to get current editor content.
	 */
	getContent: function()
	{
		return this.tinyMceTextArea.getEditorContent();
	},

	/**
	 * Check if the editor content already contains the signature.
	 */
	hasSignature: function()
	{
		var component = this.getLayout().activeItem;

		// Check if the active editor is TinyMCE
		if (component instanceof Ext.ux.form.TinyMCETextArea) {
			return component.hasSignature();
		} else {
			// For plain text editor, convert the HTML signature to plain text
			var content = component.getValue();
			var plainSignature = Zarafa.core.HTMLParser.convertHTMLToPlain(this.currentSignatureHtml);

			return content.includes(plainSignature);
		}
	},

	/**
	 * Replace the content of the existing signature.
	 * @param {String} value The text which is to be replaced for in-place-signature edit
	 */
	replaceSignatureContent: function(newContent) {
		var component = this.getLayout().activeItem;

		// Check if the active editor is TinyMCE
		if (component instanceof Ext.ux.form.TinyMCETextArea) {
			component.replaceSignatureContent(newContent);
		} else {
			// For plain text editor, convert the HTML signature to plain text
			var content = component.getValue();
			var plainSignature = Zarafa.core.HTMLParser.convertHTMLToPlain(this.currentSignatureHtml);

			// Replace the plain text signature
			if (content.includes(plainSignature)) {
			var beforeSignature = content.split(plainSignature)[0];
			component.setValue(beforeSignature + Zarafa.core.HTMLParser.convertHTMLToPlain(newContent)); // Replace with converted plain text
			}
		}
	},

	/**
	 * Skip moveToSignature for plain text editor
	 */
	moveToSignature: function() {
		var component = this.getLayout().activeItem;

		// Check if the active editor is TinyMCE
		if (component instanceof Ext.ux.form.TinyMCETextArea) {
			component.moveToSignature();
		}
	},

	/**
	 * Function inserts HTML text into the editor field where cursor is positioned.
	 * @param {String} value The text which must be inserted at the cursor position
	 */
	insertAtCursor: function(value)
	{
		var component = this.getLayout().activeItem;
		component.insertAtCursor(value);
	},

	/**
	 * Function select the text in editor by given selector.
	 *
	 * @param {String} selector The selector query which used to select the text in editor.
	 * @return {boolean} return true if text is selected in editor else false.
	 */
	selectBySelector: function(selector)
	{
		var component = this.getLayout().activeItem;
		return component.selectBySelector(selector);
	},

	/**
	 * bind a record to this component
	 * overridden because a record is needed for the HTML editor when there are inline images
	 * @param {Zarafa.core.data.IPMRecord} record
	 */
	bindRecord: function(record)
	{
		if(this.isHtmlEditor()) {
			var component = this.getLayout().activeItem;
			component.bindRecord(record);
		}
	},

	/**
	 * Function used to get the configured editor xtype.
	 * @return {String} configured editor xtype.
	 */
	getHTMLEditorXtype: function()
	{
		var editorPlugins = container.getPlugins().filter(function (htmlEditorPlugin){
			return htmlEditorPlugin instanceof Zarafa.core.HtmlEditorPlugin;
		});
		var configuredEditor = container.getSettingsModel().get('zarafa/v1/contexts/mail/html_editor');
		var editorPlugin = editorPlugins.find(function(editorPlugin){
			return editorPlugin.getName() === configuredEditor;
		});

		if (editorPlugin) {
			// Don't fail if a plugin didn't define an editorXType
			this.htmlXtype = editorPlugin.editorXType || this.htmlXtype;
		}
		return this.htmlXtype;
	}
});

Ext.reg('zarafa.editorfield', Zarafa.common.ui.EditorField);
Ext.namespace('Zarafa.common.ui');

/**
 * @class Zarafa.common.ui.IconClass
 *
 * Class returns icon class for different records, like folder and message records,
 * based on it's properties like container_class/message_class, distlist_type,
 * display_type, icon_index.
 *
 * @singleton
 */
Zarafa.common.ui.IconClass = {
	/**
	 * Convenience method for getting the icon class for the record.
	 *
	 * It will check for {@link Zarafa.core.mapi.ObjectType ObjectType} of record.
	 * and on the basis of it, It will return CSS iconClass.
	 *
	 * @param {Zarafa.core.data.MAPIRecord} record The record for which we need to find iconClass.
	 * @return {String} icon class.
	 */
	getIconClass: function(record)
	{
		var iconIndex = record.get('icon_index');
		var objectType = record.get('object_type');
		var iconCls = '';

		// When the iconIndex is provided, we prefer to use this index
		// rather then guessing based on a different property.
		// One exception exists however which is the 'appt_recurring' icon
		// for Meeting requests/responses. For those types of messages we
		// do need to read the message class.
		if(!Ext.isEmpty(iconIndex)) {
			if ((iconIndex !== Zarafa.core.mapi.IconIndex['appt_recurring'] &&
				 iconIndex !== Zarafa.core.mapi.IconIndex['appt_appointment'] ) ||
				!Zarafa.core.MessageClass.isClass(record.get('message_class'), ['IPM.Schedule.Meeting'], true)) {
				iconCls = Zarafa.core.mapi.IconIndex.getClassName(iconIndex, 'icon');
			}
		}

		// If assignee uses older version webapp and he/she can decline task
		// then it shows wrong icon for the declined task to assigner because
		// earlier we use 0x00000502 icon index (icon_task_assignee class) for
		// declined/accepted task in assigner task grid but after KW-1283 ticket
		// we use 0x00000506 and 0x00000503 icon index for the declined and accepted task in
		// assigner task grid.
		if (!Ext.isEmpty(iconCls) &&
			objectType === Zarafa.core.mapi.ObjectType.MAPI_MESSAGE &&
			Ext.isFunction(record.isMessageClass) &&
			record.isMessageClass('IPM.Task')) {
			if (record.isTaskOrganized() && iconCls === 'icon_task_assignee'){
				if(record.isTaskDeclined()) {
					iconCls = 'icon_task_declined';
				} else if (record.isTaskAccepted()) {
					iconCls = 'icon_task_assigned';
				}
			}
		}

		// If the iconIndex could not be resolved to a CSS class, we have to
		// read the other properties to see what CSS class might be applied
		if (Ext.isEmpty(iconCls)) {
			switch(objectType) {
				case Zarafa.core.mapi.ObjectType.MAPI_STORE:
				case Zarafa.core.mapi.ObjectType.MAPI_FOLDER:
					iconCls = this.getIconClassFromContainerClass(record);
					break;
				case Zarafa.core.mapi.ObjectType.MAPI_MESSAGE:
					iconCls = this.getIconClassFromMessageClass(record);
					break;
				case Zarafa.core.mapi.ObjectType.MAPI_MAILUSER:
				case Zarafa.core.mapi.ObjectType.MAPI_DISTLIST:
				case Zarafa.core.mapi.ObjectType.MAPI_ABCONT:
					iconCls = this.getIconClassFromDisplayType(record);
					break;
				case Zarafa.core.mapi.ObjectType.MAPI_ATTACH:
					iconCls = this.getIconClassFromAttachMethod(record);
					break;
				default:
					var distlistType = record.get('distlist_type');
					if (Ext.isDefined(distlistType)) {
						iconCls = this.getIconClassFromDistlistType(record);
					}
			}
		}

		// Add special class in case the message was unread
		if ( iconCls === 'icon_mail') {
			if (objectType === Zarafa.core.mapi.ObjectType.MAPI_MESSAGE) {
				if (Ext.isFunction(record.isRead) && !record.isRead()) {
					iconCls = 'icon_mail_unread';
				} else {
					iconCls = 'icon_mail_read';
				}
			}
		}

		return iconCls;
	},

	/**
	 * Convenience method for getting the icon class for the
	 * {@link Zarafa.core.data.IPFRecord IPFRecord}.
	 *
	 * Function will first check whether folder is IPM_Subtree then return store icon,
	 * Then check whether it's a default folder than set particular default folder icon,
	 * otherwise get icon class from container_class.
	 *
	 * @param {Zarafa.hierarchy.data.IPFRecord} folder The mapi folder/store record.
	 * @param {String} containerClass (optional) The container class for the folder,
	 * if not provided, the container_class property from the folder will be used.
	 * @return {String} icon class.
	 */
	getIconClassFromContainerClass: function(folder, containerClass)
	{
		if (folder) {
			if (Ext.isFunction(folder.isFavoritesFolder) && folder.isFavoritesFolder()) {
				if(folder.isSearchFolder()) {
					return 'icon_magnifier';
				}
				return 'icon_folder_note';
			}
			// For root nodes we need to discover if it is our private or public store,
			// or if it is a shared store, in which case it can be an entire
			// store, or only the root folder for a specific folder type
			if (folder.isIPMSubTree()) {
				var mapiStore = folder.getMAPIStore();

				if (mapiStore.isSharedStore()) {
					// For a shared store, check the folder_type
					// to determine if the entire store is loaded,
					// or only a specific folder type.
					var type = mapiStore.get('folder_type');
					if (type === 'all') {
						return 'icon_folder_shared_store';
					} else {
						return 'icon_folder_shared_' + type;
					}
				} else if (mapiStore.isPublicStore()) {
					// Public store
					return 'icon_folder_public_store';
				} else {
					// Private store
					return 'icon_inbox';
				}
			} else if (Ext.isFunction(folder.isFavoritesFolder) && folder.isFavoritesRootFolder()) {
				return 'icon_favorites';
			}

			// check if the folder is a default folder
			var defaultKey = folder.getDefaultFolderKey();
			if (defaultKey) {
				return 'icon_folder_default_' + defaultKey.toLowerCase();
			}

			// Check if the container class was provided,
			// use the container_class property otherwise.
			containerClass = containerClass || folder.get('container_class');
		}

		if (Zarafa.core.ContainerClass.isClass(containerClass, 'IPF.Note.OutlookHomepage', true)) {
			return 'icon_folder_rss';
		} else if (Zarafa.core.ContainerClass.isClass(containerClass, 'IPF', true)) {
			return 'icon_folder_' + containerClass.toLowerCase().split('.')[1];
		} else {
			// No, or invalid, containerClass defined, return default icon.
			return 'icon_folder_note';
		}
	},

	/**
	 * Function will first check for icon from icon_index, if icon_index is not found
	 * It will generate icon_class from the message_class and will return it.
	 * @param {Zarafa.core.data.IPMRecord} message The mapi message record.
	 * if not provided, the message_class property from the record will be used.
	 * @return {String} icon class.
	 * @private
	 */
	getIconClassFromMessageClass: function(record)
	{
		var recurring = false;
		var counter_proposal = false;
		var declinedTask = false;
		var messageClass = record.get('message_class') || '';
		messageClass = messageClass.toUpperCase();

		if (record) {
			// If the message is a stub, then we always return the stubbed
			// icon regardless of the actual type of the message.
			if(record.get('stubbed')) {
				return 'icon_mail_stubbed';
			}

			// Check if the message is recurring.
			recurring = Ext.isFunction(record.isRecurring) && record.isRecurring();
			counter_proposal = record.get('counter_proposal');

			// If Assigner sent task request using older webapp version then KW-1283
			// and assignee declined task from latest webapp version in that case
			// mail icon shows instead of decline task icon to avoid this we add below code.
			// Note: In future we can remove this check.
			if (messageClass === 'IPM.TASK' && !Ext.isEmpty(record.get('icon_index'))) {
				declinedTask = record.isTaskOrganized() && record.isTaskDeclined();
			}
		}

		var mapping = {
			'IPM.APPOINTMENT'			: 'icon_appt_appointment',
			'IPM.TASK'				: declinedTask ? 'icon_task_declined' : 'icon_task',
			'IPM.TASKREQUEST'			: 'icon_task_request',
			'IPM.TASKREQUEST.DECLINE'		: 'icon_task_declined',
			'IPM.TASKREQUEST.ACCEPT'		: 'icon_task_accepted',
			'IPM.STICKYNOTE'			: 'icon_note_yellow',
			'IPM.CONTACT'				: 'icon_contact_user',
			'IPM.DISTLIST'				: 'icon_contact_distlist',
			'IPM.DISTLIST.ORGANIZATION'		: 'icon_contact_distlist_organization',
			'IPM.SCHEDULE.MEETING.REQUEST'		: recurring ? 'icon_appt_meeting_recurring' : 'icon_calendar_mr',
			'IPM.SCHEDULE.MEETING.RESP.POS'		: 'icon_calendar_appt_accept',
			'IPM.SCHEDULE.MEETING.RESP.TENT'	: counter_proposal ? 'icon_appt_meeting_newtime' : 'icon_calendar_appt_tentative',
			'IPM.SCHEDULE.MEETING.RESP.NEG'		: 'icon_calendar_appt_cancelled',
			'IPM.SCHEDULE.MEETING.CANCELED'		: 'icon_appt_meeting_cancel',
			'IPM.SCHEDULE.MEETING.NOTIFICATION'	: 'icon_forward',
			'IPM.NOTE'				: 'icon_mail',
			'REPORT.IPM.NOTE.IPNRN'			: 'icon_mail_read_receipt',
			'REPORT.IPM.NOTE.IPNNRN'		: 'icon_mail_nonread_receipt',
			'REPORT.IPM.NOTE.DR'			: 'icon_mail_delivery_receipt',
			'REPORT.IPM.NOTE.NDR'			: 'icon_mail_report_ndr',
			'IPM.NOTE.STORAGEQUOTAWARNING'		: 'icon_mail icon_mail_unread'
		};

		do {
			var iconClass = mapping[messageClass];
			if (!Ext.isEmpty(iconClass)) {
				return iconClass;
			}

			var index = messageClass.lastIndexOf('.');
			if (index <= 0) {
				break;
			}

			messageClass = messageClass.substr(0, index);
		} while (true);

		// No, or unknown, messageClass defined, return default icon.
		return 'icon_message';
	},

	/**
	 * Function returns icon_class from the distlist_type for showing icons for members
	 * @param {Zarafa.contact.DistlistMemberRecord} record The mapi message record.
	 * @param {String} distlistType (optional) The distlist type for the message,
	 * if not provided, the distlist_type property from the record will be used.
	 * @return {String} icon class.
	 */
	getIconClassFromDistlistType: function(record, distlistType)
	{
		if (record) {
			// Check if the distlist type was provided,
			// use the distlist_type property otherwise.
			distlistType = distlistType || record.get('distlist_type');
		}

		switch(distlistType) {
			case Zarafa.core.mapi.DistlistType.DL_DIST:
			case Zarafa.core.mapi.DistlistType.DL_DIST_AB:
				return 'icon_contact_distlist';
			case Zarafa.core.mapi.DistlistType.DL_USER:
			case Zarafa.core.mapi.DistlistType.DL_USER2:
			case Zarafa.core.mapi.DistlistType.DL_USER3:
			case Zarafa.core.mapi.DistlistType.DL_USER_AB:
			/* falls through*/
			default:
				return 'icon_contact_user';
		}
	},

	/**
	 * Function returns icon_class from the distlist_type for showing icons for members
	 * @param {Zarafa.core.data.IPMRecord} record The mapi message record.
	 * @param {String} displayType (optional) The display type for the message,
	 * if not provided, the display_type property from the record will be used.
	 * @return {String} icon class.
	 */
	getIconClassFromDisplayType: function(record, displayType)
	{
		if (record) {
			// Check if the display type was provided,
			// use the display_type property otherwise.
			if (!Ext.isDefined(displayType)) {
				displayType = record.getDisplayType();
			}
		}

		switch(displayType) {
			case Zarafa.core.mapi.DisplayType.DT_ORGANIZATION:
				return 'icon_contact_company';
			case Zarafa.core.mapi.DisplayType.DT_DISTLIST:
			case Zarafa.core.mapi.DisplayType.DT_PRIVATE_DISTLIST:
			case Zarafa.core.mapi.DisplayType.DT_AGENT:
			case Zarafa.core.mapi.DisplayTypeEx.DT_SEC_DISTLIST:
				return 'icon_contact_distlist';
			case Zarafa.core.mapi.DisplayType.DT_REMOTE_MAILUSER:
				return 'icon_contact_smtp';
			case Zarafa.core.mapi.DisplayTypeEx.DT_ROOM:
				return 'icon_contact_room';
			case Zarafa.core.mapi.DisplayTypeEx.DT_EQUIPMENT:
				return 'icon_contact_equipment';
			case Zarafa.core.mapi.DisplayType.DT_MAILUSER:
			/* falls through*/
			default:
				return 'icon_contact_user';
		}
	},

	/**
	 * Function returns icon_class from the attach_method for showing icons for attachments.
	 * @param {Zarafa.core.data.IPMAttachmentRecord} record The mapi attachment record.
	 * @return {String} icon class.
	 */
	getIconClassFromAttachMethod: function(record)
	{
		if(record.isEmbeddedMessage()) {
			return 'icon_embed_attachment';
		} else {
			return 'icon_paperclip';
		}
	},

	/**
	 * Obtain reminder icon in svg format.
	 * @param {String} color The icon color.
	 * @return {String} The reminder icon
	 */
	getReminderSvgIcon: function(counter)
	{
		return 'data:image/svg+xml;charset=utf8,' + encodeURIComponent(this.getReminderSvgStructure(counter));
	},

	/**
	 * Obtain svg structure for reminder icon.
	 * @param {Number} counter The number of reminders in {@link Zarafa.common.reminder.data.ReminderStore ReminderStore}.
	 * @return {String} The svg structure of reminder icon
	 */
	getReminderSvgStructure: function(counter)
	{
		var hasCounter = (counter !== 0 && Ext.isDefined(counter));
		var svg = '<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="-40 -51 100 120">' +
			'<g>' +
				'<path style="fill:#FFF;" d="M7.3846154 18C8.4 18 9.2307692 17.169231 9.2307692 16.153846l-3.6923077 0C5.5384615 17.169231 6.36 18 7.3846154 18l0 0zm5.5384616 -5.538461l0 -4.6153851C12.923077 5.0123077 11.409231 2.64 8.7692308 2.0123077l0 -0.6276923C8.7692308 0.61846154 8.1507692 0 7.3846154 0 6.6184615 0 6 0.61846154 6 1.3846154L6 2.0123077C3.3507692 2.64 1.8461539 5.0030769 1.8461539 7.8461539l0 4.6153851L0 14.307692l0 0.923077 14.769231 0 0 -0.923077 -1.846154 -1.846153 0 0z"/>' +
			'</g>';

		if (hasCounter) {
			svg +='<circle fill="#ee162d" cx="16" cy="2.5" r="8"/>' +
				'<text x="16" y="5.5" id="counter" style="fill:#FFF; font-weight: bold; font-family: Arial,Helvetica,sans-serif; font-size: 10px;" text-anchor="middle">'+counter+'</text>';
		}
		svg += '</svg>';
		return svg;
	}
};
/**
 * @class Zarafa.common.ui.LoadMask
 * @extends Ext.LoadMask
 *
 * This custom loadmask is created to handle search requests. When {@link Zarafa.core.data.MAPIStore MAPIStore}
 * starts search it is started as incremental search so search results are updated periodically.
 * but every time search results are updated we shouldn't show loadmask, so this custom loadmask handles
 * this situation and doesn't show load mask when load event is fired for updating search results.
 */
Zarafa.common.ui.LoadMask = Ext.extend(Ext.LoadMask, {
	/**
	 * @cfg {String} msg message that will be displayed in {@link Zarafa.common.ui.LoadMask LoadMask}
	 * when the {@link Zarafa.core.data.MAPIStore MAPIStore} is loading data.
	 */
	msg: _('Loading…'),

	/**
	 * @cfg {Boolean} isLoading it was by default false which represent that loading mask
	 * was not warp on dummy row of grid.
	 */
	isLoading: false,

	/**
	 * @cfg {String} failureMsg message that will be displayed in {@link Zarafa.common.ui.LoadMask LoadMask}
	 * when the {@link Zarafa.core.data.MAPIStore MAPIStore} has fired an exception.
	 */
	failureMsg: _('Message could not be loaded'),

	/**
	 * @cfg {String} failureMsgCls The CSS class to be applied on the {@link Zarafa.common.ui.LoadMask LoadMask}
	 * when the {@link Zarafa.core.data.MAPIStore MAPIStore} has fired an exception.
	 */
	failureMsgCls: 'x-mask-error',

	/**
	 * Function will be called whenever {@link Zarafa.core.data.MAPIStore MAPIStore}'s
	 * {@link Zarafa.core.data.MAPIStore#load} is fired, and will hide the loadmask.
	 *
	 * @param {Zarafa.core.data.IPMStore} store The store which has loaded
	 * @param {Zarafa.core.data.IPMRecord/Array} records The records which have loaded
	 * @param {Object} options The options object used for loading the store.
	 * @private
	 */
	onLoad: function(store, records, options)
	{
		if(options && (options.actionType === Zarafa.core.Actions['updatesearch'] || options.actionType === Zarafa.core.Actions['updatelist'])) {
			// don't do anything here, as we are just updating the search or live scroll results
			// so loadmask shouldn't be removed
			return;
		}

		Zarafa.common.ui.LoadMask.superclass.onLoad.apply(this, arguments);
	},

	/**
	 * Function will be called whenever {@link Zarafa.core.data.MAPIStore MAPIStore}'s
	 * {@link Zarafa.core.data.MAPIStore#beforeload} is fired, and will show the loadmask.
	 *
	 * @param {Zarafa.core.data.IPMStore} store the Store which is going to be loaded
	 * @param {Object} options The options object which is used for loading the store
	 * @private
	 */
	onBeforeLoad: function(store, options)
	{
		if(options && (options.actionType === Zarafa.core.Actions['updatesearch'] || options.actionType === Zarafa.core.Actions['updatelist'])){
			// don't do anything here, as we are just updating the search or live scroll results
			// so loadmask shouldn't be shown
			return;
		}

		Zarafa.common.ui.LoadMask.superclass.onBeforeLoad.apply(this, arguments);
	},

	/**
	 * Show the {@link #failureMsg} using the {@link #failureMsgCls}.
	 */
	showError: function()
	{
		this.el.unmask(false);
		this.el.mask(this.failureMsg, this.failureMsgCls);
	}
});
Ext.namespace('Zarafa.common.ui');

/**
 * @class Zarafa.common.ui.PagingToolbar
 * @extends Ext.PagingToolbar
 * @xtype zarafa.paging
 *
 * A simple paging tool bar for context.
 */
Zarafa.common.ui.PagingToolbar = Ext.extend(Ext.PagingToolbar, {
	/**
	 * @cfg {Boolean} displayRefresh
	 * <tt>true</tt> to display the refresh button item (defaults to <tt>false</tt>)
	 */
	displayRefresh: false,

	/**
	 * @cfg {Boolean} displayInfo
	 * <tt>true</tt> to display the paging information (defaults to <tt>false</tt>)
	 */
	displayInfo: false,

	/**
	 * Initialises the paging component.
	 * This will build the paging component listen to some exents
	 * @private
	 */
	initComponent: function()
	{
		this.cls = 'zarafa-paging-toolbar';
		this.ariaLabel = _('Pagination');

		var pagingItems = [this.first = new Ext.Toolbar.Button({
			tooltip: this.firstText,
			overflowText: this.firstText,
			iconCls: 'x-tbar-page-first',
			disabled: true,
			handler: this.moveFirst,
			scope: this
		}), this.prev = new Ext.Toolbar.Button({
			tooltip: this.prevText,
			overflowText: this.prevText,
			iconCls: 'x-tbar-page-prev',
			disabled: true,
			handler: this.movePrevious,
			scope: this
		}),
		this.beforePageText,
		this.inputItem = new Ext.form.NumberField({
			cls: 'x-tbar-page-number',
			allowDecimals: false,
			allowNegative: false,
			enableKeyEvents: true,
			selectOnFocus: true,
			submitValue: false,
			fieldLabel: _('Page number'),
			listeners: {
				scope: this,
				keydown: this.onPagingKeyDown,
				blur: this.onPagingBlur
			}
		}), this.afterTextItem = new Ext.Toolbar.TextItem({
			text: String.format(this.afterPageText, 1)
		}),
		this.next = new Ext.Toolbar.Button({
			tooltip: this.nextText,
			overflowText: this.nextText,
			cls: 'x-btn-page-next',
			iconCls: 'x-tbar-page-next',
			disabled: true,
			handler: this.moveNext,
			scope: this
		}), this.last = new Ext.Toolbar.Button({
			tooltip: this.lastText,
			overflowText: this.lastText,
			iconCls: 'x-tbar-page-last',
			disabled: true,
			handler: this.moveLast,
			scope: this
		})];


		var userItems = this.items || this.buttons || [];
		if (this.prependButtons) {
			this.items = userItems.concat(pagingItems);
		} else {
			this.items = pagingItems.concat(userItems);
		}
		delete this.buttons;

		// display refresh item if displayRefresh is set
		if(this.displayRefresh){
			this.items.push('-');
			this.items.push(this.refresh = new Ext.Toolbar.Button({
					tooltip: this.refreshText,
					overflowText: this.refreshText,
					iconCls: 'x-tbar-loading',
					handler: this.doRefresh,
					scope: this
				})
			);
		}

		// display info if displayInfo is set
		if(this.displayInfo){
			this.items.push('->');
			this.items.push(this.displayItem = new Ext.Toolbar.TextItem({}));
		}

		// Make pagination display info a live region for screen readers
		if (this.displayItem) {
			this.displayItem.on('afterrender', function() {
				if (this.displayItem.el) {
					this.displayItem.el.set({ 'role': 'status', 'aria-live': 'polite' });
				}
			}, this);
		}

		Ext.PagingToolbar.superclass.initComponent.call(this);

		this.addEvents(
			/**
			 * @event change
			 * Fires after the active page has been changed.
			 * @param {Ext.PagingToolbar} this
			 * @param {Object} pageData An object that has these properties:<ul>
			 * <li><code>total</code>: Number <div class="sub-desc">The total number of records in the dataset as
			 * returned by the server</div></li>
			 * <li><code>activePage</code>: Number <div class="sub-desc">The current page number</div></li>
			 * <li><code>pages</code>: Number <div class="sub-desc">The total number of pages (calculated from
			 * the total number of records in the dataset as returned by the server and the current {@link #pageSize})</div></li>
			 * </ul>
			 */
			'change',
			/**
			 * @event beforechange
			 * Fires just before the active page is changed.
			 * Return false to prevent the active page from being changed.
			 * @param {Ext.PagingToolbar} this
			 * @param {Object} params An object hash of the parameters which the PagingToolbar will send when
			 * loading the required page. This will contain:<ul>
			 * <li><code>start</code>: Number <div class="sub-desc">The starting row number for the next page of records to
			 * be retrieved from the server</div></li>
			 * <li><code>limit</code>: Number <div class="sub-desc">The number of records to be retrieved from the server</div></li>
			 * </ul>
			 * <p>(note: the names of the <b>start</b> and <b>limit</b> properties are determined
			 * by the store's {@link Ext.data.Store#paramNames paramNames} property.)</p>
			 * <p>Parameters may be added as required in the event handler.</p>
			 */
			'beforechange'
		);

		this.on('afterlayout', this.onFirstLayout, this, {single: true});

		if (!Ext.isDefined(this.cursor)) {
			this.cursor = 0;
		}

		this.bindStore(this.store, true);
	},

	/**
	 * Binds the paging toolbar to the specified {@link Ext.data.Store}
	 * @param {Store} store The store to bind to this toolbar
	 * @param {Boolean} initial (Optional) true to not remove listeners
	 */
	bindStore: function(store, initial){
		Zarafa.common.ui.PagingToolbar.superclass.bindStore.apply(this, arguments);

		if(this.store) {
			this.mon(this.store, 'search', function(){
				// Reset the page size so the pagination toolbar (if shown) will work correctly.
				// It might have changed when the user sorted the search results.
				this.pageSize = container.getSettingsModel().get('zarafa/v1/main/page_size');
			}, this);

			this.mon(this.store, 'beforeupdatesearch', this.updateInfo, this);
		} else {
			this.mun(this.store, 'beforeupdatesearch', this.updateInfo, this);
		}
	},

	/**
	 * This will update the paging status message to be displayed and also the
	 * will preserve the state of refresh button to be enabled/disable on every
	 * {@link Zarafa.core.data.IPMStore#load onLoad}
	 * @private
	 */
	updateInfo: function()
	{
		if(this.displayItem){
			var count = this.store.getCount();
			var msg = count === 0 ?
				this.emptyMsg:
				String.format(
					this.displayMsg,
					this.cursor+1, this.cursor+count, this.store.getTotalCount()
				);
			this.displayItem.setText(msg);
		}
	},

	/**
	 * Event handler which is fired after the {@link Zarafa.core.data.IPMStore store} has
	 * loaded data from the server. This will enable the "Refresh" button again, causing a "loading"
	 * icon to disappear.
	 * @param {Zarafa.core.data.IPMStore} store.
	 * @param {Ext.data.Record[]} records list of records to operate on. In case of 'read' this will be ignored.
	 * @param {Object} parameters object containing user parameters such as range (pagination) information, sorting information, etc.
	 * @private
	 */
	onLoad: function(store, r, o)
	{
		if(!this.rendered){
			this.dsLoaded = [store, r, o];
			return;
		}
		var p = this.getParams();
		if (o.params && o.params.restriction) {
			if (!Ext.isEmpty(o.params.restriction[p.restriction.start])) {
				this.cursor = o.params.restriction[p.restriction.start];
			} else if(this.cursor !== 0) {
				this.cursor = 0;
			}
		}

		var d = this.getPageData(), ap = d.activePage, ps = d.pages;

		this.afterTextItem.setText(String.format(this.afterPageText, d.pages));
		this.inputItem.setValue(ap);
		this.first.setDisabled(ap == 1);
		this.prev.setDisabled(ap == 1);
		this.next.setDisabled(ap == ps);
		this.last.setDisabled(ap == ps);

		if(this.refresh){
			this.refresh.enable();
		}
		this.updateInfo();
		this.fireEvent('change', this, d);
	},

	/**
	 * Error handler which is fired when the 'load' action on the store failed.
	 * @private
	 */
	onLoadError: function()
	{
		if (!this.rendered) {
			return;
		}
		if (this.refresh) {
			this.refresh.enable();
		}
	},

	/**
	 * This will get parameters such as range (pagination) information, etc from the
	 * {@link Zarafa.core.data.IPMStore store} which is used for mapping Object for load calls.
	 * @private
	 */
	getParams: function()
	{
		// retain backwards compat, allow params on the toolbar itself, if they exist.
		var parameters = this.paramNames || this.store.paramNames;

		// Extjs puts the pagination into the parameters, move it into the restriction
		if (Ext.isDefined(parameters.start)) {
			parameters.restriction = parameters.restriction || {};
			parameters.restriction.start = parameters.start;
			delete parameters.start;
		}

		if (Ext.isDefined(parameters.limit)) {
			parameters.restriction = parameters.restriction || {};
			parameters.restriction.limit = parameters.limit;
			delete parameters.limit;
		}

		return parameters;
	},

	/**
	 * This will move the page to next or previous page and fire the load event on
	 * {@link Zarafa.core.data.IPMStore store} to get the data
	 * @param {Ext.Number} start start range from where the items should be displayed
	 * @private
	 */
	doLoad: function(start)
	{
		var o = {
			restriction: {}
		};

		var pn = this.getParams();

		o.restriction[pn.restriction.start] = start;
		o.restriction[pn.restriction.limit] = this.pageSize;
		if(this.fireEvent('beforechange', this, o) !== false){
			if (this.store.hasFilterApplied) {
				o.restriction["filter"] = this.store.getFilterRestriction(Zarafa.common.data.Filters.UNREAD);
				this.cursor = 0;
			}
			this.store.load({
				folder: this.store.lastOptions.folder,
				params: o
			});
		}
	}
});

Ext.reg('zarafa.paging', Zarafa.common.ui.PagingToolbar);
Ext.namespace('Zarafa.common.ui');

/**
 * @class Zarafa.common.ui.PreviewPanelToolbarButtons
 * @extends Object
 *
 * Contains special toolbar buttons for the previewpanel.
 */
Zarafa.common.ui.PreviewPanelToolbarButtons = Ext.extend(Object, {
	/**
	 * @cfg {@link Zarafa.core.ContextModel}
	 */
	model: undefined,

	/**
	 * @constructor
	 * @param config Configuration structure
	 */
	constructor: function(config)
	{
		Ext.apply(this, config);
	},

	/**
	 * Function called when insertion point previewpanel.toolbar.right is called,
	 * Function returns configuration object for Copy/Move, Delete and Print buttons
	 * which are added on the right side of previewpanels toolbar.
	 * @param {String} insertionPoint name of the insertion point
	 * @param {Object} options (optional) optional arguments such as scope
	 * @return {Object} Configuration object containing buttons
	 * which are added in the {@link Ext.Toolbar Toolbar}.
	 */
	getToolbarButtons: function(insertionPoint, options)
	{
		// Use model that is passed as arguments for these buttons, if any.
		var model = this.model;
		if(Ext.isDefined(options.model) && options.model instanceof Zarafa.core.ContextModel) {
			model = options.model;
		}

		var toolbarButtons = [];
		toolbarButtons = [{
			xtype: 'zarafa.toolbarbutton',
			tooltip: _('Delete'),
			overflowText: _('Delete'),
			iconCls: 'icon_delete',
			nonEmptySelectOnly: true,
			handler: this.onDelete,
			model: model
		},{
			xtype: 'splitbutton',
			cls: 'zarafa-more-options-btn',
			tooltip: _('More options'),
			overflowText: _('More options'),
			iconCls: 'icon_more',
			nonEmptySelectOnly: true,
			model: model,
			splitOnMoreMenu: true,
			menu: this.moreMenuButtons(model),
			handler: function() {
				this.showMenu();
			}
		}];

		// Display the popout button only if supported.
		if (Zarafa.supportsPopOut()) {
			toolbarButtons.push({
				xtype: 'zarafa.toolbarbutton',
				tooltip: _('Pop-out'),
				overflowText: _('Pop-out'),
				iconCls: 'icon_popout',
				ref: 'popoutBtn',
				nonEmptySelectOnly: true,
				handler: this.onPopout,
				model: model
			});
		}

		return toolbarButtons;
	},

	/**
	 * The menu items of the more button.
	 *
	 * @param {Zarafa.mail.dialogs.ShowMailToolbar} scope The scope for the menu items
	 * @return {Ext.menu.Menu} the dropdown menu for the more button
	 */
	moreMenuButtons: function(model)
	{
		return {
			xtype: 'zarafa.conditionalmenu',
			model: model,
			autoDestroy: false,
			items: [{
				xtype: 'zarafa.conditionalitem',
				text: _('Mark as read'),
				iconCls: 'icon_mail icon_mail_read',
				model: model,
				readState: false,
				beforeShow: this.onReadFlagItemBeforeShow,
				ref: 'markRead',
				handler: this.onReadFlagMenuItemClicked
			}, {
				xtype: 'zarafa.conditionalitem',
				text: _('Mark as unread'),
				iconCls: 'icon_mail icon_mail_unread',
				model: model,
				readState: true,
				ref: 'markUnread',
				beforeShow: this.onReadFlagItemBeforeShow,
				handler: this.onReadFlagMenuItemClicked
			}, {
				text: _('Copy/Move'),
				iconCls: 'icon_copy',
				model: model,
				handler: this.onCopyMove
			}, {
				text: _('Print'),
				iconCls: 'icon_print',
				model: model,
				handler: this.onPrintButton
			}, {
				text: _('Edit as New'),
				iconCls: 'icon_edit_as_new_mail',
				ref: 'editAsNew',
				model: model,
				handler: this.onEditAsNewMessage
			}, {
				text: _('Download'),
				iconCls: 'icon_download',
				ref: 'download',
				model: model,
				handler: this.onDownloadMail
			}],
			listeners: {
				beforeshow: this.onBeforeShowMoreMenu,
				scope: this
			}
		};
	},

	/**
	 * Handler for the beforeshow event of the {#moreMenuButtons more menu}. Will
	 * hide the Download and Edit-As-New buttons for any item that isn't a mail
	 * item And It will show and hide the 'Mark Read' and 'Mark Unread' depends on
	 * record read status.
	 *
	 * @param {Ext.menu.Menu} menu the dropdown menu for the more button
	 */
	onBeforeShowMoreMenu: function (menu)
	{
		var record = menu.model.getPreviewRecord();

		// Show the editAsNew and download buttons only for mail items
		var defaultFolderType = Zarafa.core.MessageClass.getDefaultFolderTypeFromMessageClass(record.get('message_class'));
		menu.editAsNew.setVisible(defaultFolderType === 'inbox');
		menu.download.setVisible(defaultFolderType === 'inbox');
	},


	/**
	 * Event handler which determines if the Read Flag button must be shown.
	 * There are two kind of read flag buttons which can both make use of this
	 * function (Mark as Read and Mark as Unread buttons).
	 *
	 * @param {Zarafa.core.ui.menu.ConditionalItem} item The item to enable/disable
	 * @private
	 */
	onReadFlagItemBeforeShow: function(item)
	{
		var record = item.model.getPreviewRecord();
		var defaultFolderType = Zarafa.core.MessageClass.getDefaultFolderTypeFromMessageClass(record.get('message_class'));
		// show and hide the 'Mark Read' and 'Mark Unread' if record read status is unread or read respectively.
		var isPreviewRecordRead = record.isRead() === item.readState;
		item.setVisible(defaultFolderType === 'inbox' && isPreviewRecordRead);
	},

	/**
	 * Open the {@link Zarafa.common.dialogs.CopyMoveContent CopyMoveContent} for copying
	 * or moving the currently selected records.
	 * @private
	 */
	onCopyMove: function()
	{
		Zarafa.common.Actions.openCopyMoveContent(this.model.getSelectedRecords());
	},

	/**
	 * Delete the currently selected messages. If any of the records is a recurring item,
	 * then the {@link #Zarafa.common.dialogs.MessageBox.select MessageBox} will be used
	 * to select between the recurring and single appointment.
	 * @private
	 */
	onDelete: function()
	{
		Zarafa.common.Actions.deleteRecords(this.model.getSelectedRecords());
	},

	/**
	 * Open the selected record in separate browser window by supplying layer type as "separateWindows".
	 * And if record is opened in {@link Zarafa.core.ui.MainContentTabPanel tab} then close the existing tab.
	 * @private
	 */
	onPopout: function()
	{
		var record = this.model.getPreviewRecord();
		// If record is already opened in tab panel then close the opened tab.
		var tabPanel = container.getTabPanel();
		var openedTab = tabPanel.getOpenedTab(record);
		if (openedTab) {
			tabPanel.getItem(openedTab).close();
		}
		Zarafa.common.Actions.openMessageContent(record, {layerType: 'separateWindows'});
	},

	/**
	 * "Edit as New" menuitem of more button handler
	 * @private
	 */
	onEditAsNewMessage: function()
	{
		Zarafa.mail.Actions.openCreateMailResponseContent(this.model.getSelectedRecords(), this.model, Zarafa.mail.data.ActionTypes.EDIT_AS_NEW);
	},

	/**
	 * Click handler for the "Download" menu item of the more button
	 * @private
	 */
	onDownloadMail: function()
	{
		Zarafa.common.Actions.openSaveEmlDialog(this.model.getPreviewRecord());
	},

	/**
	 * Event handler when the "Print" button has been pressed.
	 * This will call the {@link Zarafa.common.Actions#openPrintDialog}.
	 *
	 * @private
	 */
	onPrintButton: function ()
	{
		Zarafa.common.Actions.openPrintDialog(this.model.getPreviewRecord());
	},

	/**
	 * Event handler which is called when the item has been clicked.
	 * This will mark record as read or unread.
	 *
	 * @param {Zarafa.core.ui.menu.ConditionalItem} item The item which has been clicked.
	 * @private
	 */
	onReadFlagMenuItemClicked: function (item)
	{
		var record = this.model.getPreviewRecord();
		Zarafa.common.Actions.markAsRead(record, !item.readState);
		if (!item.isXType('zarafa.conditionalitem')) {
			item.parentMenu.hide(true);
		}
	}
});
Ext.ns('Zarafa.common.ui');

/**
 * @class Zarafa.common.ui.SpinnerField
 * @extends Ext.form.TextField
 * @xtype zarafa.spinnerfield
 *
 * Alternative for the {@link Ext.ux.form.SpinnerField SpinnerField}
 * which only supports numbers rather then custom values. Nor does
 * it support setting the plugins array in a flexible way, since it
 * forces the application of {@link Ext.ux.Spinner Spinner} which we
 * don't want either.
 *
 * This class solves all that, by enforcing flexibility, and allowing
 * different values then just numbers into the field.
 *
 * Everything in this class is poorly documented, but everything was copied
 * from {@link Ext.ux.form.SpinnerField SpinnerField} which had no documentation
 * at all. It seems to be setting a lot of ExtJs fields which are available
 * in {@link Ext.form.Field} but are also not documented... So to keep the style
 * as consistent as possible, this class is also not documented...
 */
Zarafa.common.ui.SpinnerField = Ext.extend(Ext.form.TextField, {
	/**
	 * @cfg {String} actionMode
	 */
	actionMode: 'wrap',
	/**
	 * @cfg {Boolean} deferHeight
	 */
	deferHeight: true,
	/**
	 * @cfg {Function} autoSize
	 */
	autoSize: Ext.emptyFn,

	/**
	 * @cfg {String} The text that appears beside the SpinnerField.
	 */
	boxLabel: '',

	/**
	 * @cfg {Function} onBlur
	 */
	onBlur: function(){

		this.beforeBlur();
		if(this.focusClass){
			this.el.removeClass(this.focusClass);
		}
		this.hasFocus = false;
		if(this.validationEvent !== false && (this.validateOnBlur || this.validationEvent == 'blur')){
			// check whether any plugin is added to the field if so validate the value according to plugin
			if(Ext.isDefined(this.plugins)){
				Ext.each(this.plugins, function(comp){
					if (Ext.isFunction(comp.validate)) {
						comp.validate();
					}
				},this);
			} else {
				this.validate();
			}
		}
		var v = this.getValue();
		if(String(v) !== String(this.startValue)){
			this.fireEvent('change', this, v, this.startValue);
		}
		this.fireEvent('blur', this);
		this.postBlur();
	},

	/**
	 * @cfg {Function} adjustSize
	 */
	adjustSize: Ext.BoxComponent.prototype.adjustSize,
	/**
	 * @cfg {Object} defaultValue The default value value which must be used.
	 */
	defaultValue: undefined,
	/**
	 * @cfg {Object} minValue The minimum value which can be used.
	 */
	minValue: undefined,
	/**
	 * @cfg {Object} maxValue The maximum value which can be used.
	 */
	maxValue: undefined,
	/**
	 * @cfg {String} incrementValue The value which must be used to increment
	 * the {@link #incrementField} field with when the normal increment/decrement
	 * option is used.
	 */
	incrementValue: 1,
	/**
	 * @cfg {String} alternateIncrementValue The value which must be used to increment
	 * the {@link #alternateIncrementField} field with when the alternate increment/decrement
	 * option is used.
	 */
	alternateIncrementValue: 10,
	/**
	 * @return {Ext.Element} The resize element
	 * @private
	 */
	getResizeEl: function()
	{
		return this.resizeEl || this.wrap;
	},
	/**
	 * @return {Ext.Element} The positioning element
	 * @private
	 */
	getPositionEl: function()
	{
		return this.positionEl || this.wrap;
	},
	/**
	 * @return {Ext.Element} The align error element
	 * @private
	 */
	alignErrorIcon: function()
	{
		if (this.wrap) {
			this.errorIcon.alignTo(this.wrap, 'tl-tr', [2, 0]);
		}
	},
	/**
	 * Called during {@link #onBlur}.
	 * @private
	 */
	validateBlur: function()
	{
		return true;
	}
});

Ext.reg('zarafa.spinnerfield', Zarafa.common.ui.SpinnerField);
Ext.namespace('Zarafa.common.ui');

/**
 * @class Zarafa.common.ui.TreeFilter
 * @extends Ext.tree.TreeFilter
 *
 * Filter class for the Tree, this enables filtering of the tree nodes,
 * in which tree nodes are filtered according to the filter query.
 */
Zarafa.common.ui.TreeFilter = Ext.extend(Ext.tree.TreeFilter, {
    /**
	 * @constructor
	 * @param {Ext.tree.Tree} tree The tree which this filter is being applied on
	 * @param {Object} config Configuration object
	 */
	constructor: function(tree, config)
	{
		Zarafa.common.ui.TreeFilter.superclass.constructor.apply(this, arguments);

		this.filterBy = this.filterByText.createDelegate(this);
    },
	
	/**
	 * Filter by a function. The passed function will be called with each node in the tree (or from the startNode). 
	 * If the function returns true, the node is kept otherwise it is filtered.
	 * If a node is filtered, its children are also filtered.
	 * @param {Function} fn The filter function
	 * @param {Object} scope scope of the function
	 * @param {Ext.tree.TreeNode} startNode root node of the tree
	 */
    filterByText: function (fn, scope, startNode)
    {
        startNode = startNode || this.tree.root;
        if (this.autoClear) {
            this.clear();
        }
        var found = {};
        var af = this.filtered, rv = this.reverse;
        var f = function (n) {
            if (n == startNode) {
                return true;
            }
            if (af[n.id]) {
                return false;
            }

            var m = fn.call(scope || n, n);

            if (!m || rv) {
                af[n.id] = n;
                return true;
            }

            found[n.id] = n;
            return true;
        };

        startNode.cascade(f, this);

        for (var idf in found) {
            if (typeof idf !== "function") {
                var curFoundItem = found[idf];
                var p = curFoundItem.parentNode;
                while (p) {
                    delete af[p.id];
                    p = p.parentNode;
                }
            }
        }

        for (var id in af) {
            if (typeof id !== "function") {
                var n = af[id];
                n.ui.hide();
            }
        }

        if (this.remove) {
            for (var id in af) {
                if (typeof id !== "function") {
                    var n = af[id];
                    if (n && n.parentNode) {
                        n.parentNode.removeChild(n);
                    }
                }
            }
        }
    },
    
    /**
     * Clears the current filter. Note: with the "remove" option
     * set a filter cannot be cleared.
     */
    clear : function()
    {
        var af = this.filtered;
        for (var id in af) {
            if (typeof id != "function") {
                var n = af[id];
                if (n && !Ext.isEmpty(n.ui)) {
                    n.ui.show();
                }
            }
        }
        this.filtered = {};
    }
});Ext.namespace('Zarafa.common.ui');

/**
 * @class Zarafa.common.ui.TreeGrid
 * @extends Ext.ux.tree.TreeGrid
 * @xtype zarafa.treegrid
 *
 * Extension of the {@link Ext.ux.tree.TreeGrid} containing
 * some UI fixes and enhancements to include greater similarity
 * with a normal {@link Ext.grid.GridPanel grid}.
 */
Zarafa.common.ui.TreeGrid = Ext.extend(Ext.ux.tree.TreeGrid, {
	/**
	 * @cfg {Boolean} autoFill
	 * Defaults to <tt>false</tt>. Specify <tt>true</tt> to have the column widths re-proportioned
	 * when the grid is <b>initially rendered</b>. The
	 * {@link Ext.grid.Column#width initially configured width}</tt> of each column will be adjusted
	 * to fit the grid width and prevent horizontal scrolling. If columns are later resized (manually
	 * or programmatically), the other columns in the grid will <b>not</b> be resized to fit the grid width.
	 * See <tt>{@link #forceFit}</tt> also.
	 */
	autoFill: false,

	/**
	 * @cfg {Boolean} forceFit
	 * Defaults to <tt>false</tt>. Specify <tt>true</tt> to have the column widths re-proportioned
	 * at <b>all times</b>. The {@link Ext.grid.Column#width initially configured width}</tt> of each
	 * column will be adjusted to fit the grid width and prevent horizontal scrolling. If columns are
	 * later resized (manually or programmatically), the other columns in the grid <b>will</b> be resized
	 * to fit the grid width. See <tt>{@link #autoFill}</tt> also.
	 */
	forceFit: false,

	/**
	 * @cfg {Boolean} unselectable
	 * Defaults to <tt>true</tt>. Indicate if the {@link #el} must be made {@link Ext.Element#unselectable}
	 * during {@link #onRender rendering}. This will prevent the user from selecting any text within the tree.
	 */
	unselectable: true,

	/**
	 * Initialize the component
	 * @private
	 */
	initComponent: function()
	{
		if (this.rootVisible === true) {
			// By default the Ext.ux.tree.TreeGrid is not capable of displaying
			// the root node. So when the rootVisible option is enabled, we must
			// change the 'uiProvider' and 'ui' properties of the root node,
			// to use the TreeGridNodeUI which is capable of rendering the root
			// node.
			if (this.root) {
				this.root.attributes.uiProvider = Ext.ux.tree.TreeGridNodeUI;
				this.root.ui = new this.root.attributes.uiProvider(this.root);
			} else {
				this.root = new Ext.tree.AsyncTreeNode({text: _('Root'), uiProvider: Ext.ux.tree.TreeGridNodeUI});
			}
		}

		Zarafa.common.ui.TreeGrid.superclass.initComponent.call(this);
	},

	/**
	 * Called during rendering. When {@link #autoFill} is enabled this will call
	 * {@link #autoFitColumns} to start resizing the columns.
	 * @private
	 */
	onRender: function()
	{
		Zarafa.common.ui.TreeGrid.superclass.onRender.apply(this, arguments);
		if (this.autoFill || this.forceFit) {
			this.autoFitColumns(true);
		}
		if (this.unselectable) {
			this.getEl().unselectable();
		}
	},

	/**
	 * Obtain the offset for the Scrollbar. This returns {@link #scrollOffset}
	 * and if that property is not set the default {@link Ext.getScrollBarWidth scrollbar width}.
	 * @return {Number} Scrollbar offset
	 * @private
	 */
	getScrollOffset: function()
	{
		return Ext.num(this.scrollOffset, Ext.getScrollBarWidth());
	},

	/**
	 * Called when the {@link #columns} must be resized. This will check
	 * the total available width, and reduce it by the current configured
	 * sized of all columns. The remaining available width will be equally
	 * divided over all non-{@link Ext.grid.Column#fixed fixed} columns.
	 * @private
	 */
	autoFitColumns: function(fillOnly, skipColumn)
	{
		var availableWidth = this.innerBody.getWidth() - this.getScrollOffset();
		var autoColumns = [];

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

			if (!column.hidden) {
				availableWidth -= column.width;
				if (!column.fixed && i !== skipColumn && !(fillOnly && column.width)) {
					autoColumns.push(column);
				}
			}
		}

		availableWidth = Math.floor(availableWidth / autoColumns.length);
		for (var i = 0, len = autoColumns.length; i < len; i++) {
			autoColumns[i].width += availableWidth;
		}
	},

	/**
	 * Check if {@link #forceFit} is enabled, if will call {@link #autoFitColumns}
	 * to resize all columns.
	 * @private
	 */
	updateColumnWidths: function()
	{
		if (this.forceFit) {
			this.autoFitColumns(false, this.colResizer.hdIndex);
		}

		Zarafa.common.ui.TreeGrid.superclass.updateColumnWidths.call(this);
	}
});

Ext.reg('zarafa.treegrid', Zarafa.common.ui.TreeGrid);
Ext.namespace('Zarafa.common.ui.appointmentpanel');

/**
 * @class Zarafa.common.ui.appointmentpanel.AppointmentBody
 * @extends Ext.form.FormPanel
 * @xtype zarafa.appointmentbody
 */
Zarafa.common.ui.appointmentpanel.AppointmentBody = Ext.extend(Ext.form.FormPanel, {
	/**
	 * @cfg {Ext.Template/String} headerTemplate The template or template string which
	 * must be applied to the {@link #header} when the {@link Zarafa.core.data.IPMRecord record}
	 * has been {@link #update updated}. The arguments of this template will be the
	 * {@link Zarafa.core.data.IPMRecord#data record.data} field.
	 */
	headerTemplate:
		'<div class="preview-header-titlebox">' +
			'<tpl if="!Ext.isEmpty(values.subject)">' +
				'<span class="preview-title">{subject:htmlEncode}</span>' +
			'</tpl>' +
		'</div>',

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

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

		config = Ext.applyIf(config, {
			xtype: 'zarafa.appointmentbody',
			border: false,
			header: true,
			autoScroll: true,
			unstyled: true,
			autoWidth: true,
			headerCfg: {
				cls: 'preview-header-title'
			},
			items: [{
				xtype:'fieldset',
				title: _('Appointment'),
				defaults: {
					xtype: 'displayfield'
				},
				items: [{
					fieldLabel: _('Subject'),
					name: 'subject'
				}, {
					fieldLabel: _('Location'),
					name: 'location'
				}, {
					fieldLabel: _('Label'),
					ref: '../labelPreview'
				}, {
					fieldLabel: _('Start Date'),
					ref: '../startDate'
				}, {
					fieldLabel: _('End Date'),
					ref: '../endDate'
				}, {
					fieldLabel: _('All Day Event'),
					name: 'alldayevent'
				}]
			}]
		});

		Zarafa.common.ui.appointmentpanel.AppointmentBody.superclass.constructor.call(this, config);

		if (Ext.isString(this.headerTemplate)) {
			this.headerTemplate = new Ext.XTemplate(this.headerTemplate, {
				compiled: true
			});
		}
	},

	/**
	 * Updates the container by loading data from the record data into the {@link #template}
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record to update the header panel with
	 */
	update: function(record)
	{
		this.record = record;

		this.getForm().loadRecord(record);

		if (Ext.isDefined(record)) {
			this.headerTemplate.overwrite(this.header.dom, record.data);
		} else {
			this.header.dom.innerHTML = '';
		}

		this.labelPreview.setValue(Zarafa.core.mapi.AppointmentLabels.getDisplayName(record.get('label')));

		var startDate = record.get('startdate');
		if (Ext.isDate(startDate)) {
			this.startDate.setValue(startDate);
		}

		var dueDate = record.get('duedate');
		if (Ext.isDate(dueDate)) {
			this.endDate.setValue(dueDate);
		}
	}
});

Ext.reg('zarafa.appointmentbody', Zarafa.common.ui.appointmentpanel.AppointmentBody);
Ext.namespace('Zarafa.common.ui.contactpanel');

/**
 * @class Zarafa.common.ui.contactpanel.ContactBody
 * @extends Ext.form.FormPanel
 * @xtype zarafa.contactbody
 */
Zarafa.common.ui.contactpanel.ContactBody = Ext.extend(Ext.form.FormPanel, {
	/**
	 * @cfg {Ext.Template/String} headerTemplate The template or template string which
	 * must be applied to the {@link #header} when the {@link Zarafa.core.data.IPMRecord record}
	 * has been {@link #update updated}. The arguments of this template will be the
	 * {@link Zarafa.core.data.IPMRecord#data record.data} field.
	 */
	headerTemplate:
		'<div class="preview-header-titlebox">' +
			'<tpl if="!Ext.isEmpty(values.subject)">' +
				'<span class="preview-title">{subject:htmlEncode}</span>' +
			'</tpl>' +
		'</div>',

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

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

		config = Ext.applyIf(config, {
			xtype: 'zarafa.contactbody',
			border: false,
			header: true,
			autoScroll: true,
			unstyled: true,
			autoWidth: true,
			headerCfg: {
				cls: 'preview-header-title'
			},
			items: [{
				xtype:'fieldset',
				title: _('Name'),
				defaults: {
					xtype: 'displayfield'
				},
				items: [{
					fieldLabel: _('Full name'),
					name: 'display_name'
				}, {
					fieldLabel: _('Company'),
					name: 'company_name'
				}, {
					fieldLabel: _('Job Title'),
					name: 'title'
				}, {
					fieldLabel: _('File as'),
					name: 'fileas'
				}]
			}, {
				xtype:'fieldset',
				title: _('Phone Numbers'),
				defaults: {
					xtype: 'displayfield'
				},
				items: [{
					fieldLabel: _('Business'),
					name: 'business_telephone_number'
				}, {
					fieldLabel: _('Home'),
					name: 'home_telephone_number'
				}, {
					fieldLabel: _('Business Fax'),
					name: 'business_fax_number'
				}, {
					fieldLabel: _('Mobile'),
					name: 'cellular_telephone_number'
				}]
			}, {
				xtype:'fieldset',
				title: _('Email'),
				defaults: {
					xtype: 'displayfield'
				},
				items: [{
					fieldLabel: _('Email'),
					name: 'email_address_1'
				}, {
					fieldLabel: _('Display name'),
					name: 'email_address_display_name_1'
				}, {
					fieldLabel: _('Webpage'),
					name: 'webpage'
				}, {
					fieldLabel: _('IM Address'),
					name: 'im'
				}]
			}, {
				xtype:'fieldset',
				title: _('Address'),
				defaults: {
					xtype: 'displayfield'
				},
				items: [{
					fieldLabel: _('Business'),
					name: 'business_address'
				}]
			}]
		});

		Zarafa.common.ui.contactpanel.ContactBody.superclass.constructor.call(this, config);

		if (Ext.isString(this.headerTemplate)) {
			this.headerTemplate = new Ext.XTemplate(this.headerTemplate, {
				compiled: true
			});
		}
	},

	/**
	 * Updates the container by loading data from the record data into the {@link #template}
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record to update the header panel with
	 */
	update: function(record)
	{
		this.record = record;

		this.getForm().loadRecord(record);

		if (Ext.isDefined(record)) {
			this.headerTemplate.overwrite(this.header.dom, record.data);
		} else {
			this.header.dom.innerHTML = '';
		}
	}
});

Ext.reg('zarafa.contactbody', Zarafa.common.ui.contactpanel.ContactBody);
Ext.namespace('Zarafa.common.ui.grid');

/**
 * @class Zarafa.common.ui.grid.ColumnModel
 * @extends Ext.grid.ColumnModel
 *
 * This adds some extra functionality to the default {@link Ext.grid.ColumnModel}.
 */
Zarafa.common.ui.grid.ColumnModel = Ext.extend(Ext.grid.ColumnModel, {
	/**
	 * @cfg {String} name The name of the columnModel which is currently used.
	 * This is used by the {@link Zarafa.common.ui.grid.GridPanel GridPanel}
	 * in the Settings path which is used to obtain the column configuration.
	 */
	name: 'default',

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

		this.addEvents(
			/**
			 * @event beforeconfigchange
			 * Fired right before the configuration is being changed, this allows
			 * the event handler to update the configuration if needed.
			 *
			 * @param {Ext.grid.ColumnModel} columnModel The column model which is being reconfigured
			 * @param {Object} The configurations object which is used to reconfigure the model
			 * @return (optional) if false the {@link #setConfig} call will be aborted and the column
			 * model will not be reconfigured.
			 */
			'beforeconfigchange'
		);

		Ext.apply(this, config);

		Zarafa.common.ui.grid.ColumnModel.superclass.constructor.call(this, config);
	},

	/**
	 * Reconfigures this column model according to the passed Array of column definition objects.
	 * For a description of the individual properties of a column definition object, see the {@link #columns Config Options}.
	 * Causes the {@link #beforeconfigchange} and {@link #configchange} events to be fired.
	 * A {@link #Ext.gridGridPanel GridPanel} using this ColumnModel will listen for this event and refresh its UI automatically.
	 *
	 * @param {Object} config The configuration to set on the columnmodel
	 * @param {Boolean} initial True if this is the initial configuration of the columnmodel
	 * @protected
	 */
	setConfig: function(config, initial)
	{
		// Make a fresh copy of the configuration array. The 'beforeconfigchange' allows for
		// modifications on the argument we pass it. So we need a copy to prevent the default
		// values to be overridden accidentally.
		var newConfig = [];
		for (var i = 0, len = config.length; i < len; i++) {
			newConfig.push(Ext.apply({}, config[i]));
		}

		if (this.fireEvent('beforeconfigchange', this, newConfig) !== false) {
			Zarafa.common.ui.grid.ColumnModel.superclass.setConfig.call(this, newConfig, initial);
		}
	},

	/**
	 * Setup any saved state for the column, ensures that defaults are applied.
	 * @param {Number} col The column index of the column to update
	 * @param {Object} state The state object which should be applied to the column
	 * @private
	 */
	setState: function(col, state)
	{
		var column = this.getColumnAt(col);
		if ( column.fixed === true ){
			// Fixed columns should not be reset by the state. This is necessary
			// because the widths of some fixed columns have changed when the new
			// design was implemented, but the state would restore the old width.
			state.width = column.width;
		}

		Zarafa.common.ui.grid.ColumnModel.superclass.setState.apply(this, arguments);
		// The state has been applied, this means the totalWidth is invalid
		this.totalWidth = null;
	}
});
Ext.namespace('Zarafa.common.ui.grid');

/**
 * @class Zarafa.common.ui.grid.DateTooltip
 * @extends Ext.ToolTip
 * @xtype zarafa.griddatetooltip
 *
 * Tooltip for 'nice' formatted dates in grids.
 * The tooltip will show the full date and time.
 */
Zarafa.common.ui.grid.DateTooltip = Ext.extend(Ext.ToolTip, {
	/**
	 * @constructor
	 * @param {Object} config Configuration object.
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			xtype: 'zarafa.griddatetooltip',
			cls: 'k-griddatetooltip',
			delegate: '.k-date-nice',
			dismissDelay: 0,
			trackMouse: true,
			renderTo: Ext.getBody(),
			listeners: {
				beforeshow: this.onBeforeshow,
				scope: this
			}
		});

		Zarafa.common.ui.grid.DateTooltip.superclass.constructor.call(this, config);
	},

	/**
	 * Event handler for the beforeshow event of the tooltip. Will update
	 * the contents of the tooltip
	 * @param (Ext.ToolTip) tooltip The category tooltip
	 */
	onBeforeshow: function(tooltip)
	{
		// Get the timestamp from the 'k-ts-' class
		var timestamp;
		tooltip.anchorTarget.getAttribute('class').split(' ').forEach(function(cls){
			if ( cls.substr(0, 5) === 'k-ts-' ){
				timestamp = parseInt(cls.substr(5));
			}
		});

		var dateString = (new Date(timestamp)).formatDefaultTime(_('D, d-m-Y, {0}'));

    tooltip.body.dom.innerHTML = Ext.util.Format.htmlEncode(dateString);
	}
});

Ext.reg('zarafa.griddatetooltip', Zarafa.common.ui.grid.DateTooltip);
Ext.namespace('Zarafa.common.ui.grid');

/**
 * @class Zarafa.common.ui.grid.GridDragZone
 * @extends Ext.grid.GridDragZone
 *
 * Extension of the default GridDragZone which
 * contains a fix for selecting or deselecting
 * rows from the Grid when Drag&Drop is enabled.
 */
Zarafa.common.ui.grid.GridDragZone = Ext.extend(Ext.grid.GridDragZone, {

	/**
	 * Obtain a data object containing the Drag information.
	 *
	 * This is inherited from the {@link Ext.grid.GridDragZone superclass},
	 * and does the same except that it will not call {@link Ext.grid.RowSelectionModel#handleMouseDown}.
	 * This action is deferred to the {@link #onMouseUp} event handler.
	 *
	 * <li><b>grid</b>: Ext.Grid.GridPanel<div class="sub-desc">The GridPanel from which the data is being dragged.</div></li>
	 * <li><b>ddel</b>: htmlElement<div class="sub-desc">An htmlElement which provides the "picture" of the data being dragged.</div></li>
	 * <li><b>rowIndex</b>: Number<div class="sub-desc">The index of the row which received the mousedown gesture which triggered the drag.</div></li>
	 * <li><b>selections</b>: Array<div class="sub-desc">An Array of the selected Records which are being dragged from the GridPanel.</div></li>
	 * </ul></p>
	 */
	getDragData: function(e)
	{
		var t = Ext.lib.Event.getTarget(e);
		var rowIndex = this.view.findRowIndex(t);
		if (rowIndex !== false) {
			var sm = this.grid.selModel;

			// Create a copy of the event object,
			// we need this later in the onInitDrag function
			var origEvent = new Ext.EventObjectImpl(e);

			return {
				grid: this.grid,
				ddel: this.ddel,
				rowIndex: rowIndex,
				selections: sm.getSelections(),
				origEvent: origEvent
			};
		}
		return false;
	},

	/**
	 * Called when the user starts dragging, this is always called after {@link #getDragData} (which
	 * is called when the user presses the mouse). This will check if the {@link #dragData} contains
	 * the 'origEvent' field. If so, it will call {@link Ext.grid.RowSelectionModel#handleMouseDown}
	 * to update our selection just before dragging.
	 *
	 * @param {Number} x The X coordinate from where we start dragging
	 * @param {Number} y The Y coordinate from where we start dragging
	 */
	onInitDrag: function(x, y)
	{
		// Check if the dragData contains a origEvent (as initialized by getDragData).
		// If this is present, we have to manually call the onItemClick event handler
		// on the view to ensure that the item we are about to drag is marked as selected.
		//
		// We can't do that in getDragData because of the ordering of the event handlers
		// of both this class as well as the DataView.
		var data = this.dragData;
		if (data && data.origEvent) {
			var sm = this.grid.selModel;

			if (!sm.isSelected(data.rowIndex)) {
				sm.handleMouseDown(this.grid, data.rowIndex, data.origEvent);

				// The selection has been changed, update the selections array
				data.selections = sm.getSelections();
			}

			delete this.dragData.origEvent;
		}

		Zarafa.common.ui.grid.GridDragZone.superclass.onInitDrag.apply(this, arguments);
	},

	/**
	 * Event handler that fires when a drag/drop obj gets a mouseup
	 *
	 * When the {@link #dragData} still contains the 'origEvent' field, then this
	 * function was called without that anything has been dragged (normally 'origEvent')
	 * is cleared in {@link #onInitDrag}. In this case we call {@link Ext.grid.RowSelectionModel#handleMouseDown}
	 * to update the selection inside the grid.
	 *
	 * @param {Event} e the mouseup event
	 */
	onMouseUp: function(e)
	{
		var data = this.dragData;

		// If the dragData still contains the origEvent, then onInitDrag has not been
		// called which means the user only clicked on the row and we must update
		// our selection.
		if (data && data.origEvent) {
			data.grid.selModel.handleMouseDown(data.grid, data.rowIndex, data.origEvent);
			delete data.origEvent;
		}

		Zarafa.common.ui.grid.GridDragZone.superclass.onMouseUp.apply(this, arguments);
	}
});
Ext.namespace('Zarafa.common.ui.grid');

/**
 * @class Zarafa.common.ui.grid.GridPanel
 * @extends Ext.grid.GridPanel
 * @xtype zarafa.gridpanel
 *
 * grommunio Web specific GridPanel which contain extra features and bugfixes
 * which could not be resolved by plugins or directly in extjs.
 */
Zarafa.common.ui.grid.GridPanel = Ext.extend(Ext.grid.GridPanel, {
	/**
	 * @cfg {String} ddText
	 * @hide
	 */

	/**
	 * The entryid of the {@link Zarafa.core.data.MAPIFolder folder} which is currently being displayed
	 * inside this panel. This is used by {@link #getStateName} to obtain the correct {@link #getState state}.
	 *
	 * @property
	 * @type String
	 */
	currentEntryId: undefined,

	/**
	 * The store entryid of the {@link Zarafa.core.data.MAPIFolder folder} which is currently being displayed
	 * inside this panel. This is used by {@link #getStateName} to obtain the correct {@link #getState state}.
	 *
	 * @property
	 * @type String
	 */
	currentStoreEntryId: undefined,

	/**
	 * The tooltip that will be used to show the full date when hovered over 'short' formatted dates
	 * @property
	 * @type {Zarafa.common.ui.grid.DateTooltip}
	 */
	dateTooltip: null,

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

		Ext.applyIf(config, {
			deferRowRender: false
		});

		if(Ext.isEmpty(config.view)) {
			config.viewConfig = Ext.applyIf(config.viewConfig || {}, {
				autoFill: true,
				markDirty: false
			});

			Ext.applyIf(config, {
				view: new Zarafa.common.ui.grid.GridView(config.viewConfig)
			});
		}

		Zarafa.common.ui.grid.GridPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Called when the GridPanel has been rendered.
	 * This activate the keymap on the element of this component after the normal operations of
	 * afterRender have been completed. It will activate by getting the xtype hierarchy from
	 * {@link #getXTypes} and format it into a string usable by the
	 * {@link Zarafa.core.KeyMapMgr KeyMapMgr}.
	 * @private
	 */
	afterRender: function()
	{
		Zarafa.common.ui.grid.GridPanel.superclass.afterRender.apply(this, arguments);
		var xtypes = this.getXTypes();

		// The first part leading up to zarafa.gridpanel will be stripped
		xtypes = xtypes.replace('component/box/container/panel/grid/zarafa.gridpanel','');

		// Then the "zarafa." will be stripped off from all the xtypes like "zarafa.somegrid".
		xtypes = xtypes.replace(/\/zarafa\./g,'.');

		// Finally we strip the string "grid" from all the xtypes. Otherwise each level will have
		// that "grid" mentioned in them. Also we add "grid" to the start as that sets
		// it apart from other components in the key mapping.
		xtypes = 'grid' + xtypes.replace(/grid/g, '');
		Zarafa.core.KeyMapMgr.activate(this, xtypes);

	  this.dateTooltip = new Zarafa.common.ui.grid.DateTooltip({
			target: this.getEl().dom
		});
	},

	/**
	 * Called when the GridPanel has been destroyed. Will destroy the {#dateTooltip}
	 * @private
	 */
	onDestroy: function()
	{
		Zarafa.common.ui.grid.GridPanel.superclass.onDestroy.apply(this, arguments);

		if ( this.dateTooltip ){
			this.dateTooltip.destroy();
		}
	},

	/**
	 * Initialize event handlers
	 * @private
	 */
	initEvents: function()
	{
		// First bind columnModel and then the store. The bindStore() function
		// will add some initialization which requires the model to be initialized.
		this.bindColumnModel(this.getColumnModel(), true);
		this.bindStore(this.getStore(), true);

		Zarafa.common.ui.grid.GridPanel.superclass.initEvents.call(this);

		// use our custom load mask
		if(this.loadMask) {
			// destroy loadmask created by superclass
			this.loadMask.destroy();
			this.loadMask = new Zarafa.common.ui.LoadMask(this.bwrap,
					Ext.apply( { store: this.store }, this.initialConfig.loadMask ) );
		}

		// Fixme: workaround fix for Firefox on Ubuntu and Firefox on MacOS
		if (Ext.isGecko && (Ext.isLinux || Ext.isMac)) {
			this.on('keydown', function (e) {
				var keyCode = e.keyCode;
				if (keyCode === Ext.EventObject.DOWN || keyCode === Ext.EventObject.UP) {
					this.selModel.onKeyPress(e, keyCode === Ext.EventObject.DOWN ? 'down' : 'up');
					e.preventDefault();
				}
			}, this);
		}

		this.mon(this.store, 'write', this.onWriteRecord, this);

		this.on('viewready', this.onViewReady, this);

		if (this.model && this.model.statefulRecordSelection) {
			this.mon(this.model, 'recordselectionchange', this.onRecordSelectionChange, this);
		}

		this.mon(this.getSelectionModel(), 'selectionchange', this.onGridSelectionChange, this);
	},

	/**
	 * Event handler which is fired when the recordselection in the {@link #model} has been changed.
	 * If no selection is currently active, this will automatically select the given records in the grid.
	 *
	 * @param {Zarafa.core.ContextModel} model this model.
	 * @param {Zarafa.core.data.IPMRecord[]} records The selected records
	 * @private
	 */
	onRecordSelectionChange: function(model, records)
	{
		if (!this.getSelectionModel().hasSelection() && !Ext.isEmpty(records)) {
			var index = model.getStore().indexOf(records[0]);
			this.getSelectionModel().selectRecords(records);
			this.getView().focusRow(index);
		}
	},

	/**
	 * Handler for 'write' event. If the action carried out with the write call was 'destroy' than we have to check
	 * a situation where the total loaded record is less than total page size and grid-scroll is not there.
	 * If the above mentioned situation is there than additional records needs to be fetched from server and
	 * synchronize {@link Zarafa.core.data.ListModuleStore store} with as many numbers of new {@link Zarafa.core.data.IPMRecords[] records} as deleted
	 * @param {Ext.data.Store} store The new {@link Ext.data.Store} object
	 * @param {String} action The name if action that was carried out for this write call.
	 * @param {Object} Result The data arrives from server side.
	 * @param {Object} response as it was received from server.
	 * @param {Array} recordSet Records that are deleted.
	 */
	onWriteRecord: function(store, action, result, response, recordSet)
	{
		if(action === 'destroy') {
			if (!store.syncStore) {
				var gridScroller = this.getView().scroller;
				if(Ext.isDefined(gridScroller) && !gridScroller.isScrollable()) {
					if (store.totalLoadedRecord < store.totalLength) {
						var options = {
							add: true,
							actionType: Zarafa.core.Actions['list']
						};

						// load store with as many new records as deleted before scrollbar has disappeared
						// For that sets start and limit base on remaining number of records.
						// For example if we have 11 remaining records left then start = 11 and limit = page_size - 11
						Ext.applyIf(options, store.lastOptions);
						var limit = container.getSettingsModel().get('zarafa/v1/main/page_size');
						options.params.restriction.limit = limit - store.getCount();
						options.params.restriction.start = store.getCount();
						store.syncStore = true;
						if (store.loadMask) {
							store.loadMask.hide();
							store.loadMask = undefined;
						}
						store.load(options);
					}
				}
			}

			var deletedRecord = Array.isArray(recordSet) ? recordSet[recordSet.length - 1] : recordSet;
			// lastIndex is the index of deleted record from grid/store.
			// lastIndex is added by destroyRecord function of Ext.data.Store
			this.onRowRemoved(deletedRecord.lastIndex, deletedRecord);
		}
	},

	/**
	 * Called after a row has been removed for the GridView.
	 * This will check if the store has a next/previous row to select in the Grid
	 * @param rowIndex {Number} rowIndex the index of row was deleted from grid.
	 * @private
	 */
	onRowRemoved: function(rowIndex)
	{
		if (container.getShadowStore().isExecuting("open")) {
			container.getShadowStore().proxy.cancelRequests('open');
			return;
		}

		var itemCount = this.getStore().getCount();
		if (itemCount > 0) {
			var sm = this.getSelectionModel();
			var selections = sm.getSelections();

			if (!Ext.isEmpty(selections)) {
				return;
			}

			// check for the next item in store else select the previous item
			if(rowIndex < itemCount) {
				sm.selectRow(rowIndex);
			} else {
				sm.selectRow(rowIndex - 1);
			}
		}
	},

	/**
	 * Reconfigures the grid to use a different Store and Column Model and fires the 'reconfigure' event.
	 * The View will be bound to the new objects and refreshed.
	 * Be aware that upon reconfiguring a GridPanel, certain existing settings may become invalidated.
	 * For example the configured {@link #autoExpandColumn} may no longer exist in the new ColumnModel.
	 * Also, an existing {@link Ext.PagingToolbar PagingToolbar} will still be bound to the old Store,
	 * and will need rebinding. Any {@link #plugins} might also need reconfiguring with the new data
	 *
	 * @param {Ext.data.Store} store The new {@link Ext.data.Store} object
	 * @param {Ext.grid.ColumnModel} model The new {@link Ext.grid.ColumnModel} object
	 */
	reconfigure: function(store, colModel)
	{
		// First bind columnModel and then the store. The bindStore() function
		// will add some initialization which requires the model to be initialized.
		this.bindColumnModel(colModel);
		this.bindStore(store);

		Zarafa.common.ui.grid.GridPanel.superclass.reconfigure.call(this, store, colModel);
	},

	/**
	 * Bind a new column model to this gridpanel. This will hook to all
	 * important events with configuration changes which must be stored into the {@link #getState state}.
	 *
	 * @param {Ext.grid.ColumnModel} model The model which must be bound to the panel
	 * @param {Boolean} initialize True if this is called from the constructor to initialize
	 * the panel for the first time.
	 * @private
	 */
	bindColumnModel: function(model, initialize)
	{
		var oldModel = this.getColumnModel();

		if (initialize === true || oldModel !== model) {
			if (oldModel) {
				this.mun(oldModel, 'beforeconfigchange', this.onBeforeConfigChange, this);
				this.mun(oldModel, 'configchange', this.onConfigChange, this);
			}

			if (model) {
				this.mon(model, 'beforeconfigchange', this.onBeforeConfigChange, this);
				this.mon(model, 'configchange', this.onConfigChange, this);
			}
		}
	},

	/**
	 * Bind a new store to this gridpanel. This will hook to all
	 * important events with configuration changes which must be stored into the {@link #getState state}..
	 *
	 * @param {Ext.data.Store} store The store which must be bound to the panel
	 * @param {Boolean} initialize True if this is called from the constructor to initialize
	 * the panel for the first time.
	 * @private
	 */
	bindStore: function(store, initialize)
	{
		var oldStore = this.getStore();

		if (initialize === true || oldStore !== store) {
			if (oldStore) {
				this.mun(oldStore, 'beforeload', this.onStoreBeforeLoad, this);
				this.mun(oldStore, 'load', this.onStoreLoad, this);
			}

			if (store) {
				this.mon(store, 'beforeload', this.onStoreBeforeLoad, this);
				this.mon(store, 'load', this.onStoreLoad, this);
				// In case the store was already loaded, just call the
				// event handler directly.
				if (store.lastOptions) {
					this.onStoreBeforeLoad(store, store.lastOptions);
				}
			}
		}
	},

	/**
	 * Event handler for the {@link #viewready} event. The {@link Ext.grid.GridPanel Extjs GridPanel},
	 * or more accurately the {@link Zarafa.common.ui.LoadMask} do not handle the case nicely when the store
	 * is already busy loading when the grid is being rendered. Because we do like such an optimization,
	 * we have to check at this time if the store is loading, and display the loadmask.
	 * @private
	 */
	onViewReady: function()
	{
		var show = this.store && (this.store.isExecuting(Zarafa.core.Actions['list']) === true
			|| this.store.isExecuting(Zarafa.core.Actions['search']) === true);

		if (this.loadMask && show) {
			this.loadMask.show();
		}
	},

	/**
	 * Called right before the {@link #store} sends a {@link Ext.data.Store#load} request to the server,
	 * this will check which entryId is loaded from the server and will {@link #applyState apply the state}
	 * for the new folder onto the panel. The 'options' argument will be updated according to the sorting
	 * requirements from the {@link Ext.state.Manager#get state}.
	 * @param {Ext.data.Store} store The store which fired the event
	 * @param {Object} options The options used for loading the new data from the store
	 * @private
	 */
	onStoreBeforeLoad: function(store, options)
	{
		/*
		 * if action type is updatelist and grid has no dummy row with warped
		 * loading mask, then show the loading mask on the grid row rather to
		 * show load mask on whole grid.
		 */
		if(options && (options.actionType === Zarafa.core.Actions['updatelist'])) {
			if(!this.isLoading) {
				this.getView().showGridRowLoadMask(this.loadMask.msg);
				this.isLoading = true;
			}
			return;
		}
		// No need to reset the column model when we are searching
		// We must use the column model the user set for the folder
		// in which he is searching
		if ( !Ext.isEmpty(store) && store.hasSearchResults ===true ){
			return;
		}

		var model = this.getColumnModel();
		var folder = options.folder;
		var entryId;
		var storeEntryId;

		if (!Ext.isEmpty(folder)) {
			if (Array.isArray(folder)) {
				entryId = folder[0].get('entryid');
				storeEntryId = folder[0].get('store_entryid');
			} else {
				entryId = folder.get('entryid');
				storeEntryId = folder.get('store_entryid');
			}
		}

		if (this.currentEntryId === entryId) {
			return;
		}

		this.currentEntryId = entryId;
		this.currentStoreEntryId = storeEntryId;

		model.setConfig(model.columns, false);
	},

	/**
	 * Called when {@link Ext.grid.GridPanel#store store} on the {@link #field} is
	 * loading new data. At this point we must check which folder entryid is being loaded.
	 *
	 * @param {Zarafa.core.data.MAPIStore} store The store which being loaded.
	 * @param {Ext.data.Record[]} records The records which have been loaded from the store
	 * @param {Object} options The loading options that were specified (see {@link Ext.data.Store#load load} for details)
	 * @private
	 */
	onStoreLoad: function(store, records, options)
	{
		/*
		 * if action type is updatelist and grid has dummy row which was warped
		 * with loading mask, then remove that dummy row from grid. also don't
		 * show the loading mask on whole grid.
		 */
		if(options && (options.actionType === Zarafa.core.Actions['updatelist'])) {
			if(this.isLoading){
				this.getView().removeGridRowLoadMask();
				this.isLoading = false;
			}
			return;
		}
	},

	/**
	 * Event handler for the {@link #beforeconfigchange} event which is fired at the start of
	 * {@link Zarafa.common.ui.grid.ColumnModel#setConfig}.
	 *
	 * @param {Ext.gridColumnModel} columnModel The model which is being configured
	 * @param {Object} config The configuration object
	 * @private
	 */
	onBeforeConfigChange: Ext.emptyFn,

	/**
	 * Called when {@link Ext.grid.ColumnModel#setConfig} has completed the configuration changes.
	 *
	 * @param {Ext.gridColumnModel} columnModel The model which is being configured
	 * @private
	 */
	onConfigChange: Ext.emptyFn,

	/**
	 * Event handler for the selectionchange event of the grid's selectionmodel.
	 * Will set the class 'zarafa-multiselection' on selected rows when more than
	 * one row is selected. This class can then be used to not show the
	 * 'add categories' button when hovering.
	 *
	 * @param {Ext.grid.RowSelectionModel} selectionModel The selection model of this grid
	 */
	onGridSelectionChange: function(selectionModel)
	{
		var view = this.getView();
		var store = this.getStore();
		var selection = selectionModel.getSelections();

		// Remove the multiselection class from previously marked rows using a DOM query
		// which is safe across sorting, store reloads, and view refreshes.
		var el = view.mainBody || view.el;
		if ( el ){
			var rows = el.query('.zarafa-multiselection');
			for ( var i=0; i<rows.length; i++ ){
				Ext.fly(rows[i]).removeClass('zarafa-multiselection');
			}
		}

		if ( selection.length > 1 ){
			// Multiple rows are selected. Make sure they all have the multiple selection class.
			Ext.each(selection, function(record){
				var idx = store.indexOf(record);
				if ( idx >= 0 ) {
					view.addRowClass(idx, 'zarafa-multiselection');
				}
			}, this);
		}
	},

	/**
	 * Called to get grid's drag proxy text. Opposite to the superclass,
	 * this will not return {@link Ext.grid.GridPanel#ddText},
	 * but will use 'ngettext' to return a properly formatted plural sentence.
	 * @return {String} The text
	 */
	getDragDropText: function()
	{
		var count = this.selModel.getCount();
		return String.format(ngettext('{0} selected row', '{0} selected rows', count), count);
	},

	/**
	 * Obtain the path in which the {@link #getState state} must be saved.
	 * This option is only used when the {@link Zarafa.core.data.SettingsStateProvider SettingsStateProvider} is
	 * used in the {@link Ext.state.Manager}. This returns {@link #statefulName} if provided, or else generates
	 * a custom name.
	 * @return {String} The unique name for this component by which the {@link #getState state} must be saved.
	 */
	getStateName: function()
	{
		var options = this.store.lastOptions;
		var folder;

		if (options && options.folder) {
			folder = options.folder;

			if (Array.isArray(folder)) {
				folder = folder[0];
			}
		}

		return container.getHierarchyStore().getStateName(folder, 'list');
	},

	/**
	 * When {@link #stateful} the State object which should be saved into the
	 * {@link Ext.state.Manager}.
	 * @return {Object} The state object
	 * @protected
	 */
	getState: function()
	{
		var state = Zarafa.common.ui.grid.GridPanel.superclass.getState.call(this);

		// Sorting is handled by the ContextModel rather the by the grid,
		// hence we remove the sorting information from this location.
		var wrap = { sort: state.sort };
		delete state.sort;
		wrap[this.getColumnModel().name] = state;

		return wrap;
	},

	/**
	 * Apply the given state to this object activating the properties which were previously
	 * saved in {@link Ext.state.Manager}.
	 * @param {Object} state The state object
	 * @protected
	 */
	applyState: function(state)
	{
		if (state) {
			// Sorting is handled by the ContextModel rather then by the grid,
			// if the unwrap object contains 'sort' then this function would trigger
			// a reload of the store which is not what we need.
			var unwrap = { sort: state.sort };
			delete state.sort;
			Ext.apply(unwrap, state[this.getColumnModel().name]);

			Zarafa.common.ui.grid.GridPanel.superclass.applyState.call(this, unwrap);
		}
	}
});

Ext.reg('zarafa.gridpanel', Zarafa.common.ui.grid.GridPanel);
Ext.namespace('Zarafa.common.ui.grid');

/**
 * @class Zarafa.common.ui.grid.GridView
 * @extends Ext.grid.GridView
 * @xtype zarafa.gridview
 *
 * grommunio Web specific GridView which contain extra features and bugfixes
 * which could not be resolved by plugins or directly in extjs.
 */
Zarafa.common.ui.grid.GridView = Ext.extend(Ext.grid.GroupingView, {

	/**
	 * @cfg {Boolean} disableScrollToTop {@link Ext.grid.GridView} by default scrolls to top when data is loaded in
	 * {@link Zarafa.core.data.MAPIStore MAPIStore}, but in our case {@link Zarafa.core.ContextModel ContextModel} handles
	 * selection of records based on settings. so this flag will disable default functionality of {@link Ext.grid.GridView}.
	 */
	disableScrollToTop: undefined,

	/**
	 * @cfg {Boolean} isBuffering by default it was false which represent that no more rows are in buffer to
	 * insert in to {@link Zarafa.mail.ui.MailGrid mailgrid} and if it is true means there are some rows
	 * in buffer which are going to insert in {@link Zarafa.mail.ui.MailGrid mailgrid}, also it will not allow
	 * to fire {@link #livescrollstart} event till {@link #isBuffering} get false.
	 */
	isBuffering: false,

	/**
	 * @cfg {Boolean} enableGrouping true will enable grouping grid, false otherwise.
	 */
	enableGrouping: false,

	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		Ext.applyIf(config, {
			enableGroupingMenu: false,
			groupTextTpl: '{text:htmlEncode}',
			disableScrollToTop: false,
			deferEmptyText: true,
			emptyText: '<div class="emptytext">' + _('There are no items to show in this list') + '</div>',
			forceFit: true,
			autoFill: true,
			showGroupName: false
		});

		this.addEvents(
			/**
			 * @event beforelivescrollstart
			 * Fires when the scroll is being {@link #onScroll started}.
			 * @param {Zarafa.common.ui.grid.GridView} grid view The grid view which fired the event
			 * @param {HtmlElement} target The target of the event.
			 */
			'beforelivescrollstart',
			/**
			 * @event livescrollstart
			 * Fires when the scroll is being {@link #onScroll started}.
			 * @param {Number} cursor the cursor contains the last index of record in grid.
			 */
			'livescrollstart',
			/**
			 * @event beforesort
			 * Fires when the header of grid is being {@link #onHeaderClick clicked}.
			 * @param {Number} cursor the cursor contains the last index of record in grid.
			 */
			'beforesort'
		);
		Zarafa.common.ui.grid.GridView.superclass.constructor.call(this, config);
	},

	/**
	 * Binds a new Store and ColumnModel to this GridView. Removes any listeners from the old objects (if present)
	 * and adds listeners to the new ones
	 * @param {Ext.data.Store} newStore The new Store instance
	 * @param {Ext.grid.ColumnModel} newColModel The new ColumnModel instance
	 * @private
	 */
	initData: function(newStore, newColModel)
	{
		if (this.ds) {
			this.ds.un('exception', this.onException, this);
		}

		Zarafa.common.ui.grid.GridView.superclass.initData.apply(this, arguments);

		if (this.ds) {
			this.ds.on('exception', this.onException, this);
		}
	},

	/**
	 * Event handler will be called when the {@link Zarafa.core.data.MAPIStore Store} has
	 * fired an exception event.
	 * @param {Ext.data.DataProxy} proxy The proxy which fired the event.
	 * No event handler may modify any properties inside the provided record.
	 * @param {String} type See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception}
	 * for description.
	 * @param {String} action See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception}
	 * for description.
	 * @param {Object} options See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception}
	 * for description.
	 * @param {Object} response See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception}
	 * for description.
	 * @param {Zarafa.core.data.MAPIRecord} record The record which was subject of the request
	 * that encountered an exception.
	 * @param {String} error (Optional) Passed when a thrown JS Exception or JS Error is
	 * available.
	 * @private
	 */
	onException: function(proxy, type, action, options, response, arg)
	{
		if (options && options.actionType === 'list') {
			this.mainBody.update('<div class="x-grid-empty"><div class="emptytext">' + response.error.info.display_message + '</div></div>');
		}
	},

	/**
	 * Event handler is used to execute {@link #scrollToTop} method to scroll to first item after
	 * {@link Zarafa.core.data.MAPIStore MAPIStore} has completed loading all data, but we don't need this
	 * default behaviour and handle it differently through {@link Zarafa.core.ContextModel ContextModel},
	 * so to disable this functionality we have provided a config {@link #disableScrollToTop}.
	 * @param {Ext.data.Store} store The store which was loaded
	 * @param {Ext.data.Record[]} records The records which were loaded into the store
	 * @param {Object} options The options which were used to load the data
	 */
	onLoad: function(store, record, options)
	{
		// if grid view is destroyed then we shouldn't call this function.
		// if reload/search is called than we don't want to scroll, and if load is called than we should scroll to top.
		if (!Ext.isDefined(options.reload) && this.disableScrollToTop !== true && Ext.isDefined(this.scroller.dom) && options.actionType == 'list') {
			Zarafa.common.ui.grid.GridView.superclass.onLoad.apply(this, arguments);
		}
	},

	/**
	 * Event handler which is called when the data inside the store has changed.
	 * Because {@link #refresh} blocks {@link #applyEmptyText}, we have to call it
	 * here manually again.
	 * @private
	 */
	init: function(grid)
	{
		Zarafa.common.ui.grid.GridView.superclass.init.apply(this, arguments);

		// Keep track of which records the rendered rows belong to; the store
		// may already contain different data by the time the view refreshes
		// (see #getScrollAnchor).
		this.on('refresh', this.updateRenderedRecordIds, this);
		this.on('rowsinserted', this.updateRenderedRecordIds, this);
		this.on('rowremoved', this.updateRenderedRecordIds, this);
	},

	/**
	 * Remembers the ids of the records the currently rendered rows belong to.
	 * @private
	 */
	updateRenderedRecordIds: function()
	{
		this.renderedRecordIds = Ext.pluck(this.grid.store.getRange(), 'id');
	},

	onDataChange: function()
	{
		// Preserve the scroll position: the refresh caused by a data change
		// (e.g. expanding/collapsing a conversation, or a background reload
		// when new mail arrives) must not make the list jump.
		var anchor = this.getScrollAnchor();

		Zarafa.common.ui.grid.GridView.superclass.onDataChange.apply(this, arguments);
		this.applyEmptyText();

		this.restoreScrollAnchor(anchor);
	},

	/**
	 * Returns the current scroll position, anchored to the topmost visible row.
	 * Restoring an anchored position (see {@link #restoreScrollAnchor}) keeps
	 * that row in place even when rows above it were added or removed, which a
	 * plain pixel offset would not.
	 *
	 * Note that this runs when the store was already modified, while the DOM
	 * still shows the previous data: the anchored record is therefore resolved
	 * through {@link #renderedRecordIds}, not through the store.
	 *
	 * @return {Object|Boolean} The anchor, or false when there is nothing to restore.
	 * @private
	 */
	getScrollAnchor: function()
	{
		if (!this.scroller || !this.scroller.dom || !this.grid) {
			return false;
		}

		var scrollTop = this.scroller.dom.scrollTop;
		var anchor = { top: scrollTop };
		if (scrollTop === 0 || Ext.isEmpty(this.renderedRecordIds)) {
			return anchor;
		}

		var rows = this.getRows();
		for (var i = 0; i < rows.length; i++) {
			if (rows[i].offsetTop + rows[i].offsetHeight > scrollTop) {
				if (this.renderedRecordIds[i]) {
					anchor.recordId = this.renderedRecordIds[i];
					anchor.delta = scrollTop - rows[i].offsetTop;
				}
				break;
			}
		}

		return anchor;
	},

	/**
	 * Restores a scroll position obtained through {@link #getScrollAnchor}.
	 * When the anchored row is still present its position is restored exactly;
	 * otherwise the plain pixel offset is used.
	 *
	 * @param {Object|Boolean} anchor The anchor to restore.
	 * @private
	 */
	restoreScrollAnchor: function(anchor)
	{
		if (anchor === false || !this.scroller || !this.scroller.dom) {
			return;
		}

		var top = anchor.top;
		if (anchor.recordId) {
			var index = this.grid.store.indexOfId(anchor.recordId);
			if (index > -1) {
				var row = this.getRow(index);
				if (row) {
					top = row.offsetTop + anchor.delta;
				}
			}
		}

		this.scroller.dom.scrollTop = top;
	},

	/**
	 * Displays the configured emptyText if there are currently no rows to display
	 * @private
	 */
	applyEmptyText: function()
	{
		// When we are reloading, do not apply the empty text, we do not
		// want to confuse the user by indicating no items are found, while we in fact
		// are still downloading the list of items.
		if (!this.grid.store.isExecuting(Zarafa.core.Actions['list'])) {
			Zarafa.common.ui.grid.GridView.superclass.applyEmptyText.call(this);
		}
	},

	/**
	 * This is called internally, once, by this.render after the HTML elements are added to the grid element.
	 * This is always intended to be called after renderUI. Sets up listeners on the UI elements
	 * and sets up options like column menus, moving and resizing.
	 * This function is overridden to use a {@link Zarafa.common.ui.grid.GridDragZone} for the {@link #dragZone},
	 * rather then the default {@link Ext.grid.GridDragZone}.
	 * This is always intended to be called after renderUI. Sets up listeners on the UI elements
	 * and sets up options like column menus, moving and resizing. It will overwrite to hide the
	 * 'Group By This Field' button from header menu and change css class for the check box.
	 * @private
	 */
	afterRenderUI: function()
	{
		// Called parent function of grouping view because
		// we have to override the header menu items for
		// grouping views.
		Ext.grid.GroupingView.superclass.afterRenderUI.call(this);

		// The grid focus anchor must never make the browser scroll: ExtJS
		// focuses it deferred after a mousedown, and when a refresh has moved
		// it in the meantime (onDataChange syncs it to row 0), the browser
		// would scroll the grid to wherever it ended up — this made the list
		// jump to the top on the first click after a data change. The grid
		// does its own scrolling through ensureVisible.
		if (this.focusEl && this.focusEl.dom) {
			var focusDom = this.focusEl.dom;
			var originalFocus = focusDom.focus;
			focusDom.focus = function() {
				originalFocus.call(this, { preventScroll: true });
			};
		}

		if (this.enableGroupingMenu && this.hmenu) {
			if (this.enableNoGroups) {
				this.hmenu.add({
					itemId: 'sortSepOne',
					xtype: 'menuseparator'
				}, {
					itemId:'showGroups',
					text: this.showGroupsText,
					checked: true,
					cls: 'showGroups',
					iconCls: this.enableGrouping ? '' : 'k-hide-img',
					checkHandler: this.onShowGroupsClick,
					scope: this
				});
			}

			this.hmenu.on('beforeshow', this.beforeMenuShow, this);
		}

		if (this.dragZone) {
			this.dragZone.destroy();

			this.dragZone = new Zarafa.common.ui.grid.GridDragZone(this.grid, {
				ddGroup: this.grid.ddGroup || 'GridDD'
			});
		}
	},

	/**
	 * Event handler which is triggered when scroll bar of an {@link Ext.grid.GridPanel Grid}
	 * scrolled. here it will fire {@link #livescrollstart} event, if {@link Ext.grid.GridPanel Grid}
	 * as rows and scroll bar is at the end of {@link Ext.grid.GridPanel Grid}.
	 * @param {Ext.EventObject} event The {@link Ext.EventObject} encapsulating the DOM event.
	 * @param {HtmlElement} target The target of the event.
	 * @param {Object} option The options configuration passed to the {@link #addListener} call.
	 */
	onScroll: function(event, target, option)
	{
		if(this.fireEvent('beforelivescrollstart', this, target) !== false) {
			// chrome dose not support scrollTopMax so we have to find the scrollTopMax manually.
			var scrollTopMax;
			if(!Ext.isDefined(target.scrollTopMax)) {
				scrollTopMax = target.scrollHeight - target.offsetHeight;
			} else {
				scrollTopMax = target.scrollTopMax;
			}

			var scrollState = scrollTopMax * 0.90;
			if(scrollState < target.scrollTop && !this.isBuffering) {
				// Use getStoreLength when the store provides it: it returns the
				// number of loaded folder items, which is the correct paging
				// offset when the store contains extra rows (conversation
				// headers, fetched sent items) or hides the rows of collapsed
				// conversations.
				var cursor = Ext.isFunction(this.ds.getStoreLength) ? this.ds.getStoreLength() : this.ds.getCount();
				if(cursor !== this.ds.totalLength) {
					this.fireEvent('livescrollstart', cursor);
				}
			}
		}
	},

	/**
	 * Function was used to reset the scroller position to top if it is not. also it will
	 * set the focus to first line of grid.
	 */
	resetScroll: function()
	{
		if(this.getScrollState().top > 0) {
			this.scrollToTop();
			// it is required in case when user load all records in grid and switch the
			// view of grid, then it will show the extra space in grid. once user click on grid
			// it will resize grid and remove the extra space. it happens because focus element of grid
			// will not resize when user switch the view so we have to set the focus on grid.
			this.focusRow(1);
		}
	},

	/**
	 * Called when the GridView has been rendered.
	 * This will check if the store has been loaded already, and apply
	 * the emptyText if needed.
	 * @private
	 */
	afterRender: function()
	{
		Zarafa.common.ui.grid.GridView.superclass.afterRender.apply(this, arguments);

		// When deferEmptyText is false, the emptyText will only be applied after
		// the store has been loaded (the 'load' event has been fired). However, when
		// we arrive here, while the store has already been loaded, we would show nothing,
		// since we missed the event.
		var store = this.grid.store;
		if (this.deferEmptyText === false && store) {
			if (!store.lastOptions || !Ext.isEmpty(Object.keys(store.lastOptions))) {
				this.applyEmptyText();
			}
		}
		this.scroller.on('scroll', this.onScroll, this);
	},

	/**
	 * Event handler triggered when header of {@link Ext.grid.GridPanel Grid}
	 * was clicked for sort the data of {@link Ext.grid.GridPanel Grid}.
	 * also it will fire the {@link #beforesort} event.
	 * @param {Ext.grid.GridPanel} grid The grid on which the user clicked
	 * @param {Number} index The index number of the header which clicked.
	 * @private
	 */
	onHeaderClick: function(grid, index)
	{
		var store = grid.getStore();
		if (this.enableGrouping) {
			this.hdCtxIndex = index;
			var colModel = grid.colModel;
			var dataIndex = colModel.getColumnAt(index).dataIndex;
			if (!this.isAllowGrouping(dataIndex)) {
				this.clearGrouping();
			} else {
				store.remoteGroup = false;
				store.remoteSort = false;
				this.applyGrouping();
				store.remoteGroup = true;
				store.remoteSort = true;
			}
		}

		if (this.fireEvent('beforesort', this) !== false) {
			Zarafa.common.ui.grid.GridView.superclass.onHeaderClick.apply(this, arguments);
		}
	},

	/**
	 * Check that given field support grouping. default sorted fields are by default
	 * support grouping.
	 *
	 * @param {String} dataIndex The dataIndex of field which needs to check
	 * field support grouping.
	 *
	 * @return return true if given field is supported groupings else false.
	 */
	isAllowGrouping: function(dataIndex)
	{
		var supportedColumns = ['message_size', 'sent_representing_name'];
		var store = this.grid.getStore();
		if (store.defaultSortInfo.field != dataIndex && supportedColumns.indexOf(dataIndex) === -1) {
			return false;
		}
		return true;
	},

	/**
	 * Event handler triggered when header menu item was clicked and the column show/hide submenu (if available).
	 * Performs sorting if the sorter buttons were clicked, otherwise hides/shows the column that was clicked.
	 * also it will fire the {@link #beforesort} event.
	 * @param {Ext.menu.Item} item The item which is being clicked
	 * @private
	 */
	handleHdMenuClick: function(item)
	{
		if(this.fireEvent('beforesort', this) !== false) {
			Zarafa.common.ui.grid.GridView.superclass.handleHdMenuClick.apply(this, arguments);
		}
	},

	/**
	 * Function used to insert the dummy row in grid and warp the loading mask on it.
	 * also it was {@link #isBuffering} set to true.
	 *
	 * @param {String} msg the message which load mask shows while loading.
	 */
	showGridRowLoadMask: function(msg)
	{
		this.isBuffering = true;
		var height = this.getRow(1).offsetHeight;
		var width = this.getTotalWidth();

		// add dummy row at bottom of the grid.
		var style = {styles: 'width:'+width+'px; height:'+height+'px'};
		var tpl = new Ext.Template('<div id="dummy-row" style = "{styles}"> </div>');
		var html = tpl.apply(style);
		var dom = Ext.DomHelper.insertHtml('beforeEnd',this.mainBody.dom, html);

		// wrap the loading mask on the dummy row.
		Ext.get(dom).mask(msg, 'x-mask-loading x-mask-loading-row');
		var el = Ext.get(dom).child('.ext-el-mask', true);
		el.className += 'x-mask-row';
	},

	/**
	 * Function use to remove the dummy row which use to show the loading mask and
	 * {@link #isBuffering} set to false.
	 */
	removeGridRowLoadMask: function()
	{
		var rowMask = Ext.query('div#dummy-row', this.mainBody.dom)[0];
		if(Ext.isDefined(rowMask)) {
			Ext.get(rowMask).remove();
		}
		this.isBuffering = false;
	},

	/**
	 * Handler triggers when 'Show in groups' button was clicked.
	 * It is toggle the 'Show in groups' button and set the enable_grouping
	 * user settings.
	 *
	 * @param {Ext.menu.Item} mi The menu item which clicked.
	 * @param {Boolean} checked The checked true grouping is enabled else false.
	 */
	onShowGroupsClick: function(mi, checked)
	{
		if (!this.isAllowGrouping(this.cm.getDataIndex(this.hdCtxIndex))) {
			return true;
		}

		if (checked && this.enableGrouping) {
			checked = false;
		}
		Zarafa.common.ui.grid.GridView.superclass.onShowGroupsClick.apply(this, arguments);

		var imgTag = mi.el.child('img');
		if(checked) {
			imgTag.addClass('x-menu-item-icon');
			imgTag.removeClass('k-hide-img');
		} else {
			imgTag.removeClass('x-menu-item-icon');
		}
		container.getSettingsModel().set('zarafa/v1/contexts/mail/enable_grouping', checked);
	},

	/**
	 * Handler triggers only when grouping is going to enable.
	 * @override
	 * @private
	 */
	onGroupByClick: function ()
	{
		this.applyGrouping();
		// Make sure the checkboxes get properly set when changing groups
		this.beforeMenuShow();
	},

	/**
	 * Apply grouping on store.
	 * It will apply grouping on default sort field.
	 */
	applyGrouping: function ()
	{
		var grid = this.grid;
		this.enableGrouping = true;

		// Grouping is allowed on default sort fields and message_size field
		// e.g For Inbox = message_delivery_time, Draft = last_modification_time
		// and message_size so on.
		var store = grid.getStore();

		var clickHeader = this.cm.getDataIndex(this.hdCtxIndex);
		var groupField = this.isAllowGrouping(clickHeader) ? clickHeader : store.defaultSortInfo.field;
		store.sortInfo.field = groupField;

		var direction = store.sortToggle[groupField];
		store.sortInfo.direction = Ext.isDefined(direction) ? direction : 'ASC';
		store.groupField = groupField;
		store.applyGrouping();
		grid.fireEvent('groupchange', grid, store.getGroupState());
		this.refresh();
	},

	/**
	 * Clear grouping from store.
	 */
	clearGrouping: function ()
	{
		var store = this.grid.getStore();
		store.remoteGroup = false;
		store.remoteSort = false;
		store.clearGrouping();
		store.remoteGroup = true;
		store.remoteSort = true;
		this.grid.fireEvent('groupchange', this, null);
	},

	/**
	 * Function which stop toggle the grouping.
	 */
	toggleGroup: Ext.emptyFn
});

Ext.reg('zarafa.gridview', Zarafa.common.ui.grid.GridView);
Ext.namespace('Zarafa.common.ui.grid');

/**
 * @class Zarafa.common.ui.grid.Renderers
 * Methods of this object can be used as renderers for grid panels, to render
 * cells in custom format according to data type
 * @singleton
 */
Zarafa.common.ui.grid.Renderers = {
	/**
	 * Render the cell as Importance
	 *
	 * @param {Object} value The data value for the cell.
	 * @param {Object} p An object with metadata
	 * @param {Ext.data.record} record The {Ext.data.Record} from which the data was extracted.
	 * @return {String} The formatted string
	 */
	importance: function(value, p, record)
	{
		if (value >= 0) {
			p.css = Zarafa.core.mapi.Importance.getClassName(value);
		}

		// add extra css class for empty cell
		p.css += ' zarafa-grid-empty-cell';

		return '';
	},

	/**
	 * Render the cell as Icon
	 *
	 * @param {Object} value The data value for the cell.
	 * @param {Object} p An object with metadata
	 * @param {Ext.data.record} record The {Ext.data.Record} from which the data was extracted.
	 * @return {String} The formatted string
	 */
	icon: function(value, p, record)
	{
		var conversationCount = record.get('conversation_count');
		var depth = record.get('depth');
		var cssClass = '';
		var result = '';

		if (conversationCount > 0 && depth === 0) {
			// Conversation header records show the expand/collapse arrow in place
			// of a message icon. Putting the arrow class on the cell centers it
			// exactly like any other icon in this column. The item counter is
			// rendered in the sender column by {@link #sender}.
			var headerStore = record.getStore();
			var opened = headerStore && Ext.isFunction(headerStore.isConversationOpened) &&
				headerStore.isConversationOpened(record);
			cssClass = 'k-conversation-header-icon ' + (opened ? 'arrow_down_l' : 'arrow_right_l');
		} else if (depth !== 1) {
			cssClass = Zarafa.common.ui.IconClass.getIconClass(record);
		}

		// add extra css class for empty cell
		cssClass += ' zarafa-grid-empty-cell';

		p.css = cssClass;

		return result;
	},

	/**
	 * Render the cell as Attachment
	 *
	 * @param {Object} value The data value for the cell.
	 * @param {Object} p An object with metadata
	 * @param {Ext.data.record} record The {Ext.data.Record} from which the data was extracted.
	 * @return {String} The formatted string
	 */
	attachment: function(value, p, record)
	{
		if (Ext.isDefined(record) && record.get('hide_attachments') === true) {
			return '';
		}

		// Conversation header records show the paperclip when any item in the
		// conversation has an attachment.
		if (Ext.isFunction(record.isConversationHeaderRecord) && record.isConversationHeaderRecord()) {
			value = Zarafa.common.ui.grid.Renderers.conversationHasAttachment(record);
		}

		p.css = (value === true) ? 'icon_paperclip' : 'icon_noattachment';

		// add extra css class for empty cell
		p.css += ' zarafa-grid-empty-cell';

		return '';
	},

	/**
	 * Helper function that finds if there is a conversation record that has an attachment
	 *
	 * @param {Zarafa.core.data.IPMRecord} headerRecord The header record of a conversation
	 * @return {Boolean} True if a record with an attachment was found, false otherwise
	 */
	conversationHasAttachment: function(headerRecord)
	{
		var store = headerRecord.getStore();
		if (!store || !Ext.isFunction(store.getConversationItemsFromHeaderRecord)) {
			return false;
		}

		var conversationRecords = store.getConversationItemsFromHeaderRecord(headerRecord);
		return conversationRecords.some(function(r) {
			return r.get('hasattach') && r.get('hide_attachments') !== true;
		});
	},

	/**
	 * Helper function that renders the item counter badge shown on a
	 * conversation header row in front of the participant names.
	 *
	 * @param {Number} count The number of items in the conversation
	 * @return {String} The formatted counter badge
	 */
	conversationCount: function(count)
	{
		var cls = 'k-conversation-count';
		if (count > 9) {
			cls += ' k-two-digit-counter';
			if (count > 99) {
				count = '99+';
			}
		}
		return '<span unselectable="on" class="' + cls + '">' + count + '</span> ';
	},

	/**
	 * Render the cell as Recurrence
	 *
	 * @param {Object} value The data value for the cell.
	 * @param {Object} p An object with metadata
	 * @param {Ext.data.record} record The {Ext.data.Record} from which the data was extracted.
	 * @return {String} The formatted string
	 */
	recurrence: function(value, p, record)
	{
		p.css = value ? 'icon_recurrence' : 'zarafa-grid-empty-cell';
		return '';
	},

	/**
	 * Renderer for reminder column
	 *
	 * @param {Object} value The data value for the cell.
	 * @param {Object} p An object with metadata
	 * @param {Ext.data.record} record The {Ext.data.Record} from which the data was extracted.
	 * @return {String} The formatted string
	 */
	reminder: function (value, p, record)
	{
		p.css = value ? 'icon_reminder' : 'zarafa-grid-empty-cell';
		if (Ext.isDefined(record) && !Ext.isEmpty(record.get('reminder_time'))) {
			var reminderTime = record.get('reminder_time');
			var tooltip = String.format(_('Reminder is set on: {0}, {1}'), reminderTime.format(_('d-m-Y')), reminderTime.formatDefaultTime());
			p.attr = 'ext:qtip=\"'+Ext.util.Format.htmlEncode(tooltip)+'\"';
		}
		return '';
	},

	/**
	 * Renders the cell as categories view
	 *
	 * @param {Object} value The data value for the cell.
	 * @param {Object} p An object with metadata
	 * @param {Ext.data.record} record The {Ext.data.Record} from which the data was extracted.
	 * @return {String} The formatted string
	 */
	categories: function(value, p, record)
	{
		// Render the categories
		var categories = Zarafa.common.categories.Util.getCategories(record);
		return Zarafa.common.categories.Util.getCategoriesHtml(categories);
	},

	/**
	 * Render the cell as Name
	 *
	 * @param {Object} value The data value for the cell.
	 * @param {Object} p An object with metadata
	 * @param {Ext.data.record} record The {Ext.data.Record} from which the data was extracted.
	 * @return {String} The formatted string
	 */
	name: function(value, p, record)
	{
		p.css = 'mail_from';

		if(Ext.isEmpty(value)) {
			// if value is empty then add extra css class for empty cell
			p.css += ' zarafa-grid-empty-cell';
		}

		return Ext.util.Format.htmlEncode(value);
	},

	/**
	 * Render the cell as Display Name with presence status
	 *
	 * @param {Object} value The data value for the cell.
	 * @param {Object} p An object with metadata
	 * @param {Ext.data.record} record The {Ext.data.Record} from which the data was extracted.
	 * @return {String} The formatted string
	 */
	displayName: function(value, p, record)
	{
		var userName = Zarafa.common.ui.grid.Renderers.name(value, p, record);
		return Zarafa.common.ui.grid.Renderers.presenceStatus(userName, p, record);
	},

	/**
	 * Render the full_name cell in the address book respecting the
	 * configured name display format (first-last vs last-first).
	 *
	 * @param {Object} value The data value for the cell.
	 * @param {Object} p An object with metadata
	 * @param {Ext.data.record} record The {Ext.data.Record} from which the data was extracted.
	 * @return {String} The formatted string
	 */
	fullName: function(value, p, record)
	{
		var fmt = container.getSettingsModel().get('zarafa/v1/main/addressbook_name_format');
		if (fmt === 'firstlast') {
			var parts = [];
			var givenName = record.get('given_name');
			var middleName = record.get('middle_name');
			var surname = record.get('surname');

			if (!Ext.isEmpty(givenName)) {
				parts.push(givenName);
			}
			if (!Ext.isEmpty(middleName)) {
				parts.push(middleName);
			}
			if (!Ext.isEmpty(surname)) {
				parts.push(surname);
			}

			if (parts.length > 0) {
				return Ext.util.Format.htmlEncode(parts.join(' '));
			}
		}

		return Ext.util.Format.htmlEncode(value);
	},

	/**
	 * Render the cell as Sender
	 *
	 * @param {Object} value The data value for the cell.
	 * @param {Object} p An object with metadata
	 * @param {Ext.data.record} record The {Ext.data.Record} from which the data was extracted.
	 * @return {String} The formatted string
	 */
	sender: function(value, p, record)
	{
		var retVal = '';
		var userRecord = false;
		var conversationCount = record.get('conversation_count');
		var depth = record.get('depth');

		if (conversationCount > 0 && depth === 0) {
			// Conversation header record: show the item counter followed by the
			// (unique) first names of all senders that participate in the conversation.
			var store = record.getStore();
			if (store && Ext.isFunction(store.getConversationItemsFromHeaderRecord)) {
				var records = store.getConversationItemsFromHeaderRecord(record);

				var names = records.filter(function(val, index) {
					for (var i = 0; i < index; i++) {
						// We use the value of sent_representing_name to find unique senders.
						// This means that two persons with the same name in a conversation
						// will be shown as a single name in the header.
						if (val.get('sent_representing_name') === records[i].get('sent_representing_name')) {
							return false;
						}
					}
					return true;
				}).map(function(val) {
					var name = val ? val.get('sent_representing_name') || '' : '';
					return Ext.util.Format.htmlEncode(name.split(' ')[0]);
				}).filter(function(name) {
					return !Ext.isEmpty(name);
				}).join(', ');

				if (!Ext.isEmpty(names)) {
					return Zarafa.common.ui.grid.Renderers.conversationCount(conversationCount) + names;
				}
			}

			// No participant names could be determined; fall through to the
			// normal sender rendering (the header record carries the sender of
			// the newest conversation item), prefixed with the counter.
			retVal = Zarafa.common.ui.grid.Renderers.conversationCount(conversationCount);
		} else if (depth > 0) {
			// Conversation item: the message icon is rendered in front of the sender
			// because the icon column shows the thread line instead.
			retVal = '<span class="k-icon ' + Zarafa.common.ui.IconClass.getIconClass(record) + '"></span>';

			// Own (sent) messages of a conversation show their recipients instead
			// of the sender, like Outlook does; otherwise sent and received
			// messages are indistinguishable (especially in a conversation with
			// yourself).
			if (record.get('folder_name') === 'sent_items') {
				var recipients = String(record.get('display_to') || '').split(';').map(function(name) {
					return Ext.util.Format.htmlEncode(name.trim().split(' ')[0]);
				}).filter(function(name) {
					return !Ext.isEmpty(name);
				}).join(', ');

				// The arrow marks the message as sent (to the shown recipients);
				// deliberately language-neutral.
				return retVal + '<span class="k-conversation-sent-to">&rarr; ' + recipients + '</span>';
			}
		}

		// Check which of the 2 properties must be used
		// FIXME: sent representing seems to be always set...
		value = record.get('sent_representing_name');
		if ( Ext.isEmpty(value) && Ext.isFunction(Ext.isFunction(record.getSender))) {
			value = record.get('sender_name');
			userRecord = record.getSender();
		} else if (Ext.isFunction(record.getSentRepresenting)) {
			userRecord = record.getSentRepresenting();
		}
		var userName = Zarafa.common.ui.grid.Renderers.name(value, p, record);

		return retVal + Zarafa.common.ui.grid.Renderers.presenceStatus(userName, p, userRecord);
	},

	/**
	 * Render the cell as Organizer
	 *
	 * @param {Object} value The data value for the cell.
	 * @param {Object} p An object with metadata
	 * @param {Ext.data.record} record The {Ext.data.Record} from which the data was extracted.
	 * @return {String} The formatted string
	 */
	organizer: function(value, p, record)
	{
		// Only render the cell as non-empty if the
		// record is actually a meeting.
		if (Ext.isDefined(record.isMeeting) && record.isMeeting()) {
			return Zarafa.common.ui.grid.Renderers.sender(value, p, record);
		} else {
			// if value is empty then add extra css class for empty cell
			p.css += ' zarafa-grid-empty-cell';
			return '';
		}
	},

	/**
	 * Render the cell as Subject
	 *
	 * @param {Object} value The data value for the cell.
	 * @param {Object} p An object with metadata
	 * @param {Ext.data.record} record The {Ext.data.Record} from which the data was extracted.
	 * @return {String} The formatted string
	 */
	subject: function(value, p, record)
	{
		p.css = 'mail_subject';

		if(Ext.isEmpty(value)) {
			// if value is empty then add extra css class for empty cell
			p.css += ' zarafa-grid-empty-cell';
		}

		return Ext.util.Format.htmlEncode(value);
	},

	/**
	 * Renders the cell as Body for conversation item and for header it will render 'normalized_subject'.
	 * Apart from that it will render default value given in the param.
	 *
	 * @param {Object} value The data value for the cell.
	 * @param {Object} p An object with metadata
	 * @param {Ext.data.record} record The {Ext.data.Record} from which the data was extracted.
	 * @return {String} The formatted string
	 */
	body: function(value, p, record)
	{
		/**
		 * @FIXME: value must always be record.get('body').
		 * Currently we are getting subject as a value for mailGrid in non compact view
		 * because the dataIndex will remain same for the 'subject' column.
		 * If the record is non-conversation item then this will return
		 * default value which is in param.
		 */
		p.css = 'mail_body';
		if(Ext.isEmpty(value)) {
			// if value is empty then add extra css class for empty cell
			p.css += ' zarafa-grid-empty-cell';
		}

		return Ext.util.Format.htmlEncode(value);
	},

	/**
	 * Render the cell as text view
	 *
	 * @param {Object} value The data value for the cell.
	 * @param {Object} p An object with metadata
	 * @param {Ext.data.record} record The {Ext.data.Record} from which the data was extracted.
	 * @return {String} The formatted string
	 */
	text: function(value, p, record)
	{
		if(Ext.isEmpty(value)) {
			// if value is empty then add extra css class for empty cell
			p.css = 'zarafa-grid-empty-cell';
		}

		return Ext.util.Format.htmlEncode(value);
	},

	/**
	 * Render the cell as the recipient list.
	 *
	 * @param {Object} value The data value for the cell.
	 * @param {Object} p An object with metadata
	 * @param {Ext.data.record} record The {Ext.data.Record} from which the data was extracted.
	 * @return {String} The formatted string
	 */
	to: function(value, p, record)
	{
		p.css = 'mail_to';

		if(Ext.isEmpty(value)) {
			// if value is empty then add extra css class for empty cell
			p.css += ' zarafa-grid-empty-cell';
		}

		return Ext.util.Format.htmlEncode(value);
	},

	/**
	 * Render the cell as the message size
	 *
	 * @param {Object} value The data value for the cell.
	 * @param {Object} p An object with metadata
	 * @param {Ext.data.record} record The {Ext.data.Record} from which the data was extracted.
	 * @return {String} The formatted string
	 */
	size: function(value, p, record)
	{
		p.css = 'mail_size';

		if(Ext.isEmpty(value)) {
			// if value is empty then add extra css class for empty cell
			p.css += ' zarafa-grid-empty-cell';
		}

		return Ext.util.Format.fileSize(value);
	},

	/**
	 * Renderer for percentage column
	 *
	 * @param {Object} value The data value for the cell.
	 * @param {Object} p An object with metadata
	 * @param {Ext.data.record} record The {Ext.data.Record} from which the data was extracted.
	 * @return {String} The formatted string
	 */
	percentage: function (value, p, record)
	{
		p.css = 'task_percentage';
		if ( isNaN(value) ){
			// 'value' will not be available as there is no 'complete' property while
			// rendering this column for mail record, using 'flag_status' instead.
			if ( record.get('flag_status') === Zarafa.core.mapi.FlagStatus.completed ) {
				return Ext.util.Format.percentage(1, 0);
			}
			return '';
		}
		return Ext.util.Format.percentage(value, 0);
	},

	/**
	 * Render the cell as Date (l d/m/y) string
	 *
	 * @param {Object} value The data value for the cell.
	 * @param {Object} p An object with metadata
	 *
	 * @return {String} The formatted string
	 */
	date: function(value, p)
	{
		p.css = 'mail_date';

		if ( !Ext.isDate(value) ){
			return _('None');
		}

		if ( container.getSettingsModel().get('zarafa/v1/main/datetime_display_format') === 'short' ){
			return value.getNiceFormat(false);
		} else {
			return value.format(_('l d/m/Y'));
		}
	},

	/**
	 * Render the cell as Date (l d/m/y) string, where the Date is should be represented in UTC
	 *
	 * @param {Object} value The data value for the cell.
	 * @param {Object} p An object with metadata

	 * @return {String} The formatted string
	 */
	utcdate: function(value, p)
	{
		if ( Ext.isDate(value) ){
			value = value.toUTC();
		}

		return Zarafa.common.ui.grid.Renderers.date(value, p);
	},

	/**
	 * Render the cell as date with time (l d/m/Y G:i) when the user has set the long display format
	 * or the 'nice' date when the the short display format has been set.
	 *
	 * @param {Object} value The data value for the cell.
	 * @param {Object} p An object with metadata
	 * @param {Ext.data.record} record The {Ext.data.Record} from which the data was extracted.
	 * @param {Integer} row The row in the grid for which a cell is rendered
	 * @param {Integer} column The column in the grid for which a cell is rendered
	 * @param {Zarafa.mail.MailStore} The store of the grid that is being rendered
	 * @param {Object} meta An object with meta data that can be used by the renderer function
	 *
	 * @return {String} The formatted string
	 */
	datetime: function(value, p, record, row, column, store, meta)
	{
		p.css = 'mail_date';

		if ( meta && meta.css ){
			p.css += ' ' + meta.css;
		}

		if ( !Ext.isDate(value) ){
			return _('None');
		}

		if ( container.getSettingsModel().get('zarafa/v1/main/datetime_display_format') === 'short' ){
			// Add one class that the tooltip can use to recognize a 'nice' date.
			// Add one class so the tooltip can easily get the timestamp of the date.
			p.css += ' k-date-nice k-ts-'+value.getTime();

			return value.getNiceFormat(true);
		} else {
			return value.formatDefaultTime(_('l d/m/Y {0}'));
		}
	},

	/**
	 * Render the cell as date with time (l d/m/Y G:i when the user has set the Long display
	 * format, or d-m-Y, G:i when the short format has been set) This renderer should be used
	 * instead of {#datetime} when a time should always be shown, e.g. for the reminder time.
	 *
	 * @param {Object} value The data value for the cell.
	 * @param {Object} p An object with metadata
	 *
	 * @return {String} The formatted string
	 */
	dateWithTime: function(value, p)
	{
		p.css = '';

		if ( !Ext.isDate(value) ){
			return _('None');
		}

		if ( container.getSettingsModel().get('zarafa/v1/main/datetime_display_format') === 'short' ){
			// Add one class that the tooltip can use to recognize a 'nice' date.
			// Add one class so the tooltip can easily get the timestamp of the date.
			p.css += ' k-date-nice k-ts-'+value.getTime();
			return value.formatDefaultTime(_('d-m-Y {0}'));
		} else {
			return value.formatDefaultTime(_('l d/m/Y {0}'));
		}
	},

	/**
	 * Render the duration field
	 *
	 * @param {Object} value The data value for the cell.
	 * @param {Object} p An object with metadata
	 * @param {Ext.data.record} record The {Ext.data.Record} from which the data was extracted.
	 * @return {String} The formatted string
	 */
	duration: function(value, p, record)
	{
		p.css = 'mail_duration';

		return value ? Ext.util.Format.duration(value, 1) : '';
	},

	/**
	 * Render the duration field by only using the hours annotation.
	 *
	 * @param {Object} value The data value for the cell.
	 * @param {Object} p An object with metadata
	 * @param {Ext.data.record} record The {Ext.data.Record} from which the data was extracted.
	 * @return {String} The formatted string
	 */
	durationHours: function(value, p, record)
	{
		p.css = 'mail_duration';
		if ( !Ext.isDefined(value) ){
			return '';
		}

		return String.format(ngettext('{0} hour', '{0} hours', value), value);
	},

	/**
	 * Render the folder field (converts entryid into foldername).
	 *
	 * @param {Object} value The data value for the cell.
	 * @param {Object} p An object with metadata
	 * @param {Ext.data.record} record The {Ext.data.Record} from which the data was extracted.
	 * @return {String} The formatted string
	 */
	folder: function(value, p, record)
	{
		var folder = container.getHierarchyStore().getFolder(value);
		if (folder) {
			return folder.get('display_name');
		} else {
			return _('Unknown');
		}
	},

	/**
	 * Render the Busy status.
	 *
	 * @param {Object} value The data value for the cell.
	 * @param {Object} p An object with metadata
	 * @param {Ext.data.record} record The {Ext.data.Record} from which the data was extracted.
	 * @return {String} The formatted string
	 */
	busystatus: function(value, p, record)
	{
		return Zarafa.core.mapi.BusyStatus.getDisplayName(value);
	},

	/**
	 * Render the Appointment label.
	 *
	 * @param {Object} value The data value for the cell.
	 * @param {Object} p An object with metadata
	 * @param {Ext.data.record} record The {Ext.data.Record} from which the data was extracted.
	 * @return {String} The formatted string
	 */
	label: function(value, p, record)
	{
		return Zarafa.core.mapi.AppointmentLabels.getDisplayName(value);
	},

	/**
	 * Render the Sensitivity.
	 *
	 * @param {Object} value The data value for the cell.
	 * @param {Object} p An object with metadata
	 * @param {Ext.data.record} record The {Ext.data.Record} from which the data was extracted.
	 * @return {String} The formatted string
	 */
	sensitivity: function(value, p, record)
	{
		return Zarafa.core.mapi.Sensitivity.getDisplayName(value);
	},

	/**
	 * Render the Meeting status.
	 *
	 * @param {Object} value The data value for the cell.
	 * @param {Object} p An object with metadata
	 * @param {Ext.data.record} record The {Ext.data.Record} from which the data was extracted.
	 * @return {String} The formatted string
	 */
	meetingstatus: function(value, p, record)
	{
		return Zarafa.core.mapi.MeetingStatus.getDisplayName(value);
	},

	/**
	 * Render the Recipient Type.
	 *
	 * @param {Object} value The data value for the cell.
	 * @param {Object} p An object with metadata
	 * @param {Ext.data.record} record The {Ext.data.Record} from which the data was extracted.
	 * @return {String} The formatted string
	 */
	recipienttype: function(value, p, record)
	{
		switch (value) {
			case Zarafa.core.mapi.RecipientType.MAPI_TO:
			case Zarafa.core.mapi.RecipientType.MAPI_ORIG:
				if(record.isMeetingOrganizer()) {
					return _('Meeting Organizer');
				}
				return _('Required Attendee');
			case Zarafa.core.mapi.RecipientType.MAPI_BCC:
				return _('Resource');
			case Zarafa.core.mapi.RecipientType.MAPI_CC:
			/* falls through */
			default:
				return _('Optional Attendee');
		}
	},

	/**
	 * Render the Response Status.
	 *
	 * @param {Object} value The data value for the cell.
	 * @param {Object} p An object with metadata
	 * @param {Ext.data.record} record The {Ext.data.Record} from which the data was extracted.
	 * @return {String} The formatted string
	 */
	responsestatus: function(value, p, record)
	{
		return Zarafa.core.mapi.ResponseStatus.getDisplayName(value);
	},

	/**
	 * Render the Follow-up Flag Status.
	 *
	 * @param {Object} value The data value for the cell.
	 * @param {Object} p An object with metadata
	 * @param {Ext.data.record} record The {Ext.data.Record} from which the data was extracted.
	 * @return {String} The formatted string
	 */
	flag: function(value, p, record)
	{
		// add extra css class for empty cell
		p.css += 'zarafa-grid-empty-cell';

		var flagStatus = record.get('flag_status');
		var dueDate = record.get('duedate');

		if ( flagStatus === Zarafa.core.mapi.FlagStatus.completed ){
			p.css += ' icon_flag_complete';
			return '';
		}

		if (!record.isMessageClass('IPM.Task') && flagStatus!==Zarafa.core.mapi.FlagStatus.flagged){
			p.css += ' icon_flag';
			return '<div class="k-followup-flag" title="' + _('Follow up') + '"></div>';
		}

		// Now find the color we must show

		if ( !dueDate ){
			p.css += ' icon_flag_red';
			return '';
		}

		// Since we are interested in days and not in the exact time,
		// we will set all times to 12am so it will be easy to compare days
		dueDate.setToNoon();
		var dueDateTimestamp = dueDate.getTime();
		var today = new Date().setToNoon();
		var todayTimestamp = today.getTime();

		// If the due date is today or before today we will show a red flag
		if ( dueDateTimestamp <= todayTimestamp ){
			p.css += ' icon_flag_red';
			return '';
		}

		// If the due date is tomorrow (timestamp difference will be 24 hours),
		// we will show a dark orange flag
		if ( dueDateTimestamp-todayTimestamp === 24*60*60*1000 ){
			p.css += ' icon_flag_orange_dark';
			return '';
		}

		if ( dueDate.inSameWeekAs(today) ){
			p.css += ' icon_flag_orange';
			return '';
		}

		if ( today.inNextWeek(dueDate) ){
			p.css += ' icon_flag_yellow';
			return '';
		}

		p.css += ' icon_flag_red';
		return '';
	},

	/**
	 * Render the dueby field
	 *
	 * @param {Object} value The data value for the cell.
	 * @param {Object} p An object with metadata
	 * @param {Ext.data.record} record The {Ext.data.Record} from which the data was extracted.
	 * @return {String} The formatted string
	 */
	dueBy: function(value, p, record)
	{
		var result = '';

		/*
		 * this one is really ugly hack appointments use reminder_time property to show due by time
		 * and task uses task_duedate property
		 */
		if (Zarafa.core.MessageClass.isClass(record.get('message_class'), 'IPM.Task', true)) {
			value = record.get('task_duedate');
		}

		if (!Ext.isDate(value)) {
			// if somehow no reminder_time/task_duedate property is present then we can't do anything here
			// because showing data from flagdueby property will not be good as it will contain
			// incorrect value
			return result;
		}

		var time1 = value.getTime();
		var time2 = new Date().getTime();

		// get diff in minutes
		var diff = Math.floor(Math.abs(time1 - time2)/60000);

		if(diff === 0) { // Now
			return _('Now');
		} else {
			result = Ext.util.Format.duration(diff);
		}

		if (time1 - time2 < 0){
			return String.format(_('{0} overdue'), result);
		}

		return result;
	},

	/**
	 * Render the Task Status.
	 *
	 * @param {Object} value The data value for the cell.
	 * @param {Object} p An object with metadata
	 * @param {Ext.data.record} record The {Ext.data.Record} from which the data was extracted.
	 * @return {String} The formatted string
	 */
	taskstatus: function(value, p, record)
	{
		return Zarafa.core.mapi.TaskStatus.getDisplayName(value);
	},

	/**
	 * Render the Colors in textual format.
	 *
	 * @param {Object} value The data value for the cell.
	 * @param {Object} p An object with metadata
	 * @param {Ext.data.record} record The {Ext.data.Record} from which the data was extracted.
	 * @return {String} The formatted string
	 */
	colorTextValue: function(value, p, record)
	{
		return Zarafa.core.mapi.NoteColor.getColorText(value);
	},

	/**
	 * Render the presence status of user along with user name.
	 *
	 * @param {Object} value The data value for the cell.
	 * @param {Object} p An object with metadata
	 * @param {Ext.data.record} record The {Ext.data.Record} from which the data was extracted.
	 * @return {String} The formatted string
	 */
	presenceStatus: function (value, p, record)
	{
		var presenceStatus = Zarafa.core.data.PresenceStatus.UNKNOWN;
		if (record !== false) {
			var user = Zarafa.core.data.UserIdObjectFactory.createFromRecord(record);
			presenceStatus = Zarafa.core.PresenceManager.getPresenceStatusForUser(user);
		}
		return '<span class="zarafa-presence-status ' + Zarafa.core.data.PresenceStatus.getCssClass(presenceStatus) + '">' +
			'<span class="zarafa-presence-status-icon" aria-hidden="true"></span>' + value + '</span>';
	}
};
Ext.namespace('Zarafa.common.ui.grid');

/**
 * @class Zarafa.common.ui.grid.RowSelectionModel
 * @extends Ext.grid.RowSelectionModel
 *
 * This selection model makes use of {@link Zarafa.core.data.MAPIStore#getRecordKey} function
 * to provide a different unique id to access data. This is mainly done to provide support for records
 * which has same entryid but we need to create multiple instances of it so we need to append another
 * property to entryid to make it unique.
 */
Zarafa.common.ui.grid.RowSelectionModel = Ext.extend(Ext.grid.RowSelectionModel, {
	/**
	 * @constructor
	 * @param {Object} config configuration object.
	 */
	constructor: function(config)
	{
		Zarafa.common.ui.grid.RowSelectionModel.superclass.constructor.call(this, config);

		// override and create new MixedCollection to use our own implementation of getKey function
		// this will discard the data stored by constructor in the MixedCollection, but i am sure that
		// constructor is not going to store any data in this MixedCollection, so that will not affect
		this.selections = new Ext.util.MixedCollection(false, function(o) {
			var store = o.store;

			if(store && Ext.isFunction(store.getRecordKey)) {
				return store.getRecordKey(o);
			}

			return o.id;
		});
	},

	/**
	 * Clears all selections if the selection model {@link Ext.grid.AbstractSelectionModel#isLocked is not locked}.
	 * This method is overridden to make sure that we don't directly access {@link Zarafa.core.data.MAPIRecord#id}
	 * instead we get uniqueid using {@link Zarafa.core.data.MAPIStore#getRecordKey}.
	 * @param {Boolean} fast (optional) <tt>true</tt> to bypass the
	 * conditional checks and events described in {@link #deselectRow}.
	 */
	clearSelections: function(fast)
	{
		if(this.isLocked()){
			return;
		}

		if(fast !== true) {
			var ds = this.grid.getStore(),
				s = this.selections;
			s.each(function(r){
				var id = (Ext.isFunction(ds.getRecordKey) && ds.getRecordKey(r)) || r.id;
				this.deselectRow(ds.indexOfId(id));
			}, this);
			s.clear();
		} else {
			this.selections.clear();
		}

		this.last = false;
	},

	/**
	 * Returns <tt>true</tt> if the specified row is selected.
	 * This method is overridden to make sure that we don't directly access {@link Zarafa.core.data.MAPIRecord#id}
	 * instead we get uniqueid using {@link Zarafa.core.data.MAPIStore#getRecordKey}.
	 * @param {Number/Record} index The record or index of the record to check
	 * @return {Boolean}
	 */
	isSelected: function(index)
	{
		var store = this.grid.getStore();
		var r = Ext.isNumber(index) ? store.getAt(index) : index;
		var id = (Ext.isFunction(store.getRecordKey) && store.getRecordKey(r)) || r.id;

		return (r && this.selections.key(id) ? true : false);
	}
});
Ext.namespace('Zarafa.common.ui.htmleditor');

/**
 * @class Zarafa.common.ui.htmleditor.Fonts
 * Singleton holding a list of available fonts for TinyMCE
 * @singleton
 */
Zarafa.common.ui.htmleditor.Fonts = function(){
	return {
		/**
		 * @return {Object} object with fonts, key is the setting in 'zarafa/v1/mail/default_font_size'
		 * and the value returns the font-size
		 */
		getFonts: function()
		{
			// NOTE: Someone thought it would be a good idea to store a key of the
			// following object as the default font in the settings. This great
			// idea has the consequence that we cannot 'rename' the keys of the
			// object, hence the 'strange' 1.1 and 2.1 keys that were added later.
			// The keys can now be ordered alphabetically to get the correct order.
			return {
				1: 8,
				'1.1': 9,
				2: 10,
				'2.1': 11,
				3: 12,
				4: 14,
				5: 18,
				6: 24,
				7: 36
			};
		},

		/**
		 * Returns a space separated string of font sizes that can be used with
		 * the html editor.
		 * @return {String} a space separated string of font sizes
		 */
		getFontSizeString: function()
		{
			var fontSizes = this.getFonts();

			// We must order the fontSizes alphabetically.
			// Read the comment in #getFonts for more information.
			var keys = Object.keys(fontSizes).sort();
			var fontSizeString = '';
			Ext.each(keys, function(key){
				fontSizeString += fontSizes[key] + 'pt ';
			});

			return fontSizeString.trim();
		},

		/**
		 * @return {String} string with font families
		 */
		getFontFamilies: function()
		{
			var fontFamilies = "Andale Mono=andale mono,monospace;" +
				"Arial=arial,helvetica,sans-serif;" +
				"Arial Black=arial black,sans-serif;" +
				"Book Antiqua=book antiqua,palatino,serif;" +
				"Comic Sans MS=comic sans ms,sans-serif;" +
				"Courier New=courier new,courier,monospace;" +
				"Fira Mono=fira mono,monospace;" +
				"Georgia=georgia,palatino,serif;" +
				"Helvetica=helvetica,arial,sans-serif;" +
				"Impact=impact,sans-serif;" +
				"Symbol=symbol;" +
				"Tahoma=tahoma,arial,helvetica,sans-serif;" +
				"Terminal=terminal,monaco,monospace;" +
				"Times New Roman=times new roman,times,serif;" +
				"Trebuchet MS=trebuchet ms,geneva,sans-serif;" +
				"Verdana=verdana,geneva,sans-serif;" +
				"Webdings=webdings;" +
				"Wingdings=wingdings,zapf dingbats";

			return fontFamilies;
		},

		/**
		 * Returns the default font-size which the user has selected in compose mail settings
		 * @return {String} returns the default user font
		 */
		getDefaultFontSize: function()
		{
			var fonts = this.getFonts();
			var defaultFontSize = container.getSettingsModel().get('zarafa/v1/main/default_font_size');
			return fonts[defaultFontSize] + 'pt';
		}
	};
}();
Ext.namespace('Zarafa.common.ui.htmleditor');

/**
 * @class Zarafa.common.ui.htmleditor.LanguageMap
 * Singleton used to get the correct language file for TinyMCE
 * @singleton
 */
Zarafa.common.ui.htmleditor.LanguageMap = {
	fileMapping: {
		ar_DZ: 'ar',
		ar_SA: 'ar_SA',
		bg_BG: 'bg_BG',
		ca_ES: 'ca',
		cs_CZ: 'cs',
		da_DK: 'da',
		de_DE: 'de',
		de_CH: 'de',
		el_GR: 'el',
		en_GB: '', // Special case, as this is the default language of tinymce
		en_US: '', // Special case, as this is the default language of tinymce
		es_ES: 'es',
		et_EE: 'et',
		fa_IR: 'fa',
		fi_FI: 'fi',
		fr_FR: 'fr_FR',
		he_IL: 'he_IL',
		hr_HR: 'hr',
		hu_HU: 'hu_HU',
		is_IS: 'is_IS',
		it_IT: 'it',
		ja_JP: 'ja',
		ko_KR: 'ko_KR',
		lt_LT: 'lt',
		nb_NO: 'nb_NO',
		nl_NL: 'nl',
		pl_PL: 'pl',
		pt_BR: 'pt_BR',
		pt_PT: 'pt_PT',
		ru_RU: 'ru',
		sl_SL: 'sl_SI',
		sv_SE: 'sv_SE',
		tr_TR: 'tr',
		uk_UA: 'uk',
		zh_CN: 'zh_CN',
		zh_TW: 'zh_TW'
	},

	/**
	 * Converts the official language code to the language code that tinymce
	 * uses for their editor.
	 * @param {String} webappLanguageCode The official language code that grommunio Web uses
	 * @return {String} the language code that tinymce uses
	 */
	getTinyLanguageCode: function(webappLanguageCode){
		// Strip '.UTF-8' from the end if there
		if ( webappLanguageCode.indexOf('.UTF-8') >= 0 ){
			webappLanguageCode = webappLanguageCode.replace('.UTF-8', '');
		}

		if ( this.fileMapping[webappLanguageCode] ){
			return this.fileMapping[webappLanguageCode];
		}

		// By default use en_GB
		return 'en_US';
	}
};
Ext.namespace('Zarafa.common.ui.layout');

/**
 * @class Zarafa.common.ui.layout.CollapsibleLayout
 * @extends Ext.layout.FitLayout
 *
 * Special layout which functions when 2 {@link Ext.Container containers} are used,
 * in which the first container is collapsible, and the second container must be resized
 * automatically based on the remaining size available in the parent container.
 *
 * The container which is {@link Ext.Panel#collapsible} will be saved in the {@link #activeItem}
 * field, while the other {@link Ext.Panel} will be saved in the {@link #resizeItem}. No more then
 * 2 different {@link Ext.Panel panels} are supported by this layout.
 */
Zarafa.common.ui.layout.CollapsibleLayout = Ext.extend(Ext.layout.FitLayout, {
	/**
	 * The non-collapsible {@link Ext.Panel} which is being handled by this layout.
	 * @property
	 * @type Ext.Panel
	 */
	resizeItem: undefined,

	/**
	 * Apply event listeners for the {@link Ext.Panel#collapse} and {@link Ext.panel#expand} events
	 * when this component is {@link Ext.Panel#collapsible}.
	 * @param {Ext.Component} c The component being configured
	 * @private
	 */
	configureItem: function(c)
	{
		var position = this.container.items.indexOf(c);
		if (position < 2) {
			if (c.collapsible) {
				this.activeItem = c;
				c.on('collapse', this.onCollapseState, this);
				c.on('expand', this.onCollapseState, this);
			} else {
				this.resizeItem = c;
			}
		}

		Zarafa.common.ui.layout.CollapsibleLayout.superclass.configureItem.call(this, c, position);
	},

	/**
	 * Remove all registered event listeners from {@link #configureItem}.
	 * @param {Ext.Component} c The component being removed
	 * @private
	 */
	onRemove: function(c)
	{
		if (c.collapsible) {
			c.un('collapse', this.onCollapseState, this);
			c.un('expand', this.onCollapseState, this);
		}

		if (c === this.activeItem) {
			delete this.activeItem;
		}

		if (c === this.resizeItem) {
			delete this.resizeItem;
		}

		Zarafa.common.ui.layout.CollapsibleLayout.superclass.onRemove.call(this, c);
	},

	/**
	 * Called whenever the {@link #activeItem} has expanded or collapsed or when calculating
	 * the initial height for the component. This will adjust the size of the {@link #resizeItem}
	 * to make sure it will fit inside the height offered by the {@link #container}.
	 * @private
	 */
	doResizeItem: function()
	{
		if (this.resizeItem && this.activeItem) {
			var totalHeight = this.container.getInnerHeight ? this.container.getInnerHeight() : this.container.getHeight();
			var activeHeight = this.activeItem.getHeight();
			this.resizeItem.setHeight(totalHeight - activeHeight);
		}
	},

	/**
	 * Event handler which is fired when a component has been {@link Ext.Panel#collapse collapsed}
	 * or {@link Ext.Panel#expand expanded}. This will check if below the panel, another panel is
	 * located which can be resized accordingly.
	 *
	 * @param {Ext.Panel} c The panel which is expanded or collapsed
	 * @private
	 */
	onCollapseState: function(c)
	{
		this.doResizeItem();
	},

	/**
	 * Called when the container is being laid out. This will first use the {@link Ext.layout.Fitlayout FitLayout}
	 * to resize the {@link #activeItem}, and based on the size the activeItem takes in, we can resize the
	 * {@link #resizeitem} to fill the remaining space.
	 *
	 * @param {Ext.Container} ct The container which is being laid out
	 * @param {Ext.Element} target The target element which is being laid out
	 * @private
	 */
	onLayout: function(ct, target)
	{
		Zarafa.common.ui.layout.CollapsibleLayout.superclass.onLayout.call(this, ct, target);
		this.doResizeItem();
	}
});

Ext.Container.LAYOUTS['zarafa.collapsible'] = Zarafa.common.ui.layout.CollapsibleLayout;
Ext.namespace('Zarafa.common.ui.layout');

/**
 * @class Zarafa.common.ui.layout.SwitchBorderLayout
 * @extends Ext.layout.BorderLayout
 *
 * Extension to the BorderLayout to support switching a panel between the "north" and "west" panel,
 * or between the "south" and "east" panel. The {@link #orientation} can be controlled by the {@link #setOrientation}
 * and passing it an {@link Zarafa.common.ui.layout.SwitchBorderLayout.Orientation orientation}.
 *
 * When applying this layout on a {@link Ext.Container}, only 2 items may exist in the {@link Ext.Container},
 * the 'center' {@link Ext.Component component} and one {@link Ext.Component} in either
 * 'north', 'west', 'south' or 'east'.
 *
 * When {@link Zarafa.common.ui.layout.SwitchBorderLayout.Orientation#OFF} is enabled, then only
 * the 'center' panel will be displayed full-sized. For the orientation
 * {@link Zarafa.common.ui.layout.SwitchBorderLayout.Orientation#HORIZONTAL} the panel located in
 * the 'north' will be moved to the 'west', or the panel in the 'south' will be moved to the 'east'
 * (Transformation depends on the {@link #switchMap}. For the orientation
 * {@link Zarafa.common.ui.layout.SwitchBorderLayout.Orientation#VERTICAL} the 'west' panel will be
 * moved to the 'north', or the 'east' panel will be moved to the 'south' (again this depends on the
 * {@link ##switchMap}.
 */
Zarafa.common.ui.layout.SwitchBorderLayout = Ext.extend(Ext.layout.BorderLayout, {
	/**
	 * The region of the non-centered {@link Ext.Component} for the horizontal orientation
	 * this can be either 'west' or 'east'.
	 * @property
	 * @type String
	 */
	horizontalRegion: undefined,

	/**
	 * The region of the non-centered {@link Ext.Component} for the vertical orientation
	 * this can be either 'north' or 'south'. This is determined based on the {@link #switchMap}.
	 * @property
	 * @type String
	 */
	verticalRegion: undefined,

	/**
	 * @cfg {Object} switchMap The switchMap on how {@link #horizontalRegion} and {@link #verticalRegion}
	 * relate to each other. A vertical region always maps to a horizontal region, so 'north' can
	 * be mapped to 'east' or 'west' and 'west' can be mapped to 'north' and 'south'.
	 */
	switchMap: {
		'north': 'west',
		'west': 'north',
		'south': 'east',
		'east': 'south'
	},

	/**
	 * @cfg {Zarafa.common.ui.layout.SwitchBorderLayout.Orientation} orientation The currently active
	 * orientation mode.
	 */
	orientation: undefined,

	/**
	 * This will layout the entire container, if the component has not been rendered yet
	 * it will allocate the {@link Zarafa.common.ui.layout.SwitchBorderLayout.SwitchRegion Region}
	 * or {@link Zarafa.common.ui.layout.SwitchBorderLayout.SwitchSplitRegion SplitRegion} depending
	 * on the {@link #split} configuration option on the panel.
	 * @param {Ext.Container} ct The component which is being laid out
	 * @param {Ext.Element} target The target Element in which the layout occurs
	 * @private
	 */
	onLayout: function(ct, target)
	{
		if (!this.rendered) {
			var collapsed = [];
			var i, c, pos, items = ct.items.items, len = items.length;

			for(i = 0; i < len; i++) {
				c = items[i];
				pos = c.region;
				if(c.collapsed){
					collapsed.push(c);
				}
				c.collapsed = false;
				if(!c.rendered){
					c.render(target, i);
					c.getPositionEl().addClass('x-border-panel');
				}

				// Allocate the region
				this[pos] = pos != 'center' && c.split ?
					new Zarafa.common.ui.layout.SwitchBorderLayout.SwitchSplitRegion(this, c.initialConfig, pos):
					new Zarafa.common.ui.layout.SwitchBorderLayout.SwitchRegion(this, c.initialConfig, pos);
				this[pos].render(target, c);

				// If this is not the center region, we can create
				// the alternative region to which we can switch later.
				if (pos != 'center') {
					var altPos = this.switchMap[pos];

					this[altPos] = this[pos].split ?
						new Zarafa.common.ui.layout.SwitchBorderLayout.SwitchSplitRegion(this, c.initialConfig, altPos):
						new Zarafa.common.ui.layout.SwitchBorderLayout.SwitchRegion(this, c.initialConfig, altPos);
					this[altPos].render(target, c, true);

					// Update the region references
					if (pos === 'north' || pos === 'south') {
						this.horizontalRegion = altPos;
						this.verticalRegion = pos;
					} else {
						this.horizontalRegion = pos;
						this.verticalRegion = altPos;
					}
				}
			}
			this.rendered = true;

			// Seed region state from panel dimensions so getState()
			// still includes them after a context-switch recreate.
			if (this.verticalRegion && this[this.verticalRegion]) {
				var p = this[this.verticalRegion].panel;
				if (p) {
					if (Ext.isNumber(p.height)) {
						this[this.verticalRegion].state.height = p.height;
					}
					if (Ext.isNumber(p.width) && this[this.horizontalRegion]) {
						this[this.horizontalRegion].state.width = p.width;
					}
				}
			}

			// We just rendered everything, ensure
			// we apply the currently configured orientation
			var orientation = this.orientation;
			delete this.orientation;
			this.setOrientation(orientation);

			// setOrientation() will have called layout()
			// which arrives in onLayout() recursively.
			// so no need to continue further.
			return;

		}
		Zarafa.common.ui.layout.SwitchBorderLayout.superclass.onLayout.apply(this, arguments);
	},

	/**
	 * Toggle the currently active {@link Zarafa.common.ui.layout.SwitchBorderLayout.Orientation orientation}
	 * and {@link #layout} the {@link #container} again according to the new orientation.
	 * @param {Zarafa.common.ui.layout.SwitchBorderLayout.Orientation} orientation The new orientation which
	 * should be applied to the container.
	 */
	setOrientation: function(orientation)
	{
		if (this.orientation === orientation) {
			return;
		}
		this.orientation = orientation;

		// Don't continue if we are not yet rendered,
		// we must wait until the parent is being laid out
		// by Extjs for the first time.
		if (!this.rendered) {
			return;
		}

		var horizontalRegion = this[this.horizontalRegion];
		var verticalRegion = this[this.verticalRegion];

		switch (orientation) {
			case Zarafa.common.ui.layout.SwitchBorderLayout.Orientation.OFF:
				// Hide the separators, as resizing is not
				// allowed when we are hiding the panel.
				if (horizontalRegion.split) {
					horizontalRegion.splitEl.setVisible(false);
				}
				if (verticalRegion.split) {
					verticalRegion.splitEl.setVisible(false);
				}

				// Mark both regions as invisible
				horizontalRegion.setVisible(false);
				verticalRegion.setVisible(false);

				// Simplest way to hide the panel while maintaining
				// its dimensions is to collapse the entire panel.
				// Don't use animations as that will upset or size
				// calculations later.
				horizontalRegion.panel.collapse(false);

				break;
			case Zarafa.common.ui.layout.SwitchBorderLayout.Orientation.HORIZONTAL:
			/* falls through */
			default:
				// In case our previous orientation was 'OFF'
				// we must expand the panel (Again don't use animations
				// to prevent problems with size calculations).
				verticalRegion.panel.expand(false);

				// The horizontal region is visible
				horizontalRegion.setVisible(true);
				verticalRegion.setVisible(false);

				// Only show the horizontal separator
				if (horizontalRegion.split) {
					horizontalRegion.splitEl.setVisible(true);
				}
				if (verticalRegion.split) {
					verticalRegion.splitEl.setVisible(false);
				}
				break;
			case Zarafa.common.ui.layout.SwitchBorderLayout.Orientation.VERTICAL:
				// In case our previous orientation was 'OFF'
				// we must expand the panel (Again don't use animations
				// to prevent problems with size calculations).
				horizontalRegion.panel.expand(false);

				// The vertical region is visible
				horizontalRegion.setVisible(false);
				verticalRegion.setVisible(true);

				// Only show the vertical separator
				if (horizontalRegion.split) {
					horizontalRegion.splitEl.setVisible(false);
				}
				if (verticalRegion.split) {
					verticalRegion.splitEl.setVisible(true);
				}
				break;
		}

		// Call the doLayout on the container,
		// this enforced all child elements
		// to be laid out as well.
		this.container.doLayout();
	}
});

Ext.Container.LAYOUTS['zarafa.switchborder'] = Zarafa.common.ui.layout.SwitchBorderLayout;
Ext.namespace('Zarafa.common.ui.messagepanel');

/**
 * @class Zarafa.common.ui.messagepanel.AttachmentLinks
 * @extends Ext.DataView
 * @xtype zarafa.attachmentlinks
 *
 * If the {@link Zarafa.core.plugins.RecordComponentUpdaterPlugin} is installed
 * in the {@link #plugins} array of this component, this component will automatically
 * load the {@link Zarafa.core.data.MAPIRecord record} into the component.
 * Otherwise the user of this component needs to call {@link #setRecord}.
 */
Zarafa.common.ui.messagepanel.AttachmentLinks = Ext.extend(Ext.DataView, {
	/**
	 * @cfg {Number} maximum length of text allowed before truncations,
	 * truncation will be replaced with ellipsis ('...').
	 */
	ellipsisStringStartLength: 20,

	/**
	 * @cfg {Number} maximum length of text allowed after truncations,
	 * truncation will be replaced with ellipsis ('...').
	 */
	ellipsisStringEndLength: 20,

	/**
	 * @cfg {Number} maxHeight The maximum height the element which holds all
	 * recipient is allowed to take before a scrollbar will be shown.
	 */
	maxHeight: 72,

	/**
	 * The custom {@link DataTransfer} MIME type under which the attachment
	 * payload is exposed while dragging an attachment out of grommunio Web. A
	 * cooperating receiving web application reads this type on drop and
	 * reconstructs the file. The value is a JSON string of the form
	 * <tt>{name, type, size, data}</tt> where <tt>data</tt> is the base64
	 * encoded file content.
	 * @property {String}
	 * @private
	 */
	attachmentDragOutType: 'application/x-grommunio-attachment',

	/**
	 * Cache of base64 encoded attachment payloads, keyed by
	 * {@link #getAttachmentCacheKey}. Populated by {@link #prefetchAttachmentFile}.
	 * Each entry is an object <tt>{payload: String|null}</tt>; the payload is the
	 * JSON string placed on the drag {@link DataTransfer}.
	 * @property {Object}
	 * @private
	 */
	attachmentPayloadCache: undefined,

	/**
	 * @cfg {String} fieldLabel The label which must be applied to template
	 * as a prefix to the list of attachments.
	 */
	/* # TRANSLATORS: This message is used as label for the field which indicates which attachments are inside the message */
	fieldLabel: pgettext('mail.previewpanel', 'Attachments'),

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

		config = Ext.applyIf(config, {
			xtype: 'zarafa.attachmentlinks',
			plugins: [ 'zarafa.recordcomponentupdaterplugin' ],
			border: false,
			autoScroll:true,
			anchor: '100%',
			iconCls: 'icon_paperclip',
			cls: 'preview-header-attachments',
			multiSelect: false,
			overClass: 'zarafa-attachment-link-over',
			itemSelector: 'span.zarafa-attachment-link',
			tpl: new Ext.XTemplate(
				'<div class="preview-header-attachmentbox">' +
					'<div class="preview-attachment-title icon_paperclip" aria-hidden="true"></div>' +
					'<div class="preview-attachment-data" style="max-height: {this.maxHeight}px">' +
						'<tpl for=".">' +
							'<span class="zarafa-attachment-link x-zarafa-boxfield-item" viewIndex="{viewIndex}" draggable="true">' +
								'{values.name:htmlEncodeElide(this.ellipsisStringStartLength, this.ellipsisStringEndLength)} ' +
								'<tpl if="!Ext.isEmpty(values.size) && values.size &gt; 0">' +
									'({values.size:fileSize})' +
								'</tpl>' +
							'</span> ' +
						'</tpl>' +
					'</div>' +
				'</div>',
				{
					compiled: true,
					maxHeight: config.maxHeight || this.maxHeight,
					ellipsisStringStartLength: config.ellipsisStringStartLength || this.ellipsisStringStartLength,
					ellipsisStringEndLength: config.ellipsisStringEndLength || this.ellipsisStringEndLength
				}
			)
		});

		// The fieldLabel should not be sent to the superclass
		this.fieldLabel = config.fieldLabel || this.fieldLabel;
		delete config.fieldLabel;

		Zarafa.common.ui.messagepanel.AttachmentLinks.superclass.constructor.call(this, config);
	},

	/**
	 * Initialize the component.
	 * This will register the {@link #onAttachmentClicked} event handler and the {@link #onNodeContextMenu} event handler.
	 * @private
	 */
	initComponent: function()
	{
		Zarafa.common.ui.messagepanel.AttachmentLinks.superclass.initComponent.call(this);

		this.on({
			'contextmenu': this.onNodeContextMenu,
			'click': this.onAttachmentClicked,
			'render': this.onRenderRegisterDragOut,
			scope: this
		});
	},

	/**
	 * Returns whether the attachment drag-out feature is enabled by the server
	 * (see ENABLE_ATTACHMENT_DRAG_OUT in config.php) and supported by the
	 * browser (requires <tt>window.fetch</tt> and <tt>FileReader</tt>).
	 * @return {Boolean} True if attachment drag-out is available
	 * @private
	 */
	isDragOutEnabled: function()
	{
		var serverConfig = container.getServerConfig();
		if (serverConfig && Ext.isFunction(serverConfig.isAttachmentDragOutEnabled) && !serverConfig.isAttachmentDragOutEnabled()) {
			return false;
		}

		return Ext.isFunction(window.fetch) && typeof FileReader === 'function';
	},

	/**
	 * Returns the maximum attachment size (in bytes) whose content may be
	 * embedded in a drag operation (see ATTACHMENT_DRAG_OUT_MAX_SIZE in
	 * config.php). Defaults to 25 MiB.
	 * @return {Number} maximum embeddable attachment size in bytes
	 * @private
	 */
	getDragOutMaxSize: function()
	{
		var serverConfig = container.getServerConfig();
		if (serverConfig && Ext.isFunction(serverConfig.getAttachmentDragOutMaxSize)) {
			return serverConfig.getAttachmentDragOutMaxSize();
		}

		return 26214400;
	},

	/**
	 * Overrides {@link Ext.DataView#refresh} to eagerly prefetch and encode the
	 * content of the currently displayed attachments (up to the configured
	 * maximum size), so they are immediately available to be dragged into a
	 * cooperating web application without the user having to hover first.
	 * @override
	 */
	refresh: function()
	{
		Zarafa.common.ui.messagepanel.AttachmentLinks.superclass.refresh.apply(this, arguments);

		if (!this.store || !this.isDragOutEnabled()) {
			return;
		}

		this.store.each(function(record) {
			if (record.get('hidden') !== true) {
				this.prefetchAttachmentFile(record);
			}
		}, this);
	},

	/**
	 * Event handler for the {@link #render} event. Registers the native
	 * <tt>dragstart</tt> (and, when the drag-out feature is enabled,
	 * <tt>mouseover</tt>) listeners on the view element so that attachments can
	 * be dragged out of grommunio Web. See {@link #onAttachmentDragStart}.
	 * @private
	 */
	onRenderRegisterDragOut: function()
	{
		this.mon(this.getEl(), 'dragstart', this.onAttachmentDragStart, this);

		if (this.isDragOutEnabled()) {
			// Prefetch (and base64-encode) the attachment bytes when the user
			// hovers over a link, so the payload is available synchronously at
			// 'dragstart' time for dropping into a cooperating web application.
			this.mon(this.getEl(), 'mouseover', this.onAttachmentMouseOver, this);
		}
	},

	/**
	 * Returns a stable cache key for an attachment record.
	 * @param {Zarafa.core.data.IPMAttachmentRecord} record The attachment record
	 * @return {String} cache key
	 * @private
	 */
	getAttachmentCacheKey: function(record)
	{
		return record.get('attach_num') + '|' + (record.get('tmpname') || '') + '|' + (record.get('attach_id') || '');
	},

	/**
	 * Native <tt>mouseover</tt> handler. Kicks off a background prefetch of the
	 * hovered attachment so its payload is cached before the user starts
	 * dragging. See {@link #onAttachmentDragStart}.
	 * @param {Ext.EventObject} evt The native event wrapped by Ext.
	 * @private
	 */
	onAttachmentMouseOver: function(evt)
	{
		if (!this.store) {
			return;
		}

		var node = evt.getTarget(this.itemSelector, this.getEl().dom);
		if (!node) {
			return;
		}

		var record = this.getRecord(node);
		if (record) {
			this.prefetchAttachmentFile(record);
		}
	},

	/**
	 * Fetches the attachment content and caches it as a base64 encoded JSON
	 * payload, keyed by the attachment record. Does nothing when the feature is
	 * disabled, the attachment is an embedded message, its size exceeds the
	 * configured maximum ({@link #getDragOutMaxSize}) or it is already (being)
	 * fetched. The download uses the session cookie for authentication.
	 *
	 * @param {Zarafa.core.data.IPMAttachmentRecord} record The attachment record
	 * @private
	 */
	prefetchAttachmentFile: function(record)
	{
		if (!this.isDragOutEnabled()) {
			return;
		}

		if (Ext.isFunction(record.isEmbeddedMessage) && record.isEmbeddedMessage()) {
			return;
		}

		var size = record.get('size') || 0;
		if (size > this.getDragOutMaxSize()) {
			return;
		}

		this.attachmentPayloadCache = this.attachmentPayloadCache || {};
		var key = this.getAttachmentCacheKey(record);
		if (this.attachmentPayloadCache[key]) {
			// already cached or in-flight
			return;
		}

		var url = record.getAttachmentUrl();
		if (Ext.isEmpty(url)) {
			return;
		}

		var name = record.get('name') || _('Untitled');
		var mimeType = record.get('filetype') || 'application/octet-stream';
		var entry = { payload: null };
		this.attachmentPayloadCache[key] = entry;

		window.fetch(url, { credentials: 'include' }).then(function(response) {
			if (!response.ok) {
				throw new Error('HTTP ' + response.status);
			}
			return response.blob();
		}).then(function(blob) {
			return new Promise(function(resolve, reject) {
				var reader = new FileReader();
				reader.onload = function() {
					// readAsDataURL yields "data:<mime>;base64,<data>"; keep only
					// the base64 portion.
					var result = String(reader.result);
					var comma = result.indexOf(',');
					resolve({ base64: comma !== -1 ? result.substring(comma + 1) : '', size: blob.size });
				};
				reader.onerror = function() {
					reject(reader.error || new Error('FileReader failed'));
				};
				reader.readAsDataURL(blob);
			});
		}).then(Ext.createDelegate(function(data) {
			// Component may have been destroyed while fetching.
			if (this.isDestroyed) {
				return;
			}
			entry.payload = JSON.stringify({
				name: name,
				type: mimeType,
				size: data.size,
				data: data.base64
			});
		}, this)).catch(Ext.createDelegate(function() {
			// Drop the failed entry so a later hover/drag can retry.
			if (this.attachmentPayloadCache) {
				delete this.attachmentPayloadCache[key];
			}
		}, this));
	},

	/**
	 * Native <tt>dragstart</tt> handler which enables dragging an attachment out
	 * of grommunio Web. It populates the drag {@link DataTransfer} with two
	 * complementary payloads so the same drag works for both drop targets:
	 *
	 * - The Chromium <tt>DownloadURL</tt> type
	 *   (<tt>mimeType:fileName:absoluteURL</tt>) so the attachment can be dropped
	 *   onto the operating system (file manager / Windows Explorer / desktop),
	 *   which then downloads the file to the drop location. (Chromium only;
	 *   Firefox and other browsers ignore this type.)
	 * - When the drag-out feature is enabled and the attachment content has been
	 *   {@link #prefetchAttachmentFile prefetched}, the payload is also exposed
	 *   under the custom {@link #attachmentDragOutType} MIME type as a JSON string
	 *   <tt>{name, type, size, data(base64)}</tt>, so a cooperating receiving web
	 *   application can reconstruct the file on drop.
	 *
	 * The two do not conflict: when a web page's drop handler calls
	 * <tt>preventDefault()</tt> (as a drop zone must), the browser hands over the
	 * custom payload and does NOT download. The <tt>DownloadURL</tt> download only
	 * happens when the drop target does not accept the drop, i.e. the operating
	 * system (Explorer/desktop) or a page area without a drop handler.
	 *
	 * @param {Ext.EventObject} evt The native event wrapped by Ext.
	 * @private
	 */
	onAttachmentDragStart: function(evt)
	{
		if (!this.store) {
			return;
		}

		var node = evt.getTarget(this.itemSelector, this.getEl().dom);
		if (!node) {
			return;
		}

		var record = this.getRecord(node);
		if (!record) {
			return;
		}

		// Embedded messages are not backed by a single downloadable file, so
		// leave the drag as a plain (in-app) drag for those.
		if (Ext.isFunction(record.isEmbeddedMessage) && record.isEmbeddedMessage()) {
			return;
		}

		var browserEvent = evt.browserEvent || evt;
		var dataTransfer = browserEvent.dataTransfer;
		if (!dataTransfer || !Ext.isFunction(dataTransfer.setData)) {
			return;
		}

		var name = record.get('name') || _('Untitled');
		var mimeType = record.get('filetype') || 'application/octet-stream';

		// Build an absolute URL; DownloadURL requires a fully qualified URL.
		var url = record.getAttachmentUrl();
		if (url && url.indexOf('://') === -1) {
			var loc = window.location;
			url = loc.protocol + '//' + loc.host + (url.charAt(0) === '/' ? '' : loc.pathname.replace(/[^/]*$/, '')) + url;
		}

		// (1) Custom type for dropping into a cooperating web application. Only
		// available when the feature is enabled and the payload was prefetched.
		var haveCustomPayload = false;
		if (this.isDragOutEnabled()) {
			this.attachmentPayloadCache = this.attachmentPayloadCache || {};
			var entry = this.attachmentPayloadCache[this.getAttachmentCacheKey(record)];
			if (entry && entry.payload) {
				try {
					dataTransfer.setData(this.attachmentDragOutType, entry.payload);
					haveCustomPayload = true;
				} catch (e) {
					// Browser rejected the custom type; ignore.
				}
			} else {
				// Not prefetched yet (or in-flight): start fetching so a
				// subsequent drag can embed the payload.
				this.prefetchAttachmentFile(record);
			}
		}

		// (2) DownloadURL for dropping onto the operating system (file manager /
		// Windows Explorer / desktop). This coexists with the custom type: a web
		// drop zone that calls preventDefault() receives the custom payload and
		// no download is triggered; only the OS (which cannot honour the custom
		// type) uses the DownloadURL to save the file.
		try {
			if (!Ext.isEmpty(url)) {
				// ':' is the field separator in a DownloadURL value; newlines
				// are not permitted.
				var safeName = String(name).replace(/[\r\n]+/g, ' ').replace(/:/g, '_');
				dataTransfer.setData('DownloadURL', mimeType + ':' + safeName + ':' + url);
				dataTransfer.setData('text/uri-list', url);
			}
			// A human-readable label; useful when dropping onto a text field.
			dataTransfer.setData('text/plain', haveCustomPayload || Ext.isEmpty(url) ? name : url);
			dataTransfer.effectAllowed = 'copyLink';
		} catch (e) {
			// Ignore browsers that reject one of the data types.
		}
	},

	/**
	 * <p>Function which can be overridden which returns the data object passed to this
	 * DataView's {@link #tpl template} to render the whole DataView.</p>
	 * <p>This is usually an Array of data objects, each element of which is processed by an
	 * {@link Ext.XTemplate XTemplate} which uses <tt>'&lt;tpl for="."&gt;'</tt> to iterate over its supplied
	 * data object as an Array. However, <i>named</i> properties may be placed into the data object to
	 * provide non-repeating data such as headings, totals etc.</p>
	 * @param {Array} records An Array of {@link Ext.data.Record}s to be rendered into the DataView.
	 * @param {Number} startIndex the index number of the Record being prepared for rendering.
	 * @return {Array} An Array of data objects to be processed by a repeating XTemplate. May also
	 * contain <i>named</i> properties.
	 */
	collectData: function(records, startIndex)
	{
		var r = [];
		for (var i = 0, len = records.length; i < len; i++) {
			var record = records[i];
			if (record.get('hidden') !== true) {
				r[r.length] = this.prepareData(record.data, record.get('attach_num'), record);
			}
		}
		return r;
	},

	/**
	 * Apply a record on the DataView. If the record is {@link Zarafa.core.data.IPMRecord#isOpened},
	 * then the {@link Zarafa.core.data.IPMRecord#getAttachmentStore attachment store} will be
	 * {@link #bindStore bound} to this view.
	 * @param {Zarafa.core.data.IPMRecord} record The record to apply
	 */
	setRecord: function(record)
	{
		if (record && record.get('hasattach') && record.hasVisibleAttachments()) {
			if (record.isOpened()) {
				// Defer binding the attachment store so message text renders first.
				Ext.defer(function(){
					this.bindStore(record.getAttachmentStore());
					this.setVisible(true);
				}, 100, this);
			}
		} else {
			this.bindStore(null);
			this.setVisible(false);
		}
	},

	/**
	 * overridden to get the viewIndex from an HTML element's attribute
	 * by default the index is taken from the element's position within the group;
	 * however if there are more than one groups, the indexes are wrong
	 * @private
	 */
	updateIndexes: function(startIndex, endIndex)
	{
		var ns = this.all.elements;
		startIndex = startIndex || 0;
		endIndex = endIndex || ((endIndex === 0) ? 0: (ns.length - 1));
		for(var i = startIndex; i <= endIndex; i++){
				ns[i].viewIndex = ns[i].getAttribute('viewIndex');
		}
	},

	/**
	 * overridden to provide the correct index to {@link Ext.DataView#getRecord}
	 * otherwise behaviour breaks when there is more than one group in the records (e.g. CC, BCC, etc.)
	 * @param {Zarafa.core.data.IPMRecipientRecord} data The recipient record to be prepared
	 * @param {Number} index
	 * @param {Ext.data.Record} record
	 * @return {Object} new object with index appended
	 * @private
	 */
	prepareData: function(data, index, record)
	{
		return Ext.apply({viewIndex: index}, data);
	},

	/**
	 * Gets an array of the records from an array of nodes
	 * @param {Array} nodes The nodes to evaluate
	 * @return {Array} records The {@link Ext.data.Record} objects
	 */
	getRecords: function(nodes)
	{
		var records = [];

		for(var i = 0, len = nodes.length; i < len; i++){
			records[records.length] = this.getRecord(nodes[i]);
		}
		return records;
	},

	/**
	 * Gets a record from a node
	 * @param {HTMLElement} node The node to evaluate
	 * @return {Record} record The {@link Ext.data.Record} object
	 * @override
	 */
	getRecord: function(node)
	{
		return this.store.getAt(this.store.findExact('attach_num', parseInt(node.viewIndex, 10)));
	},

	/**
	 * Update the components with the given record.
	 *
	 * @param {Zarafa.core.data.MAPIRecord} record The record to update in this component
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 */
	update: function(record, contentReset)
	{
		if ( !this.getEl().dom ) {
			return;
		}

		if (record && record instanceof Zarafa.core.data.MAPIRecord) {
			// In case the recordcomponentupdaterplugin is installed
			// we have a special action to update the component.
			if (contentReset && record.isOpened()) {
				this.setRecord(record);
			}
		} else {
			// The recordcomponentupdaterplugin is not installed and the
			// caller really wants to perform the update() function. Probably
			// a bad move, but lets not disappoint the caller.
			Zarafa.common.ui.messagepanel.AttachmentLinks.superclass.update.apply(this, arguments);
		}
	},

	/**
	 * Called when the {@link #click} event is fired. This will
	 * {@link Zarafa.core.data.UIFactory#openViewRecord open} the selected attachment
	 * @param {Ext.DataView} dataview Reference to this object
	 * @param {Number} index
	 * @param {HTMLElement} node The target HTML element
	 * @param {Object} evt Event object
	 * @private
	 */
	onAttachmentClicked: function(dataview, index, node, evt)
	{
		// We should actually never be called without a record set on
		// this container. So this is likely a bug...
		if (!this.store) {
			return;
		}

		var attachment = dataview.getRecord(node);
		Zarafa.common.Actions.openAttachmentRecord(attachment);
	},
	/**
	 * Called when user right-clicks on an item in {@link Zarafa.common.ui.messagepanel.AttachmentLinks}
	 * invokes {@link Zarafa.core.data.UIFactory#openDefaultContextMenu} with the selected {@link Zarafa.core.data.IPMRecord}
	 * @param {Ext.DataView} dataView DataView from which the event comes
	 * @param {Number} index
	 * @param {HTMLElement} node HTML node from which the event originates
	 * @param {Ext.EventObject} evt Event object
	 * @private
	 */
	onNodeContextMenu: function(dataView, index, node, evt)
	{
		Zarafa.core.data.UIFactory.openDefaultContextMenu(dataView.getRecord(node), {
			position: evt.getXY(),
			model: this.model
		});
	}
});

Ext.reg('zarafa.attachmentlinks', Zarafa.common.ui.messagepanel.AttachmentLinks);
Ext.namespace('Zarafa.common.ui.messagepanel');

/**
 * @class Zarafa.common.ui.messagepanel.CategoryLinks
 * @extends Ext.Container
 * @xtype zarafa.categorylinks
 *
 * Renders the categories as colored labels in the
 * {@link Zarafa.common.ui.messagepanel.MessageHeader}.
 */
Zarafa.common.ui.messagepanel.CategoryLinks = Ext.extend(Ext.Container, {
	/**
	 * @cfg {Zarafa.core.data.IPMRecord} record Holds the current record
	 */
	record: undefined,

	/**
	 * Holds the tooltip that will be used to show the full category name
	 * of truncated categories
	 * @property
	 * @type {Zarafa.common.categories.ui.Tooltip}
	 */
	tooltip: null,

	/**
	 * @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,{
			xtype: 'zarafa.categorylinks',
			border: false,
			anchor: '100%',
			cls: 'k-preview-header-categories',
			listeners: {
				render: this.onRenderCategoryLinks,
				scope: this
			}
		});

		Zarafa.common.ui.messagepanel.CategoryLinks.superclass.constructor.call(this, config);
	},

	/**
	 * Event handler for the render event of the category links. Creates
	 * the {@link Zarafa.common.categories.ui.Tooltip tooltip object} that will
	 * be used to display truncated categories. And adds a listener for
	 * the contextmenu event.
	 */
	onRenderCategoryLinks: function()
	{
		// Create a tooltip for truncated categories
		this.tooltip = new Zarafa.common.categories.ui.Tooltip({
			target: this.el
		});

		// Create the contextmenu for the category labels
		this.mon(this.el, 'contextmenu', this.onContextMenu, this);
	},

	/**
	 * Event handler for the contextmenu event of the category labels. Will
	 * show the context menu if the click was on a label.
	 * @param {Ext.EventObject} event The event object
	 * @param {HtmlElement} targetElement The element on which the click happened
	 */
	onContextMenu: function(event, targetElement)
	{
		targetElement = Ext.get(targetElement);
		if ( targetElement.hasClass('k-category-block') ){
			Zarafa.core.data.UIFactory.openContextMenu(Zarafa.core.data.SharedComponentType['common.contextmenu.category'], [this.record], {
				category: targetElement.dom.textContent,
				position: event.getXY()
			});
		}
	},

	/**
	 * Update the {@link Zarafa.common.ui.messagepanel.CategoryLinks header} with the data
	 * from the {@link Zarafa.core.data.IPMRecord record}. Updates the panel
	 * by loading data from the record data into the template.
	 * Attach mouse handlers on the anchors
	 * @param {Zarafa.core.data.IPMRecord} record to update the header panel with
	 */
	update: function(record)
	{
		this.record = record;

		if ( this.el && this.el.dom ){
			if (!record) {
				this.el.dom.innerHTML = '';
				return;
			}
			// Render the categories
			var categories = Zarafa.common.categories.Util.getCategories(record);
			var html = Zarafa.common.categories.Util.getCategoriesHtml(categories);
			this.el.dom.innerHTML = html;
		}
	}
});

Ext.reg('zarafa.categorylinks', Zarafa.common.ui.messagepanel.CategoryLinks);
Ext.namespace('Zarafa.common.ui.messagepanel');

/**
 * @class Zarafa.common.ui.messagepanel.ExtraInfoLinks
 * @extends Ext.Container
 * @xtype zarafa.extrainfolinks
 */
Zarafa.common.ui.messagepanel.ExtraInfoLinks = Ext.extend(Ext.Container, {
	/**
	 * The record which has been loaded by this container. This record
	 * is provided through the {@link #update} function.
	 *
	 * @property
	 * @type Zarafa.core.data.IPMRecord
	 */
	record: undefined,

	/**
	 * @cfg {String} importantInfoString string which must be displayed in the {@link #header}
	 * if there is any priority set in {@link Zarafa.core.data.IPMRecord record}
	 */
	// # TRANSLATORS: This sentence is given in the Email previewpanel, and indicates with which
	// # importance the message was sent. For example:
	// # 'This message was sent with high importance.'
	// # 'This message was sent with low importance.'
	importantInfoString: pgettext('mail.previewpanel', 'This message was sent with {0} importance.'),

	/**
	 * @cfg {String} sensitivityInfoString string which must be displayed in the {@link #header}
	 * if there is any sensitivity set in {@link Zarafa.core.data.IPMRecord record}
	 */
	// # TRANSLATORS: This sentence is given in the Email previewpanel, and indicates which
	// # sensitivity level applies to the message. For example:
	// # 'Please treat this as Personal.'
	// # 'Please treat this as Private.'
	// # 'Please treat this as Confidential.'
	sensitivityInfoString: pgettext('mail.previewpanel', 'Please treat this as {0}.'),

	/**
	 * @cfg {String} blockStatusInfoString string which must be displayed in the {@link #header}
	 * if there is any external content blocked in the {@link Zarafa.core.data.IPMRecord record} body.
	 */
	blockStatusInfoString: pgettext('mail.previewpanel', 'Click here to download pictures. To help protect your privacy, grommunio Web prevented automatic download of some pictures in this message.'),

	/**
	 * @cfg {String} faultyMessageInfoString string which will be displayed in the {@link #header}
	 * if the current {@link Zarafa.core.data.IPMRecord record} is faulty and we will not be able to
	 * perform operations on this record.
	 */
	faultyMessageInfoString: pgettext('mail.previewpanel', 'This message is corrupt and will not be displayed correctly, Click here to fix it.'),

	/**
	 * @cfg {String} acceptInfoString string which must be displayed in the {@link #header}
	 * if user has accepted the meeting request.
	 */
	acceptInfoString: pgettext('mail.previewpanel', '{0} has accepted.'),

	/**
	 * @cfg {String} acceptDelegateInfoString string which must be displayed in the {@link #header}
	 * if delegate has accepted the meeting request on behalf of delegator.
	 */
	acceptDelegateInfoString: pgettext('mail.previewpanel', '{0} has accepted on behalf of {1}.'),

	/**
	 * @cfg {String} tentativeInfoString string which must be displayed in the {@link #header}
	 * if user has tentatively accepted the meeting request.
	 */
	tentativeInfoString: pgettext('mail.previewpanel', '{0} has tentatively accepted.'),

	/**
	 * @cfg {String} tentativeDelegateInfoString string which must be displayed in the {@link #header}
	 * if delegate has tentatively accepted the meeting request on behalf of delegator.
	 */
	tentativeDelegateInfoString: pgettext('mail.previewpanel', '{0} has tentatively accepted on behalf of {1}.'),

	/**
	 * @cfg {String} tentativeInfoString string which must be displayed in the {@link #header}
	 * if user has declined the meeting request.
	 */
	declineInfoString: pgettext('mail.previewpanel', '{0} has declined.'),

	/**
	 * @cfg {String} declineDelegateInfoString string which must be displayed in the {@link #header}
	 * if delegate has declined the meeting request on behalf of delegator.
	 */
	declineDelegateInfoString: pgettext('mail.previewpanel', '{0} has declined on behalf of {1}.'),

	/**
	 * @cfg {String} counterProposalAcceptedString string which must be displayed in the {@link #header}
	 * if any attendee has tentatively accepted the meeting and proposed a new time for the meeting.
	 */
	counterProposalTentativeAcceptedString: pgettext('mail.previewpanel', '{0} tentatively accepted and proposed a new time for this meeting.'),

	/**
	 * @cfg {String} counterProposalTentativeAcceptedDelegateString string which must be displayed in the {@link #header}
	 * if any delegate has tentatively accepted the meeting and proposed a new time for the meeting on behalf of delegator.
	 */
	counterProposalTentativeAcceptedDelegateString: pgettext('mail.previewpanel', '{0} tentatively accepted on behalf of {1} and proposed a new time for this meeting.'),

	/**
	 * @cfg {String} counterProposalDeclinedString string which must be displayed in the {@link #header}
	 * if any attendee has declined the meeting and proposed a new time for the meeting.
	 */
	counterProposalDeclinedString: pgettext('mail.previewpanel', '{0} declined and proposed a new time for this meeting.'),

	/**
	 * @cfg {String} counterProposalDeclinedDelegateString string which must be displayed in the {@link #header}
	 * if any delegate has declined the meeting and proposed a new time for the meeting on behalf of delegator.
	 */
	counterProposalDeclinedDelegateString: pgettext('mail.previewpanel', '{0} declined on behalf of {1} and proposed a new time for this meeting.'),

	/**
	 * @cfg {String} meetingNotFoundInfoString string which must be displayed in the {@link #header}
	 * if there is any change in meeting time set in {@link Zarafa.core.data.IPMRecord record}
	 */
	meetingNotFoundInfoString: pgettext('mail.previewpanel', 'This meeting is not in the Calendar; it may have been moved or deleted.'),

	/**
	 * @cfg {String} meetingOutDatedInfoString string which must be displayed in the {@link #header}
	 * if meeting has occurred in the past or expired {@link Zarafa.core.data.IPMRecord record}
	 */
	meetingOutDatedInfoString: pgettext('mail.previewpanel', 'This meeting request was updated after this message was sent. You should open the latest update or open the item from the calendar.'),

	/**
	 * @cfg {String} organizerAsAttendeeInfoString string which must be displayed in the {@link #header}
	 * if change in meeting request is been received by the organizer himself in {@link Zarafa.core.data.IPMRecord record}
	 */
	organizerAsAttendeeInfoString: pgettext('mail.previewpanel', 'As the meeting organizer, you do not need to respond to the meeting'),

	/**
	 * @cfg {String} meetingUpdatedInfoString string which must be displayed in the {@link #header}
	 * if proposed time for meeting request has been accepted by the organizer himself in {@link Zarafa.core.data.IPMRecord record}
	 */
	meetingUpdatedInfoString: pgettext('mail.previewpanel','The meeting was updated after the attendee sent this response'),

	/**
	 * @cfg {String} elapsedTimeInfoString string which must be displayed in the dialog
	 * if meeting time set in {@link Zarafa.core.data.IPMRecord record} is already elapsed.
	 */
	elapsedTimeInfoString: pgettext('mail.previewpanel', 'This appointment occurs in the past.'),

	/**
	 * @cfg {String} responseRequiredString string which must be displayed in the dialog
	 * if meeting accepted time set in {@link Zarafa.core.data.IPMRecord record} is empty.
	 * which means that, meeting request is not responded yet
	 */
	responseRequiredString: pgettext('mail.previewpanel', 'Please respond.'),

	/**
	 * @cfg {String} meetingUpdatedString string which must be displayed in the dialog if the
	 * {@link Zarafa.core.data.IPMRecord record} is an update of a previously sent meeting request.
	 */
	meetingUpdatedString: pgettext('mail.previewpanel', 'This is an update of a previously sent meeting request.'),

	/**
	 * @cfg {String} delegatorInfoString string which must be displayed in the dialog if the
	 * {@link Zarafa.core.data.IPMRecord record} is received as a delegate.
	 */
	delegatorInfoString: pgettext('mail.previewpanel', 'Received for {0}.'),

	/**
	 * @cfg {String} repliedString string which must be displayed in the dialog if the
	 * {@link Zarafa.core.data.IPMRecord record} last_verb_executed property is 102 or 103. Which
	 * means that the user has replied on the email.
	 */
	repliedString: pgettext('mail.previewpanel', 'You replied to this message.'),

	/**
	 * @cfg {String} forwardString string which must be displayed in the dialog if the
	 * {@link Zarafa.core.data.IPMRecord record} last_verb_executed property is 104. Which
	 * means that the user has forwarded this email.
	 */
	forwardString: pgettext('mail.previewpanel', 'You forwarded this message.'),

	/**
	 * @cfg {String} repliedStringTime string which must be displayed in the dialog if the
	 * {@link Zarafa.core.data.IPMRecord record} last_verb_executed property is 102 or 103. Which
	 * means that the user has replied on the email. Including the timestamp.
	 */
	repliedStringTime: pgettext('mail.previewpanel', 'You replied to this message on {0}.'),

	/**
	 * @cfg {String} forwardStringTime string which must be displayed in the dialog if the
	 * {@link Zarafa.core.data.IPMRecord record} last_verb_executed property is 104. Which
	 * means that the user has forwarded this email. Including the timestamp.
	 */
	forwardStringTime: pgettext('mail.previewpanel', 'You forwarded this message on {0}.'),

	/**
	 * @cfg {String} receivedTaskRequest string which must be displayed in the dialog if the
	 * {@link Zarafa.core.data.IPMRecord record} taskhistory property is {@link Zarafa.core.mapi.TaskHistory#ASSIGNED}
	 * and {@link Zarafa.task.TaskRecord#isTaskReceived task is received}. Which
	 * means that user has received task request from assigner.
	 */
	receivedTaskRequest: _('Assigned by {0} on {1}.'),

	/**
	 * @cfg {String} sentTaskRequest string which must be displayed in the dialog if the
	 * {@link Zarafa.core.data.IPMRecord record} taskhistory property is {@link Zarafa.core.mapi.TaskHistory#ASSIGNED}
	 * and {@link Zarafa.task.TaskRecord#isTaskReceived task is not received}. Which
	 * means that user has assigned task to assignee.
	 */
	sentTaskRequest: _('Waiting for response from recipient'),

	/**
	 * @cfg {String} acceptedTaskRequest string which must be displayed in the dialog if the
	 * {@link Zarafa.core.data.IPMRecord record} taskhistory property is {@link Zarafa.core.mapi.TaskHistory#ACCEPTED}
	 * Which means that task is accepted by assignee.
	 */
	acceptedTaskRequest: _('Accepted by {0} on {1}.'),

	/**
	 * @cfg {String} acceptDelegateTaskRequestInfoString string which must be displayed in the {@link #header}
	 * if delegate has accepted the task request on behalf of delegator.
	 */
	acceptDelegateTaskRequestInfoString: _('{0} has accepted on behalf of {1} {2}.'),

	/**
	 * @cfg {String} declineTaskRequest string which must be displayed in the
	 * dialog if the {@link Zarafa.core.data.IPMRecord record} taskhistory property is
	 * {@link Zarafa.core.mapi.TaskHistory#DECLINED}, and taskstate property is not
	 * {@link Zarafa.core.mapi.TaskState#ACCEPT}. Which means task is assigner received decline
	 * task request response from assignee.
	 */
	declineAssignerTaskRequest: _('Declined by {0} on {1}.'),

	/**
	 * @cfg {String} acceptDelegateTaskRequestInfoString string which must be displayed in the {@link #header}
	 * if delegate has declined the task request on behalf of delegator.
	 */
	declineDelegateTaskRequestInfoString: _('{0} has declined on behalf of {1} {2}.'),

	/**
	 * @cfg {String} declineTaskRequest string which must be displayed in the
	 * dialog if the {@link Zarafa.core.data.IPMRecord record} taskhistory property is
	 * {@link Zarafa.core.mapi.TaskHistory#UPDATED}, and
	 * {@link Zarafa.task.TaskRecord#isTaskRequest record} is not task request record.
	 * Which means task is assigner received update task request response from assignee.
	 */
	updateTaskRequest: _('Last update sent on {0}.'),

	/**
	 * @cfg {String} acceptDelegateTaskRequestInfoString string which must be displayed in the {@link #header}
	 * if delegate has updated the task request on behalf of delegator.
	 */
	updateDelegateTaskRequestInfoString: _('{0} has updated on behalf of {1} {2}.'),

	/**
	 * @cfg {String} itemCls css class that will be added to every item of this component.
	 */
	itemCls: 'preview-header-extrainfobox-item',

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

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

		config = Ext.applyIf(config, {
			xtype: 'zarafa.extrainfolinks',
			cls: 'preview-header-extrainfobox'
		});

		Zarafa.common.ui.messagepanel.ExtraInfoLinks.superclass.constructor.call(this, config);
	},

	/**
	 * Set sensitivity, important/priority information from {@link Zarafa.core.data.IPMRecord record}.
	 * it will also handle showing/hiding of {@link Zarafa.common.ui.messagepanel.ExtraInfoLinks ExtraInfoLinks}.
	 *
	 * @param {Zarafa.core.data.IPMRecord} record to update the {@link Zarafa.common.ui.messagepanel.ExtraInfoLinks ExtraInfoLinks} with
	 */
	update: function(record)
	{
		// clear the previous contents
		var el = this.getEl();
		if (!Ext.isDefined(el.dom)) {
			return;
		}
		el.dom.innerHTML = '';

		if(!Ext.isDefined(record)) {
			this.setVisible(false);
			return;
		}

		this.record = record;

		var entryid = record.get('entryid');
		if (entryid) {
			var objectIdText = Zarafa.core.EntryId.formatObjectId(entryid);
			if (objectIdText) {
				record.set('x_midtext', objectIdText);
			}
		}

		var isVisible = false;
		var sensitivity = record.get('sensitivity');
		var importance = record.get('importance');
		var lastVerbExecuted = record.get('last_verb_executed');
		var lastVerbExecutionTime = record.get('last_verb_execution_time');

		// 104 is set in createmailitemmodule::setReplyForwardInfo when a message is forwarded.
		if(lastVerbExecuted === 104) {
			var text = lastVerbExecutionTime ? String.format(this.forwardStringTime, lastVerbExecutionTime.formatDefaultTime('j-m-Y {0}')) :
				this.forwardString;
			el.createChild({tag: 'div', html: text, cls: this.itemCls});

			isVisible = true;
		} else if(lastVerbExecuted === 102 || lastVerbExecuted == 103) { // 102/103 is set when the message is a reply.
			var text = lastVerbExecutionTime ? String.format(this.repliedStringTime, lastVerbExecutionTime.formatDefaultTime('j-m-Y {0}')) :
				this.repliedString;
			el.createChild({tag: 'div', html: text, cls: this.itemCls});

			isVisible = true;
		}

		if(Ext.isDefined(sensitivity) && sensitivity != Zarafa.core.mapi.Sensitivity.NONE && this.isShowExtraInfo()){
			var text = String.format(this.sensitivityInfoString, Zarafa.core.mapi.Sensitivity.getDisplayName(sensitivity));
			el.createChild({tag: 'div', html: text, cls: this.itemCls});
			isVisible = true;
		}

		if(Ext.isDefined(importance) && importance != Zarafa.core.mapi.Importance.NORMAL && this.isShowExtraInfo()){
			var text = String.format(this.importantInfoString, Zarafa.core.mapi.Importance.getDisplayName(importance));
			el.createChild({tag: 'div', html: text, cls: this.itemCls});
			isVisible = true;
		}

		if(record.isFaultyMessage()) {
			var faultyInfoElement = el.createChild({tag: 'div', html: this.faultyMessageInfoString, cls: this.itemCls});
			faultyInfoElement.set({ 'role': 'button', 'tabindex': '0' });

			// add click event
			this.mon(faultyInfoElement, 'click', this.onExtraInfoFaultyClick, this);
			this.mon(faultyInfoElement, 'keydown', function(e) {
				if (e.getKey() === e.ENTER || e.getKey() === e.SPACE) {
					e.stopEvent();
					this.onExtraInfoFaultyClick();
				}
			}, this);

			// add class to show that this element is clickable
			faultyInfoElement.addClassOnOver('preview-header-extrainfobox-item-over');

			isVisible = true;
		}

		if(this.record instanceof Zarafa.core.data.MessageRecord) {
			if(record.isExternalContentBlocked()) {
				var blockElement = el.createChild({tag: 'div', html: this.blockStatusInfoString, cls: this.itemCls});
				blockElement.set({ 'role': 'button', 'tabindex': '0' });

				// add click event
				this.mon(blockElement, 'click', this.onExtraInfoBlockClick, this);
				this.mon(blockElement, 'keydown', function(e) {
					if (e.getKey() === e.ENTER || e.getKey() === e.SPACE) {
						e.stopEvent();
						this.onExtraInfoBlockClick();
					}
				}, this);

				// add class to show that this element is clickable
				blockElement.addClassOnOver('preview-header-extrainfobox-item-over');

				isVisible = true;
			}

			// extra info for meeting items
			if(this.record instanceof Zarafa.calendar.MeetingRequestRecord) {
				isVisible = (this.setMeetingInfo(el) === true ? true : isVisible);
			} else if(this.record instanceof Zarafa.task.TaskRecord) {
				isVisible = (this.setTaskRequestInfo(el) === true ? true : isVisible);
			}
		}

		this.setVisible(isVisible);
		// Only re-layout when the component is visible.
		if (isVisible) {
			// For the task general tab we must do a layout of the complete panel because
			// the extra info is updated after the other things are rendered.
			var parent = this.findParentByType('zarafa.taskgeneraltab');
			if ( parent ){
				parent.doLayout();
			} else {
				this.doLayout();
			}
		}
	},

	/**
	 * Function will be used to determine that {@link Zarafa.core.mapi.Sensitivity Sensitivity} or
	 * {@link Zarafa.core.mapi.Importance importance} related extra info link on header of the record.
	 *
	 * @return {boolean}
	 */
	isShowExtraInfo: function ()
	{
		if (this.record.isMessageClass('IPM.Task')) {
			// Only show the extra info link if record is task and user is task organizer/assigner
			// or task is received task and still not Accepted/Updated.
			return this.record.isTaskOrganized() || (this.record.isTaskReceived() && !this.record.isTaskAccepted() && !this.record.isTaskUpdated());
		} else {
			return true;
		}
	},

	/**
	 * Function will be used to display {@link Zarafa.common.ui.messagepanel.ExtraInfoContextMenu ExtraInfoContextMenu}
	 * for showing options for mails which has external content and its blocked because of security reasons.
	 * @param {Ext.EventObject} evtObject
	 * @private
	 */
	onExtraInfoBlockClick: function(evtObject)
	{
		Zarafa.core.data.UIFactory.openContextMenu(Zarafa.core.data.SharedComponentType['common.contextmenu.previewpanel.extrainfo'], this.record, { position: evtObject.getXY() });
	},

	/**
	 * Function will be used to display a warning to user that we are going to change some properties in the corrupted
	 * message and will save the changes on server.
	 * @param {Ext.EventObject} evtObject
	 * @private
	 */
	onExtraInfoFaultyClick: function(evtObject)
	{
		Ext.MessageBox.show({
			title: _('Fix message'),
			msg:_('We are going to try to fix a corrupted mail message, do you wish to continue?'),
			fn: this.onFixFaultyMessage,
			scope: this,
			buttons: Ext.MessageBox.YESNO
		});
	},

	/**
	 * Callback function from {@link Ext.MessageBox#show} which checks
	 * if the user want to try to fix faulty mail message.
	 * @param {String} button button text which was pressed.
	 * @private
	 */
	onFixFaultyMessage: function(button)
	{
		if (button == 'yes') {
			// if the record is loaded through show mail tab/dialog then
			// we will take a shortest route to fix message and close dialog
			// because its tricky to change record in the dialog/tab
			if(this.dialog) {
				this.dialog.closeOnSave = true;
			}

			this.record.fixFaultyMessage();
		}
	},

	/**
	 * Set task request information from {@link Zarafa.core.data.IPMRecord record} data.
	 *
	 * @param {Ext.Element/HTMLElement} el The target element which is being laid out
	 * @return {Boolean} true if function added any info to {@link Zarafa.common.ui.messagepanel.ExtraInfoLinks ExtraInfoLinks}
	 * and it should be made visible else false.
	 * @private
	 */
	setTaskRequestInfo: function (el)
	{
		var isVisible;
		var taskHistory = this.record.get('taskhistory');
		var infoMsg = '';
		var taskLastUser = this.record.get('tasklastuser');
		var delegateResponse = false;
		// check if delegate has responded
		var delegateEntryid = this.record.get('sender_entryid');
		var delegatorEntryid = this.record.get('sent_representing_entryid');
		var assigneeName = '';

		// check if sender and sent representing entryids are different
		if(delegatorEntryid && !Zarafa.core.EntryId.compareABEntryIds(delegateEntryid, delegatorEntryid)) {
			delegateResponse = true;
			// get assignee information
			assigneeName = this.record.get('sent_representing_name') || this.record.get('sent_representing_email_address');
		}
		switch (taskHistory) {
			case Zarafa.core.mapi.TaskHistory.ASSIGNED:
				if (this.record.isTaskReceived()) {
					infoMsg = String.format(this.receivedTaskRequest, this.record.get('tasklastuser'), this.record.get('task_assigned_time').formatDefaultTime(_('d-m-Y {0}')));
				} else {
					infoMsg = this.sentTaskRequest;
				}
				isVisible = true;
				break;
			case Zarafa.core.mapi.TaskHistory.ACCEPTED:
				if (delegateResponse ) {
					infoMsg = String.format(this.acceptDelegateTaskRequestInfoString, taskLastUser, assigneeName, this.record.get('task_assigned_time').formatDefaultTime(_('d-m-Y {0}')));
				} else {
					infoMsg = String.format(this.acceptedTaskRequest, taskLastUser, this.record.get('task_assigned_time').formatDefaultTime(_('d-m-Y {0}')));
				}
				isVisible = true;
				break;
			case Zarafa.core.mapi.TaskHistory.DECLINED:
				if (delegateResponse ) {
					infoMsg = String.format(this.declineDelegateTaskRequestInfoString, taskLastUser, assigneeName, this.record.get('task_assigned_time').formatDefaultTime('d-m-Y {0}'));
				} else {
					infoMsg = String.format(this.declineAssignerTaskRequest, taskLastUser, this.record.get('task_assigned_time').formatDefaultTime(_('d-m-Y {0}')));
				}
				isVisible = true;
				break;
			case Zarafa.core.mapi.TaskHistory.UPDATED:
				if (delegateResponse ) {
					infoMsg = String.format(this.updateDelegateTaskRequestInfoString, taskLastUser, assigneeName, this.record.get('task_assigned_time').formatDefaultTime(_('d-m-Y {0}')));
				} else {
					infoMsg = String.format(this.updateTaskRequest, this.record.get('task_assigned_time').formatDefaultTime(_('d-m-Y {0}')));
				}
				isVisible = true;
				break;
		}

		if (isVisible) {
			if (this.record.get('task_not_found')) {
				el.createChild({tag: 'div', html: _('The matching task was not found in your task list.'), cls: this.itemCls});
			}
			el.createChild({tag: 'div', html: infoMsg, cls: this.itemCls});
		}
		return isVisible;
	},

	/**
	 * Set meeting information from {@link Zarafa.core.data.IPMRecord record} data
	 * and if found will update the panel by loading data from the record data into the template
	 *
	 * @param {Ext.Element/HTMLElement} el The target element which is being laid out
	 * @return {Boolean} true if {@link Zarafa.core.data.IPMRecord IPMRecord} contains any meeting info
	 * @private
	 */
	setMeetingInfo: function(el)
	{
		var isVisible = this.setMeetingNotFoundInfo(el);

		isVisible = (this.setDelegatorInfo(el) === true ? true : isVisible);

		// display meeting request specific info
		if(this.record.isMeetingRequest()) {
			isVisible = (this.setMeetingRequestInfo(el) === true ? true : isVisible);

			// display recurring meeting request related info
			if(this.record.isRecurringException()) {
				isVisible = (this.setRecurringMeetingRequestInfo(el) === true ? true : isVisible);
			}
		}

		// display meeting response related info
		if(this.record.isMeetingRequestResponse()) {
			isVisible = (this.setMeetingResponseInfo(el) === true ? true : isVisible);
		}

		return isVisible;
	},

	/**
	 * Set delegator info if the meeting request is forwarded by delegate meeting rule,
	 * get info from {@link Zarafa.core.data.IPMRecord record} data.
	 * @param {Ext.Element/HTMLElement} el The target element which is being laid out
	 * @return {Boolean} true if function added any info to {@link Zarafa.common.ui.messagepanel.ExtraInfoLinks ExtraInfoLinks}
	 * and it should be made visible else false.
	 * @private
	 */
	setDelegatorInfo: function(el)
	{
		var delegatedByRule = this.record.get('delegated_by_rule');
		var isVisible = false;

		if(delegatedByRule) {
			var delegatorName = this.record.get('received_representing_name') || this.record.get('received_representing_email_address');

			el.createChild({tag: 'div', html: String.format(this.delegatorInfoString, delegatorName), cls: this.itemCls});
			isVisible = true;
		}

		return isVisible;
	},

	/**
	 * Set meeting request information from {@link Zarafa.core.data.IPMRecord record} data.
	 * @param {Ext.Element/HTMLElement} el The target element which is being laid out
	 * @return {Boolean} true if function added any info to {@link Zarafa.common.ui.messagepanel.ExtraInfoLinks ExtraInfoLinks}
	 * and it should be made visible else false.
	 * @private
	 */
	setMeetingRequestInfo: function(el)
	{
		var meetingConflict = this.record.get('conflictinfo');
		var outDated = this.record.isMeetingOutOfDate();
		var senderIsReceiver = this.record.senderIsReceiver();
		var appNotFound = this.record.get('appointment_not_found');
		var isVisible = false;

		// Do not show ExtraInfo when in sentItems folder
		if(this.record.senderIsUser()) {
			return isVisible;
		}

		if(outDated) {
			// check for out dated/expired meeting status
			el.createChild({tag: 'div', html: this.meetingOutDatedInfoString, cls: this.itemCls});
			isVisible = true;
		} else {
			if (senderIsReceiver) {
				el.createChild({tag: 'div', html: this.organizerAsAttendeeInfoString, cls: this.itemCls});
				isVisible = true;
			} else {
				if(this.record.isAppointmentInPast()) {
					el.createChild({tag: 'div', html: this.elapsedTimeInfoString, cls: this.itemCls});
					isVisible = true;
				} else if(appNotFound !== true) {
					// Meeting is in future, but not replied yet
					el.createChild({tag: 'div', html: this.responseRequiredString, cls: this.itemCls});
					isVisible = true;
				}

				if(!Ext.isEmpty(meetingConflict)) {
					// display meeting conflicting with any other appointment
					el.createChild({tag: 'div', html: meetingConflict, cls: this.itemCls});
					isVisible = true;
				}
			}

			if(this.record.get('updatecounter') > 0){
				el.createChild({tag: 'div', html: this.meetingUpdatedString, cls: this.itemCls});
				isVisible = true;
			}
		}

		return isVisible;
	},

	/**
	 * Set meeting response information from {@link Zarafa.core.data.IPMRecord record} data
	 * and if found will update the panel by loading data from the record data into the template
	 * @param {Ext.Element/HTMLElement} el The target element which is being laid out
	 * @return {Boolean} true if function added any info to {@link Zarafa.common.ui.messagepanel.ExtraInfoLinks ExtraInfoLinks}
	 * and it should be made visible else false.
	 * @private
	 */
	setMeetingResponseInfo: function(el)
	{
		var isVisible = false;

		var counterProposal = this.record.get('counter_proposal');

		// check if delegate has responded
		var delegateEntryid = this.record.get('sender_entryid');
		var delegatorEntryid = this.record.get('sent_representing_entryid');

		// get attendee information
		var attendeeName = this.record.get('sent_representing_name') || this.record.get('sent_representing_email_address');
		var delegateName = '';

		// check if sender and sent representing entryids are different
		if(delegatorEntryid && !Zarafa.core.EntryId.compareABEntryIds(delegateEntryid, delegatorEntryid)) {
			delegateName = this.record.get('sender_name') || this.record.get('sender_email_address');
		}

		if(!Ext.isEmpty(attendeeName)) {
			if(this.record.get('meeting_updated')) {
				el.createChild({tag: 'div', html: this.meetingUpdatedInfoString, cls: this.itemCls});
				isVisible = true;
			}

			if(counterProposal) {
				var htmlString = '';

				if(!Ext.isEmpty(delegateName)) {
					// delegate has responded
					if (this.record.isMessageClass('IPM.Schedule.Meeting.Resp.Tent')) {
						htmlString = String.format(this.counterProposalTentativeAcceptedDelegateString, delegateName, attendeeName);
					} else if (this.record.isMessageClass('IPM.Schedule.Meeting.Resp.Neg')) {
						htmlString = String.format(this.counterProposalDeclinedDelegateString, delegateName, attendeeName);
					}
				} else {
					// attendee has responded
					if (this.record.isMessageClass('IPM.Schedule.Meeting.Resp.Tent')) {
						htmlString = String.format(this.counterProposalTentativeAcceptedString, attendeeName);
					} else if (this.record.isMessageClass('IPM.Schedule.Meeting.Resp.Neg')) {
						htmlString = String.format(this.counterProposalDeclinedString, attendeeName);
					}
				}

				if(!Ext.isEmpty(htmlString)) {
					el.createChild({tag: 'div', html: htmlString, cls: this.itemCls});
					isVisible = true;
				}
			} else {
				var htmlString = '';

				if(!Ext.isEmpty(delegateName)) {
					// delegate has responded
					if(this.record.isMessageClass('IPM.Schedule.Meeting.Resp.Pos')) {
						htmlString = String.format(this.acceptDelegateInfoString, delegateName, attendeeName);
					} else if (this.record.isMessageClass('IPM.Schedule.Meeting.Resp.Tent')) {
						htmlString = String.format(this.tentativeDelegateInfoString, delegateName, attendeeName);
					} else if (this.record.isMessageClass('IPM.Schedule.Meeting.Resp.Neg')) {
						htmlString = String.format(this.declineDelegateInfoString, delegateName, attendeeName);
					}
				} else {
					// attendee has responded
					if(this.record.isMessageClass('IPM.Schedule.Meeting.Resp.Pos')) {
						htmlString = String.format(this.acceptInfoString, attendeeName);
					} else if (this.record.isMessageClass('IPM.Schedule.Meeting.Resp.Tent')) {
						htmlString = String.format(this.tentativeInfoString, attendeeName);
					} else if (this.record.isMessageClass('IPM.Schedule.Meeting.Resp.Neg')) {
						htmlString = String.format(this.declineInfoString, attendeeName);
					}
				}

				if(!Ext.isEmpty(htmlString)) {
					el.createChild({tag: 'div', html: htmlString, cls: this.itemCls});
					isVisible = true;
				}
			}
		}

		return isVisible;
	},

	/**
	 * Set meeting not found information if meeting is not found in the calendar,
	 * get info from {@link Zarafa.core.data.IPMRecord record} data
	 * @param {Ext.Element/HTMLElement} el The target element which is being laid out
	 * @return {Boolean} true if function added any info to {@link Zarafa.common.ui.messagepanel.ExtraInfoLinks ExtraInfoLinks}
	 * and it should be made visible else false.
	 * @private
	 */
	setMeetingNotFoundInfo: function(el)
	{
		var apptNotFound = this.record.get('appointment_not_found');
		var isVisible = false;

		if(apptNotFound) {
			el.createChild({tag: 'div', html: this.meetingNotFoundInfoString, cls: this.itemCls});
			isVisible = true;
		}

		return isVisible;
	},

	/**
	 * Set recurring meeting request information from {@link Zarafa.core.data.IPMRecord record} data.
	 * @param {Ext.Element/HTMLElement} el The target element which is being laid out
	 * @return {Boolean} true if function added any info to {@link Zarafa.common.ui.messagepanel.ExtraInfoLinks ExtraInfoLinks}
	 * and it should be made visible else false.
	 * @private
	 */
	setRecurringMeetingRequestInfo: function(el)
	{
		var recurringPattern = this.record.get('recurring_pattern');
		var isVisible = false;

		if(recurringPattern) {
			el.createChild({tag: 'div', html: Ext.util.Format.htmlEncode(recurringPattern), cls: this.itemCls});
			isVisible = true;
		}

		return isVisible;
	}
});

Ext.reg('zarafa.extrainfolinks', Zarafa.common.ui.messagepanel.ExtraInfoLinks);
Ext.namespace('Zarafa.common.ui.messagepanel');

/**
 * @class Zarafa.common.ui.messagepanel.MessageBody
 * @extends Ext.Container
 * @xtype zarafa.messagebody
 */
Zarafa.common.ui.messagepanel.MessageBody = Ext.extend(Ext.Container, {
	/**
	 * @cfg {Ext.Template/String} plaintextTemplate The {@link Ext.Template} or String which must be used
	 * for the contents of the {@link #iframe} when the record has been opened, and it contains a plain-text
	 * body. The data passed to this template will be the 'body' field which must be loaded as body.
	 */
	plaintextTemplate: '<!DOCTYPE html><html><body><pre>{body}</pre></body></html>',

	/**
	 * The {RegExp} of emailPattern, this regular expression finds mailto links or email address
	 * inside string.
	 */
	emailPattern: /((mailto:)[\w-@,;.?=&%:///+ ]+)|([\w-\._\+%]+@(?:[\w-]+\.)+[\w]*)/gi,

	/**
	 * The {RegExp} of linkPattern, this regular expression finds urls inside string.
	 * Urls like http, https, ftp or www.
	 */
	linkPattern: /((?:http|ftp)s?:\/\/|www.)([\w\.\-]+)\.(\w{2,6})([\w\/\-\_\+\.\,\?\=\&\!\:\;\%\#\|]+)*/gi,

	/**
	 * The scroll position of the document in the iframe that holds the message body
	 * when the message body is inside a tab in the {@link Zarafa.core.ui.MainContentTabPanel}
	 * and the tab is deactivated.
	 * @property
	 * @type {Object}
	 */
	scrollPos: null,

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

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

		config = Ext.applyIf(config, {
			xtype: 'zarafa.messagebody',
			autoScroll:true,
			anchor: '100%',
			layout: 'fit',
			autoEl: {
				tag: 'iframe',
				cls: 'preview-iframe',
				frameborder: 0,
				src: Ext.SSL_SECURE_URL
			},
			border: false,
			listeners: {
				scope: this,
				render: this.onRenderMessageBody,
				afterrender: this.onAfterRender
			}
		});

		Zarafa.common.ui.messagepanel.MessageBody.superclass.constructor.call(this, config);

		if (Ext.isString(this.plaintextTemplate)) {
			this.plaintextTemplate = new Ext.Template(this.plaintextTemplate, {
				compiled: true
			});
		}

		/**
		 * Remember the last rendered record and format so we can avoid replacing HTML
		 * bodies with plain text while asynchronous loading is still in progress.
		 * @type {Object}
		 * @private
		 */
		this.currentRenderInfo = {
			entryId: null,
			renderedHtml: false
		};
	},

	/**
	 * Event handler for the {@link Ext.Component.render} event. Will add listener to the
	 * {@link Zarafa.core.ui.MainContentTabPanel.beforetabchange} event if the MessageBody
	 * is rendered inside a tab panel of the {@link Zarafa.core.ui.MainContentTabPanel}.
	 */
	onRenderMessageBody: function()
	{
		// Make sure we are inside the Zarafa.core.ui.MainContentTabPanel before
		// we set the listener.
		var tabPanel = this.findParentByType('tabpanel');
		if ( tabPanel ){
			this.mon(tabPanel, 'beforetabchange', this.onBeforeTabChange, this);
		}
	},

	/**
	 * Event handler for the {@link Ext.Component.afterrender} event. Will add listener to the
	 * {@link Zarafa.core.ui.MainContentTabPanel.beforetabchange} event if the MessageBody
	 * is rendered inside a tab panel of the {@link Zarafa.core.ui.MainContentTabPanel}.
	 */
	onAfterRender: function()
	{
		// In Firefox, the frame's content seems to not be recognized when no
		// initial content has been set.
		if (Ext.isGecko) {
			var iframeWindow = this.getEl().dom.contentWindow;
			var iframeDocument = iframeWindow.document;
			iframeDocument.open();
			iframeDocument.write('<!DOCTYPE html><html><body><p>&nbsp;</p></body></html>');
			iframeDocument.close();
		}
	},

	/**
	 * Event handler for the {@link Ext.TabPanel.beforetabchange} of the
	 * {@link Zarafa.core.ui.MainContentTabPanel}
	 *
	 * @param {Zarafa.core.ui.MainContentTabPanel} mainContentTabPanel The main
	 * tab panel of grommunio Web
	 * @param {Ext.Panel} newTab The tab (panel) that will be activated
	 * @param {Ext.Panel} currentTab The tab (panel) that is currently active
	 */
	onBeforeTabChange: function(mainContentTabPanel, newTab, currentTab)
	{
		// Store the scroll position of the iframe that holds the message body
		if ( currentTab === this.ownerCt.ownerCt ){
			var iframeWindow = this.getEl().dom.contentWindow;
			this.scrollPos = {
				x: iframeWindow.pageXOffset,
				y: iframeWindow.pageYOffset
			};
		}
	},

	/**
	 * Set event listeners on the iframe that will relay the
	 * event when the user performs click within iframe.
	 * @private
	 */
	setRelayEventListeners: function()
	{
		var iframeWindow = this.getEl().dom.contentWindow;
		var iframeDocument = iframeWindow.document;
		var eventsToRelay = ['mousedown'];
		if (Zarafa.isDeskApp) {
			eventsToRelay.push('wheel', 'keydown');
		}

		// mousedown needs to be relayed to hide contextmenu.
		// keydown and wheel needs to be relayed to perform zoom functionality in DeskApp.
		this.relayIframeEvent(iframeDocument, eventsToRelay);
	},

	/**
	 * Helper function to add event listeners to the given iframe element for given
	 * events with common handler.
	 * @param {HTMLElement} iframeElement The iframe node to which given events needs
	 * to be listened.
	 * @param {Array} events The set of events for which listeners should be attached
	 * to given iframe.
	 */
	relayIframeEvent: function(iframeElement, events)
	{
		events.forEach(function(event){
			var options = true;

			// Let wheel listeners be passive to avoid scroll-blocking warnings.
			if (event === 'wheel') {
				options = {
					capture: true,
					passive: true
				};
			}

			try {
				iframeElement.addEventListener(event, this.relayEventHandlers.createDelegate(this), options);
			} catch (ex) {
				iframeElement.addEventListener(event, this.relayEventHandlers.createDelegate(this), true);
			}
		}, this);
	},

	/**
	 * Function is called when specified events performed in the iframe.
	 * Basically this function relay those events to document element
	 * belongs to grommunio Web window.
	 * @param {Object} event The event object
	 */
	relayEventHandlers: function(event)
	{
		Ext.getDoc().fireEvent(event.type, event);
	},

	/**
	 * Set event listeners on the iframe that will reset the
	 * {@link Zarafa#idleTime idle time} when the user performs
	 * an action in the iframe (i.e. click, mousemove, keydown)
	 * @private
	 */
	setIdleTimeEventListeners: function()
	{
		var iframeWindow = this.getEl().dom.contentWindow;
		var iframeDocument = iframeWindow.document;

		if ( !iframeDocument.addEventListener ) {
			// User is using a browser that does not support addEventListener.
			// Probably IE<9 which we don't support.
			// However there is no reason to create errors for IE<9
			// Client timeout will still be handled by the backend though,
			// but the message will only be shown to the user when he tries to
			// connect to the backend after the session has timed out.
			return;
		}

		var clickHandler = function(){
			Zarafa.idleTime = 0;
		};
		var moveHandler = function(){
			Zarafa.idleTime = 0;
		};

		try {
			iframeDocument.addEventListener('click', clickHandler, { capture: true, passive: true });
			iframeDocument.addEventListener('mousemove', moveHandler, { capture: true, passive: true });
		} catch (ex) {
			iframeDocument.addEventListener('click', clickHandler, true);
			iframeDocument.addEventListener('mousemove', moveHandler, true);
		}

		iframeDocument.addEventListener('keydown', function(){
			Zarafa.idleTime = 0;
		}, true);
	},

	/**
	 * Remove document-level tags which are irrelevant in preview and can trigger
	 * browser parser warnings (e.g. malformed/obsolete viewport directives).
	 *
	 * @param {String} html The HTML markup to clean.
	 * @return {String} HTML safe for iframe body injection.
	 */
	sanitizePreviewMarkup: function(html)
	{
		if (Ext.isEmpty(html)) {
			return html;
		}

		var markup = String(html);

		try {
			var doc;
			if (window.DOMParser) {
				doc = new DOMParser().parseFromString('<!doctype html><html><body>' + markup + '</body></html>', 'text/html');
			} else {
				doc = document.implementation.createHTMLDocument('');
				doc.body.innerHTML = markup;
			}

			Ext.each(doc.body.querySelectorAll('meta, base'), function(node) {
				if (node && node.parentNode) {
					node.parentNode.removeChild(node);
				}
			});

			// The HTML parser hoists <style> elements from an email's <head>
			// into the document head. Preserve them by moving them into the
			// body so they still apply when we later write body.innerHTML
			// into the preview iframe (which drops head content).
			var headStyles = doc.head ? doc.head.querySelectorAll('style') : [];
			Ext.each(headStyles, function(node) {
				doc.body.insertBefore(node, doc.body.firstChild);
			});

			return doc.body.innerHTML;
		} catch (ex) {
			// Fallback for malformed markup/parser failures.
			return markup
				.replace(/<meta\b[^>]*>(?:\s*<\/meta>)?/gi, '')
				.replace(/<base\b[^>]*>(?:\s*<\/base>)?/gi, '');
		}
	},

	/**
	 * Updates the container by loading data from the record data into the {@link #template}
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record to update the header panel with
	 */
	update: function(record)
	{
		if ( !this.getEl().dom ) {
			return;
		}

		var iframeWindow = this.getEl().dom.contentWindow;
		var iframeDocument = iframeWindow.document;
		var iframeDocumentElement = new Ext.Element(iframeDocument);
		var body = '';
		var html;
		var entryId = Ext.isDefined(record) ? record.get('entryid') : null;
		var sameRecordAsBefore = this.currentRenderInfo && this.currentRenderInfo.entryId === entryId;
		var recordIsOpened = Ext.isDefined(record) && Ext.isFunction(record.isOpened) && record.isOpened();

		if (!Ext.isEmpty(iframeDocument.body)) {
			// Remove and disable old keymaps that are registered on the document element.
			Zarafa.core.KeyMapMgr.deactivate(iframeDocumentElement);
		}

		if (Ext.isDefined(record) && !recordIsOpened) {
			// Avoid rendering speculative plain-text body before the full message body is loaded.
			// This prevents text-to-HTML flicker when the opened record later provides HTML content.
			if (!sameRecordAsBefore) {
				var pendingHtmlBody = iframeDocument.getElementsByTagName('body')[0];
				if (pendingHtmlBody) {
					pendingHtmlBody.innerHTML = '';
				}

				this.currentRenderInfo = {
					entryId: entryId,
					renderedHtml: false
				};
			}

			return;
		}


		if (Ext.isDefined(record)) {
			// Display a 'loading' message. Prefer cached sanitized HTML when available so we can render
			// immediately, otherwise fall back to sanitizing or the plain-text representation.
			// During fast record switches, 'isHTML' can temporarily lag behind while
			// the opened payload is being applied to the shadow record.
			// If html_body is present, prefer rendering HTML to avoid incorrect
			// fallback to plain text for HTML messages.
			var hasHtmlBody = !Ext.isEmpty(record.get('html_body'));
			var preferHtml = (record.get('isHTML') === true) || hasHtmlBody;
			var sanitizedBody = null;

			// getSanitizedHtmlBody() internally relies on getBody(true), which in turn
			// depends on isHTML. If isHTML is stale/false while html_body is already
			// available, that path can incorrectly sanitize/render plain text.
			// Therefore only use cached/sanitized helper when record explicitly marks
			// itself as HTML.
			if (record.get('isHTML') === true && Ext.isFunction(record.getSanitizedHtmlBody) && !record.checkBlockStatus()) {
				// Use a cached sanitized body when possible to avoid redundant DOMPurify work.
				if (!Ext.isEmpty(record.sanitizedHTMLBody)) {
					sanitizedBody = record.sanitizedHTMLBody;
				} else if (preferHtml === true) {
					sanitizedBody = record.getSanitizedHtmlBody();
				}
			}

			if (sanitizedBody) {
				sanitizedBody = this.sanitizePreviewMarkup(sanitizedBody);
				html = true;
				body = "<!DOCTYPE html>" + sanitizedBody;
			} else if (preferHtml === true) {
				html = true;

				// Prefer html_body directly to avoid races on isHTML while switching mails.
				var rawHtmlBody = hasHtmlBody ? (record.get('html_body') || '') : (record.getBody(true) || '');
				if (hasHtmlBody && Ext.isFunction(record.inlineImgOutlookToZarafa)) {
					rawHtmlBody = record.inlineImgOutlookToZarafa(rawHtmlBody);
				}
				rawHtmlBody = this.sanitizePreviewMarkup(rawHtmlBody);
				if (hasHtmlBody &&
					Ext.isFunction(record.shouldBlockExternalContent) &&
					record.shouldBlockExternalContent() &&
					Ext.isDefined(Zarafa.core) &&
					Ext.isDefined(Zarafa.core.HTMLParser) &&
					Ext.isFunction(Zarafa.core.HTMLParser.blockExternalContent)) {
					rawHtmlBody = Zarafa.core.HTMLParser.blockExternalContent(rawHtmlBody);
				}

				if (container.getServerConfig().getDOMPurifyEnabled()) {
					rawHtmlBody = DOMPurify.sanitize(rawHtmlBody);
				}

				body = "<!DOCTYPE html>" + record.cleanupOutlookStyles(rawHtmlBody);
			} else {
				var plainBody = record.getBody(false) || '';
				if (Ext.isEmpty(plainBody)) {
					body = '';
				} else {
					body = this.plaintextTemplate.applyTemplate({ body: Ext.util.Format.htmlEncode(plainBody) });
				}
			}

			if (html && body) {
				// Defer loading of inline images so the text can be shown immediately.
				body = body.replace(/<img([^>]+?)src="([^"]*download_attachment.php[^"]*)"([^>]*?)>/gi,
					function(match, pre, src, post){
						return '<img' + pre + 'src="' + Ext.BLANK_IMAGE_URL + '" data-src="' + src + '"' + post + '>';
					});
			}

			if (html !== true && sameRecordAsBefore && this.currentRenderInfo.renderedHtml === true) {
				// Keep the previously rendered HTML instead of flashing the plain-text fallback
				// while the HTML body is still loading.
				return;
			}

			this.currentRenderInfo = {
				entryId: entryId,
				renderedHtml: html === true
			};
		} else {
			this.currentRenderInfo = {
				entryId: null,
				renderedHtml: false
			};
		}

		if (!body) {
			body = '';
		}

		var htmlBody = iframeDocument.getElementsByTagName('body')[0];
		htmlBody.innerHTML = body;

		// Lazy load inline images after the text has been rendered
		var inlineImgs = htmlBody.querySelectorAll('img[data-src]');
		Ext.defer(function(){
			Ext.each(inlineImgs, function(img){
				img.setAttribute('src', img.getAttribute('data-src'));
				img.removeAttribute('data-src');
			});
		}, 100);

		// Restore the scroll position if the tab panel was deactivated
		if ( this.scrollPos ){
			// Chrome needs a reset to work properly
			iframeWindow.scrollTo(0, 0);

			iframeWindow.scrollTo(this.scrollPos.x, this.scrollPos.y);
			this.scrollPos = null;
		}

		// Disable drag and drop
		Ext.EventManager.on(iframeWindow, 'dragover', Zarafa.onWindowDragDrop);
		Ext.EventManager.on(iframeWindow, 'drop', Zarafa.onWindowDragDrop);

		// Add CSS document to the previewbody
		// so the text can be styled.
		this.addCSSText(iframeDocument);

		// Add listener to enlarge image
		this.setImageClickHandler(iframeDocument);

		// Defer heavy DOM scanning to keep initial render responsive.
		this.deferLinkification(iframeDocument, iframeDocumentElement);

		var rootContainer = this.recordComponentUpdaterPlugin.rootContainer;
		if(rootContainer) {
			// Here we are passing rootContainer i.e. previewpanel as a component and editor's document
			// as element, so that when key events are fired on the element it will pass dialog
			// rootContainer as an argument in callback function.
			Zarafa.core.KeyMapMgr.activate(rootContainer, 'global', iframeDocumentElement);
			Zarafa.core.KeyMapMgr.activate(rootContainer, 'contentpanel.record.message.showmail', iframeDocumentElement);
		}
	},


	/**
	 * Schedule linkification of the message body when the browser is idle.
	 * This avoids blocking the UI with expensive DOM operations during the
	 * initial rendering of large messages.
	 * @param {Document} doc The iframe document to scan
	 * @param {Ext.Element} docEl The Ext.Element wrapper for the document
	 * @private
	 */
	deferLinkification: function(doc, docEl)
	{
		var fn = function(){
			this.scanDOMForLinks(doc);
			this.handleMailToLinks(docEl);
		}.createDelegate(this);

		if (window.requestIdleCallback) {
			window.requestIdleCallback(fn);
		} else {
			Ext.defer(fn, 0);
		}
	},

	/**
	 * Function recursively scans dom to get text nodes which contain email addresses or URLs so we can
	 * replace them with an anchor tag.
	 * @param {HTMLElement} node The parent node that will be examined to find the child text nodes
	 * @private
	 */
	scanDOMForLinks: function(node)
	{
		for(var i = 0; i < node.childNodes.length; i++) {
			var cnode = node.childNodes[i];
			if(cnode.nodeType == 1) { // Tag-node
				if(cnode.nodeName != 'A') { // Igonre Anchor-node as they are already linified
					this.scanDOMForLinks(cnode);
				}
			} else if(cnode.nodeType == 3) { // Text-node
				if(cnode.nodeValue.trim().length > 0) {
					// check if this text node is HTML link or email address
					if(cnode.nodeValue.search(this.emailPattern) != -1 || cnode.nodeValue.search(this.linkPattern) != -1) {
						this.linkifyDOMNode(cnode, node);
					}
				}
			}
		}
	},

	/**
	 * Function will replace text nodes with element nodes which contains anchor tag.
	 * @param {HTMLElement} node The node that has to be examined for links or emails
	 * @param {HTMLElement} parentNode The parent of the passed node
	 * @private
	 */
	linkifyDOMNode: function(node, parentNode)
	{
		var str = node.nodeValue;

		// Split the strings up in pieces that are normal text and pieces that contain an URL
		// We do this before checking for email addresses as an ftp-URL with username/password within will otherwise be seen as an email address
		var lookupParts = Zarafa.core.Util.splitStringByPattern(str, this.linkPattern);
		var parts = [];
		// Now loop through all the pieces split them up based on whether they contain an email address
		for(var i=0;i<lookupParts.length;i++){
			// Do not examine the piece that already contains a link
			if(lookupParts[i].search(this.linkPattern) == -1){
				// Split the pieces up based on whether they contain a link
				var tmpParts = Zarafa.core.Util.splitStringByPattern(lookupParts[i], this.emailPattern);
				parts.push.apply(parts, tmpParts);
			} else {
				parts.push(lookupParts[i]);
			}
		}

		// Create a container node to append all the textnodes and anchor nodes to
		var containerNode = Ext.DomHelper.createDom({
			tag: 'span'
		});
		for(var i=0;i<parts.length;i++){
			// Create the node for a normal link
			if(parts[i].search(this.linkPattern) != -1){
				// Create a new anchor-node for making url clickable.
				var anchorNode = Ext.DomHelper.append(containerNode, {tag: 'a', html: parts[i]});
				var link = parts[i];
				if(link.search(/(http|ftp)(s)?:\/\//gi) !== 0) {
					// Link has url in the pattern of www.something.com
					link = 'http://' + link;
				}
				anchorNode.setAttribute('href', link);
				anchorNode.setAttribute('target', '_blank');
			} else if(parts[i].search(this.emailPattern) != -1){
				// Create a new anchor-node for making an email address clickable.
				var anchorNode = Ext.DomHelper.append(containerNode, {tag: 'a', html: parts[i]});
				var link = parts[i];
				if(link.indexOf('mailto:') !== 0){
					link = 'mailto:' + link;
				}
				anchorNode.setAttribute('href', link);
			} else {
				Ext.DomHelper.append(containerNode, Ext.util.Format.htmlEncode(parts[i]));
			}
		}

		// Replace the original text node under the parent with the new anchor nodes and split up text nodes.
		for(var i=0, count=containerNode.childNodes.length;i<count;i++){
			// We remove the childNode from the parent by using this line so every loop we can add the first as the list shrinks
			parentNode.insertBefore(containerNode.childNodes.item(0), node);
		}

		// Remove the original node
		parentNode.removeChild(node);
	},

	/**
	 * Function registers handler on mailto links in {@link Zarafa.common.ui.messagepanel.MessageBody}
	 * @param {Ext.Element} iframeDocumentElement The document element of iframe
	 * @private
	 */
	handleMailToLinks: function(iframeDocumentElement)
	{
		var mailtoElements = iframeDocumentElement.query('a[href^="mailto:"]');

		if(!Ext.isEmpty(mailtoElements)){
			for (var i=0; i<mailtoElements.length ; i++)
			{
				Ext.EventManager.on(mailtoElements[i], 'click', this.onMailtoClick);
			}
		}
	},

	/**
	 * Called when any mailto links in {@link Zarafa.common.ui.messagepanel.MessageBody} is clocked
	 * This will pass url of the mailto link to URLActionMgr and it will handle the URL.
	 * @param {Ext.EventObject} event The event object
	 * @param {HTMLElement} element The element which was focussed
	 * @private
	 */
	onMailtoClick: function(event, element)
	{
		// Prevent the browsers default handling of the event.
		// i.e. opening mailto handler for the link
		event.preventDefault();

		var href = this.href || element.href;
		Zarafa.core.URLActionMgr.execute({mailto: href});
	},

	/**
	 * Adds a <style> element into the <head> tag of the given document,
	 * this will contain the special stylesheet which can be used to apply
	 * to styling to the previewpanel.
	 * @param {Document} doc The document to which the link should be added
	 * @private
	 */
	addCSSText: function(doc)
	{
		var head = doc.getElementsByTagName('head')[0];
		if (doc.getElementById('grommunio-preview-body-css')) {
			return;
		}

		var css = doc.createElement('style');
		css.setAttribute('id', 'grommunio-preview-body-css');
		css.setAttribute('type', 'text/css');
		css.appendChild(document.createTextNode('body { margin: 0; padding: 9px; } ' +
			// Make the blockquote element not use the default right margin of 40px
			'blockquote { margin-right: 0px; }' +
			// Make text in pre tags wrapped if too long for a line
			"@font-face { font-family: firamono; font-style: normal; font-weight: 400; src: url(" + window.location.pathname + "/client/resources/fonts/FiraMono-Regular.woff2) format('woff2'); }" +
			"pre { white-space: pre-wrap; overflow-wrap: anywhere; word-wrap: break-word; margin: 0; font-family: firamono, monospace; }" +

			// Scale images
			'p > span > img, div > img, p > img { max-width: 100%; height: auto !important; }' +
			'@media screen and (max-width: 250px) { p > span > img, div > img, p > img { max-width: 250px; } }' +
			// Modern scrollbar
			'* { scrollbar-color: #1f1f1f transparent; } *::-webkit-scrollbar { width: 6px; height: 6px; background: transparent; } *::-webkit-scrollbar-track { background: transparent; } ::-webkit-scrollbar-thumb { background: #c0c0c0; border-radius: 20px; min-height: 100px; }' +

			// Elements that should not have the max-width style
			// k-original: class that we set on img click
			// table img: all images in tables. Often used in newsletters
			'.k-original, table img { max-width: none !important; }'
		));

		head.appendChild(css);
	},

	/**
	 * Sets an event listener for img clicks inside an iframe
	 * @param {Document} iframeDocument The iframe document in which the event listeners are added
	 */
	setImageClickHandler: function(iframeDocument)
	{
		var images = iframeDocument.body.querySelectorAll('img');
		for (let i=0; i < images.length; i++) {
			images[i].removeEventListener('click', this.onImageClick);
			images[i].addEventListener('click', this.onImageClick);
		}
	},

	/**
	 * Toggle the k-original class when an image is clicked in an iframe
	 * @param {Event} event The click event
	 */
	onImageClick: function(event)
	{
		event.target.classList.toggle('k-original');
	},

	/**
	 * Called when this component is being rendered into a container.
	 * This will create a {@link #wrap} element around the iframe for
	 * better organize the scrolling.
	 * It will also check if a client timeout has been set, and if so call
	 * {@link #setIdleTimeEventListeners} once the iframe has been loaded.
	 *
	 * @param {Ext.Container} ct The container into which this component is being rendered
	 * @param {Number} position The position inside the container where this component is being rendered
	 * @private
	 */
	onRender: function(ct, position)
	{
		Zarafa.common.ui.messagepanel.MessageBody.superclass.onRender.call(this, ct, position);

		// Check if a client timeout has been defined
		var server = container.getServerConfig();
		var clientTimeout = server.getClientTimeout();
		if (clientTimeout){
			// Wait for the iframe to load before calling setIdleTimeEventListeners()
			this.getEl().on('load', this.setIdleTimeEventListeners, this);
		}

		// Wait for the iframe to load before calling setRelayEventListeners()
		this.getEl().on('load', this.setRelayEventListeners, this);

		this.wrap = this.el.wrap({cls: 'preview-body'});
		this.resizeEl = this.positionEl = this.wrap;
	}
});

Ext.reg('zarafa.messagebody', Zarafa.common.ui.messagepanel.MessageBody);
Ext.namespace('Zarafa.common.ui.messagepanel');

/**
 * @class Zarafa.common.ui.messagepanel.MessageHeader
 * @extends Ext.Panel
 * @xtype zarafa.messageheader
 */
Zarafa.common.ui.messagepanel.MessageHeader = Ext.extend(Ext.Panel, {
	// Insertion points for this class
	/**
	 * @insert previewpanel.toolbar.detaillinks The insertion point to add a special
	 * panel with extra details inside the header of the previewpanel.
	 * @param {Zarafa.common.ui.messagepanel.MessageHeader} panel This panel
	 */

	/**
	 * @cfg {Ext.Template/String} headerTemplate The template or template string which
	 * must be applied to the {@link #header} when the {@link Zarafa.core.data.IPMRecord record}
	 * has been {@link #update updated}. The arguments of this template will be the
	 * {@link Zarafa.core.data.IPMRecord#data record.data} field.
	 */
	headerTemplate:
		'<div class="preview-header-titlebox">' +
			'<img src="' + Ext.BLANK_IMAGE_URL + '" class="preview-header-collapse preview-header-collapse-minus" alt="" role="button" tabindex="0" aria-label="' + _('Toggle header') + '" aria-expanded="true"/>' +
			'<tpl if="!Ext.isEmpty(values.subject)">' +
				'<span class="preview-title zarafa-contextmenu-enabled">{subject:htmlEncode}</span>' +
			'</tpl>' +
		'</div>',

	/**
	 * The element which is used to {@link #collapse} or {@link #expand} the panel.
	 * This is initialized during {@link #update} as the element definition comes from {@link #headerTemplate}.
	 * @property
	 * @type Ext.Element
	 */
	collapseElement: undefined,

	/**
	 * Used by {@link #onCollapseClick} to determine if a {@link #collapse} or {@link #expand} event is
	 * already busy animating. During this animation, no concurrent {@link #collapse} or {@link #expand}
	 * events are allowed on the element.
	 * @property
	 * @type Boolean
	 */
	sliding: false,

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

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

		config = Ext.applyIf(config, {
			xtype: 'zarafa.messageheader',
			autoScroll:true,
			anchor: '100%',
			cls: 'preview-header',
			border: false,
			autoHeight: true,
			collapsible: true,
			hideCollapseTool: true,
			header: true,
			headerCfg: {
				cls: 'preview-header-title'
			},
			items: this.createHeaderInfo(config)
		});

		Zarafa.common.ui.messagepanel.MessageHeader.superclass.constructor.call(this, config);

		this.on('collapse', this.onAfterSlide, this);
		this.on('expand', this.onAfterSlide, this);

		if (Ext.isString(this.headerTemplate)) {
			this.headerTemplate = new Ext.XTemplate(this.headerTemplate, {
				compiled: true
			});
		}
	},

	createHeaderInfo: function (config)
	{
		var items = [{
			xtype: 'zarafa.extrainfolinks'
		}];

		// if owner component is "zarafa.taskpreviewpanel", which indicate that we have to
		// load the task request record in preview panel so prepare header accordingly.
		if (config.ownerCt.isXType('zarafa.taskpreviewpanel')) {
			items.push({
				xtype: 'zarafa.taskinfo'
			});
		} else {

			items.push({
					xtype: 'zarafa.sentinfolinks'
				},{
					xtype: 'zarafa.recipientlinks',
					plugins: [ 'zarafa.recordcomponentupdaterplugin' ],
					fieldLabel : pgettext('mail.previewpanel', 'To'),
					recipientType: Zarafa.core.mapi.RecipientType.MAPI_TO
				},{
					xtype: 'zarafa.recipientlinks',
					plugins: [ 'zarafa.recordcomponentupdaterplugin' ],
					fieldLabel : pgettext('mail.previewpanel', 'Cc'),
					recipientType: Zarafa.core.mapi.RecipientType.MAPI_CC
				},{
					xtype: 'zarafa.recipientlinks',
					plugins: [ 'zarafa.recordcomponentupdaterplugin' ],
					fieldLabel : pgettext('mail.previewpanel', 'Bcc'),
					recipientType: Zarafa.core.mapi.RecipientType.MAPI_BCC,
				});
		}
		items.push({
			xtype: 'zarafa.attachmentlinks',
			plugins: [ 'zarafa.recordcomponentupdaterplugin' ],
			fieldLabel : pgettext('mail.previewpanel', 'Attachments')
		},{
			xtype: 'zarafa.categorylinks',
			plugins: [ 'zarafa.recordcomponentupdaterplugin' ],
			fieldLabel : pgettext('mail.previewpanel', 'Categories')
		}, container.populateInsertionPoint('previewpanel.toolbar.detaillinks', this));
		return items;
	},

	/**
	 * Called when the container is being rendered.
	 * @private
	 */
	onRender: function()
	{
		Zarafa.common.ui.messagepanel.MessageHeader.superclass.onRender.apply(this, arguments);

		// The superclass unconditionally called this.header.unselectable(), as we want to
		// allow users to select the subject, we must make it selectable here again.
		if (this.header) {
			this.header.selectable();
		}
	},

	update: function(record, contentReset)
	{
		if (Ext.isDefined(this.header) && Ext.isDefined(this.header.dom)) {
			// If this is the initial load, then we must ensure that the panel is expanded.
			if (contentReset === true && this.collapseElement && (this.collapsed === true || this.sliding === true)) {
				// When we are currently sliding, we cannot call the expand function now,
				// so we defer the update function until a few milliseconds later.
				// Note that if the number of milliseconds is not enough, we will end
				// up here again and defer the action again.
				if (this.sliding === true) {
					this.update.defer(10, this, [record, contentReset]);
					return;
				}

				this.collapseElement.removeClass('preview-header-collapse-plus');
				this.expand(false);
				this.collapseElement.addClass('preview-header-collapse-minus');
			}

			// We are going to reset the innerHTML, so make sure to unregister any
			// events have have hooked to it.
			if (this.collapseElement) {
				this.mun(this.collapseElement, 'click', this.onCollapseClick, this);
			}

			if (Ext.isDefined(record)) {
				this.headerTemplate.overwrite(this.header.dom, record.data);

				this.collapseElement = this.header.child('.preview-header-collapse');
				if (this.collapseElement) {
					this.mon(this.collapseElement, 'click', this.onCollapseClick, this);
					this.mon(this.collapseElement, 'keydown', function(e) {
						if (e.getKey() === e.ENTER || e.getKey() === e.SPACE) {
							e.stopEvent();
							this.onCollapseClick(e);
						}
					}, this);
				}
			} else {
				this.header.dom.innerHTML = '';
			}
		}
	},
	
	/**
	 * Event handler which is fired when the {@link #collapseElement} has been clicked.
	 * This will toggle the CSS class for the event, and call {@link #toggleCollapse}
	 * to either {@link #collapse} or {@link #expand} the panel.
	 * @param {Ext.Event} event The event object
	 * @private
	 */
	onCollapseClick: function(event)
	{
		// Make sure the event was triggered for the collapseEvent which we want
		// to listen to. Should always be true, but better be safe...
		if (this.collapseElement && event.target.id == this.collapseElement.id && this.sliding === false) {
			// We're going to animate, block all concurrent animations.
			this.sliding = true;

			if (this.collapseElement.hasClass('preview-header-collapse-minus')) {
				this.collapseElement.removeClass('preview-header-collapse-minus');
				this.collapse();
				this.collapseElement.addClass('preview-header-collapse-plus');
				this.collapseElement.set({ 'aria-expanded': 'false' });
			} else {
				this.collapseElement.removeClass('preview-header-collapse-plus');
				this.expand();
				this.collapseElement.addClass('preview-header-collapse-minus');
				this.collapseElement.set({ 'aria-expanded': 'true' });
			}
		}
	},

	/**
	 * Event handler which is fired when the {@link #collapse} or {@link #expand} events have
	 * been fired which indicate that the sliding behavior has completed. This will reset the
	 * {@link #sliding} boolean to allow for new animations to be fired.
	 * @private
	 */
	onAfterSlide: function()
	{
		this.sliding = false;
	}
});

Ext.reg('zarafa.messageheader', Zarafa.common.ui.messagepanel.MessageHeader);
Ext.namespace('Zarafa.common.ui.messagepanel');

/**
 * @class Zarafa.common.ui.messagepanel.RecipientLinks
 * @extends Ext.DataView
 * @xtype zarafa.recipientlinks
 *
 * If the {@link Zarafa.core.plugins.RecordComponentUpdaterPlugin} is installed
 * in the {@link #plugins} array of this component, this component will automatically
 * load the {@link Zarafa.core.data.MAPIRecord record} into the component.
 * Otherwise the user of this component needs to call {@link #setRecord}.
 */
Zarafa.common.ui.messagepanel.RecipientLinks = Ext.extend(Ext.DataView, {
	/**
	 * @cfg {Number} ellipsisStringStartLength maximum length of text allowed before truncations,
	 * truncation will be replaced with ellipsis ('...').
	 */
	ellipsisStringStartLength: 30,

	/**
	 * @cfg {Number} ellipsisStringEndLength maximum length of text allowed after truncations,
	 * truncation will be replaced with ellipsis ('...').
	 */
	ellipsisStringEndLength: 30,

	/**
	 * @cfg {Number} maxHeight The maximum height the element which holds all
	 * recipient is allowed to take before a scrollbar will be shown.
	 */
	maxHeight: 30,

	/**
	 * @cfg {String} fieldLabel The label which must be applied to template
	 * as a prefix to the list of attachments.
	 */
	/* # TRANSLATORS: This message is used as label for the field which to indicates to whom the given mail was sent */
	fieldLabel: pgettext('mail.previewpanel', 'Recipients'),

	/**
	 * @cfg {Zarafa.core.mapi.RecipientType} recipientType The recipientType
	 * which should be displayed in the DataView. If not provided, no filtering
	 * will occur and all recipients will be displayed.
	 */
	recipientType: undefined,

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

		Ext.applyIf(config,{
			xtype: 'zarafa.recipientlinks',
			border: false,
			autoScroll:true,
			anchor: '100%',
			cls: 'preview-header-recipients',
			multiSelect: false,
			overClass: 'zarafa-recipient-link-over',
			itemSelector: 'span.zarafa-recipient-link',
			tpl: new Ext.XTemplate(
				'<tpl if="values.length &gt; 0">' +
					'<div class="preview-header-recipientbox">' +
						'<div class="preview-recipient-title" style="min-width: {[this.getMinWidth(values)]}px">{this.fieldLabel}</div>' +
						'<div class="preview-recipient-data" style="max-height: {this.maxHeight}px">' +
							'<tpl for=".">' +
								'<span viewIndex="{viewIndex}" class="zarafa-emailaddress-link zarafa-recipient-link">' +
									'<span class="zarafa-presence-status {[Zarafa.core.data.PresenceStatus.getCssClass(values.presence_status)]}">'+
										'<span class="zarafa-presence-status-icon" aria-hidden="true"></span>' +
										'<tpl if="!Ext.isEmpty(values.display_name)">' +
											'{display_name:htmlEncodeElide(this.ellipsisStringStartLength, this.ellipsisStringEndLength)}' +
										'</tpl>' +
									'</span>' +
								'</span>' +
								'<tpl if="xindex &gt; 0 && xindex != xcount">' +
									'<span>, </span>' +
								'</tpl>' +
							'</tpl>' +
						'</div>' +
					'</div>' +
				'</tpl>',
				{
					compiled: true,
					fieldLabel: config.fieldLabel || this.fieldLabel,
					maxHeight: config.maxHeight || this.maxHeight,
					ellipsisStringStartLength: config.ellipsisStringStartLength || this.ellipsisStringStartLength,
					ellipsisStringEndLength: config.ellipsisStringEndLength || this.ellipsisStringEndLength,
					getMinWidth: function(values)
					{
						return values[0].hasBcc ? 25 : 17;
					}
				}
			)
		});
		// The fieldLabel should not be sent to the superclass
		this.fieldLabel = config.fieldLabel || this.fieldLabel;
		delete config.fieldLabel;
		Zarafa.common.ui.messagepanel.RecipientLinks.superclass.constructor.call(this, config);
	},

	/**
	 * Initialize the component.
	 * This will register the {@link #onAttachmentClicked} event handler.
	 * @private
	 */
	initComponent: function()
	{
		Zarafa.common.ui.messagepanel.RecipientLinks.superclass.initComponent.call(this);

		container.populateInsertionPoint('previewpanel.toolbar.recipientlinks', this);

		this.on('contextmenu', this.onRecipientRightClick, this);
		this.on('dblclick', this.onRecipientDoubleClick, this);
		this.on('mouseenter',this.onMouseEnter, this);
		this.on('mouseleave',this.onMouseLeave, this);
	},

	/**
	 * Apply a record on the DataView. If the record is {@link Zarafa.core.data.IPMRecord#isOpened},
	 * then the {@link Zarafa.core.data.IPMRecord#getAttachmentStore attachment store} will be
	 * {@link #bindStore bound} to this view.
	 * @param {Zarafa.core.data.IPMRecord} record The record to apply
	 */
	setRecord: function(record)
	{
		this.parentRecord = record;
		if (record) {
			if (record.isOpened()) {
				this.bindStore(record.getRecipientStore());
			}
		} else {
			this.bindStore(null);
		}
	},

	/**
	 * overridden to get the viewIndex from an HTML element's attribute
	 * by default the index is taken from the element's position within the group;
	 * however if there are more than one groups, the indexes are wrong
	 * @private
	 */
	updateIndexes: function(startIndex, endIndex)
	{
		var ns = this.all.elements;
		startIndex = startIndex || 0;
		endIndex = endIndex || ((endIndex === 0) ? 0: (ns.length - 1));
		for(var i = startIndex; i <= endIndex; i++){
				ns[i].viewIndex = ns[i].getAttribute('viewIndex');
		}
	},

	/**
	 * <p>Function which can be overridden which returns the data object passed to this
	 * DataView's {@link #tpl template} to render the whole DataView.</p>
	 * <p>This is usually an Array of data objects, each element of which is processed by an
	 * {@link Ext.XTemplate XTemplate} which uses <tt>'&lt;tpl for="."&gt;'</tt> to iterate over its supplied
	 * data object as an Array. However, <i>named</i> properties may be placed into the data object to
	 * provide non-repeating data such as headings, totals etc.</p>
	 * @param {Array} records An Array of {@link Ext.data.Record}s to be rendered into the DataView.
	 * @param {Number} startIndex the index number of the Record being prepared for rendering.
	 * @return {Array} An Array of data objects to be processed by a repeating XTemplate. May also
	 * contain <i>named</i> properties.
	 * @private
	 */
	collectData: function(records, startIndex)
	{
		var r = [];

		// For meeting requests, determine the organizer entryid so
		// we can hide them from the recipient display. The store
		// may include the organizer as a regular MAPI_TO recipient.
		var organizerEntryId = '';
		if (this.parentRecord) {
			var msgClass = this.parentRecord.get('message_class') || '';
			if (msgClass.indexOf('IPM.Schedule.Meeting') === 0 || msgClass === 'IPM.Appointment') {
				organizerEntryId = this.parentRecord.get('sent_representing_entryid') || '';
			}
		}

		for (var i = 0, len = records.length; i < len; i++) {
			var record = records[i];
			if (!Ext.isDefined(this.recipientType) || this.recipientType === record.get('recipient_type')) {
				// Skip the organizer in meeting messages
				if (organizerEntryId && record.get('entryid') &&
					Zarafa.core.EntryId.compareABEntryIds(record.get('entryid'), organizerEntryId)) {
					continue;
				}
				r[r.length] = this.prepareData(record.data, record.get('rowid'), record);
			}
		}
		return r;
	},

	/**
	 * overridden to provide the correct index to {@link Ext.DataView#getRecord}
	 * otherwise behaviour breaks when there is more than one group in the records (e.g. CC, BCC, etc.)
	 * @param {Zarafa.core.data.IPMRecipientRecord} data The recipient record to be prepared
	 * @param {Number} index
	 * @param {Ext.data.Record} record
	 * @return {Object} new object with index appended
	 * @private
	 */
	prepareData: function(data, index, record)
	{
		return Ext.apply({viewIndex: index, hasBcc: (record.store.find('recipient_type', 3) !== -1)}, data);
	},

	/**
	 * Gets an array of the records from an array of nodes
	 * @param {Array} nodes The nodes to evaluate
	 * @return {Array} records The {@link Ext.data.Record} objects
	 */
	getRecords: function(nodes)
	{
		var records = [];

		for(var i = 0, len = nodes.length; i < len; i++){
			records[records.length] = this.store.getAt(this.store.findExact('rowid', parseInt(nodes[i].viewIndex)));
		}

		return records;
	},

	/**
	 * Gets a record from a node
	 * @param {HTMLElement} node The node to evaluate
	 * @return {Record} record The {@link Ext.data.Record} object
	 * @override
	 */
	getRecord: function(node)
	{
		return this.store.getAt(this.store.findExact('rowid', parseInt(node.viewIndex, 10)));
	},

	/**
	 * Update the components with the given record.
	 *
	 * @param {Zarafa.core.data.MAPIRecord} record The record to update in this component
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 */
	update: function(record, contentReset)
	{
		if (record && record instanceof Zarafa.core.data.MAPIRecord) {
			// In case the recordcomponentupdaterplugin is installed
			// we have a special action to update the component.
			if (contentReset && record.isOpened()) {
				this.setRecord(record);
			}
		} else {
			// The recordcomponentupdaterplugin is not installed and the
			// caller really wants to perform the update() function. Probably
			// a bad move, but lets not disappoint the caller.
			Zarafa.common.ui.messagepanel.RecipientLinks.superclass.update.apply(this, arguments);
		}
	},

	/**
	 * Function that is called to refresh a single node of this DataView. Overwritten
	 * because all {@link Zarafa.common.ui.messagepanel.RecipientLinks RecipientLinks} (To,
	 * CC, BCC) share a single store for which only records with the correct {@link #recipientType}
	 * are shown. Therefore we might not have rendered nodes for all records and we must
	 * keep that in mind.
	 * @param {Zarafa.core.data.IPMRecipientStore} store The store that holds the updated record
	 * @param {Zarafa.core.data.IPMRecipientRecord} record The record that was updated
	 * @private
	 */
	onUpdate: function(store, record)
	{
		if ( record.get('recipient_type') !== this.recipientType ){
			// We didn't render a node for this record, so we cannot update it
			return;
		}

		var index = this.store.indexOf(record);

		// Get the index when regarding only records with our recipientType
		// This is the major difference from the original function. Because
		// we do not render all recipients in the store but only those that
		// have the same recipient_type as this DataView, we must calculate
		// the index of the record a little differently.
		var filteredIndex = -1;
		Ext.each(this.store.getRange(), function(rec){
			if ( rec.get('recipient_type') === this.recipientType ){
				filteredIndex++;
			}
		}, this);

		if(index > -1){
		  var sel = this.isSelected(filteredIndex),
			original = this.all.elements[filteredIndex],
			node = this.bufferRender([record], index)[0];

		  this.all.replaceElement(filteredIndex, node, true);
		  if(sel){
				this.selected.replaceElement(original, node);
				this.all.item(filteredIndex).addClass(this.selectedClass);
		  }
		  this.updateIndexes(filteredIndex, filteredIndex);
		}
	},

	/**
	 * Refreshes the view by reloading the data from the store and re-rendering the template.
	 * Overridden in order to hide this component also if *filtered* records are empty
	 * @override
	 */
	refresh: function()
	{
		if (!this.getTemplateTarget().dom) {
			return;
		}

		this.clearSelections(false, true);
		var el = this.getTemplateTarget(),
			records = this.store.getRange();

		el.update('');
		if (records.length < 1) {
			if (!this.deferEmptyText || this.hasSkippedEmptyText) {
				el.update(this.emptyText);
			}
			this.all.clear();
			this.setVisible(false);
		} else {
			var filteredRecords = this.collectData(records, 0);
			Ext.each(filteredRecords, function(filteredRecord, index) {
				var user = Zarafa.core.data.UserIdObjectFactory.createFromRecord(new Ext.data.Record(filteredRecord));
				filteredRecords[index].presence_status = Zarafa.core.PresenceManager.getPresenceStatusForUser(user);
			});
			this.tpl.overwrite(el, filteredRecords);
			this.all.fill(Ext.query(this.itemSelector, el.dom));
			this.updateIndexes(0);
			if (filteredRecords.length < 1) {
				this.setVisible(false);
			} else {
				this.setVisible(true);
			}
		}
		this.hasSkippedEmptyText = true;
	},

	/**
	 * Called when user right-clicks on an item in {@link Zarafa.common.ui.messagepanel.RecipientLinks}
	 * It will show {@link Zarafa.common.recipientfield.ui.RecipientHoverCardView}
	 * @param {Ext.DataView} dataView Reference to this object
	 * @param {Number} index The index of the target node
	 * @param {HTMLElement} node The target node
	 * @param {Ext.EventObject} e The mouse event
	 * @private
	 */
	onRecipientRightClick: function (dataView, index, node, e)
	{
		var recipientRecord = this.createRecipientFromNode(node);
		Zarafa.core.data.UIFactory.openHoverCard(recipientRecord, {
			position: e.getXY(),
			recipientView: dataView,
			store:this.getStore()
		});
	},

	/**
	 * Called when user double-clicks on an item in {@link Zarafa.common.ui.messagepanel.RecipientLinks}
	 * invokes {@link Zarafa.common.Actions#openViewRecipientContent} with the selected {@link Zarafa.core.data.IPMRecord}
	 * @param {Ext.DataView} dataView Reference to this object
	 * @param {Number} index The index of the target node
	 * @param {HTMLElement} node The target node
	 * @param {Ext.EventObject} evt The mouse event
	 * @private
	 */
	onRecipientDoubleClick: function(dataView, index, node, evt)
	{
		var record = dataView.getRecord(node);
		Zarafa.common.Actions.openViewRecipientContent(record);
	},

	/**
	 * Function will get recipient record from node and will remove some properties which are not useful,
	 * because we are sending a new mail, so we don't want to copy all the properties from original recipient record.
	 * @param {Ext.Element} node data view element which is attached to the store record
	 * @return {Zarafa.core.data.IPMRecipientRecord} recipient record that will be used for sending the mail
	 * @private
	 */
	createRecipientFromNode: function(node)
	{
		var record = this.getRecord(node);
		record = Zarafa.core.data.RecordFactory.createRecordObjectByCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_RECIPIENT, record.data);

		// remove not needed properties
		record.set('rowid', '');
		record.set('recipient_trackstatus', '');
		record.set('recipient_trackstatus_time', '');

		return record;
	},

	/**
	 * Event handler which is triggered when
	 * mouse leaves the recipient links. it will
	 * just clear the timeout.
	 */
	onMouseLeave: function()
	{
		clearTimeout(this.timer);
	},

	/**
	 * Event handler which handle mouse enter event.
	 * It will show {@link Zarafa.common.recipientfield.ui.RecipientHoverCardView}
	 * after 700 ms.
	 * @param {Ext.DataView} dataView Reference to this object
	 * @param {Number} index The index of the target node
	 * @param {HTMLElement} node The target node
	 * @param {Ext.EventObject} e The mouse event
	 */
	onMouseEnter: function (dataView, index, node, e)
	{
		this.timer = setTimeout(function (scope) {
			var recipientRecord = scope.createRecipientFromNode(node);
			Zarafa.core.data.UIFactory.openHoverCard(recipientRecord, {
				position: e.getXY(),
				recipientView: dataView,
				store: scope.getStore()
			});
		}, 700, this);
	}
});

Ext.reg('zarafa.recipientlinks', Zarafa.common.ui.messagepanel.RecipientLinks);
Ext.namespace('Zarafa.common.ui.messagepanel');

/**
 * @class Zarafa.common.ui.messagepanel.SentInfoLinks
 * @extends Ext.Container
 * @xtype zarafa.sentinfolinks
 */
Zarafa.common.ui.messagepanel.SentInfoLinks = Ext.extend(Ext.Container, {
	/**
	 * @cfg {String} senderCls is the CSS class which should be applied to sender Template
	 */
	senderCls: 'preview-header-senderbox',

	/**
	 * @cfg {Number} maximum length of text allowed before truncations,
	 * truncation will be replaced with ellipsis ('...').
	 */
	ellipsisStringStartLength: 30,

	/**
	 * @cfg {Number} maximum length of text allowed after truncations,
	 * truncation will be replaced with ellipsis ('...').
	 */
	ellipsisStringEndLength: 30,
	/**
	 * @cfg {Zarafa.core.data.IPMRecord} record Holds the current record
	 */
	record: undefined,
	/**
	 * @cfg {Ext.Template/String} senderTemplate The template or template string which
	 * must be applied to the {@link #header} to build sender info when the
	 * {@link Zarafa.core.data.IPMRecord record} has been {@link #update updated}.
	 * The arguments of this template will be the {@link Zarafa.core.data.IPMRecord#data record.data} field.
	 */
	senderTemplate:
		'<tpl if="!Ext.isEmpty(values.sender_entryid) && !Ext.isEmpty(values.sent_representing_entryid) && !Zarafa.core.EntryId.compareABEntryIds(values.sent_representing_entryid, values.sender_entryid)">' +
			'<span class="preview-from">' +
				'<span class="zarafa-emailaddress-link zarafa-sentinfo-link"> ' +
					'<span class="zarafa-presence-status {[Zarafa.core.data.PresenceStatus.getCssClass(values.sender_presence_status)]}">'+
						'<span class="zarafa-presence-status-icon" aria-hidden="true"></span>' +
						'{sender_name:htmlEncode} &lt;{sender_email_address:htmlEncode}&gt; '+
					'</span>' +
				'</span>' +
				/* # TRANSLATORS: check if the mail is send by some delegator or not. if then display "on behalf of". */
				'<span>&nbsp;' + pgettext('mail.previewpanel', 'on behalf of') + '&nbsp;</span>' +
				/* Display the initials of a sender */
				'<tpl if="!Ext.isEmpty(values.user_image)">'+
					'<span class="preview-header-sender-image" style="background-image:url({user_image});" aria-hidden="true"></span>' +
				'</tpl>'+
				'<tpl if="Ext.isEmpty(values.user_image)">'+
					'<span class="preview-header-sender-initial" aria-hidden="true">{sender_initials}</span>' +
				'</tpl>'+
				'<span class="zarafa-emailaddress-link zarafa-sentinfo-on-behalf">' +
					'<span class="zarafa-presence-status {[Zarafa.core.data.PresenceStatus.getCssClass(values.sent_representing_presence_status)]}">'+
						'<span class="zarafa-presence-status-icon" aria-hidden="true"></span>' +
						'{sent_representing_name:htmlEncodeElide(this.ellipsisStringStartLength, this.ellipsisStringEndLength)}&nbsp;' +
						'<tpl if="this.shouldRenderAddress(values.sent_representing_email_address, values.record_is_opened)">' +
							'&lt;{sent_representing_email_address:htmlEncode}&gt;'+
						'</tpl>' +
					'</span>' +
				'</span>' +
			'</span>' +
		'</tpl>' +
		'<tpl if="Ext.isEmpty(values.sender_entryid) || Ext.isEmpty(values.sent_representing_entryid) || Zarafa.core.EntryId.compareABEntryIds(values.sent_representing_entryid, values.sender_entryid)">' +
				'<span class="preview-from zarafa-presence-status {[Zarafa.core.data.PresenceStatus.getCssClass(values.sender_presence_status)]}">' +
				'<span class="zarafa-presence-status-icon" aria-hidden="true"></span>' +
				/* Display the initials of a sender */
				'<tpl if="!Ext.isEmpty(values.user_image)">'+
					'<span class="preview-header-sender-image" style="background-image:url({user_image});" aria-hidden="true"></span>' +
				'</tpl>'+
				'<tpl if="Ext.isEmpty(values.user_image)">'+
					'<span class="preview-header-sender-initial" aria-hidden="true">{sender_initials}</span>' +
				'</tpl>'+
				'<span class="zarafa-emailaddress-link zarafa-sentinfo-link">' +
					'{sender_name:htmlEncodeElide(this.ellipsisStringStartLength, this.ellipsisStringEndLength)}&nbsp;' +
					'<tpl if="this.shouldRenderAddress(values.sender_email_address, values.record_is_opened)">' +
						'&lt;{sender_email_address:htmlEncode}&gt;'+
					'</tpl>' +
				'</span>' +
			'</span>' +
		'</tpl>' +
		/* don't display Sent field if item is a meeting/task/contact etc. */
		'<tpl if="!this.isMeeting(values.message_class)">' +
			'<span class="preview-timestamp">' +
		'</tpl>'+
		'<tpl if="this.isMeeting(values.message_class)">' +
			'<span class="preview-timestamp preview-meetingrequest">' +
		'</tpl>'+
		'<tpl>'+
			/* # TRANSLATORS: This message is used as label for the field which indicates the date/time on which the given message was sent. */
			'<span class="preview-timestamp-title"></span>' +
				'<tpl if="Ext.isDate(values.client_submit_time)">' +
					'<tpl if="this.isShortDateFormat()">'+
						'{client_submit_time:getNiceFormat()}' +
					'</tpl>' +
					// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
					'<tpl if="!this.isShortDateFormat()">'+
						'{client_submit_time:formatDefaultTimeString("' + _("l d/m/Y {0}") + '")}' +
					'</tpl>' +
				'</tpl>' +
				'<tpl if="!Ext.isDate(values.client_submit_time)">' +
					/* # TRANSLATORS: This message is used to indicate that no sent date is available for the message (because it has not been sent yet). */
					pgettext('mail.previewpanel', 'None') +
				'</tpl>' +
			'</span>'+
		'</tpl>',

	/**
	 * @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,{
			xtype: 'zarafa.sentinfolinks',
			border: false,
			autoScroll:true,
			anchor: '100%',
			cls: 'preview-header-sentinfo'
		});

		Zarafa.common.ui.messagepanel.SentInfoLinks.superclass.constructor.call(this, config);

		if (Ext.isString(this.senderTemplate)) {
			this.senderTemplate = new Ext.XTemplate(this.senderTemplate, {
				compiled: true,
				ellipsisStringStartLength: this.ellipsisStringStartLength,
				ellipsisStringEndLength: this.ellipsisStringEndLength,
				/*
				 * Template member Function
				 * This function will check if the passed data in the template is a meeting/scheduled items or not
				 * @private
				 */
				isMeeting: function(messageClass) {
					return (Zarafa.core.MessageClass.isClass(messageClass, 'ipm.schedule.meeting', true));
				},

				/**
				 * Function is used to check the current datetime display format.
				 * @private
				 */
				isShortDateFormat: function() {
					var dateFormat = container.settingsModel.get('zarafa/v1/main/datetime_display_format');
					return (dateFormat === 'short');
				},

				/**
				 * Hide unresolved EX/X500 addresses while the preview record is still unopened
				 * to avoid showing a transient wrong sender address.
				 */
				shouldRenderAddress: function(address, recordIsOpened) {
					if (Ext.isEmpty(address)) {
						return false;
					}

					if (recordIsOpened === true) {
						return true;
					}

					return !/^\s*\/[Oo]=/.test(address);
				},
			});
		}
	},

	/**
	 * Render the template and set the references to HTML Elements.
	 * @protected
	 */
	onRender: function()
	{
		Zarafa.common.ui.messagepanel.SentInfoLinks.superclass.onRender.apply(this, arguments);

		// Setup the HTML structure for the whole recipientLink and get references to the different elements in it
		this.senderElem = Ext.DomHelper.append(this.el.dom,{tag:'div', cls:this.senderCls});
	},

	/**
	 * Update the {@link Zarafa.common.ui.messagepanel.SentInfo header} with the data
	 * from the {@link Zarafa.core.data.IPMRecord record}. Updates the panel
	 * by loading data from the record data into the template.
	 * Attach mouse handlers on the anchors
	 * @param {Zarafa.core.data.IPMRecord} record to update the header panel with
	 */
	update: function(record)
	{
		if (this.senderElem){
			var senderElem = Ext.get(this.senderElem);

			if (!Ext.isDefined(record)) {
				//remove event handlers before template is emptied
				this.mun(senderElem.select('.zarafa-sentinfo-link'), 'contextmenu', this.onSenderRightClick, this);
				this.mun(senderElem.select('.zarafa-sentinfo-on-behalf'), 'contextmenu', this.onSenderRightClick, this);
				this.mun(senderElem.select('.zarafa-sentinfo-link'), 'mouseenter', this.onMouseEnter, this);
				this.mun(senderElem.select('.zarafa-sentinfo-on-behalf'), 'mouseenter', this.onMouseEnter, this);
				this.mun(senderElem.select('.zarafa-sentinfo-link'), 'mouseleave', this.onMouseLeave, this);
				this.mun(senderElem.select('.zarafa-sentinfo-on-behalf'), 'mouseleave', this.onMouseLeave, this);
				this.mun(senderElem.select('.zarafa-sentinfo-link'), 'dblclick', this.onDoubleClick, this);
				this.mun(senderElem.select('.zarafa-sentinfo-on-behalf'), 'dblclick', this.onDoubleClick, this);
				this.senderElem.innerHTML = '';
			} else {
				var user;
				if (Ext.isFunction(record.getSender)) {
					user = Zarafa.core.data.UserIdObjectFactory.createFromRecord(record.getSender());
					record.data.sender_presence_status = Zarafa.core.PresenceManager.getPresenceStatusForUser(user);
				}

				if (Ext.isFunction(record.getSentRepresenting)) {
					user = Zarafa.core.data.UserIdObjectFactory.createFromRecord(record.getSentRepresenting());
					record.data.sent_representing_presence_status = Zarafa.core.PresenceManager.getPresenceStatusForUser(user);
				}
				
				if (Ext.isFunction(record.getSenderInitials)) {
					record.data.sender_initials = record.getSenderInitials();
				}
				record.data.record_is_opened = Ext.isFunction(record.isOpened) && record.isOpened();

				this.senderTemplate.overwrite(senderElem, record.data);
				//bind click events after template has been populated
				this.mon(senderElem.select('.zarafa-sentinfo-link'), 'contextmenu', this.onSenderRightClick, this);
				this.mon(senderElem.select('.zarafa-sentinfo-on-behalf'), 'contextmenu', this.onSenderRightClick, this);
				this.mon(senderElem.select('.zarafa-sentinfo-link'), 'mouseenter', this.onMouseEnter, this);
				this.mon(senderElem.select('.zarafa-sentinfo-on-behalf'), 'mouseenter', this.onMouseEnter, this);
				this.mon(senderElem.select('.zarafa-sentinfo-link'), 'mouseleave', this.onMouseLeave, this);
				this.mon(senderElem.select('.zarafa-sentinfo-on-behalf'), 'mouseleave', this.onMouseLeave, this);
				this.mon(senderElem.select('.zarafa-sentinfo-link'), 'dblclick', this.onDoubleClick, this);
				this.mon(senderElem.select('.zarafa-sentinfo-on-behalf'), 'dblclick', this.onDoubleClick, this);
			}
		}
		this.record = record;
	},

	/**
	 * Called when user right-clicks on an link in {@link Zarafa.common.ui.messagepanel.SentInfoLinks}
	 * It will show {@link Zarafa.common.recipientfield.ui.RecipientHoverCardView}
	 * @param {Ext.EventObject} evt The mouse event
	 * @param {HTMLElement} elem The target node
	 * @param {Object} obj The options configuration passed to the {@link Ext.Element#addListener} call
	 * @private
	 */
	onSenderRightClick: function (evt, elem, obj)
	{
		var recipient = this.convertSenderToRecord(elem);
		// Don't show hover card if no recipient (happens when there is no sender_entryid)
		if(recipient) {
			var senderElem = Ext.get(this.senderElem);
			Zarafa.core.data.UIFactory.openHoverCard(recipient, {
				position: evt.getXY(),
				recipientView: senderElem,
				store: this.record.getSubStore('reply-to')
			});
		}
	},

	/**
	 * Called when user double-clicks on a link in {@link Zarafa.common.ui.messagepanel.SentInfoLinks}
	 * invokes {@link Zarafa.common.Actions#openViewRecipientContentPanel} with the selected {@link Zarafa.core.data.IPMRecord}
	 * @param {Ext.EventObject} evt The mouse event object
	 * @param {HTMLElement} elem The target node
	 * @param {Object} obj The options configuration passed to the {@link Ext.Element#addListener} call
 	 * @private
	 */
	onDoubleClick: function(evt, elem, obj)
	{
		var recipient = this.convertSenderToRecord(elem);
		// Don't show hover card if no recipient (happens when there is no sender_entryid)
		if(recipient) {
			Zarafa.common.Actions.openViewRecipientContent(recipient);
		}
	},

	/**
	 * Converts the sender to a record
	 * Checks which {@link HTMLElement} was clicked - 'sent by' or 'sent on behalf of'
	 * Depending on this check either {@link Zarafa.core.data.MessageRecord#getSender} or {@link Zarafa.core.data.MessageRecord#getSenderRepresenting} is invoked on the current {@link Zarafa.core.data.MessageRecord}.
	 * @return {Zarafa.core.data.IPMRecipientRecord} or undefined
	 */
	convertSenderToRecord: function(elem)
	{
		var sender;
		// Sometimes, the element clicked is the presence status of the user.
		// We need the email address link to check for the css classes - zarafa-sentinfo-link or zarafa-sentinfo-on-behalf.
		// Hence we need the parent of zarafa-presence-status i.e. zarafa-emailaddress-link.
		var element = Ext.get(elem).hasClass('zarafa-presence-status') ? Ext.get(elem.parentElement) : Ext.get(elem);

		//depending on whether the user clicked on sender or 'on behalf of', choose appropriate fields
		if(element.hasClass('zarafa-sentinfo-link')) {
			sender = this.record.getSender();
		} else if(element.hasClass('zarafa-sentinfo-on-behalf')) {
			sender = this.record.getSentRepresenting();
		}
		return sender;
	},

	/**
	 * Event handler which handle mouse enter event.
	 * It will show {@link Zarafa.common.recipientfield.ui.RecipientHoverCardView}
	 * after 700 ms.
	 * @param {Ext.EventObject} e The mouse event
	 * @param {HTMLElement} node The target node
	 */
	onMouseEnter: function (e, node)
	{
		this.timer = setTimeout(function (scope) {
			var recipient = scope.convertSenderToRecord(node);
			// Don't show hover card if no recipient (happens when there is no sender_entryid)
			if(recipient) {
				recipient.set('recipient_type', Zarafa.core.mapi.RecipientType.MAPI_TO);
				var senderElem = Ext.get(scope.senderElem);
				Zarafa.core.data.UIFactory.openHoverCard(recipient, {
					position: e.getXY(),
					recipientView: senderElem,
					store: scope.record.getSubStore('reply-to')
				});
			}
		}, 700, this);
	},

	/**
	 * Event handler which is triggered when
	 * mouse leaves the sender links. it will
	 * just clear the timeout.
	 */
	onMouseLeave: function ()
	{
		clearTimeout(this.timer);
	}
});

Ext.reg('zarafa.sentinfolinks', Zarafa.common.ui.messagepanel.SentInfoLinks);
Ext.namespace('Zarafa.contact');

/**
 * @class Zarafa.contact.Actions
 * Common actions which can be used within {@link Ext.Button buttons}
 * or other {@link Ext.Component components} with action handlers.
 * @singleton
 */
Zarafa.contact.Actions = {

	/**
	 * Opens a contact or distlist content panel based on the message class of record.
	 * Function will check record's message_class, If it is
	 * IPM.Contact It will open contact content panel - Zarafa.contact.dialogs.ContactContentPanel
	 * IPM.DistList It will open distlist content panel - Zarafa.contact.dialogs.DistListContentPanel
	 *
	 * @param {Zarafa.core.data.IPMRecord|Zarafa.core.data.IPMRecord[]} record
	 * The record/records which will be loaded in content panel for editing.
	 * @param {Object} config (optional) Configuration object for creating a ContentPanel
	 */
	openDialog: function(record, config)
	{
		if(Array.isArray(record)) {
			for(var index = 0, len = record.length; index < len; index++) {
				Zarafa.contact.Actions.openDialog(record[index], config);
			}
			return;
		}

		if(record.isMessageClass('IPM.Contact', true)) {
			this.openContactContent(record, config);
		} else if (record.isMessageClass('IPM.DistList', true)) {
			this.openDistlistContent(record, config);
		}
	},


	/**
	 * Opens a {@link Zarafa.contact.dialogs.ContactContentPanel ContactContentPanel}.
	 * @param {Zarafa.core.data.IPMRecord|Zarafa.core.data.IPMRecord[]} record
	 * The record/records which will be loaded in content panel for editing.
	 * @param {Object} config (optional) Configuration object
	 */
	openContactContent: function(record, config)
	{
		if(Array.isArray(record)) {
			for(var index = 0, len = record.length; index < len; index++) {
				Zarafa.contact.Actions.openContactContent(record[index], config);
			}
			return;
		}

		if(!Ext.isEmpty(record)) {
			Zarafa.core.data.UIFactory.openCreateRecord(record, config);
		}
	},

	/**
	 * Opens a {@link Zarafa.contact.dialogs.ContactContentPanel ContactContentPanel}.
	 * @param {Zarafa.contact.ContactContextModel} model
	 * model object that will be used to create a new {@link Zarafa.core.data.IPMRecord IPMRecord}
	 * in {@link Zarafa.core.data.IPMStore IPMStore}.
	 * @param {Object} config (optional) Configuration object
	 */
	openCreateContactContent: function(model, config)
	{
		var record = model.createRecord(undefined, false);

		Zarafa.contact.Actions.openContactContent(record, config);
	},

	/**
	 * Opens a {@link Zarafa.contact.dialogs.DistlistContentPanel DistlistContentPanel}.
	 * @param {Zarafa.core.data.IPMRecord|Zarafa.core.data.IPMRecord[]} record
	 * The record/records which will be loaded in content panel for editing.
	 * in {@link Zarafa.core.data.IPMStore IPMStore}.
	 * @param {Object} config (optional) Configuration object
	 */
	openDistlistContent: function(record, config)
	{
		if(Array.isArray(record)) {
			for(var index = 0, len = record.length; index < len; index++) {
				Zarafa.contact.Actions.openDistlistContent(record[index], config);
			}
			return;
		}

		if(!Ext.isEmpty(record)) {
			Zarafa.core.data.UIFactory.openCreateRecord(record, config);
		}
	},

	/**
	 * Function will open a new {@link Zarafa.contact.dialogs.ContactContentPanel}
	 * with all information prefilled to add a recipient as a contact.
	 *
	 * @param {Ext.Button} button button object.
	 * @param {EventObject} event The click event object.
	 * @private
	 */
	openRecipientContactContent: function(button, event) {

		// When the button belongs to one of the currently opened popout windows then
		// it is required to bring the main webapp window to front prior to switching to the contact context.
		if (!Zarafa.core.BrowserWindowMgr.isOwnedByMainWindow(button)) {
			Zarafa.core.BrowserWindowMgr.switchFocusToMainWindow(button);
		}

		var contactRecord = this.getModel().createRecord();
		var record = button.getRecords();

		Zarafa.core.data.UIFactory.openCreateRecord(contactRecord);
		contactRecord.beginEdit();
		contactRecord.set('display_name', record.get('display_name'));
		contactRecord.set('email_address_1', record.get('smtp_address'));
		contactRecord.set('email_address_type_1', record.get('address_type'));
		contactRecord.updateAddressbookProps();

		// Use the same logic as the {@link Zarafa.contact.dialogs.ContactDetailTab ContactDetailTab}
		// to set the given_name and surname.
		var ContactParser = new Zarafa.contact.data.ContactDetailsParser();
		var data = ContactParser.parseNameInfo(record.get('display_name'));
		contactRecord.set('given_name', data['given_name']);
		contactRecord.set('surname', data['surname']);
		contactRecord.endEdit();
	},

	/**
	 * Opens a {@link Zarafa.contact.dialogs.DistlistContentPanel DistlistContentPanel}
	 * @param {Zarafa.contact.ContactContextModel} model
	 * model object that will be used to create a new {@link Zarafa.core.data.IPMRecord IPMRecord}
	 * @param {Object} config (optional) Configuration object
	 */
	openCreateDistlistContent: function(model, config)
	{
		var record = model.createRecord(undefined, true);

		Zarafa.contact.Actions.openDistlistContent(record, config);
	},

	/**
	 * Function will open {@link Zarafa.contact.dialogs.ContactNameContentPanel ContactNameContentPanel}.
	 * @param {Zarafa.core.data.IPMRecord} record record that will be loaded in
	 * {@link Zarafa.contact.dialogs.ContactNameContentPanel ContactNameContentPanel}.
	 * @param {Object} config Configuration object, containing:
	 * 	- {@link Zarafa.contact.data.ContactDetailsParser} parser parser that will be used
	 * to parse details of full name and load the data in
	 * {@link Zarafa.contact.dialogs.ContactNameContentPanel ContactNameContentPanel}.
	 * 	- {@link Object} parsedData a hash map of already parsed data of full name that
	 * will be loaded in {@link Zarafa.contact.dialogs.ContactNameContentPanel ContactNameContentPanel}.
	 */
	openDetailedNameContent: function(record, config)
	{
		if(!Ext.isEmpty(record)) {
			var componentType = Zarafa.core.data.SharedComponentType['contact.dialog.contact.namedetails'];
			config = Ext.applyIf(config || {}, {
				modal: true
			});

			Zarafa.core.data.UIFactory.openLayerComponent(componentType, record, config);
		}
	},

	/**
	 * Function will open {@link Zarafa.contact.dialogs.ContactAddressContentPanel ContactAddressContentPanel}.
	 * @param {Zarafa.core.data.IPMRecord} record record that will be loaded in
	 * {@link Zarafa.contact.dialogs.ContactNameContentPanel ContactNameContentPanel}.
	 * @param {Object} config A configuration object, containing:
	 * 	- {@link Zarafa.contact.data.ContactDetailsParser} parser parser that will be used
	 * to parse details of full name and load the data in
	 * {@link Zarafa.contact.dialogs.ContactNameContentPanel ContactNameContentPanel}.
	 * 	- {@link Object} parsedData a hash map of already parsed data of full name that
	 * will be loaded in {@link Zarafa.contact.dialogs.ContactNameContentPanel ContactNameContentPanel}.
	 * 	- {@link String} property property that will be modified.
	 */
	openDetailedAddressContent: function(record, config)
	{
		if(!Ext.isEmpty(record)) {
			var componentType = Zarafa.core.data.SharedComponentType['contact.dialog.contact.addressdetails'];
			config = Ext.applyIf(config || {}, {
				modal: true
			});

			Zarafa.core.data.UIFactory.openLayerComponent(componentType, record, config);
		}
	},

	/**
	 * Function will open {@link Zarafa.contact.dialogs.ContactAddressContentPanel ContactAddressContentPanel}.
	 * @param {Zarafa.core.data.IPMRecord} record record that will be loaded in
	 * {@link Zarafa.contact.dialogs.ContactNameContentPanel ContactNameContentPanel}.
	 * @param {Object} config Configuration object containing:
	 * 	- {@link Zarafa.contact.data.ContactDetailsParser} parser parser that will be used
	 * to parse details of full name and load the data in
	 * {@link Zarafa.contact.dialogs.ContactNameContentPanel ContactNameContentPanel}.
	 * 	- {@link String} property property that will be modified.
	 */
	openDetailedPhoneContent: function(record, config)
	{
		if(!Ext.isEmpty(record)) {
			var componentType = Zarafa.core.data.SharedComponentType['contact.dialog.contact.phonedetails'];
			config = Ext.applyIf(config || {}, {
				modal: true
			});

			Zarafa.core.data.UIFactory.openLayerComponent(componentType, record, config);
		}
	},

	/**
	 * Opens the options dialog for a contact or distribution list record.
	 *
	 * @param {Zarafa.core.data.IPMRecord|Zarafa.core.data.IPMRecord[]} records
	 * The record(s) for which the options are requested.
	 * @param {Object} config (optional) Configuration object used to create the Content Panel.
	 */
	openOptionsContent: function(records, config)
	{
		if (Array.isArray(records)) {
			records = records[0];
		}

		if (!records) {
			return;
		}

		config = Ext.applyIf(config || {}, {
			modal: true
		});

		var componentType = Zarafa.core.data.SharedComponentType['contact.dialog.options'];
		Zarafa.core.data.UIFactory.openLayerComponent(componentType, records, config);
	},

	/**
	 * Opens a {@link Zarafa.addressbook.dialogs.ABMultiUserSelectionContentPanel ABMultiUserSelectionContentPanel}
	 * for configuring the categories of the given {@link Zarafa.core.data.IPMRecord records}.
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record, or records for which the categories
	 * must be configured
	 * @param {Object} config (optional) Configuration object
	 */
	openMembersSelectionContent: function(record, config)
	{
		if(Array.isArray(record)) {
			for(var index = 0, len = record.length; index < len; index++) {
				Zarafa.contact.Actions.openMembersSelectionContent(record[index], config);
			}
			return;
		}

		// Create a copy of the record, we don't want the changes
		// to be activated until the user presses the Ok button.
		var copy = record.copy();
		var store = copy.getSubStore('members');

		Zarafa.common.Actions.openABUserMultiSelectionContent({
			callback: function() {
				record.applyData(copy);
			},
			convert: function(user) {
				return user.convertToDistlistMember();
			},
			store: store,
			selectionCfg: [{
				xtype: 'zarafa.memberboxfield',
				fieldLabel: _('Members') + ':',
				height: 50,
				boxStore: store,
				flex: 1
			}]
		});
	},

	/**
	 * Function will open a modal dialog to add external contact in distribution list,
	 * It will open with two fields name and email address.
	 * Function will open {@link Zarafa.contact.dialogs.DistlistExternalMemberContentPanel DistlistExternalMemberContentPanel}.
	 * @param {Zarafa.contact.DistlistMemberRecord} record The member record which is shown in dialog
	 * @param {Object} config Configuration object containing
	 * 	- {@link Zarafa.core.data.IPMRecord} parentRecord The parent distribution list record.
	 */
	openDistlistExternalMemberContent: function(record, config)
	{
		var componentType = Zarafa.core.data.SharedComponentType['contact.dialog.distlist.externalmember'];
		config = Ext.applyIf(config || {}, {
			modal: true
		});

		Zarafa.core.data.UIFactory.openLayerComponent(componentType, record, config);
	},

	/**
	 * Function will open a
	 * {@link Zarafa.contact.dialogs.ContactContentPanel} for contact members,
	 * {@link Zarafa.contact.dialogs.DistlistContentPanel} for distlist members,
	 * {Zarafa.addressbook.dialogs.ABUserDetailContentPanel} for addressbook users,
	 * {Zarafa.addressbook.dialogs.ABGroupDetailContentPanel} for addressbook groups,
	 * and {Zarafa.contact.dialogs.DistlistExternalMemberContentPanel} for external members.
	 *
	 * @param {Zarafa.contact.DistlistMemberRecord} record The distlist member record
	 * @param {Zarafa.core.data.IPMRecord} parentRecord The distlist record which
	 * contains memberStore for distribution list members
	 */
	openDistlistMember: function(record, parentRecord)
	{
		if (record) {
			var openRecord;
			switch(record.get('distlist_type')){
				case Zarafa.core.mapi.DistlistType.DL_USER:
				case Zarafa.core.mapi.DistlistType.DL_USER2:
				case Zarafa.core.mapi.DistlistType.DL_USER3:
				case Zarafa.core.mapi.DistlistType.DL_DIST:
					// Contacts and distribution list from contact folders
					openRecord = record.convertToContactRecord();
				break;
				case Zarafa.core.mapi.DistlistType.DL_USER_AB:
				case Zarafa.core.mapi.DistlistType.DL_DIST_AB:
					// contacts and distribution list from Addressbook
					// implement at server mapisession->openentry.
					openRecord = record.convertToAddressBookRecord();
					break;
				case Zarafa.core.mapi.DistlistType.DL_EXTERNAL_MEMBER:
				/* falls through */
				default:
					// External/oneoff contacts
					Zarafa.contact.Actions.openDistlistExternalMemberContent(record, { parentRecord: parentRecord });
					break;
			}

			if(openRecord) {
				// FIXME: We put the abRecord into the ShadowStore to be able
				// to open it, and obtain all details. However, we also need to
				// find a point where we can remove it again.
				container.getShadowStore().add(openRecord);
				Zarafa.core.data.UIFactory.openViewRecord(openRecord);
			}
		}
	}
};
Ext.namespace('Zarafa.contact.data');

/**
 * @class Zarafa.contact.data.config
 * @singleton
 */
Zarafa.contact.data.config = {
	/**
	 * prefix options for the display name.
	 * @type Array
	 */
	Prefix: [
		[''],
		[_('Dr.')],
		[_('Miss')],
		[_('Mr.')],
		[_('Mrs.')],
		[_('Ms.')],
		[_('Prof.')]
	],

	/**
	 * suffix options for the display name.
	 * @type Array
	 */
	Suffix: [
		[''],
		['I'],
		['II'],
		['III'],
		[_('Jr.')],
		[_('Sr.')]
	],

	/**
	 * Carriage return (\r) value in UTF-8 hex format
	 * @type HexString
	 */
	CR: '\x0D',

	/**
	 * Line feed (\n) value in UTF-8 hex format
	 * @type HexString
	 */
	LF: '\x0A',

	/**
	 * Carriage return line feed (\r\n) value in UTF-8 hex format
	 * @type HexString
	 */
	CRLF: '\x0D\x0A',

	/**
	 * space value in UTF-8 hex format
	 * @type HexString
	 */
	SP: '\x20',

	/**
	 * non breaking space value in UTF-8 hex format
	 * @type HexString
	 */
	NBSP: '\xA0'
};
Ext.namespace('Zarafa.contact.data');

/**
 * @class Zarafa.contact.data.SearchFields
 * Search fields for search bar
 * @singleton
 */
Zarafa.contact.data.SearchFields = [{
	name: _('All text fields'),
	value: 'display_name display_name_prefix generation fileas company_name email_address_1 email_address_2 email_address_3 home_address other_address business_address home_telephone_number cellular_telephone_number business_telephone_number business_fax_number assistant_telephone_number business2_telephone_number callback_telephone_number car_telephone_number company_telephone_number home2_telephone_number home_fax_number other_telephone_number pager_telephone_number primary_fax_number primary_telephone_number radio_telephone_number telex_telephone_number ttytdd_telephone_number'
}, {
	name: _('Name'),
	value: 'display_name display_name_prefix generation'
}, {
	name: _('File as'),
	value: 'fileas'
}, {
	name: _('Company'),
	value: 'company_name'
}, {
	name: _('Email address'),
	value: 'email_address_1 email_address_2 email_address_3'
}, {
	name: _('Phone number'),
	value: 'home_telephone_number cellular_telephone_number business_telephone_number business_fax_number assistant_telephone_number business2_telephone_number callback_telephone_number car_telephone_number company_telephone_number home2_telephone_number home_fax_number other_telephone_number pager_telephone_number primary_fax_number primary_telephone_number radio_telephone_number telex_telephone_number ttytdd_telephone_number'
}, {
	name: _('Address'),
	value: 'home_address other_address business_address'
}];
Ext.namespace('Zarafa.contact.dialogs');

/**
 * @class Zarafa.contact.dialogs.ContactAddressPanel
 * @extends Ext.form.FormPanel
 * @xtype zarafa.contactaddresspanel
 */
Zarafa.contact.dialogs.ContactAddressPanel = Ext.extend(Ext.form.FormPanel, {
	/**
	 * @cfg {Zarafa.contact.data.ContactDetailsParser} parser parser object that will be used to parse information
	 */
	parser: null,
	/**
	 * @cfg {String} property property that will be modified
	 */
	property: '',
	/**
	 * @cfg {Object} parsedData if data is already parsed then it can be passed here,
	 * so no need to prase same data again
	 */
	parsedData: null,
	/**
	 * @constructor
	 * @param {Object} config Configuration structure
	 */
	constructor: function(config)
	{
		config = config || {};

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

		Ext.applyIf(config, {
			xtype: 'zarafa.contactaddresspanel',
			border: false,
			frame: true,
			padding: 5,
			items: this.createFormItems(config.property)
		});

		Zarafa.contact.dialogs.ContactAddressPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Create the form in which the address specifications can be written
	 * @param {String} propertyPrefix prefix of the properties that should be used to get values from record.
	 * @return {Object} Configuration object for the form
	 */
	createFormItems: function(propertyPrefix)
	{
		return [{
			xtype: 'textarea',
			anchor: '100%',
			fieldLabel: _('Street'),
			name: propertyPrefix + '_street'
		},{
			xtype: 'textfield',
			anchor: '100%',
			fieldLabel: _('City'),
			name: propertyPrefix + '_city'
		},{
			xtype: 'textfield',
			anchor: '100%',
			fieldLabel: _('State/Province'),
			name: propertyPrefix + '_state'
		},{
			xtype: 'textfield',
			anchor: '100%',
			fieldLabel: _('Zip/Postal code'),
			name: propertyPrefix + '_postal_code'
		},{
			xtype: 'textfield',
			anchor: '100%',
			fieldLabel: _('Country/Region'),
			name: propertyPrefix + '_country'
		},{
			xtype: 'checkbox',
			boxLabel: _('Show this again when address is incomplete or unclear.'),
			ref: 'settingCheckField',
			hideLabel: true,
			checked: container.getSettingsModel().get('zarafa/v1/contexts/contact/show_address_dialog')
		}];
	},

	/**
	 * Load record into form
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record to load
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 */
	update: function(record, contentReset)
	{
		if (!Ext.isDefined(record)) {
			return;
		}

		// If no parsed Data is provided, we must check if the parsed data can be found in the record
		var convertToProps = true;
		if (Ext.isEmpty(this.parsedData)) {
			var keys = this.getForm().getValues();
			for (var key in keys) {
				if (!Ext.isEmpty(record.get(key))) {
					this.parsedData = Ext.apply(this.parsedData || {}, record.data);
					// We don't need to convert to props
					convertToProps = false;
					break;
				}
			}

			// If the parsed data was not found in the record, check if the parseable string
			// can be found in the record instead.
			if (convertToProps && !Ext.isEmpty(record.get(this.property))) {
				var newParsedData = this.parser.parseInfo('address', record.get(this.property));
				this.parsedData = Ext.apply(this.parsedData || {}, newParsedData);
			}
		}


		// If the parsedData was constructed using the parser, apply property as prefix to every key in
		// parsed data so form will be able to add data in fields
		if (convertToProps) {
			Ext.iterate(this.parsedData, function(key, value) {
				this.parsedData[this.property + '_' + key] = value;
				delete this.parsedData[key];
			}, this);
		}

		var form = this.getForm();
		form.setValues(this.parsedData);

		// remove data after it has been put in the form fields, so consecutive requests
		// will use data from record
		this.parsedData = null;
	},

	/**
	 * Update record from form
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record to update
	 * @private
	 */
	updateRecord: function(record)
	{
		var form = this.getForm();

		if (this.settingCheckField.isDirty()) {
			container.getSettingsModel().set('zarafa/v1/contexts/contact/show_address_dialog', this.settingCheckField.getValue());
		}

		record.beginEdit();

		if (Ext.isDefined(this.parser)) {
			var formValues = form.getValues();

			// remove property as prefix from values so parser can combine data and set it in record
			Ext.iterate(formValues, function(key, value) {
				formValues[key.substr(this.property.length + 1)] = value;
				delete formValues[key];
			}, this);

			record.set(this.property, this.parser.combineInfo('address', formValues));
		}

		form.updateRecord(record);

		record.endEdit();
	}
});

Ext.reg('zarafa.contactaddresspanel', Zarafa.contact.dialogs.ContactAddressPanel);
Ext.namespace('Zarafa.contact.dialogs');

/**
 * @class Zarafa.contact.dialogs.ContactDetailTab
 * @extends Ext.form.FormPanel
 * @xtype zarafa.contactdetailtab
 *
 * This class is used to create layout of details tab panel.
 */
Zarafa.contact.dialogs.ContactDetailTab = Ext.extend(Ext.form.FormPanel, {
	/**
	 * @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, {
			xtype: 'zarafa.contactdetailtab',
			cls: 'zarafa-contactdetailtab',
			title: _('Details'),
			autoScroll: true,
			labelWidth: 120,
			labelAlign: 'left',
			defaults: {
				columnWidth: 0.5,
				border: false,
				header: false,
				xtype: 'fieldset',
				labelSeparator: ''
			},
			items: [
				this.createOfficeFieldset(),
				this.createOfficeFieldset2(),
				this.createClear(),
				this.createNameFieldset(),
				this.createDateFieldset()
			]
		});

		Zarafa.contact.dialogs.ContactDetailTab.superclass.constructor.call(this, config);
	},

	/**
	 * Creates a clearing element to separate rows in the column layout.
	 * @return {Object} config object for creating the clear panel.
	 * @private
	 */
	createClear: function()
	{
		return {
			xtype: 'panel',
			cls: 'zarafa-clear',
			columnWidth: 1
		};
	},

	/**
	 * Creates the office fieldset for details tab of form panel.
	 * @return {Object} config object for creating {@link Ext.form.FieldSet FieldSet}.
	 * @private
	 */
	createOfficeFieldset: function()
	{
		return {
			defaultType: 'textfield',
			defaults: {
				anchor: '99%'
			},
			items: [{
				fieldLabel: _('Department'),
				name: 'department_name',
				listeners: {
					scope: this,
					change: this.onFieldChange
				}
			}, {
				fieldLabel: _('Office location'),
				name: 'office_location',
				listeners: {
					scope: this,
					change: this.onFieldChange
				}
			}, {
				fieldLabel: _('Profession'),
				name: 'profession',
				listeners: {
					scope: this,
					change: this.onFieldChange
				}
			}]
		};
	},

	/**
	 * Creates the office fieldset for details tab of form panel.
	 * @return {Object} config object for creating {@link Ext.form.FieldSet FieldSet}.
	 * @private
	 */
	createOfficeFieldset2: function()
	{
		return {
			defaultType: 'textfield',
			defaults: {
				anchor: '99%'
			},
			items: [{
				fieldLabel: _('Manager\'s name'),
				name: 'manager_name',
				listeners: {
					scope: this,
					change: this.onFieldChange
				}
			}, {
				fieldLabel: _('Assistant\'s name'),
				name: 'assistant',
				listeners: {
					scope: this,
					change: this.onFieldChange
				}
			}]
		};
	},

	/**
	 * Creates the name fieldset for details tab of form panel.
	 * @return {Object} config object for creating {@link Ext.form.FieldSet FieldSet}.
	 * @private
	 */
	createNameFieldset: function()
	{
		var tplString = '<tpl for="."><div class="x-combo-list-item">{displayText:htmlEncodeDefaultValue(Zarafa.contact.data.config.NBSP)}</div></tpl>';

		return {
			defaultType: 'textfield',
			defaults: {
				anchor: '99%'
			},
			items: [{
				fieldLabel: _('Nickname'),
				name: 'nickname',
				listeners: {
					scope: this,
					change: this.onFieldChange
				}
			}, {
				xtype: 'combo',
				fieldLabel: _('Title'),
				editable: false,
				name: 'display_name_prefix',
				mode: 'local',
				triggerAction: 'all',
				store: {
					xtype: 'arraystore',
					fields: ['displayText'],
					data: Zarafa.contact.data.config.Prefix
				},
				tpl: new Ext.XTemplate(tplString, {
					compiled: true		// compile immediately
				}),
				displayField: 'displayText',
				valueField: 'displayText',
				listeners: {
					scope: this,
					change: this.onNameChange
				}
			}, {
				xtype: 'combo',
				fieldLabel: _('Suffix'),
				editable: false,
				name: 'generation',
				mode: 'local',
				triggerAction: 'all',
				store: {
					xtype: 'arraystore',
					fields: ['displayText'],
					data: Zarafa.contact.data.config.Suffix
				},
				tpl: new Ext.XTemplate(tplString, {
					compiled: true		// compile immediately
				}),
				displayField: 'displayText',
				valueField: 'displayText',
				listeners: {
					scope: this,
					change: this.onNameChange
				}
			}]
		};
	},

	/**
	 * Creates the date fieldset for details tab of form panel.
	 * @return {Object} config object for creating {@link Ext.form.FieldSet FieldSet}.
	 * @private
	 */
	createDateFieldset: function()
	{
		return {
			defaultType: 'textfield',
			defaults: {
				anchor: '99%'
			},
			items: [{
				fieldLabel: _('Spouse/Partner'),
				name: 'spouse_name',
				listeners: {
					scope: this,
					change: this.onFieldChange
				}
			}, {
				xtype: 'datefield',
				fieldLabel: _('Birthday'),
				ref: '../birthdayField',
				name: 'birthday',
				// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
				format: ('d/m/Y'),
				listeners: {
					scope: this,
					change: this.onDateChange
				}
			}, {
				xtype: 'datefield',
				fieldLabel: _('Anniversary'),
				ref: '../anniversaryField',
				name: 'wedding_anniversary',
				// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
				format: ('d/m/Y'),
				listeners: {
					scope: this,
					change: this.onDateChange
				}
			}]
		};
	},

	/**
	 * Function is used to update values of form fields when ever
	 * an updated {@link Zarafa.core.data.IPMRecord record} is received
	 * @param {Zarafa.core.data.IPMRecord} 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)
	{
		if (Ext.isEmpty(record)) {
			return;
		}

		this.record = record;

		this.getForm().loadRecord(record);
	},

	/**
	 * Update the {@link Zarafa.core.data.IPMRecord IPMRecord} with the data from the {@link Ext.Panel Panel}.
	 * @param {Zarafa.core.data.IPMRecord} record The record which has to be updated
	 */
	updateRecord: function(record)
	{
		record.beginEdit();

		// Update birthday properties, You have 3 different states for the values:
		// 1) null (usually comes from the record, when the date wasn't provided)
		// 2) empty string (usually comes from the UI when no date was typed in)
		// 3) timestamp
		// To check if it is different, we must check if either the old value, or
		// new value is a date (while the other isn't), or if they are both dates
		// and the dates refer to a different time.
		var newBirthDay = this.birthdayField.getValue();
		var oldBirthDay = record.get(this.birthdayField.getName());
		if (Ext.isDate(newBirthDay) !== Ext.isDate(oldBirthDay) || (Ext.isDate(newBirthDay) && newBirthDay !== oldBirthDay)) {
			this.onDateChange(this.birthdayField, newBirthDay, oldBirthDay);
		}

		// Update anniversary properties
		// Same logic for values applies here as for the birthday described above.
		var newAnniversary = this.anniversaryField.getValue();
		var oldAnniversary = record.get(this.anniversaryField.getName());
		if (Ext.isDate(newAnniversary) !== Ext.isDate(oldAnniversary) || (Ext.isDate(newAnniversary) && newAnniversary !== oldAnniversary)) {
			this.onDateChange(this.anniversaryField, newAnniversary, oldAnniversary);
		}

		// Load all properties from the UI
		this.getForm().updateRecord(record);

		// Regenerate the display name
		if (record.isModifiedSinceLastUpdate('display_name_prefix') ||
		  record.isModifiedSinceLastUpdate('given_name') ||
		  record.isModifiedSinceLastUpdate('middle_name') ||
		  record.isModifiedSinceLastUpdate('surname') ||
		  record.isModifiedSinceLastUpdate('generation')) {
			this.generateDisplayName();
		}

		record.endEdit();
	},

	/**
	 * Event handler for the {@link Ext.form.Field fields} which are fired
	 * when the contents of the field has been changed. This will apply
	 * the value directly into the record.
	 * @param {Ext.form.Field} field The field which fired the event
	 * @param {Mixed} newValue The new value
	 * @param {Mixed} oldValue The old value
	 * @private
	 */
	onFieldChange: function(field, newValue, oldValue)
	{
		if (field.validateValue(field.processValue(newValue))) {
			this.record.beginEdit();
			this.record.set(field.getName(), newValue);
			this.record.endEdit();
		}
	},

	/**
	 * Event handler for the {@link Ext.form.ComboBox comboboxes} for configuring
	 * the prefix or postfix of the display name of the user. This will set some
	 * additional properties (like regeneration the display_name).
	 * @param {Ext.form.Field} field The field which fired the event
	 * @param {String} newValue The new value
	 * @param {String} oldValue THe original value
	 * @private
	 */
	onNameChange: function(field, newValue, oldValue)
	{
		if (field.validateValue(field.processValue(newValue))) {
			this.record.beginEdit();

			this.record.set(field.getName(), newValue);
			this.generateDisplayName();

			this.record.endEdit();
		}
	},

	/**
	 * Event handler for the {@link Ext.form.DateField datefields} for configuring the
	 * birthday and anniversary. This will set some additional properties to ensure the
	 * corresponding calendar appointment will be updated as well.
	 * @param {Ext.form.DateField} field The field which fired the event
	 * @param {Date} newValue The new date
	 * @param {Data} oldValue The original date
	 * @private
	 */
	onDateChange: function(field, newValue, oldValue)
	{
		if (field.validateValue(field.processValue(newValue))) {
			this.record.beginEdit();

			if (Ext.isEmpty(newValue)) {
				this.record.set(field.getName(), null);
			} else {
				this.record.set(field.getName(), newValue);
			}

			// send entryid to server if anything is changed
			if (field.getName() == 'birthday') {
				this.record.set('birthday_eventid', this.record.get('birthday_eventid'), true);
			} else {
				this.record.set('anniversary_eventid', this.record.get('anniversary_eventid'), true);
			}

			this.record.set('subject', this.record.get('subject'), true);
			this.record.updateTimezoneInformation();

			this.record.endEdit();
		}
	},

	/**
	 * Regenerate the display_name of the user. This should be called when
	 * one of the name properties of the user has been changed.
	 * @private
	 */
	generateDisplayName: function()
	{
		var record = this.record;

		// Regenerate display name value
		var nameValues = {
			'display_name_prefix': record.get('display_name_prefix'),
			'given_name': record.get('given_name'),
			'middle_name': record.get('middle_name'),
			'surname': record.get('surname'),
			'generation': record.get('generation')
		};

		// update record
		record.set('display_name', this.getContactParser().combineInfo('name', nameValues));
	},

	/**
	 * Function will return {@link Zarafa.contact.data.ContactDetailsParser ContactDetailsParser} that is a parser object to parse contact details
	 * @return {Zarafa.contact.data.ContactDetailsParser} contact details parser
	 * @private
	 */
	getContactParser: function()
	{
		return this.dialog.contactParser;
	}
});

Ext.reg('zarafa.contactdetailtab', Zarafa.contact.dialogs.ContactDetailTab);
Ext.namespace('Zarafa.contact.dialogs');

/**
 * @class Zarafa.contact.dialogs.ContactNamePanel
 * @extends Ext.form.FormPanel
 * @xtype zarafa.contactnamepanel
 */
Zarafa.contact.dialogs.ContactNamePanel = Ext.extend(Ext.form.FormPanel, {
	/**
	 * @cfg {Zarafa.contact.data.ContactDetailsParser} parser parser object that will be used to parse information
	 */
	parser: null,
	/**
	 * @cfg {Zarafa.contact.dialogs.parsedNameRecord} parsedData if data is already parsed then it can be passed here,
	 * so no need to prase same data again
	 */
	parsedData: null,
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

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

		config = Ext.applyIf(config, {
			xtype: 'zarafa.contactnamepanel',
			border: false,
			frame: true,
			padding: 5,
			items: this.createFormItems()
		});

		Zarafa.contact.dialogs.ContactNamePanel.superclass.constructor.call(this, config);
	},

	/**
	 * Create the form in which the name specifications can be written
	 * @return {Object} Configuration object for the form
	 */
	createFormItems: function()
	{
		var tplString = '<tpl for="."><div class="x-combo-list-item">{displayText:htmlEncodeDefaultValue(Zarafa.contact.data.config.NBSP)}</div></tpl>';
		return [{
			xtype: 'combo',
			fieldLabel: _('Title'),
			editable: false,
			name: 'display_name_prefix',
			mode: 'local',
			triggerAction: 'all',
			store: {
				xtype: 'arraystore',
				fields: ['displayText'],
				data: Zarafa.contact.data.config.Prefix
			},
			tpl: new Ext.XTemplate(tplString, {
				compiled: true		// compile immediately
			}),
			displayField: 'displayText',
			valueField: 'displayText'
		},{
			xtype: 'textfield',
			anchor: '100%',
			fieldLabel: _('First'),
			name: 'given_name'
		},{
			xtype: 'textfield',
			anchor: '100%',
			fieldLabel: _('Middle'),
			name: 'middle_name'
		},{
			xtype: 'textfield',
			anchor: '100%',
			fieldLabel: _('Last'),
			name: 'surname'
		},{
			xtype: 'combo',
			fieldLabel: _('Suffix'),
			editable: false,
			name: 'generation',
			mode: 'local',
			triggerAction: 'all',
			store: {
				xtype: 'arraystore',
				fields: ['displayText'],
				data: Zarafa.contact.data.config.Suffix
			},
			tpl: new Ext.XTemplate(tplString, {
				compiled: true		// compile immediately
			}),
			displayField: 'displayText',
			valueField: 'displayText'
		},{
			xtype: 'checkbox',
			boxLabel: _('Show this again when name is incomplete or unclear.'),
			ref: 'settingCheckField',
			hideLabel: true,
			checked: container.getSettingsModel().get('zarafa/v1/contexts/contact/show_name_dialog')
		}];
	},

	/**
	 * Load record into form
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record to load
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 */
	update: function(record, contentReset)
	{
		if (!Ext.isDefined(record)) {
			return;
		}

		if (Ext.isEmpty(this.parsedData)) {
			var keys = this.getForm().getValues();
			for (var key in keys) {
				if (!Ext.isEmpty(record.get(key))) {
					this.parsedData = Ext.apply(this.parsedData || {}, record.data);
					break;
				}
			}

			// If the parsed data was not found in the record, check if the parseable string
			// can be found in the record instead.
			if (Ext.isEmpty(this.parsedData) && !Ext.isEmpty(record.get('display_name'))) {
				var newParsedData = this.parser.parseInfo('name', record.get('display_name'));
				this.parsedData = Ext.apply(this.parsedData || {}, newParsedData);
			}
		}

		var form = this.getForm();
		form.setValues(this.parsedData);

		// remove data after it has been put in the form fields, so consecutive requests
		// will use data from record
		this.parsedData = null;
	},

	/**
	 * Update record from form
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record to update
	 * @private
	 */
	updateRecord: function(record)
	{
		var form = this.getForm();

		if (this.settingCheckField.isDirty()) {
			container.getSettingsModel().set('zarafa/v1/contexts/contact/show_name_dialog', this.settingCheckField.getValue());
		}

		record.beginEdit();
		if (Ext.isDefined(this.parser)) {
			record.set('display_name', this.parser.combineInfo('name', form.getValues()));
		}

		form.updateRecord(record);
		record.endEdit();
	}
});

Ext.reg('zarafa.contactnamepanel', Zarafa.contact.dialogs.ContactNamePanel);
Ext.namespace('Zarafa.contact.dialogs');

/**
 * @class Zarafa.contact.dialogs.ContactOptionsPanel
 * @extends Ext.Panel
 * @xtype zarafa.contactoptionspanel
 */
Zarafa.contact.dialogs.ContactOptionsPanel = Ext.extend(Ext.Panel, {
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

		config = Ext.applyIf(config, {
			xtype: 'zarafa.contactoptionspanel',
			layout: {
				type: 'vbox',
				align: 'stretch'
			},
			border: false,
			defaults: {
				bodyStyle: 'padding-top: 5px; padding-left: 6px; padding-right: 5px; background-color: inherit;',
				border: false
			},
			items: [{
				xtype: 'zarafa.recordpropertiespanel',
				flex: 1
			}]
		});

		Zarafa.contact.dialogs.ContactOptionsPanel.superclass.constructor.call(this, config);
	}
});

Ext.reg('zarafa.contactoptionspanel', Zarafa.contact.dialogs.ContactOptionsPanel);
Ext.namespace('Zarafa.contact.dialogs');

/**
 * @class Zarafa.contact.dialogs.ContactPhonePanel
 * @extends Ext.form.FormPanel
 * @xtype zarafa.contactphonepanel
 */
Zarafa.contact.dialogs.ContactPhonePanel = Ext.extend(Ext.form.FormPanel, {
	/**
	 * @cfg {Zarafa.contact.data.ContactDetailsParser} parser parser object that will be used to parse information
	 */
	parser: null,
	/**
	 * @cfg {Zarafa.contact.dialogs.parsedNameRecord} parsedData if data is already parsed then it can be passed here,
	 * so no need to prase same data again
	 */
	parsedData: null,
	/**
	 * @cfg {String} property property that will be modified
	 */
	property: null,
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

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

		config = Ext.applyIf(config, {
			xtype: 'zarafa.contactphonepanel',
			border: false,
			frame: true,
			padding: 5,
			items: this.createFormItems()
		});

		Zarafa.contact.dialogs.ContactPhonePanel.superclass.constructor.call(this, config);
	},

	/**
	 * Create the form in which the phone specifications can be written
	 * @return {Object} Configuration object for the form
	 */
	createFormItems: function()
	{
		return [{
			xtype: 'textfield',
			anchor: '100%',
			fieldLabel: _('Country/Region code'),
			name: 'country_code'
		}, {
			xtype: 'textfield',
			anchor: '100%',
			fieldLabel: _('City/Area code'),
			name: 'city_code'
		}, {
			xtype: 'textfield',
			anchor: '100%',
			fieldLabel: _('Local number'),
			name: 'local_number'
		}, {
			xtype: 'textfield',
			anchor: '100%',
			fieldLabel: _('Extension'),
			name: 'extension'
		}];
	},

	/**
	 * Load record into form
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record to load
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 */
	update: function(record, contentReset)
	{
		if (!Ext.isDefined(record)) {
			return;
		}

		if (Ext.isEmpty(this.parsedData) && !Ext.isEmpty(record.get(this.property))) {
			var newParsedData = this.parser.parseInfo('phone', record.get(this.property));
			this.parsedData = Ext.apply(this.parsedData || {}, newParsedData);
		}

		var form = this.getForm();
		form.setValues(this.parsedData);

		// remove data after it has been put in the form fields, so consecutive requests
		// will use data from record
		this.parsedData = null;
	},

	/**
	 * Update record from form
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record to update
	 * @private
	 */
	updateRecord: function(record)
	{
		var form = this.getForm();

		record.beginEdit();

		/*
		 * set combined property first otherwise form.updateRecord will fire update event
		 * which will overwrite old values on new values.
		 */
		if (Ext.isDefined(this.parser)) {
			record.set(this.property, this.parser.combineInfo('phone', form.getValues()));
		}

		form.updateRecord(record);

		record.endEdit();
	}
});

Ext.reg('zarafa.contactphonepanel', Zarafa.contact.dialogs.ContactPhonePanel);
Ext.namespace('Zarafa.contact.dialogs');

/**
 * @class Zarafa.contact.dialogs.DistlistNotesTab
 * @extends Ext.form.FormPanel
 * @xtype zarafa.distlistnotestab
 *
 * This class is used to create layout of details tab panel.
 */
Zarafa.contact.dialogs.DistlistNotesTab = Ext.extend(Ext.form.FormPanel, {
	/**
	 * @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, {
			xtype: 'zarafa.distlistnotestab',
			// Note Tab
			title: _('Notes'),
			layout: 'fit',
			items: [{
				xtype: 'zarafa.editorfield',
				useHtml: true,
				readOnly: false,
				ref: 'editorField',
				plaintextName: 'body',
				listeners: {
					// Use the afterlayout event to place the placeholder attribute
					afterlayout: function(){
						this.editorField.getEditor().getEl().set({
							placeholder: _('Type your note here…')
						});
					},
					change: this.onPropertyChange,
					scope: this
				}
			}]
		});

		Zarafa.contact.dialogs.DistlistNotesTab.superclass.constructor.call(this, config);
	},

	/**
	 * Load record into form
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record to load
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 */
	update: function(record, contentReset)
	{
		if(Ext.isEmpty(record)) {
			return;
		}

		this.record = record;

		this.editorField.setAllowEdit(true);
		this.editorField.setReadOnly(false);

		this.getForm().loadRecord(this.record);
	},

	/**
	 * Update record from form, Get values from the form.
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record to update
	 * @private
	 */
	updateRecord: function(record)
	{
		this.getForm().updateRecord(record);
	},

	/**
	 * Event handler which is triggered when one of the Input fields
	 * has been changed by the user. It will validate the new value,
	 * and if correct, will apply it to the {@link Zarafa.core.data.IPMRecord record}.
	 * @param {Ext.form.Field} field The {@link Ext.form.Field field} which was changed.
	 * @param {Mixed} newValue The new value
	 * @param {Mixed} oldValue The old value
	 * @private
	 */
	onPropertyChange: function(field, newValue, oldValue)
	{
		this.record.set(field.getName(), newValue);
	}
});

Ext.reg('zarafa.distlistnotestab', Zarafa.contact.dialogs.DistlistNotesTab);
Ext.namespace('Zarafa.contact.dialogs');

/**
 * @class Zarafa.contact.dialogs.DistlistPanel
 * @extends Ext.Panel
 * @xtype zarafa.distlistpanel
 */
Zarafa.contact.dialogs.DistlistPanel = Ext.extend(Ext.Panel, {
	// Insertion points for this class
	/**
	 * @insert context.contact.distlistcontentpanel.tabs
	 * can be used to add extra tabs to distlistcontentpanel by 3rd party plugins
	 * @param {Zarafa.contact.dialogs.DistlistPanel} panel This panel
	 */

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

		Ext.applyIf(config, {
			xtype: 'zarafa.distlistpanel',
			border: false,
			layout: 'fit',
			items: this.createFormPanel()
		});

		Zarafa.contact.dialogs.DistlistPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Function will create form panel and will add tabs in view.
	 * distlistmemberstab and distlistnotestab will be added.
	 */
	createFormPanel: function()
	{
		return [{
				xtype: 'tabpanel',
				activeTab: 0,
				border: false,
				defaults: {
					autoHeight: false,		// autoScroll won't work if height is auto
					frame: true
				},
				items:[{
						xtype: 'zarafa.distlistmemberstab'
					}, {
						xtype: 'zarafa.distlistnotestab',
						autoScroll: false
					},
					container.populateInsertionPoint('context.contact.distlistcontentpanel.tabs', this)
				]
		}];
	}
});

Ext.reg('zarafa.distlistpanel', Zarafa.contact.dialogs.DistlistPanel);
Ext.namespace('Zarafa.contact.ui');

/**
 * @class Zarafa.contact.ui.ContactPhotoContextMenu
 * @extends Ext.menu.Menu
 * @xtype zarafa.contactphotocontextmenu
 */
Zarafa.contact.ui.ContactPhotoContextMenu = Ext.extend(Ext.menu.Menu, {
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			xtype: 'zarafa.contactphotocontextmenu',
			items: [{
				text: _('Change Picture') + '...',
				iconCls: 'icon_change_picture',
				handler: this.onChangePicture,
				scope: this
			}, {
				text: _('Remove Picture'),
				iconCls: 'icon_delete',
				handler: this.onRemovePicture,
				scope: this
			}]
		});

		Zarafa.contact.ui.ContactPhotoContextMenu.superclass.constructor.call(this, config);
	},

	/**
	 * Function is open attachment dialog to change the contact picture.
	 */
	onChangePicture: function()
	{
		var contactGeneralTab = this.parent;
		contactGeneralTab.uploadContactPhoto();
	},

	/**
	 * Function is remove the contact picture and show default contact .
	 */
	onRemovePicture: function()
	{
		var store = this.records.store;
		var contactGeneralTab = this.parent;
		contactGeneralTab.clearContactPhoto(store, this.records);
	}

});

Ext.reg('zarafa.contactphotocontextmenu', Zarafa.contact.ui.ContactPhotoContextMenu);
Ext.namespace('Zarafa.contact.ui');

/**
 * @class Zarafa.contact.ui.ContactPreviewPanel
 * @extends Ext.Panel
 * @xtype zarafa.contactpreviewpanel
 *
 * Panel that previews the contents of contact.
 */
Zarafa.contact.ui.ContactPreviewPanel = Ext.extend(Ext.Panel, {
	/**
	 * @constructor
	 * @param {Object} config configuration object.
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			xtype: 'zarafa.contactpreviewpanel',
			border: false,
			bodyCfg: {
				cls: 'preview-body'
			},
			layout: 'fit',
			items: [{
				xtype: 'zarafa.contactbody'
			}]
		});

		Zarafa.contact.ui.ContactPreviewPanel.superclass.constructor.call(this, config);
	}
});

Ext.reg('zarafa.contactpreviewpanel', Zarafa.contact.ui.ContactPreviewPanel);
Ext.namespace('Zarafa.contact.ui');

/**
 * @class Zarafa.mail.ui.ExportContactContextMenu
 * @extends Ext.menu.Menu
 * @xtype zarafa.exportcontactcontextmenu
 *
 * The ExportContactContextMenu is the context menu that is shown as submenu
 * of the {@link Zarafa.contact.ui.ContactContextMenu ContactContextMenu}.
 */
Zarafa.contact.ui.ExportContactContextMenu = Ext.extend(Zarafa.core.ui.menu.ConditionalMenu, {
	/**
	 * @cfg {Zarafa.core.data.IPMRecord[]} The records to which the actions in
	 * this context menu will apply
	 */
	records: [],

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

		Ext.applyIf(config, {
			xtype: 'zarafa.exportcontactcontextmenu',
			cls: 'k-categories',
			items: [{
				xtype: 'zarafa.conditionalitem',
				text: _('VCF file(s)'),
				iconCls: 'icon_download',
				hideOnDisabled: false,
				beforeShow: this.onExportMenuBeforeShow,
				handler: this.onContextItemVcf,
				scope: this
			}]
		});

		Zarafa.contact.ui.ExportContactContextMenu.superclass.constructor.call(this, config);
	},

	/**
	 * Event handler which is called when the user selects the 'VCF file(s)'
	 * item in the context menu. This will request to download selected contact
	 * as file (RFC6350-formatted vCard stream) with vcf extension.
	 * @private
	 */
	onContextItemVcf: function ()
	{
		Zarafa.common.Actions.openSaveEmlDialog(this.records);
	},

	/**
	 * Event handler which determines if menu items should be disable or not.
	 * It will disable respective menu item if record is not contact.
	 *
	 * @param {Zarafa.core.ui.menu.ConditionalItem} item The item to enable/disable
	 * @param {Zarafa.core.data.IPMRecord[]} records The records which must be checked
	 * to see if the item must be enabled or disabled.
	 * @private
	 */
	onExportMenuBeforeShow: function (item, records)
	{
		Ext.each(records, function(record) {
			if (!record.isMessageClass('IPM.Contact')) {
				// disable menu item and break the loop
				item.setDisabled(true);

				return false;
			}
		}, this);
	}
});

Ext.reg('zarafa.exportcontactcontextmenu', Zarafa.contact.ui.ExportContactContextMenu);
Ext.namespace('Zarafa.hierarchy');

/**
 * @class Zarafa.hierarchy.Actions
 * Hierarchy actions which can be used within {@link Ext.Button buttons}
 * or other {@link Ext.Component components} with action handlers.
 * @singleton
 */
Zarafa.hierarchy.Actions = {
	/**
	 * Open the folder. This will check if the user has rights to open
	 * the given folder, and will call {@link Zarafa.core.Container#selectFolder}
	 * if that is the case. Otherwise if this is a shared store, it will ask
	 * if the store can be closed.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord[]} folders folders to open either as an array of
	 * {@link Zarafa.hierarchy.data.MAPIFolderRecord MAPIFolder} objects or a single object.
	 */
	openFolder: function(folders)
	{
		var folder = Ext.isArray(folders) ? folders[0] : folders;
		// FIXME: We should determine which accessflag we exactly need
		// and check for that flag.
		if (folder.get('access') !== 0) {
			if (folder.isSearchFolder()) {
				var componentType = Zarafa.core.data.SharedComponentType['common.search'];
				Zarafa.core.data.UIFactory.openLayerComponent(componentType, [], {
					'searchFolder': folder,
					'title': folder.get("display_name")
				});
			} else {
				container.selectFolder(folders);
			}
		} else if (folder.isIPMSubTree() || folder.isSharedFolder()) {
			Ext.MessageBox.show({
				title: _('Insufficient permissions'),
				msg: (folder.isIPMSubTree() ?
					_('Not enough permissions to open this store, do you want to remove it from the hierarchy?'):
					_('Not enough permissions to open this folder, do you want to remove it from the hierarchy?')),
				buttons: Ext.MessageBox.YESNO,
				fn: this.onFolderPermissionBox,
				scope: folder
			});
		} else {
			Ext.MessageBox.show({
				title: _('Insufficient Permissions'),
				msg: _('Not enough permissions to open this folder.'),
				buttons: Ext.MessageBox.OK
			});
		}
	},

	/**
	 * Event handler for the {@link Ext.MessageBox#show} which was opened by {@link #openFolder}.
	 * If the "yes" button was pressed, then the folder (the 'this' context for this function)
	 * will be removed from the hierarchy.
	 * @param {String} button The button which was pressed by the user
	 * @private
	 */
	onFolderPermissionBox: function(button)
	{
		if (button === 'yes') {
			if (this.isIPMSubTree()) {
				var store = container.getHierarchyStore();
				var mapistore = this.getMAPIStore();

				store.remove(mapistore);
				store.save(mapistore);
			} else {
				var store = this.store;

				store.remove(this);
				store.save(this);
			}
		}
	},

	/**
	 * Open a {@link Zarafa.common.dialogs.CreateFolderContent CreateFolderContent} for
	 * creating a new folder in the hierarchy.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} parentFolder The parent folder underneath the new
	 * folder must be created by default.
	 * @param {Object} config (optional) Configuration object for opening the ContentPanel
	 */
	openCreateFolderContent: function(parentFolder, config)
	{
		if (!Ext.isDefined(parentFolder)) {
			// Find parentFolder based on currentContext opened
			parentFolder = container.getHierarchyStore().getDefaultFolder(container.getCurrentContext().getName());

			// Still not found then get default 'Inbox' folder
			if (!Ext.isDefined(parentFolder)) {
				parentFolder = container.getHierarchyStore().getDefaultFolder('inbox');
			}
		}

		// create new folderrecord and set 'parent_entryid'
		var record = Zarafa.core.data.RecordFactory.createRecordObjectByObjectType(Zarafa.core.mapi.ObjectType.MAPI_FOLDER, {
			'parent_entryid': parentFolder.get('entryid'),
			'store_entryid': parentFolder.get('store_entryid')
		});
		config = Ext.applyIf(config || {}, { parentFolder: parentFolder, manager: Ext.WindowMgr });
		Zarafa.core.data.UIFactory.openCreateRecord(record, config);
	},

	/**
	 * Opens a {@link Zarafa.hierarchy.dialogs.FolderPropertiesContentPanel FolderPropertiesContentPanel}
	 *
	 * @param {Zarafa.core.data.IPFRecord} record describing folder
	 * @param {Object} config (optional) Configuration object for creating the ContentPanel
	 */
	openFolderPropertiesContent: function(folder, config)
	{
		var componentType = Zarafa.core.data.SharedComponentType['hierarchy.dialog.folderproperties'];
		config = Ext.applyIf(config || {}, {
			manager: Ext.WindowMgr
		});
		Zarafa.core.data.UIFactory.openLayerComponent(componentType, folder, config);
	},

	/**
	 * Opens a {@link Zarafa.hierarchy.dialogs.FolderSizeContentPanel FolderSizeContentPanel}
	 *
	 * @param {Zarafa.core.data.IPFRecord} record describing folder
	 * @param {Object} config (optional) Configuration object for creating the ContentPanel
	 */
	openFolderSizeContent: function(folder, config)
	{
		var componentType = Zarafa.core.data.SharedComponentType['hierarchy.dialog.foldersize'];
		config = Ext.applyIf(config || {}, {
			manager: Ext.WindowMgr
		});
		Zarafa.core.data.UIFactory.openLayerComponent(componentType, folder, config);
	},

	/**
	 * Opens a {@link Zarafa.hierarchy.dialogs.SharedFolderContentPanel SharedFolderContentPanel}
	 *
	 * @param {Zarafa.hierarchy.data.SharedFolderTypes} defaultSelectedFolderType default selected folder in combolist.
	 * @param {Object} config (optional) Configuration object for creating the ContentPanel
	 */
	openSharedFolderContent: function(defaultSelectedFolderType, config)
	{
		var componentType = Zarafa.core.data.SharedComponentType['hierarchy.dialog.opensharedfolder'];
		config = Ext.applyIf(config || {}, {
			defaultSelectedFolderType: defaultSelectedFolderType,
			modal: true
		});

		Zarafa.core.data.UIFactory.openLayerComponent(componentType, undefined, config);
	},

	/**
	 * Opens a {@link Zarafa.hierarchy.dialogs.FolderSelectionContentPanel FolderSelectionContentPanel}.
	 *
	 * @param {Object} config Configuration object for the content panel
	 */
	openFolderSelectionContent: function(config)
	{
		var componentType = Zarafa.core.data.SharedComponentType['hierarchy.dialog.folderselection'];
		config = Ext.applyIf(config || {}, {
			modal: true,
			folder: container.getHierarchyStore().getDefaultFolder('inbox')
		});

		Zarafa.core.data.UIFactory.openLayerComponent(componentType, undefined, config);
	},

	/**
	 * Append and update the browser tab title based on the unread counter of Inbox folder in hierarchy.
	 * This function will set and update title based on title_counter setting which resides
	 * in {@link Zarafa.settings.ui.SettingsDisplayWidget SettingsDisplayWidget}.
	 *
	 * @param {Zarafa.hierarchy.data.HierarchyStore} hierarchyStore to get unread mail count.
	 */
	setTitleCounter: function(hierarchyStore)
	{
		// First of all check if title counter plugin's setting available else check main settings.
		var titleCounterSetting = container.getSettingsModel().getOneOf('zarafa/v1/plugins/titlecounter/enable', 'zarafa/v1/main/title_counter/show');
		if (titleCounterSetting === true) {
      var title = container.getServerConfig().getWebappTitle();
      var unreadCounter = hierarchyStore.getDefaultFolder('inbox').get('content_unread');
      if (unreadCounter > 0) {
        title = '(' + unreadCounter + ') ' + title;
      }

      Ext.getDoc().dom.title = title;
    }
	},

	/**
	 * Empty a folder using batched deletion with progress notifications.
	 * Used for folders with many items (>= 500) to provide user feedback
	 * instead of a single long-running request that freezes the UI.
	 *
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder The folder to empty
	 * @param {Number} batchSize (optional) Number of items to delete per batch, defaults to 500
	 */
	emptyFolderBatched: function(folder, batchSize)
	{
		batchSize = batchSize || 500;
		var totalItems = folder.get('content_count') || 0;
		var deletedSoFar = 0;
		var folderName = Ext.util.Format.htmlEncode(folder.get('display_name'));

		// Show persistent progress notification
		var notifyRef = container.getNotifier().notify('info.emptyfolder',
			_('Empty folder'),
			String.format(_('Emptying {0}\u2026 (0%)'), folderName),
			{ persistent: true }
		);

		var sendBatch = function() {
			var responseHandler = new Zarafa.core.data.AbstractResponseHandler();
			responseHandler.doProgress = function(response) {
				deletedSoFar += response.deleted_count;

				if (response.done) {
					// Destroy persistent notification and show transient success
					container.getNotifier().notify('info.emptyfolder', null, null, {
						destroy: true,
						reference: notifyRef
					});
					container.getNotifier().notify('info.emptyfolder',
						_('Empty folder'),
						String.format(_('Successfully emptied {0}. ({1} items removed)'), folderName, deletedSoFar)
					);
				} else {
					// Update progress notification in-place
					var pct = totalItems > 0 ? Math.round(deletedSoFar / totalItems * 100) : 0;
					container.getNotifier().notify('info.emptyfolder',
						_('Empty folder'),
						String.format(_('Emptying {0}\u2026 ({1}%)'), folderName, pct),
						{ update: true, reference: notifyRef }
					);
					// Send next batch
					sendBatch();
				}
			};
			responseHandler.doFolders = function() {
				// Folder update is handled by the bus notification system
			};

			var request = container.getRequest();
			request.reset();
			request.addRequest(
				'hierarchymodule',
				'emptyfolder_batch',
				{
					entryid: folder.get('entryid'),
					store_entryid: folder.get('store_entryid'),
					parent_entryid: folder.get('parent_entryid'),
					batch_size: batchSize
				},
				responseHandler
			);
			request.send();
		};

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

/**
 * @class Zarafa.hierarchy.data.HierarchyTreeLoader
 * @extends Ext.tree.TreeLoader
 *
 * A Special treeloader to be used by the {@link Zarafa.hierarchy.ui.HierarchyTreePanel HierarchyTree}.
 * This will dynamically load the child nodes for a given node by obtaining the subfolders of
 * the folder related to the given node.
 */
Zarafa.hierarchy.data.HierarchyTreeLoader = Ext.extend(Ext.tree.TreeLoader, {
	/**
	 * @cfg {Zarafa.hierarchy.ui.Tree} tree The tree on which this treeloader
	 * is applied.
	 */
	tree: undefined,

	/**
	 * @cfg {Zarafa.core.data.HierarchyStore} store store which will be used to get data for hierarchy.
	 */
	store: undefined,

	/**
	 * @cfg {Object} config option for {@link Zarafa.hierarchy.ui.FolderNode foldernode}
	 */
	nodeConfig: undefined,

	/**
	 * @cfg {Boolean} deferredLoading True to defer updating the Hierarchy when the panel
	 * is currently not visible. The parent {@link Ext.Container} which contains the
	 * {@link Ext.layout.CardLayout}, is stored in the {@link #deferredLoadingActiveParent}.
	 */
	deferredLoading: false,

	/**
	 * When {@link #deferredLoading} is true, this property contains the {@link Ext.Container}
	 * to which this loader will be listening to determine which Container is active.
	 * This will be initialized during {@link #onTreeAfterRender}.
	 * @property
	 * @type Ext.Container
	 * @private
	 */
	deferredLoadingActiveParent: undefined,

	/**
	 * When {@link #deferredLoading} is true, this property indicates if a call to
	 * {@link #doHierarchyLoad} has been made and has been scheduled. This implies
	 * that no events from the {@link #store} need to be handled, as a full refresh
	 * is pending.
	 * @property
	 * @type Boolean
	 * @private
	 */
	isDeferred: false,

	/**
	 * This will be used in {@link #directFn} to determine whether to set defer or not when expanding a node.
	 * For normal expansion its set to true to avoid browser crash while loading larger hierarchy.
	 * It will be set to false in function {@link #onHierarchyAddFolder} to add nested folders in proper order.
	 */
	deferExpandNode: true,

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

		if (!config.directFn) {
			// Small hack, ExtJs calls directFn in the DOMWindow scope, rather
			// then the scope of this class. Since we wish to extend the TreeLoader
			// rather then passing the directFn as a configuration option, we are now
			// just going to create a delegate and force the function into the correct
			// scope.
			config.directFn = this.directFn.createDelegate(this);
		}

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

		// If the tree is already rendered, call onTreeAfterRender directly,
		// otherwise add the event handler.
		if (this.tree.rendered) {
			this.onTreeAfterRender();
		} else {
			this.tree.on('afterrender', this.onTreeAfterRender, this, { single: true });
		}
	},

	/**
	 * Event handler which is fired when the {@link #tree} is being rendered. This will
	 * call {@link #bindStore} to hook all events from the {@link #store}.
	 * @private
	 */
	onTreeAfterRender: function()
	{
		this.bindStore(this.store, true);

		// If deferred loading is enabled, then we are going to need the parent
		// container which will have to be activated before we will load it.
		// If we can't find the parent, then don't defer loading.
		if (this.deferredLoading === true) {
			// Search for the desired container which can be activated.
			if (this.isParentCardLayout(this.tree)) {
				this.deferredLoadingActiveParent = this.tree;
			} else {
				this.deferredLoadingActiveParent = this.tree.findParentBy(this.isParentCardLayout, this);
				if (!this.deferredLoadingActiveParent) {
					this.deferredLoading = false;
				}
			}
		}
	},

	/**
	 * Returned true if the {@link Ext.Component#ownerCt owner} of the given {@link Ext.Container}
	 * contains the {@link Ext.layout.CardLayout}. This function is given in
	 * {@link #onTreeAfterRender} to determine the {@link #deferredLoadingActiveParent}.
	 * @param {Ext.Container} ct The container to check
	 * @return {Boolean} True if the parent of the given container has the CardLayout
	 * @private
	 */
	isParentCardLayout: function(ct)
	{
		return ct.ownerCt && ct.ownerCt.layout && ct.ownerCt.layout.type === 'card';
	},

	/**
	 * Bind a store to this loader. This will initialize all required event handlers.
	 * @param {Zarafa.core.data.HierarchyStore} store The store to bind
	 * @param {Boolean} init True when this is called during initialization.
	 * @private
	 */
	bindStore: function(store, init)
	{
		if (init !== true && this.store === store) {
			return;
		}

		if (this.store) {
			this.store.un('load', this.onHierarchyLoad, this);
			this.store.un('remove', this.onHierarchyStoreRemove, this);
			this.store.un('addFolder', this.onHierarchyAddFolder, this);
			this.store.un('updateFolder', this.onHierarchyUpdateFolder, this);
			this.store.un('removeFolder', this.onHierarchyRemoveFolder, this);
		}

		this.store = store;
		if (this.store) {
			this.store.on({
				'load': this.onHierarchyLoad,
				'remove': this.onHierarchyStoreRemove,
				'addFolder': this.onHierarchyAddFolder,
				'updateFolder': this.onHierarchyUpdateFolder,
				'removeFolder': this.onHierarchyRemoveFolder,
				'scope': this
			});
		}
	},

	/**
	 * Event handler for the {@link Zarafa.hierarchy.data.HierarchyStore#load load} event.
	 * This will add the loaded records into the tree as Store nodes.
	 * @param {Ext.data.Store} store The store which fired the event
	 * @param {Ext.data.Record[]} records The records which were loaded into the store
	 * @param {Object} options The additional options that were passed for loading the data.
	 * @private
	 */
	onHierarchyLoad: function(store, records, options)
	{
		// If we don't want to append a new store into the tree,
		// then remove all currently existing nodes and rebuild
		// the entire tree from scratch.
		if (options.add !== true) {
			this.tree.getRootNode().removeAll(true);
		}

		var parentCt = this.deferredLoadingActiveParent;

		if (this.deferredLoading !== true || parentCt === parentCt.ownerCt.layout.activeItem) {
			this.doHierarchyLoad();
		} else {
			// We are going to defer to doHierarchyLoad() action,
			// set isDeferred to true, so we don't need to perform
			// update event handlers.
			this.isDeferred = true;
			this.deferredLoadingActiveParent.on('activate', this.doHierarchyLoad, this, { single: true });
		}
	},

	/**
	 * Called by {@link #onHierarchyLoad} to start (re)loading the hierarchy.
	 * @private
	 */
	doHierarchyLoad: function()
	{
		var rootNode = this.tree.getRootNode();

		if (this.fireEvent('beforeload', this, rootNode, this.directFn) !== false) {
			this.directFn(rootNode.id, this.doHierarchyLoadCallback.createDelegate(this));
		}

		// The deferred action has been completed,
		// we can now listen to update events again.
		this.isDeferred = false;
	},

	/**
	 * Callback function for {@link #directFn} as used by {@link #doHierarchyLoad}.
	 * @param {Object} data The data as returned by the server
	 * @param {Object} response The response as returned by the server
	 * @private
	 */
	doHierarchyLoadCallback: function(data, response)
	{
		var rootNode = this.tree.getRootNode();

		for (var i = 0, len = data.length; i < len; i++) {
			var item = data[i];
			var folder = item.folder;
			// Check if the node already exists or not.
			var treeNode = rootNode.findChildByEntryId(folder.get('entryid'));
			if (!treeNode) {
				var node = this.createNode(item);
				rootNode.appendChild(node);
			} else if (treeNode.attributes.folder !== folder) {
				treeNode.attributes.folder = folder;
				treeNode.reload();
			} else if (folder.isFavoritesRootFolder()) {
        // Check if favorite node and favorite folder doesn't have same number of children
        // then reload the favorite node.
        var favoritesStore = folder.getMAPIStore().getFavoritesStore();
        if (treeNode.childNodes.length !== favoritesStore.getCount()) {
          treeNode.reload();
        }
      }
		}
		// when we close the shared store suggested contact folder
		// of shared store was removed but node was not removed from
		// contact context tree panel because we don't refresh/reload the tree panel
		// nodes when we switch the context so here we just reload the root node.
		if(rootNode.childNodes.length !== data.length) {
			rootNode.reload();
		}

		this.fireEvent('load', this, rootNode, response);
	},

	/**
	 * Handles the 'remove' even from the {@link Zarafa.hierarchy.data.HierarchyStore hierarchyStore}.
	 * This will lookup any folder inside the tree which belongs to the given {@link Zarafa.hierarchy.data.MAPIStoreRecord},
	 * and removes it from the tree.
	 * @param {Zarafa.hierarchy.data.HierarchyStore} store The store which fired the event
	 * @param {Zarafa.core.data.IPFRecord} record The MAPIStoreRecord which was removed from the hierarchy
	 * @param {Number} index The index from where the store was removed
	 * @private
	 */
	onHierarchyStoreRemove: function(store, record)
	{
		// A call to doHierarchyLoad is pending,
		// no need to execute this event handler.
		if (this.isDeferred === true) {
			return;
		}

		var rootNode = this.tree.getRootNode();
		var treeNode = rootNode.findChildStoreByEntryId(record.get('store_entryid'));
		if (treeNode) {
			treeNode.remove(true);
			if (this.deferredLoading === true) {
				this.deferredLoadingActiveParent.on('activate', this.doHierarchyLoad, this, { single: true });
			}
		}
	},

	/**
	 * Handles the add event on folder from the global hierarchy object. This will add the folder to the tree.
	 *
	 * @param {Zarafa.hierarchy.data.HierarchyStore} store The store which fired the event
	 * @param {Zarafa.hierarchy.data.MAPIStoreRecord} mapiStore mapi store in which new folders are added.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord/Zarafa.hierarchy.data.MAPIFolderRecord[]} folder folders which are added in hierarchy.
	 * @private
	 */
	onHierarchyAddFolder: function(store, mapiStore, record)
	{
		// A call to doHierarchyLoad is pending,
		// no need to execute this event handler.
		if (this.isDeferred === true) {
			return;
		}

		if (Array.isArray(record)) {
			for (var i = 0, len = record.length; i < len; i++) {
				this.onHierarchyAddFolder(store, mapiStore, record[i]);
			}
			return;
		}

		if (record.phantom !== true) {
			if (this.tree.nodeFilter(record)) {
				var treeNode;
				// If record/folder is favorites mark and active context is other then Mail or Home and "show all folders"
				// check box is unchecked then don't add new tree node in hierarchy.
				if (record.isFavoritesFolder() && (record.isContainerClass('IPF.Note') || !this.tree.hasFilter())) {
					treeNode = this.tree.getNodeById("favorites-"+record.get('entryid'));
				} else {
					treeNode = this.tree.getNodeById(record.get('entryid'));
				}

				if (!treeNode) {
					var parentNode = this.getFilteredParentNode(record);
					var nodeType = 'folder';

					if (!parentNode) {
						parentNode = this.tree.getRootNode();
						nodeType = 'rootfolder';
					}

					var newNode = this.createNode(Ext.apply({ nodeType: nodeType, folder: record }, this.nodeConfig));
					parentNode.appendChild(newNode);
					parentNode.expand();
					this.deferExpandNode = false;
				}
			}
		}
	},

	/**
	 * Handles the update event on folder from the global hierarchy object. Updates folder in tree.
	 * @param {Zarafa.hierarchy.data.HierarchyStore} store The store which fired the event
	 * @param {Zarafa.hierarchy.data.MAPIStoreRecord} mapiStore mapi store in which folders are updated.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} record folders which are updated in hierarchy.
	 * @private
	 */
	onHierarchyUpdateFolder: function(store, mapiStore, record)
	{
		// A call to doHierarchyLoad is pending,
		// no need to execute this event handler.
		if (this.isDeferred === true) {
			return;
		}

		var treeNode = this.tree.getTreeNode(record);
		if (!treeNode) {
			// Don't add new node in hierarchy if its parent node is
			// not expanded yet. As Extjs follow the lazy rendering so when we expand the
			// parent node, tree automatically creates respective child nodes
			var parentNode = this.getFilteredParentNode(record);
			if (Ext.isDefined(parentNode) && (!parentNode || !parentNode.isExpanded())) {
				return;
			}
			// treeNode not found, so apparently the folder change might
			// have made this folder visible in the current hierarchy.
			// Let the 'addFolder' event handler handle this case.
			this.onHierarchyAddFolder(store, mapiStore, record);
		} else if (!this.tree.nodeFilter(record)) {
			// treeNode was found, but it doesn't pass our filter...
			// That means the update affected that the folder is no
			// longer visible in the tree...
			// Let the 'removeFolder' event handler handle this case.
			this.onHierarchyRemoveFolder(store, mapiStore, record);
		} else {
			var curParentNode = treeNode.parentNode;
			var newParentNode = this.getFilteredParentNode(record);
			if (!newParentNode) {
				newParentNode = this.tree.getRootNode();
			}

			if (curParentNode !== newParentNode) {
				// It doesn't matter if there is a parentNode or not,
				// when we do appendChild, the parentNode will detect
				// if this is a move or not.
				newParentNode.appendChild(treeNode);
			} else {
				treeNode.updateUI(record);
				// Update favorites folder tree node in hierarchy if selected folder is not favorites folder but
				// it exists in favorites store(record was marked as favorites).
				if(!record.isFavoriteFolder() && record.existsInFavorites()) {
					var favRecord = record.getFavoritesFolder();
					var favContentUnread = favRecord.get('content_unread');
					var recordContentUnread = record.get('content_unread');
					if( favContentUnread !== recordContentUnread) {
						favRecord.set('content_unread', recordContentUnread, false);
					}
					treeNode = this.tree.getTreeNode(favRecord);
					// treeNode is undefined in case where webapp was reloaded recently and
					// favorites tree was in collapsible mode. Extjs is follow the lazy rendering
					// so tree node was only created when tree is in expanded mode or user expand it.
					if (Ext.isDefined(treeNode)) {
						treeNode.updateUI(favRecord);
					}
				}
			}
		}
	},

	/**
	 * Handles the delete event on folder from global hierarchy object. Deletes folder from tree.
	 * @param {Zarafa.hierarchy.data.HierarchyStore} store The store which fired the event
	 * @param {Zarafa.hierarchy.data.MAPIStoreRecord} mapiStore mapi store in which folders are removed.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder folders which are removed from hierarchy.
	 * @private
	 */
	onHierarchyRemoveFolder: function(store, mapiStore, record)
	{
		// A call to doHierarchyLoad is pending,
		// no need to execute this event handler.
		if (this.isDeferred === true) {
			return;
		}
		var treeNode = this.tree.getTreeNode(record);
		if (treeNode) {
			treeNode.remove(true);
		}

		if (record.existsInFavorites()) {
			treeNode = this.tree.getTreeNode(record.getFavoritesFolder());
			if (treeNode) {
				treeNode.remove(true);
			}
		}
	},

	/**
	 * This is called when a node in the HierarchyTree is being expanded, this will read
	 * the {@link Zarafa.hierarchy.data.MAPIFolderRecord} to find the child nodes which
	 * are positioned below the expanded node.
	 *
	 * @param {String} node The ID of the node which is being expanded
	 * @param {Function} fn The function which must be called with the JSON data of
	 * the nodes below the provided node.
	 * @private
	 */
	directFn: function(node, fn)
	{
		var treeNode = this.tree.getNodeById(node);
		var data = [];

		// The root can not be loaded through this function,
		// we only handle that through the 'load' event handler
		// onHierarchyLoad which will generate the special Store nodes.
		if (treeNode.isRoot) {
			var stores = this.store.getRange();
			for (var i = 0, len = stores.length; i < len; i++) {
				var store = stores[i];
				var folder = store.getSubtreeFolder();

				if (folder && this.tree.IPMSubTreeNodeFilter(folder)) {
					// The IPM_SUBTREE of a shared stores doesn't need to be shown when
					// a filter has been applied and the IPM_SUBTREE itself isn't a shared folder.
					// This could be the case when we only loaded the Inbox or Calendar of the
					// other store, but we didn't load the entire store itself. In that situation
					// the IPM_SUBTREE is really a fake entry which exists for displaying in
					// a non-filtered tree.
					var visibleRoot = !this.tree.hasFilter() || !store.isSharedStore() || folder.isSharedFolder();

					if (visibleRoot && this.tree.nodeFilter(folder)) {
						data.push(Ext.apply({ nodeType: 'rootfolder', folder: folder }, this.nodeConfig));
					} else {
						data = data.concat(this.getFilteredChildNodes(folder, 'rootfolder'));
					}
				}

				var favoritesFolder = store.getFavoritesRootFolder();
				if (store.isDefaultStore() && Ext.isDefined(favoritesFolder) && this.tree.nodeFilter(favoritesFolder)) {
					data.push(Ext.apply({ nodeType: 'rootfolder', folder: favoritesFolder }, this.nodeConfig));
				}
			}

			fn(data, {status: true});
		} else {
			// When we are not loading the root, we want to defer the loading
			// for 1 ms. This helps with large hierarchies where the JS would normally
			// try to load the entire tree in a single thread which might take so long
			// that the browser will kill it.
			/*eslint no-unused-vars: ["error", { "varsIgnorePattern": "defer" }]*/

			// Note: Adding a defer while adding folders into hierarchy makes problem in finding parent node.
			// And because of this nodes gets attached to its grand parent node and so on.
			// So, only do defer while doing normal expand of a node.
			// Ref: KW-1494.
			data = this.getFilteredChildNodes(treeNode.getFolder(), 'folder');
			if (this.deferExpandNode) {
				var defer = function(node, fn, data) {
					fn(data, { status: true });
				}.defer(1, this, [node, fn, data]);
			} else {
				this.deferExpandNode = true;
				fn(data, { status: true });
			}
		}
	},

	/**
	 * Obtain the list of nodes which are positioned below the given {@link Zarafa.hierarchy.data.MAPIFolderRecord folder}.
	 * If a subfolder is {@link Zarafa.hierarchy.ui.Tree#nodeFilter filtered} the subfolder will not be added itself,
	 * but subfolder will be used for a recursive call to {@link #getFilteredChildNodes} to see if the sub-subfolders
	 * do match the filter.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder The folder which is checked
	 * @param {String} nodeType The nodeType which must be applied to each node
	 * @return {Object[]} The array of nodes which must be created as subfolders
	 * @private
	 */
	getFilteredChildNodes: function(folder, nodeType)
	{
		var subfolders = folder.getChildren();
		var nodes = [];

		for (var i = 0, len = subfolders.length; i < len; i++) {
			var subfolder = subfolders[i];
			if (this.tree.nodeFilter(subfolder)) {
				nodes.push(Ext.apply({ nodeType: nodeType, folder: subfolder }, this.nodeConfig));
			} else if (subfolder.get('has_subfolder')) {
				nodes = nodes.concat(this.getFilteredChildNodes(subfolder, nodeType));
			}
		}

		return nodes;
	},

	/**
	 * Obtain the parent node for the given {@link Zarafa.hierarchy.data.MAPIFolderRecord folder},
	 * normally only the node with an id which matches the 'parent_entryid' of the original
	 * folder would be sufficient, but when the tree is filtered, this might not be the case.
	 * Hence we start calling this function recursively until we find the first node in the chain
	 * which should act as parent or grandparent of the given folder.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder The folder which is being checked
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} base The root folder for the search, if
	 * this folder is encountered as parent, this search ends.
	 * @return {Ext.tree.TreeNode} The parent node for the folder
	 * @private
	 */
	getFilteredParentNode: function(folder, base)
	{
		var parentfolder;
		if(folder.isFavoritesFolder()) {
			parentfolder = folder.getFavoritesRootFolder();
		} else {
			parentfolder = folder.getParentFolder();
		}

		var node = false;

		if (parentfolder) {
			if (parentfolder === base) {
				node = base;
			} else if (this.tree.nodeFilter(parentfolder)) {
				node = this.tree.getNodeById(parentfolder.get('entryid'));
			}

			if (!node) {
				node = this.getFilteredParentNode(parentfolder);
			}
		}

		return node;
	},

	/**
	 * Add extra attributes for a new {@link Zarafa.hierarchy.ui.FolderNode folderNode} which is about
	 * to be created. This will check the {@link Zarafa.hierarchy.ui.FolderNode#folder folder} to
	 * see what properties must be set.
	 * @param {Object} attr The attributes which will be used to create the node
	 * @return {Zarafa.hierarchy.ui.FolderNode} The created node
	 */
	createNode: function(attr)
	{
		var folder = attr.folder;

		if (folder) {
			// To uniquely identify the favorites tree nodes we append the "favorites-" key word with node id
			// when the node is created.
			attr.id = folder.isFavoritesFolder() ? "favorites-" + folder.get('entryid') : folder.get('entryid');
			if (folder.isFavoritesRootFolder()) {
				attr.leaf = folder.get('assoc_content_count') === 0;
			} else {
				attr.leaf = !folder.get('has_subfolder');
			}

			attr.uiProvider = Zarafa.hierarchy.ui.FolderNodeUI;
			attr.expanded = this.tree.isFolderOpened(folder);
			attr.allowDrag = !folder.isDefaultFolder() && !folder.isSearchFolder();
		}

		return Zarafa.hierarchy.data.HierarchyTreeLoader.superclass.createNode.apply(this, arguments);
	},

	/**
	 * Destroys the TreeLoader
	 */
	destroy: function()
	{
		this.bindStore(null);
		Zarafa.hierarchy.data.HierarchyTreeLoader.superclass.destroy.apply(this, arguments);
	}
});
Ext.namespace('Zarafa.hierarchy.dialogs');

/**
 * @class Zarafa.common.attachment.dialogs.MixAttachItemPanel
 * @extends Ext.Panel
 * @xtype zarafa.brokenfilespanel
 *
 * Panel to list out the broken eml while import from PC-drive.
 */
Zarafa.hierarchy.dialogs.BrokenFilesPanel = Ext.extend(Ext.Panel, {
	/**
	 * @cfg {Array} records The {@link Zarafa.core.data.IPMRecord record(s)} which are being
	 * used in this panel
	 */
	records: undefined,

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

		Ext.applyIf(config, {
			xtype: 'zarafa.brokenfilespanel',
			layout: {
				type: 'vbox',
				align: 'stretch'
			},
			border: false,
			items: this.getMixAttachComponents()
		});

		Zarafa.hierarchy.dialogs.BrokenFilesPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Prepare all the components of warning dialog.
	 * @return {Array} Array containing all the required components
	 */
	getMixAttachComponents: function()
	{
		return [{
			xtype: 'displayfield',
			value: _('Unable to import:'),
			hideLabel: true,
			htmlEncode: true
		}, {
			xtype: 'textarea',
			hideLabel: true,
			flex: 1,
			readOnly: true,
			listeners: {
				afterrender: this.onAfterRenderTextArea,
				scope: this
			}
		}, {
			xtype: 'displayfield',
			value: _('The files are not valid'),
			hideLabel: true,
			htmlEncode: true
		}];
	},

	/**
	 * Handler which is called when 'textarea' is rendered. it prepares a list of all the unsupported attachments,
	 * which is to be shown in the text area, line by line.
	 * @param {Ext.form.TextArea} leftoutTextArea The textarea
	 * @return {String} list of unsupported attachments, separated by escaping sequence
	 */
	onAfterRenderTextArea: function(leftOutTextArea)
	{
		var leftOutList = "";

		Ext.each(this.records, function(record) {
			leftOutList += record.name + "\n" ;
		});

		leftOutTextArea.setValue(leftOutList);
	}
});

Ext.reg('zarafa.brokenfilespanel', Zarafa.hierarchy.dialogs.BrokenFilesPanel);
Ext.namespace('Zarafa.hierarchy.dialogs');

/**
 * @class Zarafa.hierarchy.dialogs.CreateFolderPanel
 * @extends Ext.Panel
 * @xtype zarafa.createfolderpanel
 */
Zarafa.hierarchy.dialogs.CreateFolderPanel = Ext.extend(Ext.Panel, {
	/**
	 * @cfg {Zarafa.hierarchy.data.MAPIFolderRecord} parentFolder (optional) The parent folder
	 * underneath the new folder will be created.
	 */
	parentFolder: undefined,
	/**
	 * @cfg {String} preferredContainerClass (optional) The preferred container
	 * class for the newly created Folder.
	 */
	preferredContainerClass: undefined,
	/**
	 * @constructor
	 * @param {Object} config Configuration structure
	 */
	constructor: function(config)
	{
		config = config || {};

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

		config = Ext.applyIf(config, {
			layout: {
				type: 'vbox',
				align: 'stretch'
			},
			border: false,
			items: [ this.createPanel() ],
			buttons: [{
				text: _('Ok'),
				handler: this.onSubmit,
				scope: this
			},{
				text: _('Cancel'),
				handler: this.onCancel,
				scope: this
			}]
		});

		Zarafa.hierarchy.dialogs.CreateFolderPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Creates body for {@link Zarafa.hierarchy.dialogs.CreateFolderContentPanel CreateFolderContentPanel}
	 * @return {Object} Configuration object for the tree panel.
	 * @private
	 */
	createPanel: function()
	{
		return {
			xtype: 'panel',
			layout: 'form',
			border: false,
			flex: 1,
			bodyStyle: 'background-color: inherit;',
			defaults: {
				anchor:'100%',
				style: 'margin-bottom: 10px;'
			},
			labelAlign: 'top',

			items: [{
				xtype: 'textfield',
				fieldLabel: _('Name'),
				cls: 'form-field-name',
				ref: '../newNameField',
				listeners: {
					scope: this,
					specialkey: function(field, e) {
						if (e.getKey() == e.ENTER) {
							this.onSubmit();
						}
					}
				}
			},{
				xtype: 'combo',
				fieldLabel: _('Folder contains'),
				cls: 'form-field-folder-contains',
				typeAhead: true,
				triggerAction: 'all',
				lazyRender: true,
				mode: 'local',
				store: new Ext.data.ArrayStore({
					fields: [ 'value', 'displayText'],
					data: [
						['IPF.Appointment', _('Calendar Items')],
						['IPF.Note', _('Mail and Post Items')],
						['IPF.Contact', _('Contacts Items')],
						['IPF.StickyNote', _('Note Items')],
						['IPF.Task', _('Task Items')]
					]
				}),
				editable: false,
				valueField: 'value',
				displayField: 'displayText',
				ref: '../folderListCombo'
			},{
				xtype: 'zarafa.hierarchytree',
				fieldLabel: _('Select where to place the folder'),
				cls: 'form-field-hierarchy',
				border: true,
				forceLayout: true,
				anchor: '100% 65%',
				ref: '../hierarchyTree',
				treeSorter: true,
				hideTodoList: true,
				hideFavorites: true
			}]
		};
	},

	/**
	 * Initialize the event handlers
	 * @protected
	 */
	initEvents: function()
	{
		Zarafa.hierarchy.dialogs.CreateFolderPanel.superclass.initEvents.apply(this, arguments);

		// If there is a folder we should select, the enable the 'load' event handler
		// as we will have to wait until the correct node has been loaded.
		if (this.parentFolder) {
			this.mon(this.hierarchyTree, 'load', this.onTreeNodeLoad, this);
		}
		this.mon(this.hierarchyTree.getSelectionModel(), 'selectionchange', this.onHierarchyNodeSelect, this);
	},

	/**
	 * Fired when the {@link Zarafa.hierarchy.ui.Tree Tree} fires the {@link Zarafa.hierarchy.ui.Tree#load load}
	 * event. This function will try to select the {@link Ext.tree.TreeNode TreeNode} in
	 * {@link Zarafa.hierarchy.ui.Tree Tree} initially. When the given node is not loaded yet, it will try again
	 * later when the event is fired again.
	 *
	 * @private
	 */
	onTreeNodeLoad: function()
	{
		// If the folder could be selected, then unregister the event handler.
		if (this.hierarchyTree.selectFolderInTree(this.parentFolder)) {
			this.mun(this.hierarchyTree, 'load', this.onTreeNodeLoad, this);
			// Force focus on input field
			this.newNameField.focus();
		}
	},

	/**
	 * Function is used to update values of form fields when ever
	 * an updated {@link Zarafa.core.data.IPMRecord record} is received.
	 * @param {Zarafa.core.data.IPMRecord} 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;
	},

	/**
	 * Fired when a node has been selected from the {@link Zarafa.hierarchy.ui.Tree HierarchyTree}.
	 * This will update the {@link Ext.form.ComboBox ComboBox} containing the container types to the
	 * selected folders container type.
	 *
	 * @param {Ext.tree.DefaultSelectionModel} model The selection model which was used to select the
	 */
	onHierarchyNodeSelect: function(model)
	{
		var node = model.getSelectedNode();
		if (node) {
			var folder = node.getFolder();

			if (folder) {
				if (this.folderListCombo.findRecord('value', folder.get('container_class'))) {
					this.folderListCombo.setValue(folder.get('container_class'));
				} else if (this.preferredContainerClass) {
					this.folderListCombo.setValue(this.preferredContainerClass);
				} else {
					// As a last resort, but this means we are setting some invalid
					// value into the combobox...
					this.folderListCombo.setValue(folder.get('container_class'));
				}
			} else if (this.preferredContainerClass) {
				this.folderListCombo.setValue(this.preferredContainerClass);
			}
		}
	},

	/**
	 * First validates submission and calls {@link Zarafa.hierarchy.data.HierarchyStore HierarchyStore} to
	 * create new folder.
	 * @private
	 */
	onSubmit: function()
	{
		if (this.newNameField.getValue().trim().length === 0) {
			Ext.MessageBox.show({
				title: _('No folder name'),
				msg: _("You must specify a name."),
				buttons: Ext.MessageBox.OK,
			});

			return;
		}

		var node = this.hierarchyTree.getSelectionModel().getSelectedNode();
		if (node) {
			var newParentFolder = node.getFolder();

			this.record.beginEdit();
			this.record.set('parent_entryid', newParentFolder.get('entryid'));
			this.record.set('store_entryid', newParentFolder.get('store_entryid'));
			this.record.set('display_name', this.newNameField.getValue());
			this.record.set('container_class', this.folderListCombo.getValue());
			this.record.endEdit();

			this.dialog.saveRecord();
		} else {
			Ext.MessageBox.show({
				title: _('No destination'),
				msg: _("You must select a destination folder."),
				buttons: Ext.MessageBox.OK,
			});

			return;
		}
	},

	/**
	 * Closes {@link Zarafa.core.ui.CreateFolderContentPanel CreateFolderContentPanel} contentpanel
	 * @private
	 */
	onCancel: function()
	{
		this.dialog.close();
	}
});

Ext.reg('zarafa.createfolderpanel', Zarafa.hierarchy.dialogs.CreateFolderPanel);
Ext.namespace('Zarafa.hierarchy.dialogs');

/**
 * @class Zarafa.hierarchy.dialogs.FolderPropertiesGeneralTab
 * @extends Ext.form.FormPanel
 * @xtype zarafa.folderpropertiesgeneraltab
 *
 * General tab in the {@link Zarafa.hierarchy.dialogs.FolderPropertiesContentPanel}
 * that is used to show/change folder properties.
 */
Zarafa.hierarchy.dialogs.FolderPropertiesGeneralTab = Ext.extend(Ext.form.FormPanel, {
	/**
	 * @constructor
	 * @param {Object} config
	 */
	constructor: function(config)
	{
		config = config || {};

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

		Ext.applyIf(config, {
			xtype: 'zarafa.folderpropertiesgeneraltab',
			cls: 'tab-general',
			border: false,
			bodyStyle: 'background-color: inherit;',
			labelAlign: 'left',
			defaults: {
				border: false,
				xtype: 'panel',
				layout: 'form'
			},

			items: [
				this.createNameInfoPanel(),
				this.createDescriptionInfoPanel(),
				this.createContentInfoPanel()
			]
		});

		Zarafa.hierarchy.dialogs.FolderPropertiesGeneralTab.superclass.constructor.call(this, config);
	},
	/**
	 * @return {Object} Configuration object for the panel which shows folders properties
	 * @private
	 */
	createNameInfoPanel: function()
	{
		return {
			style: {
				borderBottomWidth: '1px',
				borderBottomStyle: 'solid'
			},
			items: [{
				xtype: 'displayfield',
				cls: 'display-name',
				name: 'display_name',
				htmlEncode: true,
				ref: 'displayField',
				hideLabel: true
			}],
			ref: 'nameInfoPanel'
		};
	},
	/**
	 * @return {Object} Configuration object for the panel which shows folders properties
	 * @private
	 */
	createDescriptionInfoPanel: function()
	{
		return {
			cls: 'description-panel',
			style: {
				borderBottomWidth: '1px',
				borderBottomStyle: 'solid'
			},
			defaults: {
				anchor: '100%'
			},
			items: [{
				xtype: 'displayfield',
				fieldLabel: _('Type'),
				htmlEncode: true,
				ref: 'folderTypeField'
			},{
				xtype: 'displayfield',
				fieldLabel: _('Location'),
				ref: 'locationField',
				htmlEncode: true
			},{
				xtype: 'textarea',
				fieldLabel: _('Description'),
				height: 72,
				name: 'comment',
				listeners: {
					change: this.onFieldChange,
					scope: this
				}
			}],
			ref: 'descriptionPanel'
		};
	},
	/**
	 * @return {Object} Configuration object for the panel which shows folders properties
	 * @private
	 */
	createContentInfoPanel: function()
	{
		return {
			cls: 'content-info-panel',
			border: false,
			items: [{
				xtype: 'displayfield',
				fieldLabel: _('Items'),
				htmlEncode: true,
				name: 'content_count'
			},{
				xtype: 'displayfield',
				fieldLabel: _('Unread'),
				htmlEncode: true,
				name: 'content_unread'
			},{
				xtype: 'zarafa.displayfield',
				fieldLabel: _('Size'),
				renderer: Ext.util.Format.fileSize,
				name: 'message_size'
			},{
				xtype : 'textfield',
				fieldLabel : _('Object ID'),
				name : 'x_fidtext'
			}],
			buttonAlign: 'left',
			buttons: [{
				xtype: 'button',
				cls: 'zarafa-normal',
				text: _('Folder size') + '...',
				handler: this.onFolderSize,
				scope: this
			}]
		};
	},

	/**
	 * Enable/disable/hide/unhide all {@link Ext.Component Components} within the {@link Ext.Panel Panel}
	 * using the given {@link Zarafa.core.data.IPMRecord IPMRecord}.
	 * @param {Zarafa.core.data.IPMRecord} record The record to update the panel with
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 * @private
	 */
	updateUI: function(record, contentReset)
	{
		var layout = false;

		if (contentReset === true) {
			var container_class = record.get('container_class').substr(4).replace(/\./,"").toLowerCase();
			var icon = Zarafa.common.ui.IconClass.getIconClass(record);
			if ( container_class === 'appointment' ){
				var calendarContext = container.getContextByName('calendar');
				var calendarContextModel = calendarContext.getModel();
				var scheme = calendarContextModel.getColorScheme(record.get('entryid'));
				this.nameInfoPanel.displayField.getEl().setStyle(
					'background-image',
					'url(\'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" style="color:'+scheme.base+';position:relative;top:5px;left:-1px;"><g><g class="icbg" style="fill:currentColor;stroke:none"><rect width="0" height="0" x="0" y="0" /></g><g><path class="icgr" style="fill:currentColor" d="M17.75 3A3.25 3.25 0 0 1 21 6.25v11.5A3.25 3.25 0 0 1 17.75 21H6.25A3.25 3.25 0 0 1 3 17.75V6.25A3.25 3.25 0 0 1 6.25 3h11.5Zm1.75 5.5h-15v9.25c0 .966.784 1.75 1.75 1.75h11.5a1.75 1.75 0 0 0 1.75-1.75V8.5Zm-11.75 6a1.25 1.25 0 1 1 0 2.5 1.25 1.25 0 0 1 0-2.5Zm4.25 0a1.25 1.25 0 1 1 0 2.5 1.25 1.25 0 0 1 0-2.5Zm-4.25-4a1.25 1.25 0 1 1 0 2.5 1.25 1.25 0 0 1 0-2.5Zm4.25 0a1.25 1.25 0 1 1 0 2.5 1.25 1.25 0 0 1 0-2.5Zm4.25 0a1.25 1.25 0 1 1 0 2.5 1.25 1.25 0 0 1 0-2.5Zm1.5-6H6.25A1.75 1.75 0 0 0 4.5 6.25V7h15v-.75a1.75 1.75 0 0 0-1.75-1.75Z"/></g></svg>\')'
				);
			} else {
				this.nameInfoPanel.displayField.addClass(String.format('folder-dialog-icon ' + icon));
			}

			var type = String.format(_('Folder containing {0} Items'), Zarafa.common.data.FolderContentTypes.getContentName(record.get('container_class')));
			this.descriptionPanel.folderTypeField.setValue(type);
			layout = true;

			this.descriptionPanel.locationField.setValue(record.getPath());
		}

		if (layout) {
			this.doLayout();
		}
	},

	/**
	 * Update the {@link Ext.Panel Panel} with the given {@link Zarafa.core.data.IPMRecord IPMRecord}
	 * @param {Zarafa.core.data.IPMRecord} record The record to 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;
		this.updateUI(record, contentReset);
		this.getForm().loadRecord(record);

		var entryid = record.get('entryid');
		if (entryid) {
			var provider = entryid.substr(8, 32);
			var fdguid   = entryid.substr(44, 32);
			var fidgcv   = parseInt(entryid.substr(76, 12), 16);
			record.set('x_fidtext', fidgcv + "/0x" + fidgcv.toString(16) +
				"; dbguid=" + fdguid + "; store=" + provider);
		}
	},

	/**
	 * Update the {@link Zarafa.core.data.IPMRecord IPMRecord} with the data from the {@link Ext.Panel Panel}.
	 * @param {Zarafa.core.data.IPMRecord} record The record which has to be updated
	 */
	updateRecord: function(record)
	{
		this.getForm().updateRecord(record);
	},

	/**
	 * Event handler which is fired when a field has been changed.
	 * This will update the corresponding field inside the {@link Zarafa.core.data.IPMRecord record}.
	 * @param {Ext.form.Field} field The field which has changed
	 * @param {Mixed} newValue The new value for the field
	 * @param {Mixed} oldValue The original value for the field
	 * @private
	 */
	onFieldChange: function(field, newValue, oldValue)
	{
		this.record.set(field.getName(), newValue);
	},

	/**
	 * Event handler which is fired when the "Folder Size" button is pressed. This
	 * will open an dialog to display all folder sizes.
	 * @param {Ext.Button} btn The button which was pressed
	 * @private
	 */
	onFolderSize: function(btn)
	{
		Zarafa.hierarchy.Actions.openFolderSizeContent(this.record);
	}
});

Ext.reg('zarafa.folderpropertiesgeneraltab', Zarafa.hierarchy.dialogs.FolderPropertiesGeneralTab);
Ext.namespace('Zarafa.hierarchy.dialogs');

/**
 * @class Zarafa.hierarchy.dialogs.FolderPropertiesPanel
 * @extends Ext.Panel
 * @xtype zarafa.folderpropertiespanel
 *
 * This class is used as wrapper class for all tabs, individual tab will have its own class,
 * extra tabs can be added using insertion point in this dialog.
 */
Zarafa.hierarchy.dialogs.FolderPropertiesPanel = Ext.extend(Ext.Panel, {
	// Insertion points for this class
	/**
	 * @insert folderpropertiescontentpanel.tabs
	 * can be used to add extra tabs to folder properties content panel
	 * @param {Zarafa.hierarchy.dialogs.FolderPropertiesPanel} panel This panel
	 */

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

		Ext.applyIf(config, {
			xtype: 'zarafa.folderpropertiespanel',
			cls: 'zarafa-folderproperties tabpanel-container',
			border: false,
			layout: 'fit',
			deferredRender: false,
			items: [{
				xtype: 'tabpanel',
				border: false,
				activeTab: config.activeTab,
				layoutOnTabChange: true,
				items: [{
					xtype: 'zarafa.folderpropertiesgeneraltab',
					title: _('General')
				},{
					xtype: 'zarafa.folderpropertiespermissiontab',
					emptyText: config.emptyText,
					isAppointmentDialog: config.isAppointmentDialog,
					title: _('Permissions')
				},
				container.populateInsertionPoint('folderpropertiescontentpanel.tabs', this)
				]
			}]
		});

		Zarafa.hierarchy.dialogs.FolderPropertiesPanel.superclass.constructor.call(this, config);
	}
});

Ext.reg('zarafa.folderpropertiespanel', Zarafa.hierarchy.dialogs.FolderPropertiesPanel);
Ext.namespace('Zarafa.hierarchy.dialogs');

/**
 * @class Zarafa.hierarchy.dialogs.FolderPropertiesPermissionTab
 * @extends Ext.Panel
 * @xtype zarafa.folderpropertiespermissiontab
 *
 * Permissions tab in the {@link Zarafa.hierarchy.dialogs.FolderPropertiesContentPanel}
 * that is used to set permissions on folder for other users.
 */
Zarafa.hierarchy.dialogs.FolderPropertiesPermissionTab = Ext.extend(Ext.Panel, {
	/**
	 * @constructor
	 * @param {Object} config
	 */

	constructor: function(config)
	{
		config = config || {};

		config.plugins = Ext.value(config.plugins, []);
		config.plugins.push('zarafa.recordcomponentupdaterplugin');
		var emptyText = Ext.isDefined(config.emptyText) ? config.emptyText : _('No permissions granted');
		var isAppointmentDialog = Ext.isDefined(config.isAppointmentDialog) ? config.isAppointmentDialog : false;

		Ext.applyIf(config, {
			xtype: 'zarafa.folderpropertiespermissiontab',
			cls: 'tab-permissions',
			layout: {
				type: 'vbox',
				align: 'stretch'
			},
			border: false,
			items: [
				this.createUserListPanel(emptyText),
				this.createProfilePanel(isAppointmentDialog),
				this.createPermissionPanel(isAppointmentDialog)
			]
		});

		Zarafa.hierarchy.dialogs.FolderPropertiesPermissionTab.superclass.constructor.call(this, config);
	},

	/**
	 * @param {String} emptyText The string which shows in grid when grid is empty.
	 * @return {Object} Configuration object for the panel which shows users to which permissions are set
	 * @private
	 */
	createUserListPanel: function(emptyText)
	{
		return {
			xtype: 'panel',
			cls: 'userlist-panel',
			layout: 'fit',
			flex: 1,
			border: false,
			items: [{
				xtype: 'grid',
				ref: '../userView',
				store: new Zarafa.hierarchy.data.MAPIFolderPermissionsSubStore(),
				hideHeaders: true,
				enableHdMenu: false,
				border: true,
				viewConfig: {
					forceFit: true,
					deferEmptyText: false,
					emptyText: '<div class="emptytext">' + emptyText + '</div>'
				},
				sm: new Ext.grid.RowSelectionModel({
					singleSelect: true,
					listeners: {
						selectionchange: this.onUserSelectionChange,
						scope: this
					}
				}),
				columns: [{
					dataIndex: 'display_name',
					header: '&#160;',
					renderer: Ext.util.Format.htmlEncode
				}],
				listeners: {
					viewready: this.onViewReady,
					scope: this
				}
			}],
			buttons: [{
				cls: 'zarafa-normal',
				text: _('Add'),
				ref: '../../addUserBtn',
				handler: this.onUserAdd,
				scope: this,
				autoWidth: false
			},{
				text: _('Remove'),
				ref: '../../removeUserBtn',
				handler: this.onUserRemove,
				scope: this,
				autoWidth: false
			}]
		};
	},

	/**
	 * @return {Object} Configuration object for the panel for permissions
	 * @private
	 */
	createProfilePanel: function(isAppointmentDialog)
	{
		var profileStore = {
			xtype: 'jsonstore',
			fields: ['name', 'value'],
			data: isAppointmentDialog ? Zarafa.hierarchy.data.PermissionProfilesCalendar : Zarafa.hierarchy.data.PermissionProfiles
		};

		return {
			xtype: 'panel',
			layout: 'fit',
			cls: 'profile-panel',
			autoHeight: true,
			border: false,
			items: {
				xtype: 'container',
				autoHeight: true,
				items: [{
					xtype: 'form',
					layout: {
						type: 'table',
						columns: 2
					},
					border: false,
					ref: '../profileForm',
					items: [{
						xtype: 'label',
						autoWidth: true,
						autoHeight: true,
						forId: 'profile-combo',
						hideLabel: false,
						text: _('Profile') + ':',
						ref: '../../profileLabel'
					},{
						xtype: 'combo',
						ref: '../../../profileCombo',
						width: 250,
						flex: 1,
						id: 'profile-combo',
						fieldLabel: _('Profile'),
						labelWidth: undefined,
						labelStyle: 'width: auto',
						hideLabel: true,
						autoHeight: true,
						store: profileStore,
						mode: 'local',
						triggerAction: 'all',
						displayField: 'name',
						valueField: 'value',
						lazyInit: false,
						autoSelect: true,
						forceSelection: true,
						editable: false,
						defaultValue: Zarafa.core.mapi.Rights.RIGHTS_NO_RIGHTS,
						listeners: {
							select: this.onProfileSelect,
							scope: this
						}
					}]
				}]
			}
		};
	},

	/**
	 * @return {Object} Configuration object for the panel for permissions
	 * @private
	 */
	createPermissionPanel: function(isAppointmentDialog)
	{
		const readRadioGroupItems = isAppointmentDialog ? [{
			name: 'allowread',
			boxLabel: _('None'),
			hideLabel: true,
			rightsValue: Zarafa.core.mapi.Rights.RIGHTS_NO_RIGHTS
		},{
			name: 'allowread',
			boxLabel: _('Free/Busy time'),
			hideLabel: true,
			rightsValue: Zarafa.core.mapi.Rights.RIGHTS_CAL_FBSIMPLE
		},{
			name: 'allowread',
			boxLabel: _('Free/Busy time, subject, location'),
			hideLabel: true,
			rightsValue: Zarafa.core.mapi.Rights.RIGHTS_CAL_FBSIMPLE | Zarafa.core.mapi.Rights.RIGHTS_CAL_FBDETAILED
		},{
			name: 'allowread',
			boxLabel: _('Full Details'),
			hideLabel: true,
			rightsValue: Zarafa.core.mapi.Rights.RIGHTS_READ_ANY | Zarafa.core.mapi.Rights.RIGHTS_CAL_FBSIMPLE | Zarafa.core.mapi.Rights.RIGHTS_CAL_FBDETAILED | Zarafa.core.mapi.Rights.RIGHTS_FOLDER_VISIBLE
		}] : [{
				name: 'allowread',
				boxLabel: _('None'),
				hideLabel: true,
				rightsValue: Zarafa.core.mapi.Rights.RIGHTS_NO_RIGHTS
			},{
				name: 'allowread',
				boxLabel: _('Full Details'),
				hideLabel: true,
				rightsValue: Zarafa.core.mapi.Rights.RIGHTS_READ_ANY | Zarafa.core.mapi.Rights.RIGHTS_FOLDER_VISIBLE
			},
		];
		return {
			xtype: 'form',
			layout: 'fit',
			cls: 'permissions-panel',
			autoHeight: true,
			columnWidth: 1,
			border: false,
			ref: 'permissionsForm',
			items: [{
				xtype: 'fieldset',
				autoHeight: true,
				autoSizeColumn: true,
				cls: 'zarafa-fieldset',
				items: [{
					xtype: 'container',
					layout: 'column',
					autoSizeColumn: true,
					columnWidth: 1,
					items: [{
						xtype: 'fieldset',
						columnWidth: 0.49,
						cls: 'zarafa-fieldset',
						title: _('Read'),
						items: [{
							xtype: 'radiogroup',
							style: 'margin-right: 2px;',
							hideLabel: true,
							columns: 1,
							items: readRadioGroupItems,
							listeners: {
								change: this.onPermissionChecked,
								scope: this
							}
						}]
					},{
						xtype: 'fieldset',
						columnWidth: 0.49,
						cls: 'zarafa-fieldset',
						title: _('Write'),
						items: [{
							xtype: 'checkboxgroup',
							style: 'margin-left: 2px;',
							hideLabel: true,
							columns: 1,
							items: [{
								xtype: 'checkbox',
								boxLabel: _('Create items'),
								rightsValue: Zarafa.core.mapi.Rights.RIGHTS_CREATE
							},{
								xtype: 'checkbox',
								boxLabel: _('Create subfolders'),
								rightsValue: Zarafa.core.mapi.Rights.RIGHTS_CREATE_SUBFOLDER
							},{
								xtype: 'checkbox',
								boxLabel: _('Edit own'),
								rightsValue: Zarafa.core.mapi.Rights.RIGHTS_EDIT_OWNED
							},{
								xtype: 'checkbox',
								boxLabel: _('Edit all'),
								rightsValue: Zarafa.core.mapi.Rights.RIGHTS_EDIT_OWNED | Zarafa.core.mapi.Rights.RIGHTS_EDIT_ANY
							}],
							listeners: {
								change: this.onPermissionChecked,
								scope: this
							}
						}]
					}]
				},{
					xtype: 'container',
					style: 'margin-top: 2px;',
					columnWidth: 1,
					autoSizeColumn: true,
					layout: 'column',
					items: [{
						xtype: 'fieldset',
						columnWidth: 0.49,
						cls: 'zarafa-fieldset',
						title: _('Delete items'),
						items: [{
							xtype: 'radiogroup',
							columns: 1,
							hideLabel: true,
							items: [{
								name: 'allowdelete',
								boxLabel: _('None'),
								hideLabel: true,
								rightsValue: Zarafa.core.mapi.Rights.RIGHTS_NONE
							},{
								name: 'allowdelete',
								boxLabel: _('Own'),
								hideLabel: true,
								rightsValue: Zarafa.core.mapi.Rights.RIGHTS_DELETE_OWNED
							},{
								name: 'allowdelete',
								boxLabel: _('All'),
								hideLabel: true,
								rightsValue: Zarafa.core.mapi.Rights.RIGHTS_DELETE_ANY | Zarafa.core.mapi.Rights.RIGHTS_DELETE_OWNED
							}],
							listeners: {
								change: this.onPermissionChecked,
								scope: this
							}
						}]
					},{
						xtype: 'fieldset',
						columnWidth: 0.49,
						cls: 'zarafa-fieldset',
						title: _('Other'),
						items: [{
							xtype: 'checkboxgroup',
							style: 'margin-left: 2px;',
							hideLabel: true,
							columns: 1,
							items: [{
								xtype: 'checkbox',
								boxLabel: _('Folder owner'),
								rightsValue: Zarafa.core.mapi.Rights.RIGHTS_CREATE_FOLDER | Zarafa.core.mapi.Rights.RIGHTS_FOLDER_CONTACT | Zarafa.core.mapi.Rights.RIGHTS_FOLDER_VISIBLE
							},{
								xtype: 'checkbox',
								boxLabel: _('Folder contact'),
								rightsValue: Zarafa.core.mapi.Rights.RIGHTS_FOLDER_CONTACT
							},{
								xtype: 'checkbox',
								boxLabel: _('Folder visible'),
								rightsValue: Zarafa.core.mapi.Rights.RIGHTS_FOLDER_VISIBLE
							}],
							listeners: {
								change: this.onPermissionChecked,
								scope: this
							}
						}]
					}]
				},{
					xtype: "checkbox",
					style: 'margin-top: 8px;',
					name: 'recursive',
					boxLabel: _('Apply (copy) changed permissions recursively'),
					handler: this.onFolderFieldChange,
					scope: this,
					ref: 'recursivePermissionsCheckbox'
				}]
			}]
		};
	},

	/**
	 * Event handler which is fired when the user presses the "Add" button to add a new
	 * user to the permission store. This will call {@link Zarafa.common.Actions#openABUserSelectionContent}
	 * to open the addressbook selection content panel.
	 * @private
	 */
	onUserAdd: function()
	{
		Zarafa.common.Actions.openABUserSelectionContent({
			callback: this.onUserSelected,
			scope: this,
			hideContactsFolders: true,
			listRestriction: {
				hide_users: [ 'non_security' ],
				hide_groups: [ 'non_security' ],
				hide_companies: [ 'non_security' ]
			}
		});
	},

		/**
	 * Event handler which is fired when a field has been changed.
	 * This will update the corresponding field inside the {@link Zarafa.core.data.IPMRecord record}.
	 * @param {Ext.form.Field} field The field which has changed
	 * @param {Mixed} newValue The new value for the field
	 * @param {Mixed} oldValue The original value for the field
	 * @private
	 */
		onFolderFieldChange: function(field, newValue, oldValue)
		{
			this.record.set(field.getName(), newValue);
		},

	/**
	 * Event handler for the {@link Zarafa.common.Actions#openABUserSelectionContent} function,
	 * which is used to select a user to be added to the permissions store.
	 * @param {Zarafa.addressbook.AddressBookRecord} record The selected user/group
	 * @private
	 */
	onUserSelected: function(record)
	{
		var store = this.userView.getStore();
		var permission = record.convertToUserPermission();

		if (store.findExact('entryid', permission.get('entryid')) < 0) {
			store.add(permission);
			this.userView.getSelectionModel().selectRecords([permission]);
		} else {
			var msg;
			if (permission.get('object_type') === Zarafa.core.mapi.ObjectType.MAPI_MAILUSER) {
				msg = _('User already exists');
			} else {
				msg = _('Group already exists');
			}
			container.getNotifier().notify('error.permissions', _('Error'), msg, {
				container: this.getEl()
			});
		}
	},

	/**
	 * Event handler which is fired when the user presses the "Remove' button. This will
	 * remove the currently selected user from the permissions store.
	 * @private
	 */
	onUserRemove: function()
	{
		var store = this.userView.getStore();
		var selection = this.userView.getSelectionModel().getSelected();

		if (this.selectedUser === selection) {
			this.clearPermissionsForm();
		}
		store.remove(selection);
	},

	/**
	 * Check if the given permissions masks can be represented by a
	 * {@link Zarafa.hierarchy.data.PermissionProfiles profile}. If it doesn't
	 * match anything the 'Other' profile will be displayed in the profile combobox.
	 * @param {Number} permissions The permissions to load
	 * @private
	 */
	updateProfileCombo: function(permissions)
	{
		var store = this.profileCombo.store;

		// Check if the given permissions mask is set on one
		// of the profiles in the combobox. If that is the case,
		// then we enable that profile, and otherwise we apply
		// 'null' to ensure that the 'Other' profile is selected.
		var index = store.findExact('value', permissions);
		if (index >= 0) {
			this.profileCombo.setValue(permissions);
		} else {
			this.profileCombo.setValue(null);
		}
	},

	/**
	 * Load a permissions mask into the {@link Ext.form.CheckboxGroup} or {@Link Ext.form.RadioGroup}.
	 * This will toggle all radios and checkboxes to ensure they reflect the given permission mask.
	 * @param {Ext.form.CheckboxGroup} group The group to update
	 * @param {Number} permissions The permission mask to load into the group
	 * @private
	 */
	loadPermissionToGroup: function(group, permissions)
	{
		var items = group.items;

		group.suspendEvents(false);
		if (group instanceof Ext.form.RadioGroup) {
			// If in calendar dialog
			if(items.get(3)){
				var none = items.get(0);
				var fb = items.get(1);
				var fbdetailed = items.get(2);
				var all = items.get(3);

				if ((permissions & all.rightsValue) === all.rightsValue) {
					none.setValue(false);
					fb.setValue(false);
					fbdetailed.setValue(false);
					all.setValue(true);
				} else if ((permissions & fbdetailed.rightsValue) === fbdetailed.rightsValue) {
					none.setValue(false);
					fb.setValue(false);
					all.setValue(false);
					fbdetailed.setValue(true);
				} else if ((permissions & fb.rightsValue) === fb.rightsValue) {
					none.setValue(false);
					fbdetailed.setValue(false);
					all.setValue(false);
					fb.setValue(true);
				} else {
					fb.setValue(false);
					fbdetailed.setValue(false);
					all.setValue(false);
					none.setValue(true);
				}
				// If in other dialog
			} else if (items.get(2)){
				var none = items.get(0);
				var own = items.get(1);
				var all = items.get(2);

				if ((permissions & all.rightsValue) === all.rightsValue) {
					none.setValue(false);
					own.setValue(false);
					all.setValue(true);
				} else if ((permissions & own.rightsValue) === own.rightsValue) {
					none.setValue(false);
					all.setValue(false);
					own.setValue(true);
				} else {
					own.setValue(false);
					all.setValue(false);
					none.setValue(true);
				}
			} else {
				var none = items.get(0);
				var all = items.get(1);

				if ((permissions & all.rightsValue) === all.rightsValue) {
					none.setValue(false);
					all.setValue(true);
				} else {
					all.setValue(false);
					none.setValue(true);
				}
			}
		} else {
			for (var i = 0, len = items.getCount(); i < len; i++) {
				var option = items.get(i);
				if (Ext.isDefined(option.rightsValue)) {
					var enabled = (permissions & option.rightsValue) === option.rightsValue;
					option.setValue(enabled);
				}
			}
		}
		group.resumeEvents();
	},

	/**
	 * Read a {@link Ext.form.CheckboxGroup} or {@Link Ext.form.RadioGroup} to detect which
	 * permissions have been enabled in the group. This will be applied to the given
	 * permissions argument
	 * @param {Ext.form.CheckboxGroup} group The group to read
	 * @param {Number} permissions The current permissions value which must be updated by this group
	 * @return {Number} The updated permissions value
	 * @private
	 */
	updatePermissionFromGroup: function(group, permissions)
	{
		var mask = 0;
		var flag = 0;
		var items = group.items;
		items.each(function(item) {
			if (Ext.isDefined(item.rightsValue)) {
				if (item.rightsValue != Zarafa.core.mapi.Rights.RIGHTS_READONLY &&
					item.rightsValue != Zarafa.core.mapi.Rights.RIGHTS_CAL_READ_FULL) {
				mask |= item.rightsValue;
				}
				if (item.checked) {
					flag |= item.rightsValue;
					// If "Edit all" is checked, check "Edit own"
					if (item.rightsValue === Zarafa.core.mapi.Rights.RIGHTS_CAL_EDIT_ALL) {
						var option = items.get(2)
						option.setValue(true);
					}
					// If "Folder owner" is checked, check other folder permissions
					if (item.rightsValue === Zarafa.core.mapi.Rights.RIGHTS_FOLDER_OWNER) {
						var option = items.get(1)
						option.setValue(true);
						var option = items.get(2)
						option.setValue(true);
					}
				}
			}
		});

		return (permissions & ~mask) | flag;
	},

	/**
	 * Load the given {@link Zarafa.core.mapi.Rights permissions mask} into the UI.
	 * This will detect which profile must be enabled, and toggle all required
	 * {@link Ext.form.CheckBox checkboxes} and {@link Ext.form.Radio radios}.
	 * @param {Number} permissions Mask of {@link Zarafa.core.mapi.Rights rights values}.
	 * @private
	 */
	loadPermissionValue: function(permissions)
	{
		this.updateProfileCombo(permissions);

		// Search for all checkbox and radiogroups,
		// and start toggle the appropriate components
		// based on the given mask.
		this.permissionsForm.cascade(function(item) {
			if (item instanceof Ext.form.CheckboxGroup) {
				this.loadPermissionToGroup(item, permissions);
			}
		}, this);
	},

	/**
	 * Read the UI to determine the {@link Zarafa.core.mapi.Rights permissions mask}
	 * which is currently enabled. This will read if a predefined profile is selected,
	 * or will otherwise read all the {@link Ext.form.CheckBox checkboxes} and
	 * {@link Ext.form.Radio radios} to generate the value.
	 * @returns {Number} Mask of {@link Zarafa.core.mapi.Rights rights values}.
	 * @private
	 */
	updatePermissionValue: function()
	{
		var permissions = 0;

		// Search for all checkbox and radiogroups,
		// and apply the value to the permissions mask
		// from the toggled components
		this.permissionsForm.cascade(function(item) {
			if (item instanceof Ext.form.CheckboxGroup) {
				permissions = this.updatePermissionFromGroup(item, permissions);
			}
		}, this);

		return permissions;
	},

	/**
	 * Update the {@link #selectedUser} and update the contents
	 * of the entire permissions form
	 * @param {Zarafa.hierarchy.data.UserPermissionRecord} user The user to load into the form
	 * @private
	 */
	loadPermissionsForm: function(user)
	{
		this.selectedUser = user;
		this.removeUserBtn.enable();
		this.permissionsForm.getForm().items.each(function(item) { item.enable(); });
		this.loadPermissionValue(user.get('rights'));
	},

	/**
	 * Clear the {@link #selectedUser} and clear the contents
	 * of the entire permissions form
	 * @private
	 */
	clearPermissionsForm: function()
	{
		this.selectedUser = undefined;
		this.loadPermissionValue(this.profileCombo.defaultValue);
		this.removeUserBtn.disable();
		this.permissionsForm.getForm().items.each(function(item) { item.disable(); });
	},

	/**
	 * Event fired when the Permissions {@link Ext.grid.GridPanel grid} fires the
	 * {@link Ext.grid.GridPanel#viewready viewready} event. This will check a
	 * {@link #record} has been set, and if so it will select the first
	 * record on the 'folders' substore.
	 * @private
	 */
	onViewReady: function()
	{
		if (this.record && this.record.isOpened()) {
			var store = this.record.getSubStore('permissions');
			if (store.getCount() > 0) {
				this.userView.getSelectionModel().selectFirstRow();
			}
		}
	},

	/**
	 * Event handler which is fired when the user selects a user/group
	 * from the userlist.
	 * @param {Ext.grid.RowSelectionModel} selectionmodel The selectionmodel which fired the event
	 * @private
	 */
	onUserSelectionChange: function(selectionModel)
	{
		var record = selectionModel.getSelected();

		if (!Ext.isEmpty(record)) {
			this.loadPermissionsForm(record);
		} else {
			this.clearPermissionsForm();
		}
	},

	/**
	 * Event handler which is fired when the user selects a profile from
	 * the dropdown box.
	 * @param {Ext.form.ComboBox} combo The combobox which fired the event
	 * @param {Ext.data.Record} record The record which was selected
	 * @param {Number} index The selected index
	 * @private
	 */
	onProfileSelect: function(combo, record, index)
	{
		var permissions = record.get('value');
		if (permissions !== null) {
			this.loadPermissionValue(permissions);
			if (this.selectedUser) {
				this.selectedUser.set('rights', permissions);
			}
		}
	},

	/**
	 * Event handler which is fired when a {@link Ext.form.CheckBox} or {@link Ext.form.Radio}
	 * has been toggled. This will update the rights property inside the {@link #selectedUser}
	 * and check if the new permissions match a profile in the combobox.
	 * @param {Ext.form.CheckboxGroup} group The group to which the checkbox or radio belongs
	 * @param {Boolean} True if the checkbox is enabled or not
	 * @private
	 */
	onPermissionChecked: function(group, checked)
	{
		var permissions;
		if (this.selectedUser) {
			permissions = this.selectedUser.get('rights');
			permissions = this.updatePermissionFromGroup(group, permissions);

			this.selectedUser.set('rights', permissions);
			this.updateProfileCombo(permissions);
		} else {
			permissions = this.updatePermissionValue();
			this.updateProfileCombo(permissions);
		}
	},

	/**
	 * Update the {@link Ext.Panel Panel} with the given {@link Zarafa.core.data.IPMRecord IPMRecord}
	 * @param {Zarafa.core.data.IPMRecord} record The record to 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.isOpened()) {

			var store = record.getSubStore('permissions');
			this.userView.reconfigure(store, this.userView.getColumnModel());
			if (store.getCount() > 0) {
				this.userView.getSelectionModel().selectFirstRow();
			} else {
				this.clearPermissionsForm();
			}
		}
	},

	/**
	 * Update the {@link Zarafa.core.data.IPMRecord IPMRecord} with the data from the {@link Ext.Panel Panel}.
	 * @param {Zarafa.core.data.IPMRecord} record The record which has to be updated
	 */
	updateRecord: function(record)
	{
		if (this.selectedUser) {
			var permissions = this.updatePermissionValue();
			this.selectedUser.set('rights', permissions);
		}
	}
});

Ext.reg('zarafa.folderpropertiespermissiontab', Zarafa.hierarchy.dialogs.FolderPropertiesPermissionTab);
Ext.namespace('Zarafa.hierarchy.dialogs');

/**
 * @class Zarafa.hierarchy.dialogs.FolderSelectionPanel
 * @extends Ext.Panel
 * @xtype zarafa.folderselectionpanel
 */
Zarafa.hierarchy.dialogs.FolderSelectionPanel = Ext.extend(Ext.Panel, {
	/**
	 * @cfg {Boolean} hideTodoList True to hide the To-do list.
	 */
	hideTodoList: false,

	/**
	 * @cfg {Zarafa.hierarchy.data.MAPIFolderRecord} folder The Folder object
	 * which is selected by default.
	 */
	folder: undefined,

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

		config = Ext.applyIf(config, {
			layout: 'fit',
			border: false,
			items: [{
				xtype: 'zarafa.hierarchytree',
				ref: 'hierarchyTree',
				IPMSubTreeFilter: config.IPMSubTreeFilter,
				IPMFilter: config.IPMFilter ? config.IPMFilter : undefined,
				permissionFilter: config.permissionFilter ? config.permissionFilter : undefined,
				border: true,
				hideFavorites: true,
				forceLayout: true,
				treeSorter: true,
				hideTodoList: !!config.hideTodoList,
			}]
		});

		Zarafa.hierarchy.dialogs.FolderSelectionPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Initialize the events
	 * @private
	 */
	initEvents: function()
	{
		Zarafa.hierarchy.dialogs.FolderSelectionPanel.superclass.initEvents.apply(this, arguments);

		if (this.folder) {
			this.mon(this.hierarchyTree, 'load', this.onTreeNodeLoad, this);
		}
	},

	/**
	 * Fired when the {@link Zarafa.hierarchy.ui.Tree Tree} fires the {@link Zarafa.hierarchy.ui.Tree#load load}
	 * event. This function will try to select the {@link Ext.tree.TreeNode TreeNode} in
	 * {@link Zarafa.hierarchy.ui.Tree Tree} initially. When the given node is not loaded yet, it will try again
	 * later when the event is fired again.
	 *
	 * @private
	 */
	onTreeNodeLoad: function()
	{
		// If the folder could be selected, then unregister the event handler.
		if (this.hierarchyTree.selectFolderInTree(this.folder)) {
			this.mun(this.hierarchyTree, 'load', this.onTreeNodeLoad, this);
		}
	},

	/**
	 * Obtain the currently selected {@link Zarafa.hierarchy.data.MAPIFolderRecord folder}
	 * @return {Zarafa.hierarchy.data.MAPIFolderRecord} The selected folder
	 */
	getFolder: function()
	{
		return this.hierarchyTree.getSelectionModel().getSelectedNode().getFolder();
	}
});

Ext.reg('zarafa.folderselectionpanel', Zarafa.hierarchy.dialogs.FolderSelectionPanel);
Ext.namespace('Zarafa.hierarchy.dialogs');

/**
 * @class Zarafa.hierarchy.dialogs.FolderSizePanel
 * @extends Ext.Panel
 * @xtype zarafa.foldersizepanel
 *
 * This will display a {@link Zarafa.core.ui.ContentPanel contentpanel}
 * for displaying the size of the {@link Zarafa.hierarchy.data.MAPIFolderRecord folder} and the subfolders.
 */
Zarafa.hierarchy.dialogs.FolderSizePanel = Ext.extend(Ext.Panel, {

	/**
	 * @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, {
			xtype: 'zarafa.foldersizepanel',
			layout: {
				type: 'vbox',
				align: 'stretch'
			},
			border: false,
			items: [
				this.createFolderSizePanel(),
				this.createSubfolderSizePanel()
			]
		});

		Zarafa.hierarchy.dialogs.FolderSizePanel.superclass.constructor.call(this, config);
	},

	/**
	 * Create the {@link Ext.form.FormPanel form} which contains the size information
	 * for the currently selected folder.
	 * @return {Object} configuration object for the form
	 * @private
	 */
	createFolderSizePanel: function()
	{
		return {
			xtype: 'form',
			ref: 'folderPanel',
			autoHeight: true,
			border: false,
			labelWidth: 200,
			items: [{
				xtype: 'zarafa.displayfield',
				fieldLabel: _('Folder Name'),
				htmlEncode: true,
				name: 'display_name'
			},{
				xtype: 'zarafa.displayfield',
				fieldLabel: _('Size (without subfolders)'),
				renderer: Ext.util.Format.fileSize,
				name: 'message_size'
			},{
				xtype: 'zarafa.displayfield',
				fieldLabel: _('Total size (including subfolders)'),
				renderer: Ext.util.Format.fileSize,
				name: 'total_message_size'
			}]
		};
	},

	/**
	 * Create the {@link Ext.grid.GridPanel grid} which contains the overview
	 * of all subfolders for the current folder combined with their respective
	 * sizes and total sizes.
	 */
	createSubfolderSizePanel: function()
	{
		return {
				xtype: 'grid',
				ref: 'subfolderView',
				flex: 1,
				store: new Zarafa.hierarchy.data.IPFSubStore(),
				enableColumnHide: false,
				border: true,
				loadMask: {
					msg: _('Loading subfolder sizes') + '...'
				},
				viewConfig: {
					forceFit: true,
					deferEmptyText: false,
					emptyText: '<div class="emptytext">' + _('No subfolders present') + '</div>'
				},
				sm: new Ext.grid.RowSelectionModel({ singleSelect: true }),
				columns: [{
					header: _('Subfolder'),
					sortable: true,
					dataIndex: 'folder_pathname',
					renderer: Ext.util.Format.htmlEncode,
					tooltip: _('Sort by: Name of the subfolder')
				},{
					header: _('Size'),
					sortable: true,
					dataIndex: 'message_size',
					renderer: Ext.util.Format.fileSize,
					width: 50,
					tooltip: _('Sort by: Size (without subfolders)')
				},{
					header: _('Total size'),
					sortable: true,
					dataIndex: 'total_message_size',
					renderer: Ext.util.Format.fileSize,
					width: 50,
					tooltip: _('Sort by: Total size (including subfolders)')
				}]
		};
	},

	/**
	 * Update the {@link Ext.Panel Panel} with the given {@link Zarafa.core.data.IPMRecord IPMRecord}
	 * @param {Zarafa.core.data.IPMRecord} record The record to update the panel with
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 */
	update: function(record, contentReset)
	{
		if (contentReset) {
			if (this.record !== record) {
				this.record = record;

				this.subfolderView.loadMask.show();

				var store = record.getStore();

				// Open the record to request for the foldersize information
				this.mon(store, 'open', this.onOpenFolderSize, this);
				record.open({ actionType: 'foldersize', forceLoad: true });
			}
		}
	},

	/**
	 * Event handler which is fired when the {@link Zarafa.core.data.MAPIStore store}
	 * fires the {@link Zarafa.core.data.MAPIStore#open} event. This will load all
	 * new information into the form and grid.
	 * @param {Ext.data.Store} store The store which fired the event
	 * @param {Ext.data.Record} record The record which was opened
	 * @private
	 */
	onOpenFolderSize: function(store, record)
	{
		// Check if this is the record which we need
		if (this.record !== record) {
			return;
		}

		// Load all information into the panel
		this.folderPanel.getForm().loadRecord(record);

		var substore = record.getSubStore('folders');
		var columns = this.subfolderView.getColumnModel();

		// Load the subfolders into the grid
		this.subfolderView.reconfigure(substore, columns);

		// By default we will sort on the folder path
		substore.sort(columns.getDataIndex(0), 'ASC');

		// Remove event handler
		this.mun(store, 'open', this.onOpenFolderSize, this);
		this.subfolderView.loadMask.hide();
	}
});

Ext.reg('zarafa.foldersizepanel', Zarafa.hierarchy.dialogs.FolderSizePanel);
Ext.namespace('Zarafa.hierarchy.ui');

/**
 * @class Zarafa.hierarchy.ui.FolderNode
 * @extends Ext.tree.AsyncTreeNode
 *
 * This will register itself as 'folder' nodetype in the {@link Ext.tree.TreePanel#nodeTypes} object.
 */
Zarafa.hierarchy.ui.FolderNode = Ext.extend(Ext.tree.AsyncTreeNode, {
	/**
	 * @cfg {Zarafa.core.data.IPFRecord} folder the folder for which this tree node will be created
	 */
	folder: undefined,

	/**
	 * @cfg {Boolean} isNodeSelected the isNodeSelected is true when the folder is selected or checked, false otherwise.
	 */
	isNodeSelected: false,

	/**
	 * @cfg {String/Ext.XTemplate} tpl The template which must be applied on the {@link #text} for
	 * rendering into the {@link Ext.tree.TreePanel tree}.
	 */
	tpl: new Ext.XTemplate('{text:htmlEncode}', { compiled: true }),

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

		var folder = config.folder;

		// If a folder is provided we have an extra opportunity to
		// apply some extra defaults.
		if (Ext.isDefined(folder)) {
			Ext.applyIf(config, {
				id:folder.get('entryid')
			});
		}

		Ext.applyIf(config, {
			containerCls: 'zarafa-tree-container',
			cls: 'zarafa-tree-node'
		});

		Zarafa.hierarchy.ui.FolderNode.superclass.constructor.call(this, config);

		if (Ext.isString(this.tpl)) {
			this.tpl = new Ext.XTemplate(this.tpl, { compiled: true });
		}

		if (folder) {
			this.updateUI(folder);
		}
	},

	/**
	 * @return {Zarafa.core.data.IPFRecord} The folder which is represented by this node
	 */
	getFolder: function()
	{
		return this.attributes.folder;
	},

	/**
	 * Sets checked status to the passed value
	 * @param {Boolean} checked if true, set checked else uncheck
	 */
	setChecked: function(checked)
	{
		if (this.rendered) {
			this.getUI().toggleCheck(checked);
		}
	},

	/**
	 * @return {Boolean} true, if {@link Zarafa.hierarchy.ui.FolderNode folderNode} is checked else false
	 */
	isChecked: function()
	{
		var ui = this.getUI();
		return ui.checkbox && ui.checkbox.checked;
	},

	/**
	 * Updates UI of node
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder
	 */
	updateUI: function(folder)
	{
		// Update attributes
		this.attributes.folder = folder;

		this.updateCounterNode(folder);

		// pass the text through text renderer
		this.setText(this.getTextFromFolder(folder));

		// Only update the icon class if the icon is not an svg icon
		// Otherwise we will get errors
		if ( !this.ui.getIconEl() || this.ui.getIconEl().tagName !== 'svg' ){
			// pass the icon through the icon renderer
			this.setIcon(this.getIconFromFolder(folder));

			// pass the class through to the renderer
			this.setContainerCls(this.attributes.containerCls);
		}

		if (!folder.get('has_subfolder') && !folder.isFavoritesRootFolder()) {
			this.removeAll(true);
		}
	},

	/**
	 * Sets the icon for this node
	 * @param {String} iconCls
	 */
	setIcon: function(iconCls)
	{
		var oldIconCls = this.iconCls;
		this.iconCls = this.attributes.iconCls = iconCls;

		if (this.rendered) {
			this.getUI().onIconChange(this, iconCls, oldIconCls);
		}
	},

	/**
	 * Sets the class for this node's container
	 * @param {String} cls
	 */
	setContainerCls: function(cls)
	{
		var oldCls = this.containerCls;
		this.containerCls = this.attributes.containerCls = cls;

		if (this.rendered) {
			this.getUI().onContainerClsChange(this, cls, oldCls);
		}
	},

	/**
	 * Updates UI of counter node.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder
	 * @private
	 */
	updateCounterNode: function(folder)
	{
		if (this.rendered === true) {
			this.getUI().updateCounter(this);
		}
	},

	/**
	 * Obtain the Icon Class which must be applied to this node.
	 * This uses {@link Zarafa.common.ui.IconClass#getIconClass} to
	 * obtain the correct class for the {@link #folder}.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder The folder for which the icon class is requested
	 * @return {String} The iconclass for this folder
	 * @protected
	 */
	getIconFromFolder: function(folder)
	{
		return Zarafa.common.ui.IconClass.getIconClass(folder);
	},

	/**
	 * Obtain the Display Name for the current {@link #folder}.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder The folder for which the display name is requested
	 * @return {String} The name of the folder which must be shown.
	 * @protected
	 */
	getTextFromFolder: function(folder)
	{
		return folder.getDisplayName();
	},

	/**
	 * Update the node based on the {@link #folder} data.
	 * @param {Boolean} deep True to call {@link #update} on all
	 * {@link #childNodes}.
	 */
	update: function(deep)
	{
		var folder;

		if (this.ownerTree && this.ownerTree.model) {
			folder = this.ownerTree.model.getFolder(this.getFolder().get('entryid'));
		}

		if (Ext.isDefined(folder)) {
			this.setChecked(true);
		} else {
			this.setChecked(false);
		}

		if (deep) {
			for(var i = 0, len = this.childNodes.length; i < len; i++) {
				this.childNodes[i].update(deep);
			}
		}
	},

	/**
	 * Cascades down the tree from this node, calling the specified function with each node. 
	 * The arguments to the function will be the args provided or the current node. 
	 * If the function returns false at any point, the cascade is stopped on that branch.
	 * 
	 * @param {Function} fn function to call on every node. 
	 * @param {Object} scope scope of the function.
	 * @param {Array} args arguments to call the function with
	 */
	cascade: function(fn, scope, args)
	{	
		if(fn.apply(scope || this, args || [this]) !== false) {
			var cs = this.childNodes;
			var cascadechildren =  function(cs) {
				cs.forEach(function(child) {
					child.cascade(fn, scope, args);
				});
			};
		
			// If children are available then focefully expand this node to get the data.
			if(this.hasChildNodes() && Ext.isEmpty(cs)) {
				// Collapse the node which was forcefully expanded.
				var expandCallback = function(node) {
					cs = this.childNodes;
					cascadechildren(cs);
					node.collapse(false, false);
				};				
				this.expand(false, false, expandCallback, this);
			} else {
				cascadechildren(cs);
			}
		}
	}
});

Ext.tree.TreePanel.nodeTypes.folder = Zarafa.hierarchy.ui.FolderNode;
Ext.namespace('Zarafa.hierarchy.ui');

/**
 * @class Zarafa.hierarchy.ui.FolderNodeUI
 * @extends Ext.tree.TreeNodeUI
 *
 * {@link Ext.tree.TreeNodeUI TreeNodeUI} has limitation that you can't add html tags to textNode as it should only
 * contain text, not anything else. but we need to add counter of folders to textnode and also make some changes according to
 * counters shown or not. So this class changes the template that is used to create {@link Zarafa.hierarchy.ui.FolderNode FolderNode}
 * so we can create a new element for showing counters so it will not interfere with default functionality of text nodes.
 *
 * The default layout of extjs for treenodes is something like this
 <pre><code>
 	 <div unselectable="on" class="x-tree-node-el x-tree-node-leaf x-unselectable" >	// element node
 		<span class="x-tree-node-indent">		// for indentation
 			<img class="x-tree-elbow-line">
 			<img class="x-tree-elbow-line">
 		</span>
 		<img class="x-tree-ec-icon x-tree-elbow">	// expand icon
 		<img unselectable="on" class="x-tree-node-icon icon_folder_note">	// folder icon
 		<a tabindex="1" href="" class="x-tree-node-anchor" hidefocus="on">
 			<span unselectable="on"> node text </span>						// text node
 		</a>
 	 </div>
 </code></pre>
 * but for our custom needs we need to change that layout to accommodate counters also
 <pre><code>
 	<div unselectable="on" class="x-tree-node-el x-tree-node-leaf x-unselectable" >	// element node
 		<span class="x-tree-node-indent">		// for indentation
 			<img class="x-tree-elbow-line">
 			<img class="x-tree-elbow-line">
 		</span>
 		<img class="x-tree-ec-icon x-tree-elbow">	// expand icon
 		<img unselectable="on" class="x-tree-node-icon icon_folder_note">	// folder icon
 		<a tabindex="1" href="" class="x-tree-node-anchor" hidefocus="on">
 			<span unselectable="on" class="zarafa-hierarchy-node-text"> node text </span>	// text node
 			<span unselectable="on" class="zarafa-hierarchy-node-unread-count">(2)</span>	// counter node
 		</a>
 	</div>
 </code></pre>
 */
Zarafa.hierarchy.ui.FolderNodeUI = Ext.extend(Ext.tree.TreeNodeUI, {

	/**
	 * The currently active countertype for this folder node
	 * @property
	 * @type Zarafa.hierarchy.data.CounterTypes
	 */
	currentCounterType: undefined,

	/**
	 * Function will render {@link Zarafa.hierarchy.ui.FolderNode FolderNode} based on modified template for
	 * our custom needs.
	 * @param {Zarafa.hierarchy.ui.FolderNode} n tree node.
	 * @param {Object} a config object of {@link Zarafa.hierarchy.ui.FolderNode FolderNode}.
	 * @param {Ext.Element} targetNode element in which {@link Zarafa.hierarchy.ui.FolderNode FolderNode} will be rendered.
	 * @param {Boolean} bulkRender
	 */
	renderElements: function(n, a, targetNode, bulkRender)
	{
		// add some indent caching, this helps performance when rendering a large tree
		this.indentMarkup = n.parentNode ? n.parentNode.ui.getChildIndent() : '';

		var scheme;
		var cb = Ext.isBoolean(a.checked);
		var isCalenderNode = a.folder.isCalendarFolder();
		var calendarSVGIcon = '';

		if (isCalenderNode) {
			var calendarContextModel = n.getOwnerTree().model;

			// We started providing color choosing facility to all the calendar tree-nodes.
			// CalendarContextModel is responsible for this facility.
			// There is no CalendarContextModel available in the case where that particular
			// calendar-tree-node doesn't belongs to MultiSelectHierarchyTree.
			// So, simply made that ContextModel available to current HierarchyTree.
			if (!calendarContextModel) {
				var calendarContext = container.getContextByName('calendar');
				calendarContextModel = calendarContext.getModel();
				n.getOwnerTree().model = calendarContextModel;
			}

			scheme = calendarContextModel.getColorScheme(a.folder.get('entryid'));

			// Get the scheme base only if we are able to get scheme successfully,
			// otherwise let it be undefined instead of a JS fatal error.
			if(scheme && scheme.base) {
				calendarSVGIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" aria-hidden="true" style="color:'+scheme.base+';position:relative;top:5px;left:-1px;"><g><g class="icbg" style="fill:currentColor;stroke:none"><rect width="0" height="0" x="0" y="0" /></g><g><path class="icgr" style="fill:currentColor" d="M17.75 3A3.25 3.25 0 0 1 21 6.25v11.5A3.25 3.25 0 0 1 17.75 21H6.25A3.25 3.25 0 0 1 3 17.75V6.25A3.25 3.25 0 0 1 6.25 3h11.5Zm1.75 5.5h-15v9.25c0 .966.784 1.75 1.75 1.75h11.5a1.75 1.75 0 0 0 1.75-1.75V8.5Zm-11.75 6a1.25 1.25 0 1 1 0 2.5 1.25 1.25 0 0 1 0-2.5Zm4.25 0a1.25 1.25 0 1 1 0 2.5 1.25 1.25 0 0 1 0-2.5Zm-4.25-4a1.25 1.25 0 1 1 0 2.5 1.25 1.25 0 0 1 0-2.5Zm4.25 0a1.25 1.25 0 1 1 0 2.5 1.25 1.25 0 0 1 0-2.5Zm4.25 0a1.25 1.25 0 1 1 0 2.5 1.25 1.25 0 0 1 0-2.5Zm1.5-6H6.25A1.75 1.75 0 0 0 4.5 6.25V7h15v-.75a1.75 1.75 0 0 0-1.75-1.75Z"/></g></svg>';
			}
		}

		var checkboxMarkup = '';
		if (cb) {
			var checkboxId = Ext.id(null, 'zarafa-hierarchy-node-cb-');
			checkboxMarkup = '<input class="x-tree-node-cb zarafa-hierarchy-node-cb" type="checkbox" id="' + checkboxId + '" name="' + checkboxId + '"' + (a.checked ? ' checked="checked"' : '') + ' />';
		}

		var icon = '<img src="' + (a.icon || this.emptyIcon) + '" class="x-tree-node-icon" unselectable="on" alt="" />',
		nel,
		href = a.href ? a.href : Ext.isGecko ? "" : "#",
		buf = '<li class="x-tree-node">' +
				'<div ext:tree-node-id="' + n.id + '" class="x-tree-node-el x-tree-node-leaf x-unselectable zarafa-hierarchy-node" unselectable="on">' +
					// indent space
					'<span class="x-tree-node-indent" aria-hidden="true">' + this.indentMarkup + "</span>" +
					// expand icon
					'<img src="' + this.emptyIcon + '" class="x-tree-ec-icon x-tree-elbow" alt="" aria-hidden="true" />' +
					// checkbox
					checkboxMarkup +
					// node icon
					(isCalenderNode ? calendarSVGIcon : icon) +
					// node element (this.elNode)
					'<a hidefocus="on" class="x-tree-node-anchor zarafa-hierarchy-node-anchor" ' +
						'href="' + href + '" tabIndex="0" ' +
						(a.hrefTarget ? ' target="' + a.hrefTarget + '"' : "") + ">" +
							// hierarchy node text (this.textNode)
							'<span class="zarafa-hierarchy-node-foldername" unselectable="on">' + (n.tpl ? n.tpl.apply(a) : n.text) + '</span>' +
							// counter node (this.counterNode)
							'<span class="zarafa-hierarchy-node-counter" unselectable="on"></span>' +
							'<span class="zarafa-hierarchy-node-owner" unselectable="on"></span>'+
					"</a>" +
				"</div>" +
				'<ul class="x-tree-node-ct" style="display:none;"></ul>' +
			"</li>";

		if (bulkRender !== true && n.nextSibling && (nel = n.nextSibling.ui.getEl())) {
			this.wrap = Ext.DomHelper.insertHtml("beforeBegin", nel, buf);
		} else {
			this.wrap = Ext.DomHelper.insertHtml("beforeEnd", targetNode, buf);
		}

		this.elNode = this.wrap.childNodes[0];
		this.ctNode = this.wrap.childNodes[1];
		var cs = this.elNode.childNodes;
		this.indentNode = cs[0];
		this.ecNode = cs[1];
		this.iconNode = cs[2];
		var index = 3;
		if (cb) {
			this.checkbox = cs[2];
			this.iconNode = cs[3];

			// Get child elements of calendar icon which is used to register in drag and drop manager.
			var groupContainerNode = this.iconNode.childNodes[0];
			var groupNode = groupContainerNode.childNodes[0];
			var rectNode = groupNode.childNodes[0];
			var pathNode = groupContainerNode.childNodes[1];
			this.calendarSVGIconChilds = [rectNode, pathNode];
			index++;
		}
		this.anchor = cs[index];
		this.textNode = cs[index].firstChild;

		this.counterNode = cs[index].firstChild.nextSibling;
		this.folderOwnerNode = this.counterNode.nextSibling;
		// Apply some optional CSS classes
		var elNode = Ext.get(this.elNode);
		var iconNode = Ext.get(this.iconNode);
		var containerNode = Ext.get(this.wrap);
		var textNode = Ext.get(this.textNode);
		if (isCalenderNode) {
			textNode.addClass('zarafa-hierarchy-node-color');
		}
		if (!Ext.isEmpty(a.cls)) {
			elNode.addClass(a.cls);
		}

		if (a.icon) {
			iconNode.addClass('x-tree-node-inline-icon');
		}

		if (a.iconCls) {
			iconNode.addClass(a.iconCls);
		}

		if (!Ext.isEmpty(a.containerCls)) {
			containerNode.addClass(a.containerCls);
		}

		this.updateCounter(n);
		this.showFolderOwner(n);
	},

	/**
	 * Function is used to show folder owner name along with {@link Zarafa.hierarchy.data.FavoritesFolderRecord favorites} folder name.
	 * @param {Zarafa.hierarchy.ui.FolderNode} node which has to show folder owner name.
	 */
	showFolderOwner: function (node)
	{
		var folder = node.getFolder();

		// Display owner of a folder:
		// - When a favorite folder.
		// - When a folder exists in a filtered tree ('Show all folders' checkbox is unchecked) in Tasks, Contacts, Calendar, Notes contexts.
		if (!Ext.isDefined(folder) || (!folder.isFavoritesFolder() && node.attributes.nodeType !== 'rootfolder') || folder.isIPMSubTree() || folder.isFavoritesRootFolder()) {
			return;
		}

		var ownerNode = Ext.get(this.folderOwnerNode);
		var store = container.getHierarchyStore().getById(folder.get('store_entryid'));
		var ownerName = '';
		if(store.isPublicStore()) {
			ownerName = ' - ' + store.get('display_name');
		} else if(store.get('mailbox_owner_name') !== container.getUser().getDisplayName()) {
			ownerName = ' - ' + store.get('mailbox_owner_name');
		}
		ownerNode.update(ownerName);
		ownerNode.repaint();
	},

	/**
	 * Update the {@link #counterNode counter} with the correct value.
	 * @param {Zarafa.hierarchy.ui.FolderNode} node The node which is being updated
	 */
	updateCounter: function(node)
	{
		var folder = node.getFolder();

		// Don't show counters for the To-do list
		if ( folder.isTodoListFolder() ){
			return;
		}

		var elNode = Ext.get(this.elNode);
		var counterNode = Ext.get(this.counterNode);

		if (!Ext.isDefined(folder)) {
			return;
		}

		var currentCounterType = this.currentCounterType;
		var newCounterType = folder.getCounterType();

		switch (newCounterType) {
			case Zarafa.hierarchy.data.CounterTypes.TOTAL:
				// update total count, only update the CSS classes when something has changed.
				if (!Ext.isDefined(currentCounterType)) {
					elNode.addClass('zarafa-hierarchy-node-total-count');
				} else if (currentCounterType !== newCounterType) {
					elNode.replaceClass('zarafa-hierarchy-node-unread-count', 'zarafa-hierarchy-node-total-count');
				}
				counterNode.removeClass('zarafa-hierarchy-node-nopadding');
				counterNode.update("[" + folder.getCounterValue() + "]");
				counterNode.repaint();
				break;
			case Zarafa.hierarchy.data.CounterTypes.UNREAD:
				// update unread count, only update the CSS classes when something has changed.
				if (!Ext.isDefined(currentCounterType)) {
					elNode.addClass('zarafa-hierarchy-node-unread-count');
				} else if (currentCounterType !== newCounterType) {
					elNode.replaceClass('zarafa-hierarchy-node-total-count', 'zarafa-hierarchy-node-unread-count');
				}
				counterNode.removeClass('zarafa-hierarchy-node-nopadding');
				counterNode.update(folder.getCounterValue());
				counterNode.repaint();
				break;
			case Zarafa.hierarchy.data.CounterTypes.NONE:
			/* falls through */
			default:
				// remove values from counter node, only update the CSS classes when something has changed.
				if (Ext.isDefined(currentCounterType) && currentCounterType !== newCounterType) {
					elNode.removeClass(['zarafa-hierarchy-node-total-count', 'zarafa-hierarchy-node-unread-count']);
				}
				counterNode.addClass('zarafa-hierarchy-node-nopadding');
				counterNode.update('');
				counterNode.repaint();
				break;
		}
		this.currentCounterType = newCounterType;

		// Bold is driven by unread count, independent of counter type.
		var hasUnread = folder.get('content_unread') > 0;
		if (hasUnread) {
			elNode.addClass('zarafa-hierarchy-node-withcounter');
		} else {
			elNode.removeClass('zarafa-hierarchy-node-withcounter');
		}
	},

	/**
	 * Called when the node has changed the text, this will re-apply the text to the node
	 * @param {Ext.tree.Node} node The node which must be updated
	 * @param {String} text The text which must be applied to the node
	 * @param {String} oldText The previous text which was set on the node
	 * @private
	 */
	onTextChange: function(node, text, oldText)
	{
		if (this.rendered) {
			this.textNode.innerHTML = node.tpl ? node.tpl.apply(node.attributes) : text;
		}
	},

	/**
	 * Called when the node is going to change the icon.
	 * @param {Ext.tree.Node} node The node which must be updated
	 * @param {String} iconCls The iconCls which must be applied to the {@link #iconNode}
	 * @param {String} oldIconCls The old iconCls which must be removed from the {@link #iconNode}.
	 * @private
	 */
	onIconChange: function(node, iconCls, oldIconCls)
	{
		if (this.rendered) {
			var iconNode = Ext.get(this.iconNode);
			if (!Ext.isEmpty(oldIconCls)) {
				iconNode.replaceClass(oldIconCls, iconCls);
			} else {
				iconNode.addClass(iconCls);
			}
		}
	},

	/**
	 * Called when the node is going to change the class.
	 * @param {Ext.tree.Node} node The node which must be updated
	 * @param {String} cls The class which must be applied to the {@link #iconNode}
	 * @private
	 */
	onContainerClsChange: function(node, cls, oldCls)
	{
		if(this.rendered) {
			var containerNode = Ext.get(this.wrap);
			if (!Ext.isEmpty(oldCls)) {
				containerNode.replaceClass(oldCls, cls);
			} else {
				containerNode.addClass(cls);
			}
		}
	},

	/**
	 * Function returns array of {@link Ext.Element nodes} which should be registered with
	 * drag and drop manager for {@link Zarafa.hierarchy.ui.Tree Tree}.
	 * @return {Ext.Element[]} nodes which should be registered with dnd manager.
	 */
	getDDHandles: function()
	{
		// register counter node, icon node, text node to dnd manager
		var nodes = [this.iconNode, this.textNode, this.counterNode, this.elNode];

		// If we have calendar context then register SVG icon child's components to dnd manager
		if (Ext.isDefined(this.calendarSVGIconChilds)) {
			nodes = nodes.concat(this.calendarSVGIconChilds);
		}
		return nodes;
	},

	/**
	 * Event handler triggered when folder node has been clicked by the user.
	 */
	onClick : function(e)
	{
		var folder = this.node.getFolder();
		if (!container.getServerConfig().isWidgetEnabled() && folder.isOwnRoot()){
			var ownerTree = this.node.getOwnerTree();
			var store = folder.getMAPIStore();
			var inboxFolder = store ? store.getDefaultFolder("inbox") : undefined;

			if (ownerTree && inboxFolder) {
				e.preventDefault();
				if (Ext.isFunction(ownerTree.selectFolderInTree)) {
					ownerTree.selectFolderInTree(inboxFolder, true);
				}
				Zarafa.hierarchy.Actions.openFolder(inboxFolder);
				return;
			}
		}

		Zarafa.hierarchy.ui.FolderNodeUI.superclass.onClick.apply(this, arguments);
	}
});
Ext.namespace('Zarafa.hierarchy.ui');

/**
 * @class Zarafa.hierarchy.ui.HierarchyRootNode
 * @extends Ext.tree.AsyncTreeNode
 *
 * Utility TreeNode which is the root node for the entire hierarchy,
 * which by default is invisible. The direct childnodes for this nodes
 * are the opened stores.
 */
Zarafa.hierarchy.ui.HierarchyRootNode = Ext.extend(Ext.tree.AsyncTreeNode, {

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

		Ext.applyIf(config, {
			text: 'root',
			leaf: false,
			expanded: true,
			uiProvider: Ext.tree.RootTreeNodeUI
		});

		Zarafa.hierarchy.ui.HierarchyRootNode.superclass.constructor.call(this, config);
	},

	/**
	 * Finds a TreeNode which represents the given EntryId
	 * @param {String} entryid The Entryid to find
	 * @return {Zarafa.hierarchy.ui.RootFolderNode} The found node
	 */
	findChildByEntryId: function(entryid)
	{
		return this.findChildBy(function(node) {
			return Zarafa.core.EntryId.compareEntryIds(node.attributes.folder.get('entryid'), entryid);
		});
	},

	/**
	 * Find a store treenode by the given Entryid
	 * @param {String} entryid The Store Entryid to find
	 * @return {Zarafa.hierarchy.ui.RootFolderNode} The found store node
	 */
	findChildStoreByEntryId: function(entryid)
	{
		return this.findChildBy(function(node) {
			return Zarafa.core.EntryId.compareStoreEntryIds(node.attributes.folder.get('store_entryid'), entryid);
		});
	}
});
Ext.namespace('Zarafa.hierarchy.ui');

/**
 * @class Zarafa.hierarchy.ui.HierarchyTreeBottomBar
 * @extends Ext.Container
 * @xtype zarafa.hierarchybottombar
 */
Zarafa.hierarchy.ui.HierarchyTreeBottomBar = Ext.extend(Ext.Container, {
	/**
	 * @cfg {Zarafa.hierarchy.data.SharedFolderTypes} defaultSelectedSharedFolderType The default
	 * type of Shared Folder that is selected in the dialog that will be opened.
	 */
	defaultSelectedSharedFolderType: null,

	/**
	 * @cfg {String} buttonText The text that should be displayed on the button to open shared folders.
	 */
	buttonText: _('Open Shared Folders'),

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

		var buttonText = Ext.util.Format.htmlEncode(config.buttonText || this.buttonText);

		Ext.applyIf(config, {
			cls: 'zarafa-hierarchy-treepanel-bottombar',
			layout:'table',
			defaultSelectedSharedFolderType: Zarafa.hierarchy.data.SharedFolderTypes['ALL'],

			items: [{
				cls: 'zarafa-hierarchy-treepanel-footer-opensharedfolder',
				xtype: 'button',
				tooltip: buttonText + Zarafa.core.KeyMapMgr.formatShortcutHint('Alt + S', false),
				text: buttonText + ' + ',
				handler: this.openSharedFolder.createDelegate(this)
			}]
		});

		Zarafa.hierarchy.ui.HierarchyTreeBottomBar.superclass.constructor.call(this, config);
	},

	/**
	 * Called when the button to open Shared Folders is pressed. It will open the dialog to let the
	 * user decide on what folder to open. This function is called within the scope of the
	 * {@link Zarafa.hierarchy.ui.HierarchyTreeBottomBar}.
	 * @param {Ext.Button} button, The Button
	 * @param {Ext.EventObject} event The click event
	 */
	openSharedFolder: function(button, event){
		Zarafa.hierarchy.Actions.openSharedFolderContent(this.defaultSelectedSharedFolderType);
	}
});

Ext.reg('zarafa.hierarchytreebottombar', Zarafa.hierarchy.ui.HierarchyTreeBottomBar);
Ext.namespace('Zarafa.hierarchy.ui');

/**
 * @class Zarafa.hierarchy.ui.HierarchyTreeDropZone
 * @extends Ext.tree.TreeDropZone
 *
 * Specialized {@link Ext.tree.TreeDropZone Drop Zone} which
 * is used for dragging and dropping over the {@link Zarafa.hierarchy.ui.HierarchyTreePanel Hierarchy Tree}.
 * This adds support for changing the icon depending if the Ctrl-key has been pressed.
 */
Zarafa.hierarchy.ui.HierarchyTreeDropZone = Ext.extend(Ext.tree.TreeDropZone, {
	/**
	 * @cfg {String} The CSS class returned to the drag source when drop is allowed
	 * while the user has the Ctrl-key pressed (defaults to "x-dd-drop-ok-add").
	 */
	dropAllowedAdd: 'x-dd-drop-ok-add',

	/**
	 * Called when the DropZone determines that a {@link Ext.dd.DragSource} has entered a drop node
	 * that has either been registered or detected by a configured implementation of
	 * {@link #getTargetFromEvent}.
	 * @param {Ext.Element} target The custom data associated with the drop node (as returned from {@link #getTargetFromEvent}.
	 * @param {Ext.dd.DragSource} The drag source that was dragged over this drop zone
	 * @param {Ext.EventObject} e The event
	 * @param {Object} data An object containing arbitrary data supplied by the drag source
	 * @protected
	 */
	onNodeEnter: function(target, dd, e, data)
	{
		// Change the function from the prototype to update the scope of the function.
		// This is needed to ensure we can call removeEventListener again with the correct
		// function reference later.
		this.onDragKeyDown = Zarafa.hierarchy.ui.HierarchyTreeDropZone.prototype.onDragKeyDown.createDelegate({ dd: dd, dz: this });
		this.onDragKeyUp = Zarafa.hierarchy.ui.HierarchyTreeDropZone.prototype.onDragKeyUp.createDelegate({ dd: dd, dz: this });

		// During dragging (onNodeOver) we either apply dropAllowed or dropAllowedAdd based on the Ctrl-key,
		// if the user didn't drag but just presses the button we must also update the icon. For that we
		// have these 2 event handlers.
		Ext.EventManager.on(Ext.getDoc(), 'keydown', this.onDragKeyDown, this);
		Ext.EventManager.on(Ext.getDoc(), 'keyup', this.onDragKeyUp, this);

		return Zarafa.hierarchy.ui.HierarchyTreeDropZone.superclass.onNodeEnter.apply(this, arguments);
	},

	/**
	 * Called while the DropZone determines that a {@link Ext.dd.DragSource} is being dragged over it,
	 * but not over any of its registered drop nodes.
	 * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
	 * @param {Event} e The event
	 * @param {Object} data An object containing arbitrary data supplied by the drag source
	 * @return {String} status The CSS class that communicates the drop status back to the source so that the
	 * underlying {@link Ext.dd.StatusProxy} can be updated
	 * @protected
	 */
	onContainerOver: function(dd, e, data)
	{
		var ret = Zarafa.hierarchy.ui.HierarchyTreeDropZone.superclass.onContainerOver.apply(this, arguments);

		// If the Ctrl-key is pressed, return dropAllowedAdd to
		// have the correct icon displayed which represents a copy
		// rather then a move.
		if (ret === this.dropAllowed && e.ctrlKey) {
			ret = this.dropAllowedAdd;
		}

		return ret;
	},

	/**
	 * Called while the DropZone determines that a {@link Ext.dd.DragSource} is over a drop node that
	 * has either been registered or detected by a configured implementation of {@link #getTargetFromEvent}.
	 * @param {Ext.Element} target The custom data associated with the drop node (as returned from {@link #getTargetFromEvent}.
	 * @param {Ext.dd.DragSource} The drag source that was dragged over this drop zone
	 * @param {Ext.EventObject} e The event
	 * @param {Object} data An object containing arbitrary data supplied by the drag source
	 * @protected
	 */
	onNodeOver: function(n, dd, e, data)
	{
		var ret = Zarafa.hierarchy.ui.HierarchyTreeDropZone.superclass.onNodeOver.apply(this, arguments);

		// If the Ctrl-key is pressed, return the pecial class
		// to have the correct icon displayed which represents
		// a copy rather then a move.
		if (e.ctrlKey) {
			if (ret === 'x-tree-drop-ok-append') {
				ret = 'x-tree-drop-ok-append-add';
			} else if (ret === 'x-tree-drop-ok-above') {
				ret = 'x-tree-drop-ok-above-add';
			} else if (ret === 'x-tree-drop-ok-between') {
				ret = 'x-tree-drop-ok-between-add';
			} else if (ret === 'x-tree-drop-ok-below') {
				ret = 'x-tree-drop-ok-below-add';
			}
		}

		// TODO: After implementing functionality mark folder to favorite using
		// drag and drop, this check is gets removed.
		if(n.node.getFolder().isFavoritesRootFolder() || n.node.getFolder().isSearchFolder()) {
			ret = undefined;
		}

		return ret;
	},

	/**
	 * Called when the DropZone determines that a {@link Ext.dd.DragSource} has been dragged
	 * out of the drop node without dropping.
	 * @param {Ext.Element} target The custom data associated with the drop node (as returned from {@link #getTargetFromEvent}.
	 * @param {Ext.dd.DragSource} The drag source that was dragged over this drop zone
	 * @param {Ext.EventObject} e The event
	 * @param {Object} data An object containing arbitrary data supplied by the drag source
	 * @protected
	 */
	onNodeOut: function(target, dd, e, data)
	{
		// Clear event handlers again
		Ext.EventManager.un(Ext.getDoc(), 'keydown', this.onDragKeyDown, this);
		Ext.EventManager.un(Ext.getDoc(), 'keyup', this.onDragKeyUp, this);

		return Zarafa.hierarchy.ui.HierarchyTreeDropZone.superclass.onNodeOut.apply(this, arguments);
	},

	/**
	 * Event handler which is fired when the user presses a key, this is registered during {@link #onNodeEnter}
	 * and will be released in {@link #onNodeOut} and {@link #onNodeDrop}. When the user presses the Ctrl-key
	 * and the user is allowed to drop the item on this DropZone, we update the icon to {@link #dropAllowedAdd}
	 * to visualize the action will be a copy rather then a move.
	 *
	 * NOTE: This function is called using a special scope. 'this' is an object containing 2 fields,
	 * 'dd' which is the DragZone from where the item is dragged and 'dz' which is the DragZone over which
	 * the item is hovering.
	 *
	 * @param {Ext.EventObject} e The event
	 * @private
	 */
	onDragKeyDown: function(e)
	{
		if (e.ctrlKey || e.keyCode === Ext.EventObject.CONTROL) {
			if (this.dd.proxy.dropStatus === this.dz.dropAllowed) {
				this.dd.proxy.setStatus(this.dz.dropAllowedAdd);
			} else if (this.dd.proxy.dropStatus === 'x-tree-drop-ok-append') {
				this.dd.proxy.setStatus('x-tree-drop-ok-append-add');
			} else if (this.dd.proxy.dropStatus === 'x-tree-drop-ok-above') {
				this.dd.proxy.setStatus('x-tree-drop-ok-above-add');
			} else if (this.dd.proxy.dropStatus === 'x-tree-drop-ok-between') {
				this.dd.proxy.setStatus('x-tree-drop-ok-between-add');
			} else if (this.dd.proxy.dropStatus === 'x-tree-drop-ok-below') {
				this.dd.proxy.setStatus('x-tree-drop-ok-below-add');
			}
		}
	},

	/**
	 * Event handler which is fired when the user releases a key, this is registered during {@link #onNodeEnter}
	 * and will be released in {@link #onNodeOut} and {@link #onNodeDrop}. When the user releases the Ctrl-key
	 * and the current dropStatus is {@link #dropAllowedAdd} we change it back to {@link #dropAllowed} to visualize
	 * that the action will be a moved.
	 *
	 * NOTE: This function is called using a special scope. 'this' is an object containing 2 fields,
	 * 'dd' which is the DragZone from where the item is dragged and 'dz' which is the DragZone over which
	 * the item is hovering.
	 *
	 * @param {Ext.EventObject} e The event
	 * @private
	 */
	onDragKeyUp: function(e)
	{
		if (e.ctrlKey || e.keyCode === Ext.EventObject.CONTROL) {
			if (this.dd.proxy.dropStatus === this.dz.dropAllowedAdd) {
				this.dd.proxy.setStatus(this.dz.dropAllowed);
			} else if (this.dd.proxy.dropStatus === 'x-tree-drop-ok-append-add') {
				this.dd.proxy.setStatus('x-tree-drop-ok-append');
			} else if (this.dd.proxy.dropStatus === 'x-tree-drop-ok-above-add') {
				this.dd.proxy.setStatus('x-tree-drop-ok-above');
			} else if (this.dd.proxy.dropStatus === 'x-tree-drop-ok-between-add') {
				this.dd.proxy.setStatus('x-tree-drop-ok-between');
			} else if (this.dd.proxy.dropStatus === 'x-tree-drop-ok-below-add') {
				this.dd.proxy.setStatus('x-tree-drop-ok-below');
			}
		}
	}
});
Ext.namespace('Zarafa.hierarchy.ui');

/**
 * @class Zarafa.hierarchy.ui.Tree
 * @extends Ext.tree.TreePanel
 * @xtype zarafa.hierarchytree
 *
 * TreePanel for Hierarchy List
 */
Zarafa.hierarchy.ui.Tree = Ext.extend(Ext.tree.TreePanel, {
	/**
	 * @cfg {Zarafa.core.ContextModel} model The model which is used to control the
	 * current folder selection from this hierarchy tree.
	 */
	model: undefined,

	/**
	 * @cfg {Zarafa.core.data.HierarchyStore} store store which will be used to get data for hierarchy.
	 */
	store: undefined,

	/**
	 * @cfg {String} IPMFilter The IPM String on which the hierarchy must be filtered
	 */
	IPMFilter: undefined,

	/**
	 * @cfg {Number} permissionFilter The {@link Zarafa.core.mapi.Rights} on which the hierarchy must be filtered
	 */
	permissionFilter: undefined,

	/**
	 * @cfg {Boolean} hideDeletedFolders True to hide the subfolders of "Deleted Items".
	 */
	hideDeletedFolders: false,

	/**
	 * @cfg {Boolean} hideTodoList True to hide the To-do list.
	 */
	hideTodoList: false,

	/**
	 * @cfg {Boolean} hideFavorites True to hide the favorites folder in hierarchy.
	 */
	hideFavorites: false,

	/**
	 * @cfg {Boolean} hideSearchFolders True to hide the search folder in hierarchy.
	 */
	hideSearchFolders: false,

	/**
	 @cfg {String} store_entryid of {Zarafa.hierarchy.data.MAPIStoreRecord MAPIStoreRecord} which needs to be shown in hierarchy.
	 * All stores except the Public store and the store which is given in this config will be hidden.
	 */
	IPMSubTreeFilter: undefined,

	/**
	 * @cfg {Object} config option for {@link Zarafa.hierarchy.ui.FolderNode foldernode}
	 */
	nodeConfig: undefined,

	/**
	 * @cfg {Boolean} deferredLoading True to defer updating the Hierarchy when the panel
	 * is currently not visible.
	 */
	deferredLoading: false,

	/**
	 * The editor which is used for editing a field inside this tree.
	 * @property
	 * @type Zarafa.hierarchy.ui.TreeEditor
	 */
	treeEditor: undefined,

	/**
	 * @cfg {Object} loadMask An {@link Zarafa.common.ui.LoadMask} config or true to mask the {@link Zarafa.hierarchy.ui.Tree Tree} while
	 * loading. Defaults to <code>false</code>.
	 */
	loadMask: false,

	/**
	 * @cfg {Object} treeSorter a {@link Ext.Ext.tree.TreeSorter} config or {@link Boolean}
	 * to sort the {@link Zarafa.hierarchy.ui.Tree Tree}
	 * Defaults to <code>false</code>.
	 */
	treeSorter: false,

	/**
	 * @cfg {Boolean} ddAutoScrollContainer Autodetect a valid container to
	 * {@link Ext.dd.ScrollManager#register register} to the {@link Ext.dd.ScrollManager}.
	 *
	 * When {@link #enableDD} is enabled, but this panel itself does not have any {@link #autoScroll scrollbars},
	 * enabling this option will allow the class to dynamically detect which parent panel does have scrollbars,
	 * and register that panel to the {@link Ext.dd.ScrollManager}.
	 * Alternatively {@link #ddScrollContainer} can be used to set a certain panel specifically.
	 */
	ddAutoScrollContainer: false,

	/**
	 * @cfg {Ext.Element/Ext.Container} ddScrollContainer A valid container to
	 * {@link Ext.dd.ScrollManager#register register} to the {@link Ext.dd.ScrollManager}.
	 *
	 * When {@link #enableDD} is enabled, but this panel itself does not have any {@link #autoScroll scrollbars},
	 * setting a component or element to this option will register it to the {@link Ext.dd.ScrollManager}.
	 * Alternatively {@link #ddAutoScrollContainer} can be used to have the container be detected automatically.
	 */
	ddScrollContainer: undefined,

	/**
	 * @cfg {Boolean} defaultOpen Used by {@link #isFolderOpened} to determine if the folder should be opened
	 * by default or not.
	 */
	defaultOpen: false,

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

		if (!config.store) {
			config.store = container.getHierarchyStore();
		}

		// Configure root node for this component
		Ext.applyIf(config, {
			rootVisible: false,
			autoScroll: true,
			animate: false,
			border: false,
			// The rootnode is the parent node under which all Stores will be visualized,
			// because it is not part of the actual hierarchy but more of a helper node,
			// this node is invisible by default.
			root: new Zarafa.hierarchy.ui.HierarchyRootNode()
		});

		Zarafa.hierarchy.ui.Tree.superclass.constructor.call(this, config);

		// sort tree
		if(this.treeSorter && !(this.treeSorter instanceof Ext.tree.TreeSorter)) {
			this.treeSorter = new Zarafa.hierarchy.ui.TreeSorter(this, Ext.apply({}, this.treeSorter));
		}

		// filter tree on search query.
		if (this.treeFilter && !(this.treeFilter instanceof Ext.tree.TreeFilter)) {
			this.treeFilter = new Zarafa.hierarchy.ui.HierarchyTreeFilter(this, {autoClear: true});
		}
	},

	/**
	 * Function will initialize {@link Zarafa.hierarchy.ui.Tree Tree} and creates a
	 * {@link Zarafa.common.ui.LoadMask} if {@link Zarafa.hierarchy.ui.Tree Tree} is instantiated as full tree.
	 * @protected
	 */
	initComponent: function()
	{
		// Initialize the loader
		if (!this.loader) {
			this.loader = new Zarafa.hierarchy.data.HierarchyTreeLoader({
				tree: this,
				store: this.store,
				nodeConfig: this.nodeConfig,
				deferredLoading: this.deferredLoading
			});
		}

		// call parent
		Zarafa.hierarchy.ui.Tree.superclass.initComponent.apply(this, arguments);

		// When DD is enabled within container scrolling, we must register a hook
		// to determine the container on which we are going to scroll.
		if (this.enableDD === true) {
			if (this.ddAutoScrollContainer === true) {
				this.on('load', this.autodetectScrollContainer, this);
			} else if (this.ddScrollContainer) {
				this.ddScrollContainer = Ext.Element(this.ddScrollContainer) ? this.ddScrollContainer : this.ddScrollContainer.el;
				this.registerScrollContainer(this.ddScrollContainer);
			}
		}

		// create load mask
		if(this.loadMask) {
			this.on('render', this.createLoadMask, this);
		}
	},

	/**
	 * When {@link #ddAutoScrollContainer} has been enabled, this function will walk
	 * up the parents of the current component parent which is scrollable. The {@link Ext.Container#body}
	 * will then be used for {@link #registerScrollContainer}.
	 * @private
	 */
	autodetectScrollContainer: function()
	{
		var ct = this.ownerCt;
		while (ct && ct.autoScroll !== true) {
			ct = ct.ownerCt;
		}

		if (ct) {
			// When autoScroll is enabled, the 'body' of the component will
			// have the the actual scrollbar attached to it.
			this.registerScrollContainer(ct.body || ct.el);
		}
	},

	/**
	 * Register the given {@link Ext.Element} with the
	 * {@link Ext.dd.ScrollManager ScrollManager}.
	 * @param {Ext.Element} el The element to register to the scrollmanager
	 * @private
	 */
	registerScrollContainer: function(el)
	{
		// Apply some extra configuration options
		// which are used by the ScrollManager.
		el.ddScrollConfig = {
			vthresh: 50,
			hthresh: -1,
			frequency: 100,
			increment: 25
		};

		Ext.dd.ScrollManager.register(el);
	},

	/**
	 * Function will create {@link Zarafa.common.ui.LoadMask} which will be shown
	 * when loading the {@link Zarafa.hierarchy.ui.Tree Tree}.
	 * @private
	 */
	createLoadMask: function()
	{
		this.loadMask = new Zarafa.common.ui.LoadMask(this.getEl(), Ext.apply({store: this.store}, this.loadMask));
	},

	/**
	 * @return True when this tree has an {@link #IPMFilter} applied
	 */
	hasFilter: function()
	{
		return !Ext.isEmpty(this.IPMFilter);
	},

	/**
	 * The filter which is applied for filtering nodes from the
	 * {@link Zarafa.hierarchy.ui.Tree HierarchyTree}.
	 * @param {Object} folder the folder to filter
	 * @return {Boolean} true to accept the folder
	 */
	nodeFilter: function(folder)
	{
		var hide = false;

		// Check if the folder matches the requested IPMFilter
		if (Ext.isDefined(this.IPMFilter)) {
			hide = !folder.isContainerClass(this.IPMFilter, false);
		}

		// Check if the folder matches the requested permissionFilter
		if (!hide && Ext.isDefined(this.permissionFilter)) {
			hide = !(folder.get('rights') & this.permissionFilter);
		}

		// Check if the folder is located in Deleted items
		if (!hide && this.hideDeletedFolders) {
			hide = folder.isInDeletedItems();
		}

		// Check if the to-do list should be shown
		if (!hide && this.hideTodoList) {
			hide = folder.isTodoListFolder();
		}

		// Check if the favorites list folder should be shown
		if (!hide && this.hideFavorites) {
			hide = folder.isFavoritesRootFolder();
		}

		// Check if the search folder should be shown
		if (!hide && this.hideSearchFolders) {
			hide = folder.isSearchFolder();
		}

		return !hide;
	},

	/**
	 * The filter which is applied for filtering IPMSubTree nodes from the
	 * {@link Zarafa.hierarchy.ui.Tree HierarchyTree}.
	 * @param {Object} folder the IPM_SUBTREE folder to filter
	 * @return {Boolean} true to accept the folder
	 */
	IPMSubTreeNodeFilter: function(folder)
	{
		var hide = false;

		// Check if folder does not belong to public store or the given store then hide it.
		if (Ext.isDefined(this.IPMSubTreeFilter)) {
			var mapiStore = this.store.getById(this.IPMSubTreeFilter);
			if (mapiStore) {
				hide = !folder.getMAPIStore().isPublicStore() && !mapiStore.getFolder(folder.get('entryid'));
			}
		}

		return !hide;
	},

	/**
	 * @return {Array} list of all childnodes
	 * @private
	 */
	getAllNodes: function()
	{
		return this.getRootNode().childNodes;
	},

	/**
	 * Manual selection of the treeNode to which the folder is attached in the tree.
	 * This will first ensure the given folder {@link #ensureFolderVisible is visible}
	 * and will then {@link Ext.tree.DefaultSelectionModel#select select the given node} in the tree.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder The folder to select
	 * @param {Boolean} ensureVisibility True to make given folder visible in screen moving scroll bar.
	 * @return {Boolean} True when the TreeNode for the given folder existed, and could be selected.
	 */
	selectFolderInTree: function(folder, ensureVisibility)
	{
		var treeNode;

		if (ensureVisibility !== false) {
			treeNode = this.ensureFolderVisible(folder);
		} else {
			treeNode = this.getTreeNode(folder);
		}

		if (treeNode) {
			this.getSelectionModel().select(treeNode, undefined, ensureVisibility);
			return true;
		} else {
			return false;
		}
	},

	/**
	 * This will call {@link Ext.tree.TreeNode#ensureVisible} in the node
	 * to which the given folder is attached. This will first make sure
	 * that the actual folder has been loaded by the parent folder.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder The folder which must
	 * be made visible.
	 * @return {Zarafa.hierarchy.ui.FolderNode} The node which was made visible,
	 * false if the folder was not found in the hierarchy.
	 */
	ensureFolderVisible: function(folder)
	{
		var treeNode = this.getTreeNode(folder);

		// If the tree has not been found, take the parent folder.
		if (!treeNode) {
			var parentfolder = folder.getParentFolder();
			if (!parentfolder) {
				return false;
			}

			// With the parent folder we can ensure that folder is visible,
			// and expand it to ensure the subfolders will be rendered.
			var parentNode = this.ensureFolderVisible(parentfolder);
			if (!parentNode) {
				return false;
			}

			parentNode.expand();

			// With luck, the node has now been created.
			treeNode = this.getTreeNode(folder);
		}

		if (treeNode) {
			// Ensure that the given node is visible.
			// WARNING: After this call, treeNode is destroyed
			// as the TreeLoader will have reloaded the parent!
			treeNode.ensureVisible();

			// Obtain the new treeNode reference, update the UI and return it.
			treeNode = this.getTreeNode(folder);
			treeNode.update(true);
			return treeNode;
		}

		return false;
	},

	/**
	 * Check if the folder should be opened by default or not.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder The folder to check
	 * @return {Boolean} True if the folder should be expanded by default
	 * @private
	 */
	isFolderOpened: function(folder)
	{
		return folder.isIPMSubTree() || folder.isFavoritesRootFolder() || this.defaultOpen;
	},

	/**
	 * Call {@link Zarafa.hierarchy.ui.FolderNode}#{@link Zarafa.hierarchy.ui.FolderNode#update update}
	 * on {@link #getAllNodes all nodes}.
	 * @protected
	 */
	updateAll: function()
	{
		var nodes = this.getAllNodes();
		for (var i = 0; i < nodes.length; i++) {
			nodes[i].update(true);
		}
	},

	/**
	 * Function will destroy load mask and calls parent class' beforeDestroy.
	 * @private
	 */
	beforeDestroy: function()
	{
		if (this.rendered && this.loadMask) {
			Ext.destroy(this.loadMask);
			this.loadMask = false;
		}
		if (this.treeSorter) {
			Ext.destroy(this.treeSorter);
			this.treeSorter = false;
		}
		Zarafa.hierarchy.ui.Tree.superclass.beforeDestroy.call(this);
	},

	/**
	 * Function is used to find the {@link Zarafa.hierarchy.ui.FolderNode} based on the folder.
	 * If selected folder is {@link Zarafa.common.favorites.data.FavoritesFolderRecord favorites} folder then
	 * we append "favorites-" keyword with folder entryid to uniquely identify and get the favorites marked folder node.
	 *
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord | Zarafa.common.favorites.data.FavoritesFolderRecord} folder the folder
	 * can be favorites folder or any normal folder.
	 * @returns {Zarafa.hierarchy.ui.FolderNode} folder node object
	 */
	getTreeNode: function (folder)
	{
		var id = folder.get('entryid');
		if (folder.isFavoritesFolder()) {
			id = "favorites-"+id;
		}
		return this.getNodeById(id);
	}
});

Ext.reg('zarafa.hierarchytree', Zarafa.hierarchy.ui.Tree);
Ext.namespace('Zarafa.hierarchy.ui');

/**
 * @class Zarafa.hierarchy.ui.TreeEditor
 * @extends Ext.tree.TreeEditor
 * @xtype zarafa.foldertreeeditor
 *
 * Editor for editing nodes within {@link Zarafa.hierarchy.ui.Tree HierarchyTree}
 */
Zarafa.hierarchy.ui.TreeEditor = Ext.extend(Ext.tree.TreeEditor, {
	/**
	 * Used to lock editing on treenodes
	 */
	enableEdit: undefined,

	/**
	 * @constructor
	 * @param {Zarafa.hierarchy.ui.Tree} treeObj object of parent {@link Zarafa.hierarchy.ui.Tree HierarchyTree}
	 * which uses this {@link Zarafa.hierarchy.ui.TreeEditor TreeEditor}.
	 * @param {Object} fc config of {@link Ext.form.Field Field} which can be used for editing {@link Zarafa.hierarchy.ui.FolderNode FolderNode}.
	 * @param {Object} config Configuration object for {@link Ext.tree.TreeEditor TreeEditor}
	 */
	constructor: function(treeObj, fc, config) {
		fc = fc || {};
		config = config || {};

		fc = Ext.applyIf(fc, {
			ignoreNoChange: true
		});

		config = Ext.applyIf(config, {
			cancelOnEsc: true,
			completeOnEnter: true
		});

		Zarafa.hierarchy.ui.TreeEditor.superclass.constructor.call(this, treeObj, fc, config);

		// treeEditor event handlers
		this.on({
			'beforenodeclick': this.onBeforeNodeClick,
			'beforestartedit': this.onBeforeStartEdit,
			'canceledit': this.onEditCancel,
			'complete': this.onEditComplete,
			scope: this
		});

	},

	/**
	 * Fired before triggering click on treeNode.
	 * @return {Boolean} when true it does not enter edit mode on selection of node
	 * @private
	 */
	onBeforeNodeClick: function() {
		return true;
	},

	/**
	 * Fired before triggering edit on treeNode.
	 * @return {Boolean} false if editing should be stopped.
	 * @private
	 */
	onBeforeStartEdit: function() {
		// Editing? Only if its unlocked
		if (!this.enableEdit) {
			return false;
		}
	},

	/**
	 * Fired on cancel of editing treeNode
	 * @private
	 */
	onEditCancel: function() {
		// Put edit lock back ON.
		this.enableEdit = false;
	},

	/**
	 * Fired on completion of editing of treeNode
	 * @param {Ext.tree.TreeNode} treeNode node which is edited
	 * @param {String} newName new name of node
	 * @param {String} oldName old name of node
	 * @private
	 */
	onEditComplete: function(treeNode, newName, oldName) {
		this.enableEdit = false;

		var folder = treeNode.editNode.getFolder();

		folder.set('display_name', newName);
		folder.save();
	},

	/**
	 * Triggers node editing
	 * @param {Ext.tree.TreeNode} treeNode node to be edited
	 */
	startEditingNode: function(treeNode) {
		// Release edit lock
		this.enableEdit = true;
		this.triggerEdit(treeNode);
	}
});

Ext.reg('zarafa.foldertreeeditor', Zarafa.hierarchy.ui.TreeEditor);
Ext.namespace('Zarafa.hierarchy.ui');

/**
 * @class Zarafa.hierarchy.ui.TreeSorter
 * @extends Ext.tree.TreeSorter
 *
 * Special sorting class for the HierarchyTree, this enables special sorting of the folders,
 * in which the "Stores" are sorted in such a way that the Private store is placed on top,
 * and the Public store is placed on the bottom. All Shared stores are placed alphabetical
 * order between the Private and Public stores.
 * All folders below the stores are sorted in alphabetical order.
 */
Zarafa.hierarchy.ui.TreeSorter = Ext.extend(Ext.tree.TreeSorter, {

	/**
	 * @cfg {Boolean} folderStore
	 * @hide
	 */
	/**
	 * @cfg {String} property
	 * @hide
	 */
	/**
	 * @cfg {String} leadAttr
	 * @hide
	 */
	/**
	 * @cfg {Function} sortType
	 * @hide
	 */

	/**
	 * @cfg {String} folderProperty The property from the {@link Zarafa.hierarchy.data.MAPIFolderRecord folder}
	 * which must be used in the comparison between 2 {@link Zarafa.hierarchy.data.MAPIFolderRecord folders}.
	 */
	folderProperty: 'display_name',

	/**
	 * @cfg {String} storeProperty The property from the {@link Zarafa.hierarchy.data.MAPIStoreRecord store}
	 * which must be used in the comparison between 2 {@link Zarafa.hierarchy.data.MAPIStoreRecord stores}.
	 */
	storeProperty: 'mailbox_owner_name',

	/**
	 * @cfg {String[]} folderOrder The predefined order for the folders in the hierarchy. It contains default
	 * folder keys and container classes.
	 */
	folderOrder: [
		'inbox',
		'drafts',
		'outbox',
		'sent',
		'IPF.Note',
		'wastebasket',
		'junk',
		'calendar',
		'IPF.Appointment',
		'contact',
		'IPF.Contact',
		'todolist',
		'task',
		'IPF.Task',
		'note',
		'IPF.StickyNote',
		'journal',
		'IPF.Journal',
		'IPF.Note.OutlookHomepage'
	],

	/**
	 * @constructor
	 * @param {Ext.tree.Tree} tree The tree which this sorter is being applied on
	 * @param {Object} config Configuration object
	 */
	constructor: function(tree, config)
	{
		Zarafa.hierarchy.ui.TreeSorter.superclass.constructor.apply(this, arguments);

		this.sortFn = this.hierarchySort.createDelegate(this);
	},

	/**
	 * Special sorting function which applies special sorting when the
	 * {@link Zarafa.hierarchy.data.MAPIFolderRecord folder} on the node
	 * is a {@link Zarafa.hierarchy.data.MAPIFolderRecord#isIPMSubTree IPM_SUBTREE}.
	 *
	 * @param {Ext.tree.Node} node1 The first node to be compared
	 * @param {Ext.tree.Node} node2 The second node to be compared
	 * @private
	 */
	hierarchySort: function(node1, node2)
	{
		var folder1 = node1.attributes.folder;
		var folder2 = node2.attributes.folder;
		var store1 = folder1.getMAPIStore();
		var store2 = folder2.getMAPIStore();

		var dsc = this.dir && this.dir.toLowerCase() == "desc";
		var cs = this.caseSensitive === true;

		// If folder 1 is the favorites root node (IPM_COMMON_VIEWS)
		// we directly return a value to sort it to the top of the tree.
		if (folder1.isFavoritesRootFolder()) {
			return dsc ? +1 : -1;
		}

		// If folder 2 is the favorites root node (IPM_COMMON_VIEWS)
		// we directly return a value to sort it to the top of the tree.
		if (folder2.isFavoritesRootFolder()) {
			return dsc ? -1 : +1;
		}

		// If folder 1 is the root node (IPM_SUBTREE) for the
		// default store we directly return a value to sort it
		// to the top of the tree. For the public store we return
		// a value to sort it to the bottom of the tree.
		if (folder1.isIPMSubTree()) {
			if (store1.isDefaultStore()) {
				return dsc ? +1 : -1;
			}
			if (store1.isPublicStore()) {
				return dsc ? -1 : +1;
			}
		}

		// If folder 2 is the root node (IPM_SUBTREE) for the
		// default store we directly return a value to sort it
		// to the top of the tree. For the public store we return
		// a value to sort it to the bottom of the tree.
		if (folder2.isIPMSubTree()) {
			if (store2.isDefaultStore()) {
				return dsc ? -1 : +1;
			}

			if (store2.isPublicStore()) {
				return dsc ? +1 : -1;
			}
		}

		// If both folder 1 as folder 2 are root nodes (IPM_SUBTREE) then
		// we sort by the storeProperty, allowing the sorting based on
		// the display name of the user, rather then of the folder.
		if (folder1.isIPMSubTree() && folder2.isIPMSubTree()) {
			var cmp = this.compareRecordProp(store1, store2, this.storeProperty, dsc, cs);
			// If the store properties are equal, then we have 2 shared folders which
			// belong to the same store. We are going to sort those by the folderProperty.
			if (cmp !== 0) {
				return cmp;
			}
		}

		// Sort all remaining cases by the folder property.
		return this.compareRecordProp(folder1, folder2, this.folderProperty, dsc, cs);
	},

	/**
	 * Helper function for {@link #hierarchySort}, this performs a comparison of two records.
	 * The records (folders) will be ordered based on the preset folder order defined in
	 * {@link folderOrder}. If this will not result in a order, the records will be ordered
	 * based on the on a passed property field.
	 *
	 * @param {Ext.data.Record} record1 The first record to compare
	 * @param {Ext.data.Record} record2 The second record to compare
	 * @param {String} property The property on which the records are compared
	 * @param {Boolean} descending True if the records must be sorted descending
	 * @param {Boolean} caseSensitive True if the property values must be compared case-sensitive
	 * @return {Integer} +1 if record1 should be placed before record 2 in the order, -1 otherwise
	 * @private
	 */
	compareRecordProp: function(record1, record2, property, descending, caseSensitive)
	{
		var	folderKey1;
		var	folderKey2;

		var isRecord1Store = record1.get('object_type') === Zarafa.core.mapi.ObjectType.MAPI_STORE;
		var isRecord2Store = record2.get('object_type') === Zarafa.core.mapi.ObjectType.MAPI_STORE;
		var bothRecordsStore = isRecord1Store && isRecord2Store;

		// First look at the folders Order, because they have a predefined order
		// For the sorting of folders of Favorites, get the oroginal records which helps getting FullyQualifiedName later.
		// Escape getting the oroginal records for search type folders.
		// Escape getting default folder key for Shared type folders. For the store records and other than favorites records,
    // directly get the default folder key.
		if(!bothRecordsStore && record1.isFavoritesFolder() && record2.isFavoritesFolder()) {
			record1 = !record1.isSearchFolder() ? record1.getOriginalRecordFromFavoritesRecord() : record1;
			record2 = !record2.isSearchFolder() ? record2.getOriginalRecordFromFavoritesRecord() : record2;

			var isRecord1Shared = record1.getMAPIStore().isSharedStore();
			var isRecord2Shared = record2.getMAPIStore().isSharedStore();

			folderKey1 = !isRecord1Shared ? record1.getDefaultFolderKey() : undefined;
			folderKey2 = !isRecord2Shared ? record2.getDefaultFolderKey() : undefined;
		} else {
			folderKey1 = record1.getDefaultFolderKey();
			folderKey2 = record2.getDefaultFolderKey();
		}

		// If the folder is not a default folder, we will sort it by its container class
		if ( !folderKey1 ){
			folderKey1 = record1.get('container_class');
		}
		if ( !folderKey2 ){
			folderKey2 = record2.get('container_class');
		}

		// First check if record1 or record2 are a different default type or type
		// and if so, sort the records based on that
		var index1 = this.folderOrder.indexOf(folderKey1);
		var index2 = this.folderOrder.indexOf(folderKey2);
		if ( index1 > -1 ){
			if ( index2===-1 || index1<index2 ){
				return descending ? +1 : -1;
			} else if ( index1 > index2 ) {
				return descending ? -1 : +1;
			}
		} else if ( index2 > -1 ){
			return descending ? -1 : +1;
		}

		// Folders that have the same type will now be sorted based on the passed property

		// When sorting on display name, we will use the 'fully qualified display name', so
		// we will use 'Calendar of Hank Bla' instead of just 'Calendar' to sort.
		var v1 = property == 'display_name' && record1.getFullyQualifiedDisplayName ? record1.getFullyQualifiedDisplayName() : record1.get(property);
		var v2 = property == 'display_name' && record2.getFullyQualifiedDisplayName ? record2.getFullyQualifiedDisplayName() : record2.get(property);

		// For case insensitive sorting, convert to lowercase, this will correctly position
		// folders which start with '_' to be sorted first (when converting to uppercase, those
		// folders are otherwise sorted last.
		if (!caseSensitive) {
			v1 = !Ext.isEmpty(v1) ? v1.toLowerCase() : v1;
			v2 = !Ext.isEmpty(v2) ? v2.toLowerCase() : v2;
		}

		var langTag = container.getSettingsModel().get('zarafa/v1/main/language').split('_')[0];
		return (descending ? -1 : 1) * Intl.Collator(langTag).compare(v1, v2);
	}
});
Ext.namespace('Zarafa.mail');

/**
 * @class Zarafa.mail.Actions
 * Common actions which can be used within {@link Ext.Button buttons}
 * or other {@link Ext.Component components} with action handlers.
 * @singleton
 */
Zarafa.mail.Actions = {
	/**
	 * Open a Panel in which a new {@link Zarafa.core.data.IPMRecord record} can be
	 * further edited.
	 *
	 * @param {Zarafa.mail.MailContextModel} model Context Model object that will be used
	 * to {@link Zarafa.mail.MailContextModel#createRecord create} the email.
	 * @param {Object} config (optional) Configuration object used to create
	 * the Content Panel.
	 */
	openCreateMailContent: function(model, config)
	{
		var record = model.createRecord();
		Zarafa.core.data.UIFactory.openCreateRecord(record, config);
	},

	/**
	 * Opens a {@link Zarafa.mail.ui.MailCreatePanel MailCreatePanel} for the given non-recipient
	 * objects. This will convert the object into a valid Recipient Record and add it to the new mail.
	 *
	 * @param {Zarafa.mail.MailContextModel} model mail context model,
	 * model object that will be used to create a new {@link Zarafa.core.data.IPMRecord IPMRecord}
	 * @param {Zarafa.core.data.MAPIRecord} contacts The records to convert to recipients.
	 * @param {Object} config (optional) Configuration object used to create
	 * the Content Panel.
	 */
	openCreateMailContentForContacts: function(model, contacts, config)
	{
		var mailRecord = model.createRecord();
		var recipientStore = mailRecord.getRecipientStore();
		var tasks = [];

		contacts = Array.isArray(contacts) ? contacts : [ contacts ];
		for (var i = 0, len = contacts.length; i < len; i++) {
			var contact = contacts[i];

			if (contact.isOpened()) {
				// The contact is opened and contains all the information which we need
				var recipient = contact.convertToRecipient(Zarafa.core.mapi.RecipientType.MAPI_TO, true);
				recipientStore.add(recipient);
			} else {
				// The contact is not opened yet, register a task to open the contact once
				// the panel has been opened.
				tasks.push({
					/* By encapsulating the task function it is possible to get the contact object
					* into the scope of the task function. When you add more tasks the contact
					* reference changes and without this encapsulation it will change the contact in
					* all the previously added task functions as well.
					*/
					fn: function(){
						// This contactRecord becomes a private variable, not changeable outside.
						var contactRecord = contact;
						return function(panel, record, task, callback) {
							var fn = function(store, record) {
								if (record === contactRecord) {
									store.un('open', fn, task);
									var recipient = contactRecord.convertToRecipient(Zarafa.core.mapi.RecipientType.MAPI_TO, true);
									recipientStore.add(recipient);
									callback();
								}
							};

							contactRecord.getStore().on('open', fn, task);
							contactRecord.open();
						};
					// This triggers the encapsulation and returns the task function
					}()
				});
			}
		}

		config = Ext.applyIf(config || {}, {
			recordComponentPluginConfig: {
				loadTasks: tasks
			}
		});

		Zarafa.core.data.UIFactory.openCreateRecord(mailRecord, config);
	},

	/**
	 * Opens a {@link Zarafa.mail.ui.MailCreatePanel MailCreatePanel} for the
	 * given {@link Zarafa.core.data.IPMRecord record} using the {@link Zarafa.mail.data.ActionTypes actionType}
	 * to format the message.
	 *
	 * @param {Zarafa.core.data.IPMRecord|Zarafa.core.data.IPMRecord[]} record The record to which will be responded.
 	 * @param {Zarafa.mail.MailContextModel} model mail context model,
	 * model object that will be used to create a new {@link Zarafa.core.data.IPMRecord IPMRecord}
	 * @param {Zarafa.mail.data.ActionTypes} actionType The action type of this response.
	 * @param {Object} config (optional) Configuration object used to create
	 * the Content Panel.
	 */
	openCreateMailResponseContent: function(records, model, actionType, config)
	{
		if (!Array.isArray(records)) {
			records = [records];
		}

		var response;

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

			if (record.isFaultyMessage()) {
				// can not reply/forward to faulty record
				continue;
			}

			if (actionType === Zarafa.mail.data.ActionTypes.FORWARD_ATTACH) {
				response = model.createResponseRecord(record, actionType, response, config);
			} else {
				var openHandler = function(store, record) {
					// This function will called in the scope of the record for
					// whom the event handler was registered.
					if (this !== record) {
						return;
					}

					if (actionType === Zarafa.mail.data.ActionTypes.FORWARD_ATTACH) {
						response = model.createResponseRecord(record, actionType, response, config);
					} else {
						response = model.createResponseRecord(record, actionType);
						Zarafa.core.data.UIFactory.openCreateRecord(response, config);

						store.un('open', openHandler, record);
					}
				};

				if (record.isOpened()) {
					response = model.createResponseRecord(record, actionType);
					Zarafa.core.data.UIFactory.openCreateRecord(response, config);
				} else {
					record.getStore().on('open', openHandler, record);
					record.open();
				}
			}
		}

		if (actionType === Zarafa.mail.data.ActionTypes.FORWARD_ATTACH) {
			Zarafa.core.data.UIFactory.openCreateRecord(response, config);
		}
	},

	/**
	 * Opens a MailOptionsPanel. For displaying advanced options for the given {@link Zarafa.core.data.IPMRecord records}.
	 *
	 * @param {Zarafa.core.data.IPMRecord} records The record, or records for which the options are requested
	 * @param {Object} config (optional) Configuration object used to create
	 * the Content Panel.
	 */
	openMailOptionsContent: function(records, config)
	{
		if (Array.isArray(records) && !Ext.isEmpty(records)) {
			records = records[0];
		}

		config = Ext.applyIf(config || {}, {
			modal: true
		});

		var componentType = Zarafa.core.data.SharedComponentType['mail.dialog.options'];
		Zarafa.core.data.UIFactory.openLayerComponent(componentType, records, config);
	},

	/**
	 * Opens a {@link Zarafa.addressbook.dialogs.ABMultiUserSelectionContentPanel ABMultiUserSelectionContentPanel}
	 * for configuring the categories of the given {@link Zarafa.core.data.IPMRecord records}.
	 *
	 * @param {Zarafa.core.data.IPMRecord} records The record, or records for which the categories
	 * must be configured
	 * @param {Object} config (optional) Configuration object used to create
	 * the Content Panel.
	 */
	openRecipientSelectionContent: function(records, config)
	{
		if (Array.isArray(records) && !Ext.isEmpty(records)) {
			records = records[0];
		}

		// Create a copy of the record, we don't want the changes
		// to be activated until the user presses the Ok button.
		var copy = records.copy();
		var store = copy.getSubStore('recipients');
		copy.isModalDialogRecord = true;
		Zarafa.common.Actions.openABUserMultiSelectionContent({
			callback: function() {
				records.applyData(copy);
			},
			convert: function(user, field) {
				return user.convertToRecipient(field ? field.defaultRecipientType : config.defaultRecipientType);
			},
			store: store,
			selectionCfg: [{
				xtype: 'zarafa.recipientfield',
				fieldLabel: _('To') + ':',
				boxStore: store,
				filterRecipientType: Zarafa.core.mapi.RecipientType.MAPI_TO,
				defaultRecipientType: Zarafa.core.mapi.RecipientType.MAPI_TO,
				flex: 1
			},{
				xtype: 'zarafa.recipientfield',
				fieldLabel: _('CC') + ':',
				boxStore: store,
				filterRecipientType: Zarafa.core.mapi.RecipientType.MAPI_CC,
				defaultRecipientType: Zarafa.core.mapi.RecipientType.MAPI_CC,
				flex: 1
			},{
				xtype: 'zarafa.recipientfield',
				fieldLabel: _('BCC') + ':',
				boxStore: store,
				filterRecipientType: Zarafa.core.mapi.RecipientType.MAPI_BCC,
				defaultRecipientType: Zarafa.core.mapi.RecipientType.MAPI_BCC,
				flex: 1
			}]
		});
	},

	/**
	 * Open a Panel in which the {@link Zarafa.core.data.IPMRecord record}
	 * can be viewed, or further edited with pre-chosen layer as separateWindows..
	 * Prepare record instance based on original record.
	 *
	 * @param {Zarafa.core.data.IPMRecord} records The records to open
	 * @param {Zarafa.core.ui.MessageContentPanel} dialog which contains the record.
	 */
	popoutMailContent: function(record, dialog) {
		var copy;

		// First create the exact same copy of record avoiding cheap copy.
		copy = Zarafa.core.data.RecordFactory.createRecordObjectByRecordData(record.data, record.id);
		copy.idProperties = record.idProperties.clone();
		copy.phantom = record.phantom;
		copy.dirty = record.dirty;
		copy.modified = record.modified;
		copy.applyData(record, false);

		// We must have to retain "id" of attachment-substore of original record to access attachments.
		var attachmentStoreId = record.getAttachmentStore().getId();
		copy.getAttachmentStore().setId(attachmentStoreId);

		var configObj = {
			layerType: 'separateWindows',
			isRecordChangeByUser: dialog.recordComponentPlugin.isChangedByUser
		};

		if(!record.phantom){
			// Add the copied record into the shadow store as the old record will be removed from the same,
			// when the tab gets closed, and store is required to attach necessary events for some functionality like markAsRead etc.
			container.getShadowStore().add(copy);

			// Prevent that RecordComponentPlugin's setRecord adds the record into the shadow store again
			// as we already add the record into shadow store in the line above.
			configObj.recordComponentPluginConfig = { useShadowStore: false };
		}

		// Close the existing tab for which a new separate browser window is created
		dialog.fireEvent('close', dialog);
		Zarafa.core.data.ContentPanelMgr.unregister(dialog);

		// Use newly created copy of original record to load into separate browser window
		Zarafa.common.Actions.openMessageContent(copy, configObj);
	},

    /**
     * Open a {@link Zarafa.mail.dialogs.DelayDeliveryContentPanel DelayDeliveryContentPanel} for
     * set DEFERRED_SEND_TIME property  in new created mail base on enter Date and Time
     *
     * @param {Zarafa.core.data.IPMRecord} record mail record
     * @param {Zarafa.core.ui.MessageContentPanel} dialog which contains the record.
     */
    openDelayedDeliveryContent: function (record, dialog)
	{
        Zarafa.core.data.UIFactory.openLayerComponent(Zarafa.core.data.SharedComponentType['mail.dialog.delayeddelivery'], record, {
            manager: Ext.WindowMgr,
            modal: true,
            mailPanel: dialog,
            resizable: false,
            scope: this
        });
	},

	/**
	 * Function will redirect to signature widget which belongs to {@link Zarafa.mail.settings.SettingsSignaturesWidget SettingsSignaturesWidget}.
	 */
	redirectToSignatureWidget: function()
	{
		if (Zarafa.core.BrowserWindowMgr.isMainWindowActive() === false){
			Zarafa.core.BrowserWindowMgr.switchFocusToMainWindow();
		}

		var context = container.getCurrentContext();
		if (context.getName() === "settings") {
			var tabPanel = container.getTabPanel();
			context.defaultActiveTab = 1;
			context.scrollToSignatureWidget = true;
			tabPanel.setActiveTab("zarafa-mainpanel-content");
		} else {
			context = container.getContextByName('settings');
			// defaultActiveTab = 1 is mail tab
			context.defaultActiveTab = 1;
			context.scrollToSignatureWidget = true;
			container.switchContext(context);
		}
	}
};
Ext.namespace('Zarafa.mail.data');

/**
 * @class Zarafa.mail.data.ActionTypes
 *
 * An enum that contains all possible Action types which
 * can be used on a {@link Zarafa.mail.MailRecord MailRecord} which
 * is being send to the server.
 *
 * @singleton
 */
Zarafa.mail.data.ActionTypes =
{
	/**
	 * The message is a reply to an different {@link Zarafa.mail.MaiLRecord MailRecord}
	 * @property
	 * @type String
	 */
	REPLY		: 'reply',

	/**
	 * The message is a reply-all to an different {@link Zarafa.mail.MaiLRecord MailRecord}
	 * @property
	 * @type String
	 */
	REPLYALL	: 'replyall',

	/**
	 * The message is a forward from an different {@link Zarafa.mail.MaiLRecord MailRecord}
	 * @property
	 * @type String
	 */
	FORWARD		: 'forward',

	/**
	 * The message is a forward-as-attachment from an different {@link Zarafa.mail.MaiLRecord MailRecord}
	 * @property
	 * @type String
	 */
	FORWARD_ATTACH	: 'forward_attach',

	/**
	 * The message is an "edit as new" message created from a different {@link Zarafa.mail.MaiLRecord MailRecord}
	 * @property
	 * @type String
	 */
	EDIT_AS_NEW	: 'edit_as_new',

	/**
	 * Method used to determine if the given parameter is one of the known {@link Zarafa.mail.data.ActionTypes actiontype}.
	 * @param {String} actionType what action type to check.
	 * @return {Boolean} true if the given action type is one of the types defined in this class.
	 */
	isSendOrForward: function(actionType)
	{
		return actionType === this.REPLY || actionType === this.REPLYALL || actionType === this.FORWARD || actionType === this.FORWARD_ATTACH;
	}
};
Ext.namespace('Zarafa.mail.data');

/**
 * @class Zarafa.mail.data.ConversationManagers
 * @extends Ext.util.Observable
 *
 */
Zarafa.mail.data.ConversationManagers = Ext.extend(Ext.util.Observable, {

    /**
     * The managers which contains the different type of managers like {@link Ext.util.MixedCollection openedRecord}
     * who manages the all opened conversations.
     *
     * @property
     * @type Object
     */
    managers: {},

    /**
     * @constructor
     * @param config
     */
    constructor: function(config)
    {
        this.managers = Ext.apply({}, {
            openedRecordManager: new Ext.util.MixedCollection()
        });

        Zarafa.mail.data.ConversationManagers.superclass.constructor.call(this, config);
    },

    /**
     * @return {Ext.util.MixedCollection} The opened conversation manager which manages the
     * all opened conversation.
     */
    getOpenedRecordManager: function ()
    {
        return this.managers.openedRecordManager;
    },

    /**
     * Setter function which set the opened Record conversation manager.
     *
     * @param {Ext.util.MixedCollection} openedRecordManager The {@link Ext.util.MixedCollection openedRecord} which manages the
     * all opened conversation
     */
    setOpenedRecord: function (openedRecordManager)
    {
        this.managers.openedRecordManager = openedRecordManager;
    },

    /**
     * Function will collapse all the conversation except the given header record in parameter
     * and if no header record is provided then it will collapse all the conversation.
     *
     * @param {Zarafa.core.IPMRecord} headerRecord The header record of conversation.
     */
    closeAll: function (headerRecord)
    {
        // Close all conversation.
        var openedRecordManager = new Ext.util.MixedCollection();

        // Close all conversation except selected conversation.
        if (headerRecord !== false) {
            openedRecordManager = this.getOpenedRecordManager().filterBy(function(item, key){
                return key === headerRecord.get("entryid");
            }, this);
        }
        this.setOpenedRecord(openedRecordManager);
    }
});
Ext.namespace('Zarafa.mail.data');

/**
 * @class Zarafa.mail.data.SearchFields
 * @singleton
 *
 * Search fields for search bar
 */
Zarafa.mail.data.SearchFields = [{
	name: _('All text fields'),
	value: 'subject sender_name sender_email_address sent_representing_name sent_representing_email_address body display_to display_cc'
}, {
	name: _('Subject'),
	value: 'subject'
}, {
	name: _('Sender'),
	value: 'sender_name sender_email_address sent_representing_name sent_representing_email_address'
}, {
	name: _('Body'),
	value: 'body'
}, {
	name: _('Subject or Sender'),
	value: 'subject sender_name sender_email_address sent_representing_name sent_representing_email_address'
}, {
	name: _('To or cc'),
	value: 'display_to display_cc'
}];
Ext.namespace('Zarafa.mail.data');

/**
 * @class Zarafa.mail.data.Templates
 *
 * An enum that contains all possible template string that can be used when rendering body in
 * mail dialog.
 *
 * @singleton
 */
Zarafa.mail.data.Templates =
{
	/**
	 * The template that will be used to show body in plain text format.
	 * @property
	 * @type String
	 */
	plaintextQuotedTemplate	: new Ext.XTemplate(
		'<tpl if="!Ext.isEmpty(values.signatureData)">' +
		'{signatureData}\n' +
		'</tpl>' +
		'\n\n' +
		'--------------------' + _('Original message') + '--------------------\n' +
		'<tpl if="!Ext.isEmpty(values.sent_representing_name)">' +
			'> ' + _('From') + ': {sent_representing_name:htmlEncode} ' +
			'<tpl if="!Ext.isEmpty(values.sent_representing_email_address)">' +
				'<{sent_representing_email_address:htmlEncode}>' +
			'</tpl>' + '\n' +
		'</tpl>' +
		'<tpl if="Ext.isDate(values.message_delivery_time)">' +
			// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
			'> ' + _('Sent') + ': {message_delivery_time:formatDefaultTimeString("' + _("l jS F Y {0}") + '")}\n' +
		'</tpl>' +
		'<tpl if="!Ext.isEmpty(values.display_to)">' +
			'> ' + _('To') + ': {display_to}\n' +
		'</tpl>' +
		'<tpl if="!Ext.isEmpty(values.display_cc)">' +
			'> ' + _('Cc') + ': {display_cc}\n' +
		'</tpl>' +
		'> ' + _('Subject') + ': {subject:htmlEncode}\n' +
		'> \n' +
		'{body}',
		{compiled: true}
	),

	/**
	 * The template that will be used to show body in html format.
	 * @property
	 * @type String
	 */
	htmlQuotedTemplate: new Ext.XTemplate(
		'<tpl if="!Ext.isEmpty(values.signatureData)">' +
			'{signatureData}' +
		'</tpl>' +
		'<p style="padding:0; margin:0; min-height:{fontSize}; font-family:{fontFamily}; font-size:{fontSize};"><br/></p>' +
		'<p style="padding:0; margin:0; min-height:{fontSize}; font-family:{fontFamily}; font-size:{fontSize};"><br/></p>' +
		'<blockquote style=\'border-left: 2px solid #325FBA; padding-left: 5px; margin: 0px 5px;\'>' +
			'<span style="font-family:Arial,Helvetica,sans-serif; font-size: 10pt;">' +
				'--------------------' + _('Original message') + '--------------------<br />' +
				'<tpl if="!Ext.isEmpty(values.sent_representing_name)">' +
					'<span>' +
					'<strong>' + _('From') + ':</strong> {sent_representing_name:htmlEncode}&nbsp;' +
					'<tpl if="!Ext.isEmpty(values.sent_representing_email_address)">' +
						'&lt;{sent_representing_email_address:htmlEncode}&gt;' +
					'</tpl>' + '</span><br />' +
				'</tpl>' +
				'<tpl if="Ext.isDate(values.message_delivery_time)">' +
					'<span>' +
					// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
					'<strong>' + _('Sent') + ':</strong> {message_delivery_time:formatDefaultTimeString("' + _("l jS F Y {0}") + '")}</span><br />' +
				'</tpl>' +
				'<tpl if="!Ext.isEmpty(values.display_to)">' +
					'<span>' +
					'<strong>' + _('To') + ':</strong> {display_to:htmlEncode}</span><br />' +
				'</tpl>' +
				'<tpl if="!Ext.isEmpty(values.display_cc)">' +
					'<span>' +
					'<strong>' + _('Cc') + ':</strong> {display_cc:htmlEncode}</span><br />' +
				'</tpl>' +
					'<span>' +
					'<strong>' + _('Subject') + ':</strong> {subject:htmlEncode}</span><br />' +
				'<br />' +
			'</span>' +
			'{body}' +
		'</blockquote>',
		{compiled: true}
	)
};
Ext.namespace('Zarafa.mail.dialogs');

/**
 * @class Zarafa.mail.dialogs.DelayedDeliveryPanel
 * @extends Ext.Panel
 * @xtype zarafa.delayeddeliverypanel
 *
 * Panel for users to set the deferred_send_time on a given {@link Zarafa.mail.MailRecord record}
 */
Zarafa.mail.dialogs.DelayedDeliveryPanel = Ext.extend(Ext.Panel, {

    /**
     * @cfg {Date} scheduledMailDateTime which is date and time when mail should be delivered
     */
    scheduledMailDateTime: undefined,

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

        config = Ext.applyIf(config, {
            xtype: 'zarafa.delayeddeliverypanel',
            border: false,
			cls: 'zarafa-delayeddeliverypanel',
            items: [{
                xtype: 'zarafa.radiospinnerfield',
				cls: 'zarafa-delayeddeliverypanel-radiospinnerfield',
                type: 'hours',
                value: _('hour(s)'),
                listeners: {
                    focus: this.onFieldFocus,
                    change: this.onFieldChange,
                    scope: this
                }
            }, {
                xtype: 'zarafa.radiospinnerfield',
                type: 'days',
				cls: 'zarafa-delayeddeliverypanel-radiospinnerfield',
                value: _('day(s)'),
                listeners: {
                    focus: this.onFieldFocus,
                    change: this.onFieldChange,
                    scope: this
                }
            }, {
                xtype: 'zarafa.radiospinnerfield',
                type: 'months',
				cls: 'zarafa-delayeddeliverypanel-radiospinnerfield',
                value: _('month(s)'),
                listeners: {
                    focus: this.onFieldFocus,
                    change: this.onFieldChange,
                    scope: this
                }
            }, {
                xtype: 'displayfield',
                value: _('at a specific time'),
				cls:'zarafa-delayeddeliverypanel-displayfield'
            }, {
                xtype: 'zarafa.radiodatetimefield',
                ref: 'delayedDeliveryDateTimeField',
				cls:'zarafa-delayeddeliverypanel-radiodatetimefield',
                listeners: {
                    focus: this.onFieldFocus,
                    change: this.onFieldChange,
                    scope: this
                }
            }, {
                xtype: 'displayfield',
				cls: 'zarafa-delayeddeliverypanel-fields',
                ref: 'timeInfofield'
            }],
            listeners: {
                afterrender: this.setDefaultValue
            }
        });
        Zarafa.mail.dialogs.DelayedDeliveryPanel.superclass.constructor.call(this, config);
    },

    /**
     * Event handler which is called when the {@link Zarafa.common.ui.RadioDateTimeField radioDateTimeField} or
     * {@link Zarafa.common.ui.RadioSpinnerField radioSpinnerField} field receives input focus,
     * this will call {@link #createMessage} for updating the delayInfoMessage value
     * @param {Zarafa.common.ui.RadioSpinnerField|Zarafa.common.ui.RadioDateTimeField} field The field which fired the event
     */
    onFieldFocus: function (field)
    {
        this.createMessage(field.getDateTime());
    },

    /**
     * Event handler which is called when the {@link Zarafa.common.ui.RadioDateTimeField radioDateTimeField} or
     * {@link Zarafa.common.ui.RadioSpinnerField radioSpinnerField} value has changed,
     * this will call {@link #createMessage} for updating the delayInfoMessage value
     * @param {Zarafa.common.ui.RadioSpinnerField|Zarafa.common.ui.RadioDateTimeField} field The field which fired the event
     * @param {Date} dateTime The date and time which is specified by user.
     */
    onFieldChange: function (field, dateTime)
    {
        this.createMessage(dateTime);
    },

    /**
     * Function is use to create and display delayInfoMessage.
     * this will call {@link #getMessageDate} and {@link #showMessage} for getting the delayInfoMessage value and display delayInfoMessage
     * @param {Date} value which is selected date and time
     */
    createMessage: function (value)
    {
        var dateTime = new Date(value);
        this.scheduledMailDateTime = dateTime;
        var delayInfoMessage = this.getMessageDate(dateTime);
        this.showMessage(delayInfoMessage);
    },

    /**
     * Function check date and create appropriate delayInfoMessage using the date.
     * @param {Date} dateTime The date and time which is specified by user.
     * @returns {string} delayInfoMessage which is display when record will be send.
     */
    getMessageDate: function (dateTime)
    {
        var delayInfoMessage = '';
        this.ownerCt.setDisabled(true);
        if (isNaN(dateTime.getTime())) {
            return delayInfoMessage;
        }
        else if (this.isPastDate(dateTime)) {

            //If user select past time then return sorry delayInfoMessage and disable send button.
            delayInfoMessage = _('The time you entered is in the past. Please reschedule your mail.');
            this.delayedDeliveryDateTimeField.addClass('zarafa-delayeddelivery-invalid-date');
            return delayInfoMessage;
        } else {

            //Create appropriate delayInfoMessage base on scheduled date and time
            // We now have 4 possible cases:
            // 1) When the date is scheduled for today, the delayInfoMessage will be shown as
            //    "Your email will be sent today at [xx:xx]"
            // 2) When the date is scheduled for tomorrow, the delayInfoMessage will be shown as
            //    "Your email will be sent tomorrow at [xx:xx]"
            // 3) When the date is scheduled within a week, the delayInfoMessage will be shown as
            //    “Your email will be sent [Day] at [xx:xx]"
            // 4) All other dates further than one week, the delayInfoMessage will be shown as
            //    “Your email will be sent at [Month] [dd]th [yyyy], [xx:xx]"
            var date = '';
            var time = dateTime.format('H:i');
            if (this.isTodayDate(dateTime)) {
                date = _('today  at ');
            } else if (this.isTomorrowDate(dateTime)) {
                date = _('tomorrow  at ');
            } else if (this.isDayInCurrentWeek(dateTime)) {
                date = String.format(_('{0} at '), dateTime.format('l '));
            } else {
                date = String.format(_('at {0}'), dateTime.format('F jS Y, '));
            }
            delayInfoMessage = String.format(_('Your email will be sent {0}{1}'), date, time);
            this.ownerCt.setDisabled(false);
            this.delayedDeliveryDateTimeField.removeClass('zarafa-delayeddelivery-invalid-date');
            return delayInfoMessage;
        }
    },

    /**
     * Function check whether specified date and time is in past or not.
     * @param {Date} mailDateTime Specified date and time.
     * @returns {boolean} true if date and time is in past
     */
    isPastDate: function (mailDateTime)
    {
        var currentDateTime = new Date().getTime();
        var mailDate = mailDateTime.getTime();
        return mailDate < (currentDateTime - 3000);
    },

    /**
     * Function check whether specified date is today's date or not.
     * @param {Date} mailDateTime Specified date and time.
     * @returns {boolean} true if date is today's date
     */
    isTodayDate: function (mailDateTime)
    {
        var todayDate = new Date().clearTime(true);
        var mailDate = new Date(mailDateTime).clearTime(true);
        return todayDate.getTime() === mailDate.getTime();
    },

    /**
     * Function check whether specified date is tomorrow's date or not.
     * @param {Date} mailDateTime Specified date and time.
     * @returns {boolean} true if date is tomorrow's date
     */
    isTomorrowDate: function (mailDateTime)
    {
        var date = new Date().clearTime(true);
        var mailDate = new Date(mailDateTime).clearTime(true);
        var tomorrowDateTime = new Date(date.add(Date.DAY, 1));
        return tomorrowDateTime.getTime() === mailDate.getTime();
    },

    /**
     * Function check whether specified date is in current week or not.
     * @param {Date} mailDateTime Specified date and time.
     * @returns {boolean} true if date is in current week else false
     */
    isDayInCurrentWeek: function (mailDateTime)
    {
        var currentDateTime = new Date();
        var first = currentDateTime.getDate() - currentDateTime.getDay();
        var firstDayOfWeek = new Date(currentDateTime.setDate(first));
        var lastDayOfWeek = firstDayOfWeek.add(Date.DAY,6);
        var mailDate = new Date(mailDateTime).clearTime(true);
        return mailDate.between(firstDayOfWeek, lastDayOfWeek);
    },

    /**
     * Function display delayInfoMessage when user select the date.
     * delayInfoMessage content when your mail will be send.
     * @param {String} delayInfoMessage Message string created using specified date.
     */
    showMessage: function (delayInfoMessage)
    {
        this.timeInfofield.setValue(delayInfoMessage);
    },

    /**
     * Function set default value in {@link Zarafa.common.ui.RadioDateTimeField radioDateTimeField}
     * If record has deferred_send_time then set this date and time otherwise set current date and time
     */
    setDefaultValue: function ()
    {
        var deferredSendTime = this.ownerCt.record.get('deferred_send_time');
        if (Ext.isEmpty(deferredSendTime)) {
            this.delayedDeliveryDateTimeField.setValue(new Date());
        } else {
            this.delayedDeliveryDateTimeField.setValue(deferredSendTime);
        }
        var initialDateTime = this.delayedDeliveryDateTimeField.getDateTime();
        this.createMessage(initialDateTime);
    },

    /**
     * Function return date and time which is selected by user.
     * @returns {Date} The date object
     */
    getMailDateTime: function ()
    {
        return this.scheduledMailDateTime;
    }
});

Ext.reg('zarafa.delayeddeliverypanel', Zarafa.mail.dialogs.DelayedDeliveryPanel);
Ext.namespace('Zarafa.mail.dialogs');

/**
 * @class Zarafa.mail.dialogs.MailCreateOptionsPanel
 * @extends Ext.Panel
 * @xtype zarafa.mailcreateoptionspanel
 *
 * Panel for users to set the options on a given {@link Zarafa.mail.MailRecord record}
 */
Zarafa.mail.dialogs.MailCreateOptionsPanel = Ext.extend(Ext.Panel, {
	/**
	 * @constructor
	 * @param config Configuration structure
	 */
	constructor: function(config)
	{
		config = config || {};

		config = Ext.applyIf(config, {
			// Override from Ext.Component
			xtype: 'zarafa.mailcreateoptionspanel',
			layout: {
				type: 'vbox',
				align: 'stretch'
			},
			border: false,
			defaults: {
				bodyStyle: 'padding-top: 5px; padding-left: 6px; padding-right: 5px; background-color: inherit;',
				border: false
			},
			items: [{
				xtype: 'zarafa.mailoptionssettingspanel'
			},{
				xtype: 'zarafa.mailoptionstrackingpanel'
			},{
				xtype: 'zarafa.mailoptionsmsgidpanel'
			}]
		});

		Ext.apply(this, config);

		Zarafa.mail.dialogs.MailCreateOptionsPanel.superclass.constructor.call(this, config);
	}
});

Ext.reg('zarafa.mailcreateoptionspanel', Zarafa.mail.dialogs.MailCreateOptionsPanel);
Ext.namespace('Zarafa.mail.dialogs');

/**
 * @class Zarafa.mail.dialogs.MailCreatePanel
 * @extends Ext.form.FormPanel
 * @xtype zarafa.mailcreatepanel
 *
 * Panel that is used to compose a mail messages.
 */
Zarafa.mail.dialogs.MailCreatePanel = Ext.extend(Ext.form.FormPanel, {
	/**
	 * @cfg {Boolean} useHtml True to enable the HTML editor in this panel.
	 */
	useHtml: false,

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

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

		config = Ext.applyIf(config, {
			xtype: 'zarafa.mailcreatepanel',
			layout: {
				type: 'vbox',
				align: 'stretch'
			},
			border: false,
			cls: 'zarafa-mailcreatepanel',
			bodyStyle: 'background-color: inherit;',
			defaults: {
				border: false
			},
			items: [
				this.initMessageFormPanel(config)
			]
		});

		Zarafa.mail.dialogs.MailCreatePanel.superclass.constructor.call(this, config);
	},

	/**
	 * Called automatically when the {@link Zarafa.mail.dialogs.MailCreateContentPanel content panel}
	 * is being rendered. This will add a listener to to the {@link Zarafa.mail.dialogs.MailCreateContentPanel#bcctoggle} button.
	 * @private
	 */
	onRender: function()
	{
		Zarafa.mail.dialogs.MailCreatePanel.superclass.onRender.apply(this, arguments);

		if (this.dialog) {
			this.mon(this.dialog, 'bcctoggle', this.onDialogBccToggle, this);
			this.mon(this.dialog, 'fromtoggle', this.onDialogFromToggle, this);
		}
	},

	/**
	 * Returns items for the splitbutton
	 * @private
	 */
	initSendAsList: function()
	{
		var recipients = container.getSettingsModel().get('zarafa/v1/contexts/mail/sendas', true);
		var items = [];

		if(Ext.isEmpty(recipients)) {
			return;
		}

		Ext.each(recipients, function(recipient) {
			var record = Zarafa.core.data.RecordFactory.createRecordObjectByCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_RECIPIENT, recipient);

			var item = {
				text: record.formatRecipient(true),
				handler: this.onSelectSendAsRecipient,
				scope: this,
				record: record
			};

			items.push(item);
		}, this);

		return items;
	},

	/**
	 * Creates the form panel
	 * @private
	 */
	initMessageFormPanel: function(config)
	{
		var sendAsMenu = this.initSendAsList();
		var hasEmptySendAsList = Ext.isEmpty(sendAsMenu);

		return [{
			xtype: 'container',
			cls: 'zarafa-mailcreatepanel-extrainfo',
			ref: 'extraInfoPanel',
			autoHeight: true,
			hidden: true
		},{
			xtype: 'zarafa.compositefield',
			hideLabel: true,
			ref: 'fromField',
			cls: hasEmptySendAsList ? 'zarafa-mailcreatepanel-field-from-large' : 'zarafa-mailcreatepanel-field-from',
			anchor: '100%',
			autoHeight: true,
			items: [{
				xtype: hasEmptySendAsList ? 'button' : 'splitbutton',
				autoHeight: true,
				text: _('From') + ':',
				handler: this.onSelectUser,
				menu: hasEmptySendAsList ? false : new Ext.menu.Menu({
					showSeparator: false,
					items: sendAsMenu
				}),
				scope: this
			},{
				xtype: 'zarafa.addressbookboxfield',
				fieldLabel: _('From'),
				enableKeyEvents: true,
				ref: '../fromRecipientField',
				boxType: 'zarafa.recipientbox',
				boxStore: new Zarafa.core.data.IPMRecipientStore({
					listeners: {
						// When recipient is added/removed/resolved in from field's
						// store set sent_representing_* properties on record.
						'add': this.onFromRecipientChanged,
						'resolved': this.onFromRecipientChanged,
						'remove': this.onFromRecipientChanged,
						scope: this
					}}),
				flex: 1,
				boxLimit: 1
			}]
		},{
			xtype: 'zarafa.resizablecompositefield',
			hideLabel: true,
			cls: 'zarafa-mailcreatepanel-field-to',
			anchor: '100%',
			autoHeight: false,
			items: [{
				xtype: 'button',
				text: _('To'),
				autoHeight: true,
				handler: function() {
					Zarafa.mail.Actions.openRecipientSelectionContent(this.record, {
						defaultRecipientType: Zarafa.core.mapi.RecipientType.MAPI_TO
					});
				},
				scope: this
			},{
				xtype: 'zarafa.recipientfield',
				ref: '../toRecipientField',
				fieldLabel: _('To'),
				enableKeyEvents: true,
				plugins: [ 'zarafa.recordcomponentupdaterplugin' ],
				flex: 1,
				filterRecipientType: Zarafa.core.mapi.RecipientType.MAPI_TO,
				defaultRecipientType: Zarafa.core.mapi.RecipientType.MAPI_TO
			}]
		},
		{
			xtype: 'zarafa.resizablecompositefield',
			hideLabel: true,
			cls: 'zarafa-mailcreatepanel-field-cc',
			anchor: '100%',
			autoHeight: false,
			items: [{
				xtype: 'button',
				autoHeight: true,
				text: _('Cc'),
				handler: function() {
					Zarafa.mail.Actions.openRecipientSelectionContent(this.record, {
						defaultRecipientType: Zarafa.core.mapi.RecipientType.MAPI_CC
					});
				},
				scope: this
			},{
				xtype: 'zarafa.recipientfield',
				fieldLabel: _('Cc'),
				enableKeyEvents: true,
				plugins: [ 'zarafa.recordcomponentupdaterplugin' ],
				flex: 1,
				filterRecipientType: Zarafa.core.mapi.RecipientType.MAPI_CC,
				defaultRecipientType: Zarafa.core.mapi.RecipientType.MAPI_CC
			}]
		},
		{
			xtype: 'zarafa.resizablecompositefield',
			ref: 'bccField',
			hideLabel: true,
			cls: 'zarafa-mailcreatepanel-field-bcc',
			anchor: '100%',
			autoHeight: false,
			items: [{
				xtype: 'button',
				autoHeight: true,
				text: _('Bcc'),
				handler: function() {
					Zarafa.mail.Actions.openRecipientSelectionContent(this.record, {
						defaultRecipientType: Zarafa.core.mapi.RecipientType.MAPI_BCC
					});
				},
				scope: this
			},{
				xtype: 'zarafa.recipientfield',
				fieldLabel: _('Bcc'),
				enableKeyEvents: true,
				plugins: [ 'zarafa.recordcomponentupdaterplugin' ],
				flex: 1,
				filterRecipientType: Zarafa.core.mapi.RecipientType.MAPI_BCC,
				defaultRecipientType: Zarafa.core.mapi.RecipientType.MAPI_BCC
			}]
		},
		{
			xtype: 'textfield',
			cls: 'zarafa-mailcreatepanel-field-subject',
			name: 'subject',
			fieldLabel: _('Subject'),
			enableKeyEvents: true,
			value: undefined,
			height: 36,
			emptyText: _('Subject') + ':',
			listeners: {
				change: this.onChange,
				scope: this
			}
		},{
			xtype: 'zarafa.resizablecompositefield',
			hideLabel: true,
			cls: 'zarafa-mailcreatepanel-field-attachments',
			anchor: '100%',
			autoHeight: true,
			items: [{
				xtype: 'zarafa.attachmentbutton',
				autoHeight: true,
				plugins: [ 'zarafa.recordcomponentupdaterplugin' ],
				text: _('Attachments')
			},{
				xtype: 'zarafa.attachmentfield',
				enableKeyEvents: true,
				plugins: [ 'zarafa.recordcomponentupdaterplugin' ],
				flex: 1,
				tabIndex: -1,
				hideLabel: true,
				value: undefined
			}]
		},{
			xtype: 'zarafa.editorfield',
			cls: 'zarafa-mailcreatepanel-field-editor',
			enableKeyEvents: true,
			ref: 'editorField',
			hideLabel: true,
			useHtml:config.useHtml,
			flex: 1,
			listeners: {
				change: this.onBodyChange,
				initialized: this.onEditorInitialized,
				valuecorrection: this.onBodyValueCorrection,
				scope: this
			}
		}];
	},

	/**
	 * Updates the panel by loading data from the record into the header template, and
	 * loading the body html into the embedded iframe.
	 *
	 * @param {Zarafa.core.data.IPMRecord} 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;
		this.getForm().loadRecord(record);
		if(contentReset) {
			this.editorField.setHtmlEditor(this.useHtml, false);
			this.editorField.bindRecord(record);

			var body = '';
			if (this.useHtml) {
				// Prefer explicit html_body for compose to avoid timing races on isOpened/isHTML.
				body = record.get('html_body');
				if (Ext.isEmpty(body) && Ext.isFunction(record.getBody)) {
					body = record.getBody(true);
				}
				if (Ext.isEmpty(body)) {
					body = Zarafa.core.HTMLParser.convertPlainToHTML(record.get('body') || '');
				}
				if (!Ext.isEmpty(body) && container.getServerConfig().getDOMPurifyEnabled()) {
					body = DOMPurify.sanitize(body);
				}
			} else if (Ext.isFunction(record.getBody)) {
				body = record.getBody(false);
			} else {
				body = record.get('body');
			}

			var applyBody = function() {
				var editorCurrentValue = this.editorField.getValue();
				if (!Ext.isEmpty(body) || Ext.isEmpty(editorCurrentValue)) {
					this.editorField.setValue(body || '');
				}
			};

			applyBody.call(this);

			// Re-apply once after editor initialization to avoid timing races where
			// TinyMCE init/default-formatting wins over the first setValue().
			this.editorField.on('initialized', function() {
				applyBody.call(this);
			}, this, { single: true });
		}

		if (contentReset) {
			record.setDefaultFromRecipeint();
			record.setDefaultCcRecipients();
		}

		this.updateExtraInfoPanel();
	},

	/**
	 * Function will update the {@link #extraInfoPanel} with extra information that should be shown
	 * @private
	 */
	updateExtraInfoPanel: function()
	{
		// clear the previous contents
		var el = this.extraInfoPanel.getEl();
		if(Ext.isDefined(el.dom)) {
			el.dom.innerHTML = '';
		}

		var infoMessage = this.getExtraInfoMessage();

		if (infoMessage) {
			el.createChild({tag: 'div', html: pgettext('calendar.dialog', infoMessage)});
		}

		this.extraInfoPanel.setVisible(infoMessage !== false);
		this.doLayout();
	},

	/**
	 * Helper function to prepare extra-info-message based on configured flag properties.
	 *
	 * @param {String|Boolean} Message to show, false otherwise.
	 */
	getExtraInfoMessage: function()
	{
		var flagStatus = this.record.get('flag_status');
		if (flagStatus !== Zarafa.core.mapi.FlagStatus.flagged) {
			return false;
		}

		var configuredFlag = Zarafa.common.flags.Util.getConfiguredFlag(this.record);
		if (configuredFlag === 'no_date') {
			return _("This message will be flagged for follow up when it is sent.");
		} else {
			return String.format("This message will be flagged for follow up {0} when it is sent.", configuredFlag);
		}
	},

	/**
	 * Event handler for the {@link Zarafa.common.ui.HtmlEditor#valuecorrection valuecorrection}
	 * event handler which is fired when the Browser has changed the HTML data after it has been set
	 * into the {@link Zarafa.common.ui.HtmlEditor}. This will apply the corrected value
	 * directly back into the {@link #record} to ensure we don't consider this as a change (as technically
	 * the data is still the same, but the browser formatted/validated it).
	 * @param {Zarafa.common.ui.HtmlEditor} field The field which fired the event
	 * @param {String} value The corrected value
	 * @param {String} oldValue the original value which was applied.
	 * @private
	 */
	onBodyValueCorrection: function(field, value, oldValue)
	{
		var record = this.record;

		if (this.editorField.isHtmlEditor()) {
			record.data.html_body = record.inlineImgZarafaToOutlook(value);
		} else {
			record.data.body = value;
		}

		// Go for valuecorrection after record gets opened
		this.record.store.on('open', this.onRecordOpen, this);
	},

	/**
	 * Event Handler for {@link Zarafa.core.data.IPMStore store} open event. Handling a
	 * special situation where two open requests made to server while double clicking
	 * the mail from grid, first for tab panel, second for preview panel.
	 * The html_body property of record, which belongs to tab panel, gets updated while
	 * handling the response of second open request discarding the tinyMCE incurred changes,
	 * left us with the situation where UI and underlying record differs.
	 * To resolve this, we need to go for valuecorrection once the record gets opened successfully.
	 *
	 * @param {Zarafa.core.data.IPMStore} store The store of the record.
	 * @param {Zarafa.core.data.IPMRecord} record The record which will be converted to a task
	 */
	onRecordOpen: function(store, record)
	{
		if (this.editorField) {
			var fieldValue = this.editorField.getValue();
			var recordBody = false;

			if (Ext.isFunction(record.getBody)) {
				recordBody = record.getBody(this.editorField.isHtmlEditor());
			}

			// Go further only if editor value differs with html_body of record
			if ( fieldValue !== recordBody ) {
				this.onBodyValueCorrection(this.editorField, fieldValue);
			}
		}
	},

	/**
	 * 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)
	{
		record.beginEdit();
		this.getForm().updateRecord(record);

		record.setBody(this.editorField.getValue(), this.editorField.isHtmlEditor());

		record.endEdit();
	},

	/**
	 * @param {Object} field The field updated field
	 * @param {Object} value The value of the field updated
	 * @private
	 */
	onChange: function(field, value)
	{
		var record = this.record;
		var fieldname = field.name;
		if(fieldname === 'subject') {
			record.set('normalized_subject', value);
			record.set(fieldname, value);
		}
	},

	/**
	 * Event handler which is triggered when one of the Input fields
	 * has been changed by the user. It will validate the new value,
	 * and if correct, will apply it to the {@link Zarafa.core.data.IPMRecord record}.
	 * @param {Ext.form.Field} field The {@link Ext.form.Field field} which was changed.
	 * @param {Mixed} newValue The new value
	 * @param {Mixed} oldValue The old value
	 * @private
	 */
	onBodyChange: function(field, newValue, oldValue)
	{
		var record = this.record;

		record.beginEdit();

		record.setBody(this.editorField.getValue(), this.editorField.isHtmlEditor());

		record.endEdit();
	},

	/**
	 * Event handler which is triggered when {@link Zarafa.common.ui.HtmlEditor editor}
	 * is created inside the editor field.
	 * @param {Zarafa.common.ui.HtmlEditor} htmlEditor The editor which is initialized.
	 * @param {tinymce.Editor} tinymceEditor The tinymce editor instance
	 * @private
	 */
	onEditorInitialized: function(htmlEditor, tinymceEditor)
	{
		/*
		 * Fixed issue with FF that when we change focus in FF using tab key it sets focus
		 * on iframe's 'document' element rather then 'body' element, so register keymap
		 * event on iframe's 'document'.
		 * Another issue is that Ext.Element.get will return null for document element of iframes
		 * because actually it isn't Ext.Element, So we need to create a dummy element explicitly
		 * using editor's(iframe's) document element.
		 */
		var element = new Ext.Element(tinymceEditor.getDoc());

		// Here we are passing dialog as a component and editor's document as element, so
		// that when key events are fired on the element it will pass dialog component
		// as an argument in callback function.
		Zarafa.core.KeyMapMgr.activate(this.dialog, 'global', element);
		Zarafa.core.KeyMapMgr.activate(this.dialog, 'contentpanel.record.message', element);
	},

	/**
	 * Event handler that is fired when the BCC field visibility must be changed.
	 * This will update UI of the {@link Zarafa.mail.dialogs.MailCreateContentPanel}
	 * @param {Zarafa.core.ui.ContentPanel} contentpanel
	 * @param {Boolean} enabled true if the the BCC field should be shown
	 * @private
	 */
	onDialogBccToggle: function(contentpanel, enabled)
	{
		this.bccField.setVisible(enabled);
		this.doLayout();
	},

	/**
	 * Event handler that is fired when the BCC field visibility must be changed.
	 * This will update UI of the {@link Zarafa.mail.dialogs.MailCreateContentPanel}
	 * @param {Zarafa.core.ui.ContentPanel} contentpanel
	 * @param {Boolean} enabled true if the the BCC field should be shown
	 * @private
	 */
	onDialogFromToggle: function(contentpanel, enabled)
	{
		this.fromField.setVisible(enabled);
		this.doLayout();
	},

	/**
	 * @param {Number} signatureId, The signatureId which is used to get correct signature.
	 * which is going to be added in editor.
	 */
	setSignatureInEditor: function(signatureId)
	{
		var model = this.dialog.getContextModel();
		var useHtmlEditor = this.editorField.isHtmlEditor();
		var signatureData = model.getSignatureData(useHtmlEditor, signatureId, false);

		if (Ext.isEmpty(signatureData)) {
			return;
		}

		if (useHtmlEditor) {
			var editorComponent = this.editorField.getEditor();
			if (editorComponent && Ext.isFunction(editorComponent.getEditor)) {
				var tinyMceEditor = editorComponent.getEditor();
				if (tinyMceEditor && tinyMceEditor.dom) {
					var bodyElement = tinyMceEditor.getBody();
					var existingSignature = null;

					Ext.each(tinyMceEditor.dom.select('.signatureContainer') || [], function(node) {
						if (node.parentNode === bodyElement) {
							existingSignature = node;
							return false;
						}
					});

					if (existingSignature) {
						// Replace the content of the existing signature container instead of appending a new one.
						tinyMceEditor.dom.setHTML(existingSignature, signatureData);
						tinyMceEditor.selection.select(existingSignature);
						tinyMceEditor.selection.collapse(false);
						this.editorField.currentSignatureHtml = tinyMceEditor.dom.getOuterHTML(existingSignature);
						return;
					}
				}
			}

			signatureData = model.wrapSignature(signatureData);
			this.editorField.currentSignatureHtml = signatureData;
		}

		this.editorField.insertAtCursor(signatureData);
	},

	/**
	 * Function will be called when user/recipients is added/removed or resolved
	 * which is added in from field.
	 * Function will set sent_representing_* properties to send mails on behalf.
	 * @private
	 */
	onFromRecipientChanged: function()
	{
		var store = this.fromRecipientField.getBoxStore();
		var record = this.record;
		record.beginEdit();

		var recipientRecord = store.getAt(0);

		if(recipientRecord) {
			this.getTopToolbar().showFrom.disable();

			record.set('sent_representing_name', recipientRecord.get('display_name'));
			record.set('sent_representing_email_address', recipientRecord.get('email_address'));
			record.set('sent_representing_address_type', recipientRecord.get('address_type'));
			record.set('sent_representing_entryid', recipientRecord.get('entryid'));
			record.set('sent_representing_search_key', recipientRecord.get('search_key'));
			var smtpAddress = recipientRecord.get('smtp_address');
			if (Zarafa.core.Util.validateEmailAddress(smtpAddress)) {
				record.set('sent_representing_smtp_address', smtpAddress);
			}
		} else {
			this.getTopToolbar().showFrom.enable();

			record.set('sent_representing_name', '');
			record.set('sent_representing_email_address', '');
			record.set('sent_representing_address_type', '');
			record.set('sent_representing_entryid', '');
			record.set('sent_representing_search_key', '');
			// Also clear the SMTP address, otherwise the removed identity
			// survives on the saved message.
			record.set('sent_representing_smtp_address', '');
		}

		record.endEdit();
	},

	/**
	 * Event handler which is fired when the user presses the 'Name' button.
	 * This will open the Address Book User Selection Dialog to select a user.
	 * @private
	 */
	onSelectUser: function()
	{
		Zarafa.common.Actions.openABUserSelectionContent({
			callback: this.abCallBack,
			scope: this,
			hideContactsFolders: true,
			listRestriction: {
				hide_users: ['system', 'everyone'],
				hide_companies: true
			}
		});
	},

	/**
	 * Event handler which is fired when the user selects a send as recipient for from field.
	 *
	 * @private
	 */
	onSelectSendAsRecipient: function(item)
	{
		if(!item.record) {
			// can't do anything here
			return;
		}

		var store = this.fromRecipientField.getBoxStore();

		// We need to maintain that in from field there should be only one user,
		// So remove earlier added user.
		store.removeAll();
		store.add(item.record);
	},

	/**
	 * Callback function for {@link Zarafa.addressbook.dialogs.ABUserSelectionContent AddressBook}
	 * @param {Ext.data.Record} record user selected from AddressBook
	 * @private
	 */
	abCallBack: function(record)
	{
		var store = this.fromRecipientField.getBoxStore();

		// We need to maintain that in from field there should be only one user,
		// So remove earlier added user.
		store.removeAll();
		store.add(record.convertToRecipient());
	}
});

Ext.reg('zarafa.mailcreatepanel', Zarafa.mail.dialogs.MailCreatePanel);
Ext.namespace('Zarafa.mail.dialogs');

/**
 * @class Zarafa.mail.dialogs.MailOptionsMiscPanel
 * @extends Ext.form.FormPanel
 * @xtype zarafa.mailoptionsmiscpanel
 *
 * Panel for users to set miscellaneous options on a given {@link Zarafa.mail.MailRecord record},
 * like the categories.
 */
var ensureRecordObjectId = function(record)
{
	if (!record) {
		return;
	}

	var objectIdText = record.get('x_midtext');
	if (!objectIdText) {
		objectIdText = Zarafa.core.EntryId.formatObjectId(record.get('entryid'));
		if (objectIdText && record.get('x_midtext') !== objectIdText) {
			record.set('x_midtext', objectIdText);
		}
	}
};

Zarafa.mail.dialogs.MailOptionsMiscPanel = Ext.extend(Ext.form.FormPanel, {

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

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

		config = Ext.applyIf(config, {
			xtype: 'zarafa.mailoptionsmiscpanel',
			title: _('Properties'),
			layout: 'form',
			items: [{
				xtype: 'textarea',
				fieldLabel: _('Internet Headers'),
				name: 'transport_message_headers',
				readOnly: true,
				anchor: '100% -50',
				autoScroll: true,
				border: false,
				// Make sure the text is not wrapped
				style: 'word-wrap: normal',
				ref: 'headersTextArea'
			},{
				xtype: 'textfield',
				fieldLabel: _('Object ID'),
				name: 'x_midtext',
				readOnly: true,
				anchor: '100%',
				autoScroll: true,
				border: false
			}]
		});

		Zarafa.mail.dialogs.MailOptionsMiscPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Update the {@link Ext.Panel Panel} with the given {@link Zarafa.core.data.IPMRecord IPMRecord}
	 * @param {Zarafa.core.data.IPMRecord} record The record to 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 (record) {
			ensureRecordObjectId(record);
			this.getForm().loadRecord(record);
		} else {
			this.headersTextArea.setValue('');
		}
	},

	/**
	 * Update the {@link Zarafa.core.data.IPMRecord IPMRecord} with the data from the {@link Ext.Panel Panel}.
	 * @param {Zarafa.core.data.IPMRecord} record The record which has to be updated
	 */
	updateRecord: function(record)
	{
		this.getForm().updateRecord(record);
	}
});

/* A reduced MiscPanel for Draft messages where PR_TRANSPORT_HEADERS does not make sense */
Zarafa.mail.dialogs.MailOptionsMsgidPanel = Ext.extend(Ext.form.FormPanel, {
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

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

		config = Ext.applyIf(config, {
			xtype: 'zarafa.mailoptionsmiscpanel',
			title: _('Properties'),
			layout: 'form',
			items: [{
				xtype: 'textfield',
				fieldLabel: _('Object ID'),
				name: 'x_midtext',
				readOnly: true,
				anchor: '100%',
				autoScroll: true,
				border: false
			}]
		});

		Zarafa.mail.dialogs.MailOptionsMsgidPanel.superclass.constructor.call(this, config);
	},

	update: function(record, contentReset)
	{
		this.record = record;

		if (record) {
			ensureRecordObjectId(record);
			this.getForm().loadRecord(record);
		} else {
			var objectIdField = this.getForm().findField('x_midtext');
			if (objectIdField) {
				objectIdField.setValue('');
			}
		}
	},

	/**
	 * Update the {@link Zarafa.core.data.IPMRecord IPMRecord} with the data from the {@link Ext.Panel Panel}.
	 * @param {Zarafa.core.data.IPMRecord} record The record which has to be updated
	 */
	updateRecord: function(record)
	{
		this.getForm().updateRecord(record);
	}
});

Ext.reg('zarafa.mailoptionsmiscpanel', Zarafa.mail.dialogs.MailOptionsMiscPanel);
Ext.reg('zarafa.mailoptionsmsgidpanel', Zarafa.mail.dialogs.MailOptionsMsgidPanel);
Ext.namespace('Zarafa.mail.dialogs');

/**
 * @class Zarafa.mail.dialogs.MailOptionsTrackingPanel
 * @extends Ext.form.FormPanel
 * @xtype zarafa.mailoptionstrackingpanel
 *
 * Panel for users to set tracking options on a given {@link Zarafa.mail.MailRecord record},
 * like read receipts.
 */
Zarafa.mail.dialogs.MailOptionsTrackingPanel = Ext.extend(Ext.form.FormPanel, {
	/**
	 * The record on which this panel is operating on. This record is provided through the
	 * {@link #update} function.
	 * @property
	 * @type Zarafa.core.data.IPMRecord
	 */
	record: undefined,

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

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

		config = Ext.applyIf(config, {
			xtype: 'zarafa.mailoptionstrackingpanel',
			title: _('Tracking Options'),
			layout: 'form',
			items: [{
				xtype: 'checkbox',
				hideLabel: true,
				ref: 'readReceiptCheckbox',
				boxLabel: _('Request a read receipt for this message.'),
				name: 'read_receipt_requested',
				handler: this.onFieldToggle,
				scope: this
			}]
		});

		Zarafa.mail.dialogs.MailOptionsTrackingPanel.superclass.constructor.call(this, config);
	},

	 /**
	 * A function called when the checked value changes for the checkbox.
	 * @param {Ext.form.Checkbox} checkbox The Checkbox being toggled.
	 * @param {Boolean} checked The new checked state of the checkbox.
	 * @private
	 */
	onFieldToggle: function(checkbox, checked)
	{
		this.record.set(checkbox.getName(), checked);
	},

	/**
	 * Update the {@link Ext.Panel Panel} with the given {@link Zarafa.core.data.IPMRecord IPMRecord}
	 * @param {Zarafa.core.data.IPMRecord} record The record to 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 (record) {
			this.getForm().loadRecord(record);

			if (!record.phantom && !record.isUnsent()) {
				this.readReceiptCheckbox.disable();
			} else {
				this.readReceiptCheckbox.enable();
			}
		} else {
			this.readReceiptCheckbox.setValue(false);
		}
	},

	/**
	 * Update the {@link Zarafa.core.data.IPMRecord IPMRecord} with the data from the {@link Ext.Panel Panel}.
	 * @param {Zarafa.core.data.IPMRecord} record The record which has to be updated
	 */
	updateRecord: function(record)
	{
		this.getForm().updateRecord(record);
	}
});

Ext.reg('zarafa.mailoptionstrackingpanel', Zarafa.mail.dialogs.MailOptionsTrackingPanel);
Ext.namespace('Zarafa.mail.dialogs');

/**
 * @class Zarafa.mail.dialogs.MailViewOptionsPanel
 * @extends Ext.Panel
 * @xtype zarafa.mailviewoptionspanel
 *
 * Panel for users to set the options on a given {@link Zarafa.mail.MailRecord record}
 */
Zarafa.mail.dialogs.MailViewOptionsPanel = Ext.extend(Ext.Panel, {
	/**
	 * @constructor
	 * @param config Configuration structure
	 */
	constructor: function(config)
	{
		config = config || {};

		config = Ext.applyIf(config, {
			// Override from Ext.Component
			xtype: 'zarafa.mailviewoptionspanel',
			layout: {
				type: 'vbox',
				align: 'stretch'
			},
			border: false,
			defaults: {
				bodyStyle: 'padding-top: 5px; padding-left: 6px; padding-right: 5px; background-color: inherit;',
				border: false
			},
			items: [{
				xtype: 'zarafa.mailoptionssettingspanel'
			},{
				xtype: 'zarafa.mailoptionstrackingpanel'
			},{
				xtype: 'zarafa.mailoptionsmiscpanel',
				flex: 1
			}]
		});

		Ext.apply(this, config);

		Zarafa.mail.dialogs.MailViewOptionsPanel.superclass.constructor.call(this, config);
	}
});

Ext.reg('zarafa.mailviewoptionspanel', Zarafa.mail.dialogs.MailViewOptionsPanel);
Ext.namespace('Zarafa.mail.settings');

/**
 * @class Zarafa.mail.settings.SafeSenderGrid
 * @extends Ext.grid.GridPanel
 * @xtype zarafa.safesendergrid
 *
 * {@link Zarafa.mail.settings.SafeSenderGrid SafeSenderGrid} will be used to display
 * list of safe sender's email address or domain name.
 */
Zarafa.mail.settings.SafeSenderGrid = Ext.extend(Ext.grid.GridPanel, {
	/**
	 * @constructor
	 * @param {Object} config Configuration structure
	 */
	constructor: function (config) {
		config = config || {};

		var store = {
			xtype: 'jsonstore',
			root: 'item',
			fields: [
				{ name: 'id', type: 'int' },
				{ name: 'name' }
			],
			idProperty: 'id',
			sortInfo: {
				field: 'name',
				direction: 'ASC'
			},
			autoDestroy: true
		};

		Ext.applyIf(config, {
			xtype: 'zarafa.safesendergrid',
			name: 'zarafa/v1/contexts/mail/safe_senders_list',
			height: 320,
			forceFit: true,
			store: store,
			listeners: {
				viewready: this.onViewReady,
				scope: this
			},
			viewConfig: {
				forceFit: true,
				deferEmptyText: false,
				emptyText: '<div class="emptytext">' + _('Safe Senders list is empty') + '</div>'
			},
			columns: [{
				dataIndex: 'name',
				header: _('Name'),
				menuDisabled: true,
				sortable: true,
				renderer: Zarafa.common.ui.grid.Renderers.text
			}]
		});

		Zarafa.mail.settings.SafeSenderGrid.superclass.constructor.call(this, config);
	},

	/**
	 * Event fired when the {@link Zarafa.mail.settings.SafeSenderGrid grid} fires the
	 * {@link Zarafa.mail.settings.SafeSenderGrid#viewready viewready} event. This will check
	 * if safesenders available in store then selects first row in grid.
	 * @private
	 */
	onViewReady: function()
	{
		this.getSelectionModel().selectFirstRow();
	},

	/**
	 * This will get all the selected records and {@link Ext.data.JsonStore#remove remove} it
	 * from {@link Zarafa.mail.settings.SafeSenderGrid#store store}
	 * @private
	 */
	deleteSafeSender: function()
	{
		var selectionModel = this.getSelectionModel();
		var safeSenderRecord = selectionModel.getSelections();
		var store = this.getStore();
		var rowToSelect;

		if(Ext.isEmpty(safeSenderRecord)) {
			Ext.Msg.alert(_('Alert'), _('Please select a safe sender record.'));
			return;
		}

		// before removing safesenders we should store row index of next available safesender,
		// because deleting a safesender will remove selection
		if (selectionModel.hasNext()) {
			rowToSelect = selectionModel.last;
		} else if (selectionModel.hasPrevious()) {
			rowToSelect = selectionModel.last-1;
		}

		store.remove(safeSenderRecord);

		if (Ext.isDefined(rowToSelect)) {
			selectionModel.selectRow(rowToSelect);
		}
	},

	/**
	 * This will get all the {@link Ext.data.Record[] records} from
	 * {@link #store} used in this GridPanel.
	 * and {@link Ext.data.JsonStore#removeAll removeAll}.
	 * @private
	 */
	deleteAllSafeSender: function()
	{
		Ext.MessageBox.show({
			title: _('Delete all safe senders'),
			msg: _('Are you sure you want to delete all safe senders?'),
			buttons: Ext.MessageBox.YESNO,
			fn: function (buttonClicked) {
				if (buttonClicked == 'yes') {
					this.getStore().removeAll();
				}
			},
			scope: this
		});
	},

	/**
	 * Helper function which returns a list of safe senders from the
	 * {@link #store} config used in this GridPanel.
	 *
	 * @return {Array} list of currently available safesenders records from store.
	 * @private
	 */
	getSafeSenders: function()
	{
		var store = this.getStore();
		var records = store.getRange();

		return records.map(function(record){
			return record.get("name");
		});
	}
});

Ext.reg('zarafa.safesendergrid', Zarafa.mail.settings.SafeSenderGrid);Ext.namespace('Zarafa.mail.ui');

/**
 * @class Zarafa.mail.ui.MailGridHeaderDropZone
 * @extends Ext.grid.HeaderDropZone
 */
Zarafa.mail.ui.MailGridHeaderDropZone = Ext.extend(Ext.grid.HeaderDropZone, {

	/**
	 * Event handler triggered when node over the grid header.
	 *
	 * @param {Ext.Element} n The node element which is drag and drop.
	 * @param {Ext.dd.DD} dd A DragDrop implementation where the linked element follows the mouse cursor during a drag.
	 * @param {Ext.EventObject} e The event object.
	 * @param {Object} data The data object which contains the {@link Ext.Element header}.
	 * @returns {string} return either "x-dd-drop-ok" or "x-dd-drop-nodrop".
	 */
	onNodeOver: function(n, dd, e, data)
	{
		var result = Zarafa.mail.ui.MailGridHeaderDropZone.superclass.onNodeOver.apply(this, arguments);

		var h = data.header;
		var view = this.view;

		var oldIndex = view.getCellIndex(h);
		var newIndex = view.getCellIndex(n);
		var cm = this.grid.colModel;

		var oldCol = cm.getColumnAt(oldIndex);
		var newCol = cm.getColumnAt(newIndex);

		var pt = this.getAppendPosition(n, e);

		if ((oldCol.preventColSwitch === true ||
			newCol.preventColSwitch === true ||
			this.isColAllowSwitch(n, pt === "after" ? "nextVisible" : "prevVisible") === false)
		) {
			return this.dropNotAllowed;
		}

		return result;
	},

	/**
	 * Helper function which used to check {@link Ext.Element header} should allow to switch if
	 * next/previous visible column is not 'preventColSwitch' true.
	 *
	 * @param {Ext.Element} n The node element which is drag and drop.
	 * @param {string} checkVisibleFn The handler name which used to get next/previous visible column.
	 * @returns {boolean} True to allow the header column to switch else false.
	 */
	isColAllowSwitch: function(n, checkVisibleFn)
	{
		if (Ext.isEmpty(checkVisibleFn) || (checkVisibleFn !== 'nextVisible' && checkVisibleFn !== 'prevVisible')) {
			console.error("checkVisibleFn should be either 'nextVisible' or 'prevVisible'.");
			return false;
		}

		var cm = this.grid.colModel;
		var view = this.view;

		var colVisibleIndex = view.getCellIndex(this[checkVisibleFn](n));

		if (colVisibleIndex === false) {
			return false;
		}

		var visibleCol = cm.getColumnAt(colVisibleIndex);
		if (visibleCol.preventColSwitch === true){
			return false;
		}

		return true;
	},

	/**
	 * Override function to get the previous visible element.
	 *
	 * @param {Ext.Element} h The element which previous visible we need to find.
	 * @returns {Ext.Element | null} element if there is some previous visible else Null.
	 */
	prevVisible: function(h)
	{
		var v = this.view, cm = this.grid.colModel;
		h = h.previousSibling;
		while(h){
			if(!cm.isHidden(v.getCellIndex(h))){
				return h;
			}
			h = h.previousSibling;
		}
		return null;
	},

	/**
	 * Helper function used to get the position where column is
	 * append before/after the header column.
	 *
	 * @param {Ext.Element} n The node element which is drag and drop.
	 * @param {Ext.dd.DD} dd A DragDrop implementation where the linked element follows the mouse cursor during a drag.
	 * @returns {string}
	 */
	getAppendPosition: function(n, e)
	{
		var x = Ext.lib.Event.getPageX(e),
			r = Ext.lib.Dom.getRegion(n.firstChild);

		return (r.right - x) <= ((r.right-r.left)/2) ? "after" : "before";
	},

	/**
	 * Event handler called when node is drop somewhere in grid header. It will check
	 * column is marked 'preventColSwitch' or next/previous visible column are not marked as a 'preventColSwitch'
	 * then dont allow to switch that column.
	 *
	 * @param {Ext.Element} n The node element which is drag and drop.
	 * @param {Ext.dd.DD} dd A DragDrop implementation where the linked element follows the mouse cursor during a drag.
	 * @param {Ext.EventObject} e The event object.
	 * @param {Object} data The data object which contains the {@link Ext.Element header}.
	 * @returns {boolean} True to allow node to drop else false.
	 */
	onNodeDrop: function(n, dd, e, data)
	{
		var h = data.header;
		if(h != n){
			var cm = this.grid.colModel,
				pt = this.getAppendPosition(n, e),
				oldIndex = this.view.getCellIndex(h),
				newIndex = this.view.getCellIndex(n);

			var oldCol = cm.getColumnAt(oldIndex);
			var newCol = cm.getColumnAt(newIndex);

			if (oldCol.preventColSwitch === true ||
				newCol.preventColSwitch === true ||
				this.isColAllowSwitch(n, pt === "after" ? "nextVisible" : "prevVisible") === false) {
				return false;
			}

			if(pt == "after"){
				newIndex++;
			}

			if(oldIndex < newIndex){
				newIndex--;
			}

			cm.moveColumn(oldIndex, newIndex);
			return true;
		}
		return false;
	}
});
Ext.namespace('Zarafa.mail.ui');

/**
 * @class Zarafa.mail.ui.MailRowSelectionModel
 * @extends Ext.grid.RowSelectionModel
 *
 * The {@link Zarafa.mail.ui.MailRowSelectionModel MailRowSelectionModel}
 * is the {@link Ext.grid.RowSelectionModel RowSelectionModel} used in the
 * {@link Zarafa.mail.ui.MailGrid MailGrid}.
 * It checks columns that have the preventRowSelection property and triggers actions depending on it.
 * See {@link #handleMouseDown}
 */
Zarafa.mail.ui.MailRowSelectionModel = Ext.extend(Ext.grid.RowSelectionModel, {
	/**
	 * This property will help to store value for {@link #last} property.
	 * As we loose the track for {@link #last} because of the dynamically addition of rows
	 * on the expansion of the conversations.
	 *
	 * @property
	 * @type Number
	 */
	updatedLast: undefined,

	/**
	 * Overridden to test for custom columns.
	 * If one of the columns of interest is clicked, then the row should not be selected.
	 * Otherwise call original handler.
	 * 'cellmousedown' does not fire when drag&drop is installed on a component,
	 * otherwise it may have been possible to cancel selection from there by returning false.
	 *
	 * @param {Zarafa.mail.ui.MailGrid} g The mail grid from which the event came
	 * @param {Number} rowIndex Index of the row that was clicked
	 * @param {Ext.EventObject} event The mouse event
	 *
	 * @override
	 * @private
	 */
	handleMouseDown: function (g, rowIndex, event) {
		var cm = g.getColumnModel();
		// get cell index from event object
		var cellIndex = g.getView().findCellIndex(event.getTarget());
		// boolean to determine what we are going to do
		var preventRowSelection = false;
		if (Ext.get(event.target).hasClass('icon_flag')) {
			preventRowSelection = true;
		} else if (cellIndex !== false && cm.config[cellIndex].dataIndex === 'sent_representing_name' && Ext.fly(event.target).hasClass('k-icon')) {
			preventRowSelection = true;
		} else if (cellIndex !== false && cellIndex >= 0) {
			preventRowSelection = cm.config[cellIndex].preventRowSelection;
		} else if (cellIndex === false && Ext.get(event.target).hasClass('k-category-add')) {
			// Prevent selection when the mousedown event is on the 'add category icon'
			preventRowSelection = true;
		}
		if (preventRowSelection === true) {
			// We must still set the focus on the clicked element, or else the grid will jump to
			// the previous focussed element when we close a dialog
			g.getView().focusRow(rowIndex);
		} else {
			if (event.shiftKey && !this.singleSelect && this.last !== false) {
				// Store the last index record as it might get changed after the expansion of the conversation.
				// So later it will be used to get the correct index for rowIndex.
				// No need to store the start index as we are maintaining {@link #updatedLast}. Which will update
				// {@link #last}.
				var store = this.grid.getStore();
				var recordAtEndIndex = store.getAt(rowIndex);

				if (this.updatedLast) {
					this.last = this.updatedLast;
					this.clearUpdatedLast();
				}

				// Index of the last record might have changed.
				// So, fetch the latest index for the the last record.
				rowIndex = store.indexOf(recordAtEndIndex);
			}

			Zarafa.mail.ui.MailRowSelectionModel.superclass.handleMouseDown.call(this, g, rowIndex, event);
		}
	},

	/**
	 * Helper function to know whether the given index is out of range for this grid or not.
	 *
	 * @param {Number} index Number which required to be checked if it exists in grid.
	 */
	isIndexOutOfRange: function(index)
	{
		return index < 0 || index >= this.grid.store.getCount();
	},

	/**
	 * Selects the row immediately following the last selected row.
	 * @param {Boolean} keepExisting (optional) <tt>true</tt> to keep existing selections
	 * @param {Integer} rowIndex (optional) The index of row whose next row needs to be selected.
	 * @return {Boolean} <tt>true</tt> if there is a next row, else <tt>false</tt>
	 */
	selectNext: function(keepExisting, rowIndex)
	{
		var nextRowIndex = Ext.isEmpty(rowIndex) ? this.last + 1 : rowIndex+1;
		if (!this.isIndexOutOfRange(nextRowIndex)){
			var store = this.grid.getStore();
			var record = store.getAt(nextRowIndex);
			// Check if its header record.
			if (record.isConversationHeaderRecord()) {
				// If conversation was opened then skip the header record and
				// select the first item of the conversation instead.
				if (store.isConversationOpened(record)) {
					return this.selectNext(keepExisting, nextRowIndex);
				}

				// If conversation was closed then expand it and select its first item.
				store.expandConversation(record);
				nextRowIndex += 1;
			}

			this.selectRow(nextRowIndex, keepExisting);
			this.grid.getView().focusRow(this.last);

			if (this.grid.expandSingleConversation && !keepExisting) {
				var currHeader = store.getHeaderRecordFromItem(store.getAt(nextRowIndex));
				store.collapseAllConversation(currHeader);
			}

			return true;
		}
		return false;
	},

	/**
	* Selects the row that precedes the last selected row.
	* @param {Boolean} keepExisting (optional) <tt>true</tt> to keep existing selections
	 * @param {Integer} rowIndex (optional) The index of row whose previous row needs to be selected.
	* @return {Boolean} <tt>true</tt> if there is a previous row, else <tt>false</tt>
	*/
	selectPrevious: function(keepExisting, rowIndex)
	{
		var prvRowIndex = Ext.isEmpty(rowIndex) ? this.last-1 : rowIndex-1;
		if (!this.isIndexOutOfRange(prvRowIndex)) {
			var store = this.grid.getStore();
			var record = store.getAt(prvRowIndex);
			var isHeaderRecord = record.isConversationHeaderRecord();
			// Check if its header record.
			if (isHeaderRecord) {
				// If conversation was opened the select previous record of the header record.
				if (store.isConversationOpened(record)) {
					this.selectPrevious(keepExisting, prvRowIndex);
					return true;
				}

				// If conversation was closed then select last item of it.
				// Note: conversation_count can include sent items that have not
				// been fetched yet, so count the actually present items.
				store.expandConversation(record);
				prvRowIndex += store.getConversationItemsFromHeaderRecord(record).length;
			}

			this.selectRow(prvRowIndex, keepExisting);
			this.grid.getView().focusRow(this.last);

			if (this.grid.expandSingleConversation && !keepExisting) {
				var currHeader = store.getHeaderRecordFromItem(record);
				store.collapseAllConversation(currHeader);
			}

			return true;
		}
		return false;
	},

	/**
	 * Helper function of {@link #onKeyPress} function.
	 * Which will update the end index for the selection range. It will also expand the conversation found at the endIndex.
	 * This might also update {@link #updatedLast} as new rows would be added in the grid due to expansion of the conversations.
	 *
	 * @param {Number} endIndex end index of the selection range
	 * @param {Boolean} up true if up key is pressed.
	 * @param {Number} add addend to add in endIndex in accordance with the direction.
	 */
	prepareEndSelectionIndex: function(endIndex, up, add)
	{
		var store = this.grid.getStore();
		var record = store.getAt(endIndex);
		var isHeaderRecord = record.isConversationHeaderRecord();

		// If header record is found at the end index of the selection range
		// and if that conversation is closed then expand that conversation.
		// And update endIndex accordig to the selection direction.
		if (isHeaderRecord && !store.isConversationOpened(record)) {
			store.expandConversation(record);
			if (up) {
				// If selection direction is up then select last item of the conversation.
				// And update the {@link #updatedLast}.
				// Note: conversation_count can include sent items that have not
				// been fetched yet, so count the actually present items.
				var conversationCount = store.getConversationItemsFromHeaderRecord(record).length;
				endIndex += conversationCount;
				this.updatedLast += conversationCount;
			} else {
				// If selection direction is down then select first item of the conversation.
				// No need to update the {@link #updatedLast} for downwards selection.
				endIndex += add;
			}
		} else if (isHeaderRecord && !this.isIndexOutOfRange(endIndex + add)) {
			// If header record of an open conversation is found at the endIndex then
			// extend the endIndex and check whether new endIndex is out of range.
			// If its in the range then check for the closed conversation at the new endIndex;
			endIndex = this.prepareEndSelectionIndex(endIndex + add, up, add);
		}

		return endIndex;
	},

	/**
	 * This function will reset the config {@link #updatedLast}
	 */
	clearUpdatedLast: function()
	{
		this.updatedLast = undefined;
	},

	/**
	 * Overridden to batch select all rows without firing events per row.
	 * The base implementation calls selectRow in a loop for every row.
	 */
	selectAll: function()
	{
		var count = this.grid.store.getCount();
		if (this.isLocked() || count === 0) {
			return;
		}
		// Clear selection data without per-row deselect events,
		// matching the base selectAll behavior.
		this.selections.clear();
		this.selectRange(0, count - 1, true);
	},

	/**
	 * Overridden to batch select rows without firing events per row.
	 * The base implementation calls selectRow in a loop, which fires
	 * selectionchange and rowselect events for every row — causing
	 * O(n²) DOM updates when selecting hundreds of rows.
	 *
	 * @param {Number} startRow The index of the first row in the range
	 * @param {Number} endRow The index of the last row in the range
	 * @param {Boolean} keepExisting (optional) True to retain existing selections
	 */
	selectRange: function(startRow, endRow, keepExisting)
	{
		if (this.isLocked()) {
			return;
		}
		if (!keepExisting) {
			this.clearSelections();
		}

		var view = this.grid.getView();
		var store = this.grid.store;
		var count = Math.abs(endRow - startRow) + 1;

		// For small selections, use the default per-row approach
		if (count <= 20) {
			var i;
			if (startRow <= endRow) {
				for (i = startRow; i <= endRow; i++) {
					this.selectRow(i, true);
				}
			} else {
				for (i = startRow; i >= endRow; i--) {
					this.selectRow(i, true);
				}
			}
			return;
		}

		// For large selections, suppress per-row events and view updates
		this.silent = true;
		var step = (startRow <= endRow) ? 1 : -1;
		var end = endRow + step;
		for (var i = startRow; i !== end; i += step) {
			if (i < 0 || i >= store.getCount()) {
				continue;
			}
			if (keepExisting && this.isSelected(i)) {
				continue;
			}
			var r = store.getAt(i);
			if (r && this.fireEvent('beforerowselect', this, i, true, r) !== false) {
				this.selections.add(r);
				this.last = this.lastActive = i;
				view.onRowSelect(i);
			}
		}
		this.silent = false;

		// Fire events once for the entire batch
		this.fireEvent('selectionchange', this);
	},

	/**
	 * Handler for the key press event.
	 *
	 * Note: we need to modify this function because on dynamically addintion of rows in grid
	 * {@link #last} might be changed. So to overcome this situation we are getting updated value
	 * for {@link #last} from {@link #selectRange} method.
	 *
	 * @param {Ext.EventObject} e keypress event
	 * @param {String} name The name of the key which is pressed.
	 */
	onKeyPress: function(e, name)
	{
		var up = name == 'up',
			method = up ? 'selectPrevious' : 'selectNext',
			add = up ? -1 : 1,
			last;
		if (!e.shiftKey || this.singleSelect){
			this[method](false);
		} else if (this.last !== false && this.lastActive !== false){
				this.updatedLast = this.last;
				var endIndex = this.lastActive + add;

				// Avoid getting endIndex for out of range index.
				if (!this.isIndexOutOfRange(endIndex)) {
					endIndex = this.prepareEndSelectionIndex(endIndex, up, add);
				}

				last = this.updatedLast;
				this.clearUpdatedLast();
				this.selectRange(last, endIndex, undefined, up);
				this.grid.getView().focusRow(this.lastActive);
				if (last !== false){
					this.last = last;
				}
		} else {
			this.selectFirstRow();
		}
	}

});

Ext.reg('zarafa.mailrowselectionmodel', Zarafa.mail.ui.MailRowSelectionModel);
Ext.namespace('Zarafa.mail.ui');

/**
 * @class Zarafa.mail.ui.MailViewPanel
 * @extends Ext.Panel
 * @xtype zarafa.mailviewpanel
 *
 * Panel that shows the contents of mail messages.
 */
Zarafa.mail.ui.MailViewPanel = Ext.extend(Ext.Panel, {
	/**
	 * @constructor
	 * @param {Object} config configuration object.
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			xtype: 'zarafa.mailviewpanel',
			border: false,
			cls: 'zarafa-mailviewpanel',
			layout: 'zarafa.collapsible',
			items: [{
				xtype: 'zarafa.messageheader'
			},{
				xtype: 'zarafa.messagebody'
			}]
		});

		Zarafa.mail.ui.MailViewPanel.superclass.constructor.call(this, config);
	}
});

Ext.reg('zarafa.mailviewpanel', Zarafa.mail.ui.MailViewPanel);
Ext.namespace('Zarafa.note');

/**
 * @class Zarafa.note.Actions
 * Common actions which can be used within {@link Ext.Button buttons}
 * or other {@link Ext.Component components} with action handlers.
 * @singleton
 */
Zarafa.note.Actions = {
	/**
	 * Open a Panel in which the {@link Zarafa.core.data.IPMRecord record}
	 * can be viewed, or further edited.
	 *
	 * @param {Zarafa.core.data.IPMRecord} records The records to open
	 * @param {Object} config (optional) Configuration object used to create
	 * the Content Panel.
	 */
	openNoteContent: function(records, config)
	{
		Ext.each(records, function(record) {
			Zarafa.core.data.UIFactory.openViewRecord(record, config);
		});
	},

	/**
	 * Open a Panel in which a new {@link Zarafa.core.data.IPMRecord record} can be
	 * further edited.
	 *
	 * @param {Zarafa.note.NoteContextModel} model Context Model object that will be used
	 * to {@link Zarafa.note.NoteContextModel#createRecord create} the Task.
	 * @param {Object} config (optional) Configuration object used to create
	 * the Content Panel.
	 */
	openCreateNoteContent: function(model, config)
	{
		var record = model.createRecord();
		Zarafa.core.data.UIFactory.openCreateRecord(record, config);
	},

	/**
	 * Opens the options dialog for a note record.
	 *
	 * @param {Zarafa.core.data.IPMRecord|Zarafa.core.data.IPMRecord[]} records
	 * The record(s) for which the options are requested.
	 * @param {Object} config (optional) Configuration object used to create the Content Panel.
	 */
	openOptionsContent: function(records, config)
	{
		if (Array.isArray(records)) {
			records = records[0];
		}

		if (!records) {
			return;
		}

		config = Ext.applyIf(config || {}, {
			modal: true
		});

		var componentType = Zarafa.core.data.SharedComponentType['note.dialog.options'];
		Zarafa.core.data.UIFactory.openLayerComponent(componentType, records, config);
	}
};
Ext.namespace('Zarafa.note.data');

/**
 * @class Zarafa.note.data.SearchFields
 * Search fields for search bar
 * @singleton
 */
Zarafa.note.data.SearchFields = [{
	name: _('All text fields'),
	value: 'body'
}, {
	name: _('Categories'),
	value: 'categories'
}];
Ext.namespace('Zarafa.note.dialogs');

/**
 * @class Zarafa.note.dialogs.NoteEditPanel
 * @extends Ext.form.FormPanel
 * @xtype zarafa.noteeditpanel
 *
 * this class is used to provide ui for note dialog
 */
Zarafa.note.dialogs.NoteEditPanel = Ext.extend(Ext.FormPanel, {
	/**
	 * The current CSS class which is applied for
	 * determining the background color for this panel.
	 * @property
	 * @type String
	 */
	currentColorCss: undefined,
	/**
	 * @constructor
	 * @param {Object} config Configuration structure
	 */
	constructor: function(config)
	{
		config = config || {};

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

		Ext.applyIf(config,{
			xtype	: 'zarafa.noteeditpanel',
			layout	: 'fit',
			border	: false,
			items : [{
				xtype: 'zarafa.editorfield',
				useHtml: false,
				ref: 'noteText',
				plaintextName: 'body',
				listeners: {
					change: this.onTextareaChange,
					scope: this
				}
			}]
		});

		Zarafa.note.dialogs.NoteEditPanel.superclass.constructor.call(this,config);
	},

	/**
	 * handler for change event of text area in note dialog
	 * @param {Object} textarea object
	 * @private
	 */
	onTextareaChange:function(textarea)
	{
		var body = textarea.getValue();
		this.record.beginEdit();
		this.record.set('body',body);
		this.record.generateSubject();
		this.record.endEdit();
	},

	/**
	 * this function returns css class in response to icon index
	 * to set background color of Note Dialog's text area
	 * @param{String/Number} String or Int icon index value
	 * @return css class name
	 * @private
	 */
	getTextAreaColor: function(iconIndex)
	{
		var textAreaCSSClass = "";

		switch(parseInt(iconIndex, 10))
		{
			case Zarafa.core.mapi.IconIndex['note_blue']:
				textAreaCSSClass = "stickynote_dialog_blue";
				break;
			case Zarafa.core.mapi.IconIndex['note_green']:
				textAreaCSSClass = "stickynote_dialog_green";
				break;
			case Zarafa.core.mapi.IconIndex['note_pink']:
				textAreaCSSClass = "stickynote_dialog_pink";
				break;
			case Zarafa.core.mapi.IconIndex['note_yellow']:
			/* falls through */
			default:
				textAreaCSSClass = "stickynote_dialog_yellow";
				break;
			case Zarafa.core.mapi.IconIndex['note_white']:
				textAreaCSSClass = "stickynote_dialog_white";
				break;
		}

		return textAreaCSSClass;
	},

	/**
	 * Function is used to update values of form fields when ever
	 * an updated {@link Zarafa.core.data.IPMRecord record} is received
	 * @param {Zarafa.core.data.IPMRecord} record The record update the panel with.
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 * @private
	 */
	update: function(record, contentReset)
	{
		var textArea = this.noteText.getEditor();

		if (record) {
			this.record = record;
			if (contentReset || record.isModifiedSinceLastUpdate('body')) {
				this.noteText.setValue(record.get('body') || '');
			}

			if (contentReset || record.isModifiedSinceLastUpdate('icon_index')) {
				textArea.removeClass(this.currentColorCss);
				var currentColor = this.getTextAreaColor(record.get('icon_index'));
				this.currentColorCss = currentColor;
				textArea.addClass(currentColor);
			}
		} else {
			this.noteText.setValue('');
			textArea.removeClass(this.currentColorCss);
		}
	},

	/**
	 * Function to update the record whenever form fields are modified
	 * @param {Zarafa.core.data.IPMRecord} the record to update
	 * @private
	 */
	updateRecord: function(record)
	{
		var body = this.noteText.getValue();
		this.record.beginEdit();
		this.record.set('body',body);
		this.record.generateSubject();
		this.record.endEdit();
	}
});

//Register note dialog edit panel xtype
Ext.reg('zarafa.noteeditpanel',Zarafa.note.dialogs.NoteEditPanel);
Ext.namespace('Zarafa.note.dialogs');

/**
 * @class Zarafa.note.dialogs.NoteOptionsPanel
 * @extends Ext.Panel
 * @xtype zarafa.noteoptionspanel
 */
Zarafa.note.dialogs.NoteOptionsPanel = Ext.extend(Ext.Panel, {
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

		config = Ext.applyIf(config, {
			xtype: 'zarafa.noteoptionspanel',
			layout: {
				type: 'vbox',
				align: 'stretch'
			},
			border: false,
			defaults: {
				bodyStyle: 'padding-top: 5px; padding-left: 6px; padding-right: 5px; background-color: inherit;',
				border: false
			},
			items: [{
				xtype: 'zarafa.recordpropertiespanel',
				flex: 1
			}]
		});

		Zarafa.note.dialogs.NoteOptionsPanel.superclass.constructor.call(this, config);
	}
});

Ext.reg('zarafa.noteoptionspanel', Zarafa.note.dialogs.NoteOptionsPanel);
Ext.namespace('Zarafa.settings');

/**
 * @class Zarafa.settings.SettingsModel
 * @extends Ext.util.Observable
 *
 * The SettingsModel class contains information about stores and folders the user has access to.
 * The settings are built up in a hierarchical way, where each node in the path is separated using
 * the {@link #pathSeparator}.
 */
Zarafa.settings.SettingsModel = Ext.extend(Ext.util.Observable, {
	/**
	 * @cfg {String} pathSeparator The separator used in settings names to separate the
	 * hierarchy keys.
	 */
	pathSeparator: '/',

	/**
	 * @cfg {Boolean} autoSave True when the settings should be saved to the server
	 * as soon as editing is {@link #afterEdit completed}.
	 */
	autoSave: true,

	/**
	 * @cfg {Object} defaults Javascript object containing the full hierarchy of the default settings.
	 * Defaults to {@link Zarafa.settings.data.SettingsDefaultValue}.
	 */
	defaults: undefined,

	/**
	 * Javascript object containing the full hierarchy of the currently
	 * active settings.
	 * @property
	 * @type Object
	 */
	settings: undefined,

	/**
	 * Flag which indicating if {@link #beginEdit} has been used to begin a transaction,
	 * this means no {@link #save saving} will be done until {@link #endEdit} has been called.
	 * @property
	 * @type Boolean
	 */
	editing: false,

	/**
	 * The number of editors working on this model. The {@link #beginEdit} and {@link #endEdit}
	 * support nested editing blocks. This means that {@link #update} will not be fired until
	 * this counter drops to 0.
	 * @property
	 * @type Number
	 * @private
	 */
	editingCount: 0,

	/**
	 * The list of properties which have been modified during a {@link #editing transaction}.
	 * Will be reset on {@link #endEdit}.
	 * @property
	 * @type Array
	 */
	modified: undefined,

	/**
	 * The list of properties which have been restored during a {@link #editing transaction}.
	 * Will be reset on {@link #endEdit}.
	 * @property
	 * @type Array
	 */
	restored: undefined,

	/**
	 * The list of properties which have been deleted during a {@link #editing transaction}.
	 * Will be reset on {@link #endEdit}.
	 * @property
	 * @type Array
	 */
	deleted: undefined,

	/**
	 * The list of properties which have been reset from server.
	 * @property
	 * @type Array
	 */
	resetSettings: undefined,

	/**
	 * The property which decide that webapp requires to reload.
	 * @property
	 * @type Boolean
	 */
	requiresReload: false,

	/**
	 * True during application startup.  While booting, automatic
	 * settings saves are suppressed so they do not compete with
	 * critical initial requests for the PHP state-file lock.
	 * Cleared by {@link #setReady}.
	 * @property
	 * @type Boolean
	 */
	booting: true,

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

		this.addEvents(
			/**
			 * @event set
			 * Fires when a property is assigned a new value.
			 * @param {Zarafa.settings.SettingsModel} settingsModel
			 * @param {Object/Array} setting The setting which was modified. The object contains a 'path' and 'value'
			 * indicating the path and the value for the modified setting respectively.
			 */
			'set',
			/**
			 * @event remove
			 * Fires when a property is removed.
			 * @param {Zarafa.settings.SettingsModel} settingsModel
			 * @param {String/Array} path The key to delete. When multiple settings are removed simultaneously, then the
			 * array of Strings is provided.
			 */
			'remove',
			/**
			 * @event exception
			 * Fires when a server-side error occurred during updating the settings in the server.
			 * (See {@link Ext.data.DataStore#exception} for better exception argument documentation).
			 * @param {Zarafa.settings.SettingsModel} model The model 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 a 'path' and 'value' field indicating
			 * respectively the Setting and corresponding value for the setting which was being saved.
			 * @param {Object} response The response object as received from the PHP-side
			 */
			'exception',
			/**
			 * @event beforesave
			 * Fires when the Settings Model is about to save data to the server
			 * @param {Zarafa.settings.SettingsModel} model The model which fired the event.
			 * @param {Object} parameters The key-value object containing the action and the corresponding
			 * settings which will be send to the server.
			 * @return {Boolean} false to prevent the save action from executing
			 */
			'beforesave',
			/**
			 * @event save
			 * Fires when the Settings Model has successfully saved the settings to the server
			 * @param {Zarafa.settings.SettingsModel} model The model which fired the event.
			 * @param {Object} parameters The key-value object containing the action and the corresponding
			 * settings which were saved to the server.
			 */
			'save'
		);

		if (!Ext.isObject(config.defaults)) {
			config.defaults = Zarafa.settings.data.SettingsDefaultValue.getDefaultValues();
		}

		Ext.apply(this, config);

		Zarafa.settings.SettingsModel.superclass.constructor.call(this, config);

		this.settings = {};
		this.modified = [];
		this.restored = [];
		this.deleted = [];
		this.resetSettings = [];

		this.saveTask = new Ext.util.DelayedTask(this.save, this);

		// Relays the exception event to the DataProxy
		Ext.data.DataProxy.relayEvents(this, ['exception']);
	},

	/**
	 * Initialize the SettingsModel with the initial batch of settings.
	 * This will first apply the {@link #defaults}
	 * on {@link #settings} and then apply the provided settings.
	 *
	 * @param {Object} obj a JSON object hierarchy representing a tree of key/value pairs.
	 */
	initialize: function(obj)
	{
		this.settings = Zarafa.core.Util.applyRecursive({}, obj, this.defaults);
	},

	/**
	 * Indicates a transaction of setting changes will commence. This will set the
	 * {@link #editing} flag to prevent automatic saving during {@link #set}.
	 *
	 * This functions supports nested calls by using {@link #editingCount}.
	 */
	beginEdit: function()
	{
		// Increase editing counter, if it is a negative value, it means that
		// it has been corrupted and we must force it to something valid.
		this.editingCount++;
		if (this.editingCount < 1) {
			this.editingCount = 1;
		}

		// If this is not a nested call, we can direct the call to the superclass.
		if (this.editingCount === 1) {
			this.editing = true;
			this.modified = this.modified || [];
			this.restored = this.restored || [];
			this.deleted = this.deleted || [];
		}
	},

	/**
	 * Indicates a transaction of setting changes has completed and that all changes must
	 * be saved to the server. This will reset the {@link #editing} field.
	 *
	 * This functions supports nested calls by using {@link #editingCount}.
	 */
	endEdit: function()
	{
		// Increase editing counter, if it is a negative value, it means that
		// it has been corrupted and we must force it to something valid.
		this.editingCount--;
		if (this.editingCount < 0) {
			this.editingCount = 0;
		}

		// If this is not a nested call, we can direct the call to the superclass.
		if (this.editingCount === 0) {
			this.editing = false;
			if (!Ext.isEmpty(this.modified) || !Ext.isEmpty(this.deleted)) {
				this.afterEdit();
			}
		}
	},

	/**
	 * Transaction completion handler which is called by {@link #endEdit} when
	 * a setting transaction has been completed or by {@link #setSettings} and {@link #removeSettings}
	 * when no transaction is being used. This will call {@link #save} to send all changes to the server.
	 * @param {Boolean} persistent whether to set a persistent setting
	 * @private
	 */
	afterEdit: function(persistent = false)
	{
		var needsSave = false;

		if (!Ext.isEmpty(this.restored)) {
			this.fireEvent('set', this, this.restored);
		}
		this.restored = [];

		if (!Ext.isEmpty(this.modified)) {
			this.fireEvent('set', this, this.modified);
			needsSave = true;
		}

		if (!Ext.isEmpty(this.deleted)) {
			this.fireEvent('remove', this, this.deleted);
			needsSave = true;
		}

		if (needsSave === true && this.autoSave !== false) {
			persistent ? this.save() : this.scheduleSave();
		}
	},

	/**
	 * Schedule a debounced {@link #save}. Multiple rapid calls within the
	 * delay window are collapsed into a single server request, which
	 * dramatically reduces the number of HTTP round-trips during
	 * initialization when many stateful components save their state
	 * in quick succession.
	 *
	 * During the {@link #booting boot phase} the timer is not started
	 * at all; changes accumulate in {@link #modified}/{@link #deleted}
	 * and are flushed once by {@link #setReady}.
	 * @private
	 */
	scheduleSave: function()
	{
		if (this.booting) {
			return;
		}
		this.saveTask.delay(300);
	},

	/**
	 * Signal that the application has finished its initial load.
	 * Any settings changes that were accumulated during boot are
	 * flushed to the server in a single batched request.
	 */
	setReady: function()
	{
		this.booting = false;
		if (!Ext.isEmpty(this.modified) || !Ext.isEmpty(this.deleted) || !Ext.isEmpty(this.resetSettings)) {
			this.save();
		}
	},

	/**
	 * Go through the given list and remove any item which also
	 * is present in the given filter. Use this to prevent duplicates
	 * in the {@link #modified}, {@link #deleted} and {@link #restored}
	 * arrays.
	 * @param {Object/String|Array} list The list which must be filtered
	 * @param {String|Array} filter The list of paths which must be filtered
	 * out of the list
	 * @private
	 */
	filterDuplicates: function(list, filter)
	{
		for (var i = 0, len = filter.length; i < len; i++) {
			var path = filter[i];
			var paths = list;
			var index = -1;

			if (Ext.isObject(list[0])) {
				paths = Ext.pluck(list, 'path');
			}

			// Also check if the item in the list exists multiple times,
			// as that is not needed either.
			while ((index = paths.indexOf(path)) >= 0) {
				list.splice(index, 1);
				if (list !== paths) {
					paths.splice(index, 1);
				}
			}
		}
	},

	/**
	 * Performs the removal of the settings from the server.
	 *
	 * @param {String/Array} path The list of setting paths which must be deleted
	 * from the settings.
	 * @private
	 */
	removeSettings: function(path)
	{
		if (!Array.isArray(path)) {
			path = [ path ];
		}

		// First remove all duplicates
		path = Zarafa.core.Util.uniqueArray(path);

		// Now remove any settings which are removed now,
		// but are already in one of the editing lists
		this.filterDuplicates(this.modified, path);
		this.filterDuplicates(this.restored, path);
		this.filterDuplicates(this.deleted, path);

		// ALl filtering is done, update the deleted array
		this.deleted = this.deleted.concat(path);
	},

	/**
	 * Performs the update of the settings on the server.
	 *
	 * @param {Object/Array} settings The list of objects containing the 'path' and 'value'
	 * of the settings which are being updated.
	 * @private
	 */
	restoreSettings: function(settings)
	{
		if (!Array.isArray(settings)) {
			settings = [ settings ];
		}

		// First remove all duplicates
		settings = Zarafa.core.Util.uniqueArray(settings, 'path');

		// Now remove any settings which are restored now,
		// but are already in one of the editing lists.
		var path = Ext.pluck(settings, 'path');
		this.filterDuplicates(this.modified, path);
		this.filterDuplicates(this.restored, path);
		this.filterDuplicates(this.deleted, path);

		// All filtering is done, update the restored array
		this.restored = this.restored.concat(settings);
	},

	/**
	 * Performs the update of the settings on the server.
	 *
	 * @param {Object/Array} settings The list of objects containing the 'path' and 'value'
	 * of the settings which are being updated.
	 * @private
	 */
	setSettings: function(settings)
	{
		if (!Array.isArray(settings)) {
			settings = [ settings ];
		}

		// First remove all duplicates
		settings = Zarafa.core.Util.uniqueArray(settings, 'path');

		// Now remove any settings which are added now,
		// but are alreasdy in one of the editing lists.
		var path = Ext.pluck(settings, 'path');
		this.filterDuplicates(this.modified, path);
		this.filterDuplicates(this.restored, path);
		this.filterDuplicates(this.deleted, path);

		// All filtering is done, update the modified array
		this.modified = this.modified.concat(settings);
	},

	/**
	 * Save the settings to the server.  All pending actions (delete,
	 * set, reset) are batched into a single HTTP request so they
	 * acquire the PHP state-file lock only once.
	 */
	save: function()
	{
		var hasDeleted = !Ext.isEmpty(this.deleted);
		var hasModified = !Ext.isEmpty(this.modified);
		var hasReset = !Ext.isEmpty(this.resetSettings);

		if (!hasDeleted && !hasModified && !hasReset) {
			return;
		}

		var request = container.getRequest();
		var moduleName = Zarafa.core.ModuleNames.getListName('settings');
		var queued = false;

		request.reset();

		if (hasDeleted) {
			queued = this.addSaveAction(request, moduleName, Zarafa.core.Actions['delete'], this.deleted) || queued;
		}

		if (hasModified) {
			queued = this.addSaveAction(request, moduleName, Zarafa.core.Actions['set'], this.modified) || queued;
		}

		if (hasReset) {
			queued = this.addSaveAction(request, moduleName, Zarafa.core.Actions['reset'], this.resetSettings) || queued;
		}

		if (queued) {
			request.send();
		}
	},

	/**
	 * Add a single settings action to the given request batch.
	 * @param {Zarafa.core.Request} request The request to add to
	 * @param {String} moduleName The settings module name
	 * @param {Zarafa.core.Actions} action The action to perform
	 * @param {Object} parameters The action parameters
	 * @return {Boolean} True if the action was queued
	 * @private
	 */
	addSaveAction: function(request, moduleName, action, parameters)
	{
		if (this.fireEvent('beforesave', this, { action: parameters }) === false) {
			return false;
		}

		request.addRequest(moduleName, action,
			{ 'setting': parameters },
			new Zarafa.core.data.ProxyResponseHandler({
				proxy: this,
				action: Ext.data.Api.actions['update'],
				options: { action: action, parameters: parameters, requiresReload: this.requiresReload },
				callback: this.onExecuteComplete,
				scope: this
			})
		);

		return true;
	},

	/**
	 * Send the save action to the server.
	 * @param {Zarafa.core.Actions} action The action which must be performed on the server
	 * @param {Object} parameters The action parameters which must be send to the server.
	 * @private
	 */
	execute: function(action, parameters)
	{
		if (this.fireEvent('beforesave', this, { action: parameters}) !== false) {
			// FIXME: Perhaps this needs to be moved into a Ext.data.DataProxy
			container.getRequest().singleRequest(
				Zarafa.core.ModuleNames.getListName('settings'),
				action,
				{ 'setting': parameters},
				new Zarafa.core.data.ProxyResponseHandler({
					proxy: this,
					action: Ext.data.Api.actions['update'],
					options: {action: action, parameters: parameters,'requiresReload': this.requiresReload },
					callback:  this.onExecuteComplete,
					scope: this
				})
			);
		}
	},

	/**
	 * Event handler which is fired when the Request made in the {@link #execute} function
	 * has been completed. This will fire the {@link #save 'save'} event.
	 * @param {Ext.data.Api} action The action which was executed
	 * @param {Object} parameters The parameters which were send to the server
	 * @param {Boolean} success True if the save was successful
	 * @private
	 */
	onExecuteComplete: function(action, parameters, success)
	{
		if (success) {
			this.fireEvent('save', this, parameters);
			this.commit();
		}
	},

	/**
	 * Called after all settings were saved, this will reset the {@link #deleted}
	 * and {@link #modified} arrays (which held all changes since the previous
	 * call to {@link #commit}.
	 */
	commit: function()
	{
		this.deleted = [];
		this.modified = [];
		this.resetSettings = [];
		this.requiresReload = false;
	},

	/**
	 * Get a javascript object containing the hierarchy from a certain position in the {@link #settings} hierarchy.
	 *
	 * Note: This function only accessed {@link #settings} it does not communicate with the Server.
	 *
	 * @param {String} path The key path from where the settings hierarchy must be loaded.
	 * @param {Object} settings (optional) The settings object on which the changes are applied.
	 * @return {Object} The settings object containing all settings from the given key position at the specified path.
	 * @private
	 */
	getSettingsObject: function(path, settings)
	{
		var pieces = path.split(this.pathSeparator);
		var obj = settings || this.settings;

		for (var i = 0, len = pieces.length; i < len; i++) {
			var piece = pieces[i];
			if (Ext.isEmpty(piece)) {
				break;
			}

			obj = obj[pieces[i]];
			if (Ext.isEmpty(obj)) {
				break;
			}
		}

		return obj;
	},

	/**
	 * Remove all settings which are positioned below the path inside the {@link #settings} hierarchy.
	 *
	 * Note: This function only accessed {@link #settings} it does not communicate with the Server.
	 *
	 * @param {String} path The key path from where all settings should be deleted.
	 * @param {Object} settings (optional) The settings object on which the changes are applied.
	 * @return {Array} The list of flat setting names which have been deleted from the hierarchy.
	 * @private
	 */
	removeSettingsObject: function(path, settings)
	{
		var lastIndex = path.lastIndexOf(this.pathSeparator);
		var parentPath = path.substring(0, lastIndex);
		var settingName = path.substring(lastIndex + 1);
		var obj = this.getSettingsObject(parentPath, settings);
		var flatSettings = [];

		if (obj) {
			var setting = obj[settingName];

			if (Ext.isObject(setting) && !Ext.isDate(setting) && !Array.isArray(setting)) {
				for (var key in setting) {
					var removed = this.removeSettingsObject(path + this.pathSeparator + key, settings);
					flatSettings = flatSettings.concat(removed);
				}
			}
			flatSettings.push(path);

			delete obj[settingName];
		}

		return flatSettings;
	},

	/**
	 * Applies all settings which are positioned below the path inside the {@link #settings} hierarchy.
	 *
	 * Note: This function only accessed {@link #settings} it does not communicate with the Server.
	 *
	 * @param {String} path The key path from where all settings should be added.
	 * @param {Object} obj The object containing the values which must be placed below the given
	 * path in the settings hierarchy.
	 * @param {Object} settings (optional) The settings object on which the changes are applied.
	 * @return {Array} The list of flat setting names which have been added to the hierarchy.
	 * @private
	 */
	applySettingsObject: function(path, obj, settings)
	{
		var flatSettings = [];

		if (Ext.isObject(obj)) {
			flatSettings.push({ path: path });

			for (var key in obj) {
				flatSettings = flatSettings.concat(this.applySettingsObject(path + this.pathSeparator + key, obj[key], settings));
			}
		} else {
			var lastIndex = path.lastIndexOf(this.pathSeparator);
			var parentPath = path.substring(0, lastIndex);
			var settingName = path.substring(lastIndex + 1);
			var pos = settings || this.settings;

			if (!Ext.isEmpty(parentPath)) {
				var pieces = parentPath.split(this.pathSeparator);
				var piecePath = '';

				for (var i = 0, len = pieces.length; i < len; i++) {
					var piece = pieces[i];

					piecePath += (!Ext.isEmpty(piecePath) ? this.pathSeparator : '') + piece;

					if (Ext.isEmpty(pos[piece])) {
						pos[piece] = {};
					}

					pos = pos[piece];
				}
			}

			if (Array.isArray(obj) || Ext.isDate(obj)) {
				pos[settingName] = obj.clone();
			} else {
				pos[settingName] = obj;
			}

			flatSettings.push({ path: path, value: obj });
		}

		return flatSettings;
	},

	/**
	 * Convert the string into a fully-validated path which is accepted
	 * by this SettingsModel. This will check if the path is a string, and
	 * remove any prefixed or postfixed {@link #pathSeparator path separators}.
	 * @param {String} path The path the convert
	 * @return {String} The converted path
	 * @private
	 */
	getPath: function(path)
	{
		if (!Ext.isString(path)) {
			return '';
		} else {
			// Remove any trailing, and ending / characters.
			return path.replace(/^\/*|\/*$/g, '');
		}
	},

	/**
	 * Sets a value. The value is immediately stored locally (in {@link #setting}),
	 * and also sent to the server. If saving the setting to the server failed, the {@link #exception} event will be fired.
	 * @param {String} path the key path of the value.
	 * @param {String} value value to set.
	 * @param {Boolean} persistent whether to set a persistent setting
	 * @return {String} the value of the requested path, or undefined if it doesn't exist.
	 */
	set: function(path, value, persistent = false)
	{
		path = this.getPath(path);

		// Compare the value with the current saved setting.
		if (JSON.stringify(this.get(path, true)) === JSON.stringify(value)) {
			return;
		}

		var deleteSettings = this.removeSettingsObject(path, this.settings);
		var newSettings = this.applySettingsObject(path, value, this.settings);

		if (!Ext.isEmpty(deleteSettings)) {
			this.removeSettings(deleteSettings);
		}

		if (!Ext.isEmpty(newSettings)) {
			this.setSettings(newSettings);
		}

		// If we are not editing in a batch, save the changes now.
		// Otherwise wait for endEdit.
		if (this.editing === false) {
			this.afterEdit(persistent);
		}
	},

	/**
	 * Removes a value. The value is deleted from the local cache immediately,
	 * and a request is sent out to the server to delete the key remotely. If deleting
	 * the setting from the server failed, the {@link #exception} event will be fired.
	 * @param {String} path the key path of the value.
	 * @param {Object} logInfo object containing information like type and message which needs to be printed in console.
	 */
	remove: function(path, logInfo)
	{
		path = this.getPath(path);

		var deleteSettings = this.removeSettingsObject(path, this.settings);

		if (!Ext.isEmpty(deleteSettings)) {
			this.removeSettings(deleteSettings);
		}

		// If we are not editing in a batch, save the changes now.
		// Otherwise wait for endEdit.
		if (this.editing === false) {
			this.afterEdit();
		}

		if (Ext.isObject(logInfo)) {
			var prefixMsg = logInfo.message;
			if (!Ext.isDefined(prefixMsg)) {
				prefixMsg = logInfo.type === 'deprecated' ? 'Removed deprecated setting: ' : 'Removed setting: ';
			}
			console.info(prefixMsg, path);
		}
	},

	/**
	 * Here it will set the path of settings in {@link #resetSettings} which is needs to be reset.
	 *
	 * @param {String} path the key path of the value.
	 */
	reset: function(path)
	{
		if (!Array.isArray(path)) {
			path = [ path ];
		}

		this.resetSettings = this.resetSettings.concat(path);
	},

	/**
	 * Restores a value to the predefined {@link #defaults}. The value is deleted from
	 * the local cache immediately and a request is sent out to the server to delete the key
	 * If deleting the setting from the server failed, the {@link #exception} event will be fired.
	 * After the setting has been removed, the {@link #defaults default value} will be set
	 * again. But this will not be saved to the server.
	 * @param {String} path the key path of the value
	 */
	restore: function(path)
	{
		path = this.getPath(path);

		var deleteSettings = this.removeSettingsObject(path, this.settings);
		var newValues = this.getSettingsObject(path, this.defaults);
		var newSettings = this.applySettingsObject(path, newValues, this.settings);

		if (!Ext.isEmpty(deleteSettings)) {
			this.removeSettings(deleteSettings);
		}

		if (!Ext.isEmpty(newSettings)) {
			this.restoreSettings(newSettings);
		}

		// If we are not editing in a batch, save the changes now.
		// Otherwise wait for endEdit.
		if (this.editing === false) {
			this.afterEdit();
		}
	},

	/**
	 * Gets a value. This getter only gets a <b>local</b> copy of the setting, meaning that this method does not initiate
	 * communication with the server. There is currently no way to get an 'up-to-date' value.
	 * @param {String} path the key path of the value.
	 * @param {Boolean} raw True to return if the pathname is only partial and the underlying JS object
	 * @param {Boolean} [returnDefaults] True to return the default value, else the set value
	 * (containing all underlying settings) must be returned. Defaults to false.
	 * @return {String} the value of the requested path, or undefined if it doesn't exist.
	 */
	get: function(path, raw, returnDefaults)
	{
		path = this.getPath(path);

		var value = returnDefaults ? this.getSettingsObject(path, this.defaults) : this.getSettingsObject(path, this.settings);
		if (Ext.isObject(value)) {
			if (raw === true) {
				return Zarafa.core.Util.applyRecursive({}, value);
			}
		} else if (Array.isArray(value) || Ext.isDate(value)) {
			return value.clone();
		} else {
			return value;
		}
	},

	/**
	 * This function will get a <b>local</b> copy of the setting for given path.
	 * If primary path setting not available then it will return secondary path setting.
	 *
	 * @param {String} pathPrimary the key path of the value with higher preference.
	 * @param {String} pathSecondary the key path of the value with lower preference.
	 * @return {String} the value of the requested path, or undefined if it doesn't exist.
	 */
	getOneOf: function(pathPrimary, pathSecondary)
	{
		var value = this.get(pathPrimary);
		return Ext.isDefined(value) ? value : this.get(pathSecondary);
	}
});
Ext.namespace('Zarafa.settings.data');

/**
 * @class Zarafa.settings.data.PersistentSettingsDefaultValue
 * Singleton holding the default settings array for the entire client
 * @singleton
 */
Zarafa.settings.data.PersistentSettingsDefaultValue = function(){
	return {
		/**
		 * Gets the array of default values for the persistent settings
		 * @public
		 * @return {Array} The array of default values for the persistent settings
		 */
		getDefaultValues: function() {
			// Default categories are defined in the config.php/defaults.php. After the first change they will be
			// stored in the persistent settings of the user.
			var defaultCategories = container.getServerConfig().getDefaultCategories();
			var additionalDefaultCategories = container.getServerConfig().getAdditionalDefaultCategories();
			if ( Array.isArray(additionalDefaultCategories) ){
				defaultCategories = defaultCategories.concat(additionalDefaultCategories);
			}

			return {
				'grommunio': {
					'main': {
						/**
						 * grommunio/main/merged_categories
						 * merged_categories object holds new name of standard category
						 * followed by standard category index.
						 * e.g. {6:VIP,..} where 6 is standard_index of Red category.
						 * @property
						 * @type Object
						 */
						'merged_categories': {},

						/**
						 * grommunio/main/categories
						 * @property
						 * @type String[]
						 */
						'categories': defaultCategories
					}
				}
			};
		}
	};
}();
Ext.namespace('Zarafa.settings.data');

/**
 * @class Zarafa.settings.data.SettingsDefaultValue
 * Singleton holding the default settings array for the entire client
 * @singleton
 */
Zarafa.settings.data.SettingsDefaultValue = function(){
	return {
		/**
		 * Gets the array of default values
		 * @public
		 * @return {Array} The array of default values
		 */
		getDefaultValues: function() {
			return {
				'zarafa': {
					'v1': {
						'main': {
							/**
							 * zarafa/v1/main/default_context
							 * @property
							 * @type String
							 */
							'default_context': 'mail',
							/**
							 * zarafa/v1/main/language
							 * @property
							 * @type String
							 */
							'language': 'en_GB',
							/**
							 * zarafa/v1/main/base_content_layer
							 * @property
							 * @type String
							 */
							'base_content_layer': 'tabs',
							/**
							 * zarafa/v1/main/confirm_close_dialog
							 * @property
							 * @type Boolean
							 */
							'confirm_close_dialog': true,
							/**
							 * zarafa/v1/main/week_start
							 * @property
							 * @type Number
							 */
							'week_start': 1,
							/**
							 * zarafa/v1/main/working_days
							 * @property
							 * @type Number[]
							 */
							'working_days': [ 1, 2, 3, 4, 5 ],
							/**
							 * zarafa/v1/main/start_working_hour 9
							 * @property
							 * @type Boolean
							 */
							'start_working_hour': 9 * 60,
							/**
							 * zarafa/v1/main/end_working_hour
							 * @property
							 * @type Boolean
							 */
							'end_working_hour': 17 * 60,

							/**
							 * zarafa/v1/main/datetime_display_format
							 * Format to use when displaying a date and/or time in a grid.
							 * Can be 'long' or 'short'
							 * @property
							 * @type String
							 */
							'datetime_display_format': 'short',

							/**
							 * zarafa/v1/main/datetime_time_format
							 * Format to use when displaying time in a grid.
							 * Can be 'g:i A' or 'G:i'
							 * @property
							 * @type String
							 */
							'datetime_time_format': 'G:i',

							/**
							 * zarafa/v1/main/default_font
							 * Default font for writing email
							 * @property
							 * @type String
							 */
							'default_font': 'tahoma,arial,helvetica,sans-serif',

							/**
							 * zarafa/v1/main/default_font_size
							 * Default font size for writing email
							 * possible values are {@link Zarafa.common.ui.htmleditor.FontSize#fontSizes}
							 * @property
							 * @type Number
							 */
							'default_font_size': '2.1',

							/**
							 * zarafa/v1/main/keycontrols
							 * Keycontrol options, possible values are
							 * {@link Zarafa.settings.data.KeyboardSettings}.
							 * @property
							 * @type String
							 */
							'keycontrols': Zarafa.settings.data.KeyboardSettings.BASIC_KEYBOARD_SHORTCUTS,

							/**
							 * zarafa/v1/main/page_size
							 * Default page size for single page.
							 * @property
							 * @type Number
							 */
							'page_size': 50,

							/**
							 * zarafa/v1/main/unread_borders
							 * Show borders for unread items
							 * @property
							 * @type Boolean
							 */
							'unread_borders': true,

							'notifier': {
								/**
								 * zarafa/v1/main/notifier/default
								 * Default plugin for user notifications without a plugin configured
								 * @property
								 * @type String
								 */
								'default': 'toast',

								'pagination': {
									/**
									 * zarafa/v1/main/notifier/pagination/paging/value
									 * Default plugin for the {@link Zarafa.common.ui.PagingToolbar PagingToolbar} slider.
									 * @property
									 * @type String
									 */
									'paging': {
										'value': 'pagingslider'
									},

									/**
									 * zarafa/v1/main/notifier/pagination/livescroll/value
									 * Default plugin for the live scroll loaded information slider.
									 * @property
									 * @type String
									 */
									'livescroll': {
										'value': 'livescrollslider'
									}

								},

								'error': {
									/**
									 * zarafa/v1/main/notifier/error/value
									 * Default plugin for the "Error" user notifications
									 * @property
									 * @type String
									 */
									'value': 'toast',

									'connection': {
										/**
										 * zarafa/v1/main/notifier/error/connection/value
										 * Default plugin for the "Connection loss" user notifications
										 * @property
										 * @type String
										 */
										'value': 'toast'
									},

									'license': {
										/**
										 * zarafa/v1/main/notifier/error/license/value
										 * Default plugin for the "Expired or over used license" user notifications
										 * @property
										 * @type String
										 */
										'value': 'toast'
									},

									'hierarchy': {

										'defaultfolder': {
											/**
											 * zarafa/v1/main/notifier/error/hierarchy/defaultfolder/value
											 * Default plugin for the "Missing Default folder" user notifications
											 * @property
											 * @type String
											 */
											'value': 'toast'
										},
									},

									'proxy': {
										/**
										 * zarafa/v1/main/notifier/error/proxy/value
										 * Default plugin for the "Error" user notifications
										 * @property
										 * @type String
										 */
										'value': 'toast'
									}
								},

								'warning': {
									/**
									 * zarafa/v1/main/notifier/warning/value
									 * Default plugin for the "Warning" user notifications
									 * @property
									 * @type String
									 */
									'value': 'toast',

									'console': {
										/**
										 * zarafa/v1/main/notifier/warning/console/value
										 * Default plugin for the "Warning" console notifications
										 * @property
										 * @type String
										 */
										'value': 'console'
									},
								},

								'info': {
									/**
									 * zarafa/v1/main/notifier/info/value
									 * Default plugin for the "Info" user notifications
									 * @property
									 * @type String
									 */
									'value': 'toast',

									'reminder': {
										/**
										 * zarafa/v1/main/notifier/info/reminder/value
										 * Default plugin for the "Reminder" user notifications
										 * @property
										 * @type String
										 */
										'value': 'none'
									},

									'newmail': {
										/**
										 * zarafa/v1/main/notifier/info/newmail/value
										 * Default plugin for the "New Mail" user notifications
										 * @property
										 * @type String
										 */
										'value': 'toast'
									},

									'import': {
										/**
										 * zarafa/v1/main/notifier/info/import/value
										 * Default plugin for the "Import" user notifications
										 * @property
										 * @type String
										 */
										'value': 'toast'
									},

									'sent': {
										/**
										 * zarafa/v1/main/notifier/info/sent/value
										 * Default plugin for the "Sent" user notifications
										 * @property
										 * @type String
										 */
										'value': 'toast'
									},
									'saved': {
										/**
										 * zarafa/v1/main/notifier/info/saved/value
										 * Default plugin for the "Saved" user notifications
										 * @property
										 * @type String
										 */
										'value': 'toast'
									},
									'mailsaved': {
										/**
										 * zarafa/v1/main/notifier/info/mailsaved/value
										 * Default plugin for the "Saved Mail" user notifications
										 * @property
										 * @type String
										 */
										'value': 'toolbarnotifierplugin'
									},
									'sending': {
										/**
										 * zarafa/v1/main/notifier/info/sending/value
										 * Default plugin for the "sending" user notifications in dialog
										 * @property
										 * @type String
										 */
										'value': 'toast'
									},

									'saving': {
										/**
										 * zarafa/v1/main/notifier/info/saving/value
										 * Default plugin for the "saving" user notifications in dialog
										 * @property
										 * @type String
										 */
										'value': 'toast'
									},

									'mailsaving': {
										/**
										 * zarafa/v1/main/notifier/info/mailsaving/value
										 * Default plugin for the "Saving Mail" user notifications in dialog
										 * @property
										 * @type String
										 */
										'value': 'toolbarnotifierplugin'
									},

									'unresolved_recipients': {
										/**
										 * zarafa/v1/main/notifier/info/unresolved_recipients/value
										 * Default plugin for the "unresolved_recipients" user notifications in dialog
										 * @property
										 * @type String
										 */
										'value': 'toast'
									},

									'meeting': {
										/**
										 * zarafa/v1/main/notifier/info/meeting/value
										 * Default plugin for the "meeting" user notifications
										 * @property
										 * @type String
										 */
										'value': 'toast'
									}

								}
							},

							'reminder': {
								/**
								 * zarafa/v1/main/reminder/polling_interval
								 * (in seconds)
								 * @property
								 * @type Number
								 */
								'polling_interval': 30,
								/**
								 * zarafa/v1/main/reminder/default_snooze_time
								 * (in minutes)
								 * @property
								 * @type Number
								 */
								'default_snooze_time': 5
							},

							'new_features_dialog': {
								/**
								 * zarafa/v1/main/new_features_dialog/show
								 * Whether or not the dialog with new feature info should be shown
								 * @property
								 * @type Boolean
								 */
								'show': true,

								'last_version': {
									/**
									 * zarafa/v1/main/last_version/webapp
									 * The version of grommunio Web for which the What's new dialog was shown last time
									 * @property
									 * @type String
									 */
									'webapp': '0'
								}
							},

							/**
							 * zarafa/v1/main/kdeveloper_tool
							 */
							'kdeveloper_tool': {
								/**
								 * zarafa/v1/main/kdeveloper_tool/kdeveloper
								 */
								'kdeveloper': false,

								/**
								 * zarafa/v1/main/kdeveloper_tool/itemdata
								 */
								'itemdata': false
							},

							/**
							 * zarafa/v1/main/help_manual
							 */
							'help_manual': {
								/**
								 * zarafa/v1/main/help_manual/show
								 * Whether or not the 'help' button in top-right corner should be shown
								 * @property
								 * @type Boolean
								 */
								show: true
							},

							/**
							 * zarafa/v1/main/title_counter
							 */
							'title_counter': {
								/**
								 * zarafa/v1/main/title_counter/show
								 * Whether or not the unread mail counter in application title should be shown
								 * @property
								 * @type Boolean
								 */
								show: true
							},

							/**
							 * zarafa/v1/main/desktop_notification
							 */
							'desktop_notification': {
									/**
									 * zarafa/v1/main/desktop_notification/autohide_enable
									 * Whether or not desktop notification should hide after some time.
									 * @property
									 * @type Boolean
									 */
								'autohide_enable': false,

									/**
									 * zarafa/v1/main/desktop_notification/autohide_time
									 * Time after which desktop notification should be hidden.
									 * @property
									 * @type Number
									 */
								'autohide_time': 5,

									/**
									 * zarafa/v1/main/desktop_notification/disable_sound
									 * Whether or not sound for the desktop notification should be disable.
									 * @property
									 * @type Boolean
									 */
								'disable_sound': false
							},

							/**
							 * zarafa/v1/main/file_previewer
							 *
							 */
							'file_previewer': {
								/**
								 * zarafa/v1/main/file_previewer/enable
								 *
								 * @property
								 * @type Boolean
								 */
								'enable': true,

								/**
								 * zarafa/v1/main/file_previewer/pdf_zoom
								 * Default zoom mode for PDF documents. (default: page-width) [Allowed: "auto", "page-actual", "page-width"]
								 */
								'pdf_zoom': 'page-width',

								/**
								 * zarafa/v1/main/file_previewer/odf_zoom
								 * Default zoom mode for ODF documents. (default: auto) [Allowed: "auto", "page-actual", "page-width"]
								 */
								'odf_zoom': 'page-width'
							}
						},

						'contexts': {
							'hierarchy': {
								/**
								 * zarafa/v1/contexts/hierarchy/polling_interval
								 * (in seconds)
								 * @property
								 * @type Number
								 */
								'polling_interval': 60 * 5,

								/**
								 * zarafa/v1/contexts/hierarchy/hide_favorites
								 * @property
								 * @type Boolean
								 */
								'hide_favorites': false,

								/**
								 * zarafa/v1/contexts/hierarchy/scroll_favorites
								 * @property
								 * @type Boolean
								 */
								'scroll_favorites': false
							},

							'search': {
								/**
								 * zarafa/v1/contexts/search/updatesearch_timeout (in ms)
								 * defer timeout to triggered update search if searching is running.
								 * @property
								 * @type Number
								 */
								'updatesearch_timeout': 500,

								/**
								 * zarafa/v1/contexts/search/search_criteria
								 * Object which holds search tool box information for
								 * saved search folder.
								 * @property
								 * @type Object
								 */
								'search_criteria': {}
							},

							'calendar': {
								/**
								 * zarafa/v1/contexts/calendar/default_merge_state
								 * True when the folders should be merged together
								 * @property
								 * @type Boolean
								 */
								'default_merge_state': false,
								/**
								 * zarafa/v1/contexts/calendar/default_zoom_level
								 * The default zoom level (in minutes) to be used in the calendar,
								 * should be either 5, 6, 10, 15, 30 or 60.
								 */
								'default_zoom_level': 30,
								/**
								 * zarafa/v1/contexts/calendar/default_appointment_period
								 * @property
								 * @type Number
								 */
								'default_appointment_period': 30,
								/**
								 * zarafa/v1/contexts/calendar/default_allday_busy_status
								 * The default busy status to be used while creating new all day events.
								 * default value is {@link Zarafa.core.mapi.BusyStatus#FREE}.
								 *
								 * @property
								 * @type Number
								 */
								'default_allday_busy_status': 0,
								/**
								 * zarafa/v1/contexts/calendar/default_reminder
								 * @property
								 * @type Boolean
								 */
								'default_reminder': true,
								/**
								 * zarafa/v1/contexts/calendar/default_reminder_time
								 * @property
								 * @type Number
								 */
								'default_reminder_time': 15,
								/**
								 * zarafa/v1/contexts/calendar/default_allday_reminder_time
								 * @property
								 * @type Number
								 */
								'default_allday_reminder_time': 18 * 60,
								/**
								 * zarafa/v1/contexts/calendar/datepicker_show_busy
								 * @property
								 * @type Boolean
								 */
								 'datepicker_show_busy': true
							},

							'contact': {
								/**
								 * zarafa/v1/contexts/contact/show_name_dialog
								 * @property
								 * @type Boolean
								 */
								'show_name_dialog': true,
								/**
								 * zarafa/v1/contexts/contact/show_address_dialog
								 * @property
								 * @type Boolean
								 */
								'show_address_dialog': true
							},

							'mail': {
								/**
								 * zarafa/v1/contexts/mail/sendas
								 * @property
								 * @type Array
								 */
								'sendas': [],

								/**
								 * zarafa/v1/contexts/mail/enable_live_scroll
								 * @property
								 * @type Boolean
								 */
								'enable_live_scroll': true,

								/**
								 * zarafa/v1/contexts/mail/enable_conversation_view
								 * @property
								 * @type Boolean
								 */
								'enable_conversation_view': false,

								/**
								 * zarafa/v1/contexts/mail/expand_single_conversation
								 * @property
								 * @type Boolean
								 */
								'expand_single_conversation': false,

								/**
								 * zarafa/v1/contexts/mail/enable_conversation_preview
								 * @property
								 * @type Boolean
								 */
								'enable_conversation_preview': true,

								/**
								 * zarafa/v1/contexts/mail/readreceipt_handling
								 * @property
								 * @type Number
								 */
								'readreceipt_handling': 'ask',

								/**
								 * zarafa/v1/contexts/mail/always_request_readreceipt
								 * @property
								 * @type Boolean
								 */
								'always_request_readreceipt': false,

								/**
								 * zarafa/v1/contexts/mail/autosave_enable
								 * @property
								 * @type Boolean
								 */
								'autosave_enable': true,

								/**
								 * zarafa/v1/contexts/mail/autosave_time
								 * Default timer (in seconds) to auto save mails periodically
								 * @property
								 * @type Number
								 */
								'autosave_time': 60,

								/**
								 * zarafa/v1/contexts/mail/readflag_time_enable
								 * @property
								 * @type Boolean
								 */
								'readflag_time_enable': true,


								/**
								 * zarafa/v1/contexts/mail/readflag_time
								 * Default timer to wait for before marking msg as read when opened in previewpane
								 * main/readreceipt_handling
								 * @property
								 * @type Number
								 */
								'readflag_time': 0,

								/**
								 * zarafa/v1/contexts/mail/from_address_list
								 * @property
								 * @type Array
								 */
								'from_address_list': [],

								/**
								 * zarafa/v1/contexts/mail/block_external_content
								 * @property
								 * @type Boolean
								 */
								'block_external_content': true,

								/**
								 * zarafa/v1/contexts/mail/safe_senders_list
								 * @property
								 * @type Array
								 */
								'safe_senders_list': [],

								/**
								 * zarafa/v1/context/mail/use_english_abbreviations
								 * @property
								 * @type Boolean
								 */
								'use_english_abbreviations': true,

								/**
								 * zarafa/v1/contexts/mail/close_on_respond
								 * @property
								 * @type Boolean
								 */
								'close_on_respond': true,

								/**
								 * zarafa/v1/contexts/mail/delegate_wastebasket_style
								 * @property
								 * @type Number
								 */
								'delegate_wastebasket_style': 8,

								/**
								 * zarafa/v1/contexts/mail/enable_grouping
								 * @property
								 * @type Boolean
								 */
								'enable_grouping': false,

								/**
								 * zarafa/v1/contexts/mail/html_editor
								 * @property
								 * @type string
								 */
								'html_editor': 'full_tinymce',

								// Small hack, the server determines these defaults
								// but we copy them here so the user can press
								// 'revert to defaults' safely.
								'outofoffice': {
									/**
									 * zarafa/v1/contexts/mail/outofoffice/set
									 * @property
									 * @type Number
									 */
									'set' : 0,
									/**
									 * zarafa/v1/contexts/mail/outofoffice/timerange
									 * @property
									 * @type Number
									 */
									'timerange' : 0,
									/**
									 * zarafa/v1/contexts/mail/outofoffice/internal_reply
									 * @property
									 * @type String
									 */
									'internal_reply' : '',
									/**
									 * zarafa/v1/contexts/mail/outofoffice/internal_subject
									 * @property
									 * @type String
									 */
									'internal_subject' : '',
									/**
									 * zarafa/v1/contexts/mail/outofoffice/external_reply
									 * @property
									 * @type String
									 */
									'external_reply' : '',
									/**
									 * zarafa/v1/contexts/mail/outofoffice/external_subject
									 * @property
									 * @type String
									 */
									'external_subject' : '',
									/**
									 * zarafa/v1/contexts/mail/outofoffice/from
									 * @property
									 * @type String
									 */
									'from': '',
									/**
									 * zarafa/v1/contexts/mail/outofoffice/until
									 * @property
									 * @type String
									 */
									'until' : '',
									/**
									 * zarafa/v1/contexts/mail/outofoffice/allow_external
									 * @property
									 * @type Boolean
									 */
									'allow_external' : '',
									/**
									 * zarafa/v1/contexts/mail/outofoffice/external_audience
									 * @property
									 * @type Boolean
									 */
									'external_audience' : ''
								},

								/**
								 * zarafa/v1/contexts/mail/signatures
								 * Signatures to be used when creating new mail or when creating reply/forward mail.
								 * @property
								 * @type Object
								 */
								'signatures': {
									/**
									 * zarafa/v1/contexts/mail/signatures/all
									 * This is an object of all signatures with unique ids as its keys. For each key
									 * you will have an object that is contains the following properties.
									 * <ul>
									 * <li>name ({@link String}) The name of the signature</li>
									 * <li>content ({@link String}) The body of the signature</li>
									 * <li>isHTML({@link Boolean}) Whether the signature has an HTML body</li>
									 * </ul>
									 * @property
									 * @type Object
									 */
									'all': {},

									/**
									 * zarafa/v1/contexts/mail/signatures/new_message
									 * id of the signature that will be used when creating new mail
									 * @property
									 * @type Number
									 */
									'new_message': undefined,

									/**
									 * zarafa/v1/contexts/mail/signatures/replyforward_message
									 * id of the signature that will be used when creating reply/forward mails
									 * @property
									 * @type Number
									 */
									'replyforward_message': undefined
								},

								'dialogs': {
									'mailcreate': {
										/**
										 * zarafa/v1/contexts/mail/dialogs/mailcreate/use_html_editor
										 * @property
										 * @type Boolean
										 */
										'use_html_editor': true
									}
								},

								/**
								 * zarafa/v1/contexts/mail/cc_recipients
								 * @property
								 * @type Array
								 */
								'cc_recipients':[],

								/**
								 * zarafa/v1/contexts/mail/delegate_sent_items_style
								 * @property
								 * @type String
								 */
								'delegate_sent_items_style': 'delegate',

								/**
								 * zarafa/v1/contexts/mail/use_html_email_preview
								 * @property
								 * @type Boolean
								 */
								'use_html_email_preview': true,

								/**
								 * zarafa/v1/contexts/mail/attachment_reminder_enable
								 * @property
								 * @type Boolean
								 */
								'attachment_reminder_enable': false
							},

							'task': {
								/**
								 * zarafa/v1/contexts/task/default_task_period
								 * @property
								 * @type Number
								 */
								'default_task_period': 0,
								/**
								 * zarafa/v1/contexts/task/default_reminder
								 * @property
								 * @type Boolean
								 */
								'default_reminder': false,
								/**
								 * zarafa/v1/contexts/task/default_reminder_time
								 * @property
								 * @type Number
								 */
								'default_reminder_time': 9 * 60,
								/**
								 * zarafa/v1/contexts/task/reminder_time_stepping
								 * incrementor or decrementor value in minutes for
								 * time field of reminder in task dialog.
								 * @property
								 * @type Number
								 */
								'reminder_time_stepping': 30
							},

							'today': {
								/**
								 * zarafa/v1/contexts/today/num_columns
								 * @property
								 * @type Number
								 */
								'num_columns': 3
							},
						},

						'widgets': {

							'sidebar': {
								/**
								 * zarafa/v1/widgets/sidebar/hide_widgetpanel
								 * @property
								 * @type Boolean
								 */
								'hide_widgetpanel': false
							}
						}
					}
				}
			};
		}
	};
}();
Ext.namespace('Zarafa.settings.data');

/**
 * @class Zarafa.settings.data.SettingsTreeLoader
 * @extends Ext.ux.tree.TreeGridLoader
 *
 * A Special treeloader to be used by the
 * {@link Zarafa.settings.ui.SettingsTreePanel SettingsTreePanel}.
 * This will dynamically load the child nodes for a given node by reading the
 * {@link Zarafa.core.data.SettingsModel Settings}.
 */
Zarafa.settings.data.SettingsTreeLoader = Ext.extend(Ext.ux.tree.TreeGridLoader, {
	/**
	 * @cfg {Number} autoExpandLevel The maximum level in which the TreeNodes will be
	 * expanded by default. e.g. When this is set to 1 then the node 'zarafa' will
	 * be expanded by default, when set to 2 the node 'zarafa/v1' will be expanded,
	 * etc.
	 */
	autoExpandLevel: 0,

	/**
	 * @cfg {String/RegExp} autoExpandFilter The String or Regular expressing which
	 * is used to test if the node must be expanded or not.
	 */
	autoExpandFilter: undefined,

	/**
	 * @cfg {Zarafa.settings.SettingsModel} model The model which should be used for
	 * loading the settings. This can be configured later using {@link #bindModel}.
	 */
	model: undefined,

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

		if (!config.directFn) {
			// Small hack, ExtJs calls directFn in the DOMWindow scope, rather
			// then the scope of this class. Since we wish to extend the TreeGridLoader
			// rather then passing the directFn as a configuration option, we are now
			// just going to create a delegate and force the function into the correct
			// scope.
			config.directFn = this.directFn.createDelegate(this);
		}

		Zarafa.settings.data.SettingsTreeLoader.superclass.constructor.call(this, config);
	},

	/**
	 * Initialize a new {@link Zarafa.settings.SettingsModel Settings Model}.
	 * @param {Zarafa.settings.SettingsModel} model The model to load
	 * @param {Boolean} initialize (optional) True if this function is called
	 * during initialization
	 */
	bindModel: function(model, initialize)
	{
		if (initialize || this.model !== model) {
			this.model = model;
		}
	},

	/**
	 * This is called when a node in the SettingsTree is being expanded, this will read
	 * the {@link Zarafa.core.data.SettingsModel Settings} to find the child nodes which
	 * are positioned below the expanded node.
	 *
	 * @param {String} node The ID of the node which is being expanded
	 * @param {Function} fn The function which must be called with the JSON data of
	 * the nodes below the provided node.
	 * @private
	 */
	directFn: function(node, fn)
	{
		var path = this.model.getPath(node);
		var settings = this.model.get(path, true);
		var data = [];

		for (var key in settings) {
			var leaf = !Ext.isObject(settings[key]) || Array.isArray(settings[key]);
			var nodePath = (!Ext.isEmpty(path) ? path + '/' : '') + key;
			var expanded = false;

			if (this.autoExpandFilter) {
				if (this.autoExpandFilter.test(nodePath)) {
					expanded = nodePath.split('/').length < this.autoExpandLevel;
				}
			}

			data.push({
				nodeType: 'setting',
				text: key,
				id: nodePath,
				leaf: leaf,
				expanded: !leaf && expanded,
				value: leaf ? settings[key] : undefined,
				uiProvider: Zarafa.settings.ui.SettingsTreeNodeUI
			});
		}

		fn(data, {status: true});
	}
});
Ext.namespace('Zarafa.settings.ui');

/**
 * @class Zarafa.settings.ui.QuotaBar
 * @extends Ext.BoxComponent
 * @xtype zarafa.quotabar
 *
 * Quotabar shows information about user's store size,
 * and denotes warning-quota, soft-quota and hard-quota information.
 *
 */
Zarafa.settings.ui.QuotaBar = Ext.extend(Ext.BoxComponent, {
	/**
	 * @cfg {Zarafa.hierarchy.data.MAPIStoreRecord} userStore
	 * User's store on which  quota information is set.
	*/
	userStore: undefined,

	/**
	 * @cfg {String} quotaTemplate
	 * Template for quota bar.
	 */
	quotaTemplate:'<div class="zarafa-quotabar">' +
						'<div class="zarafa-quotabar-normal"></div>' +
						'<div class="zarafa-quotabar-warn"></div>' +
						'<div class="zarafa-quotabar-soft"></div>' +
						'<div class="zarafa-quotabar-hard"></div>' +
					'</div>',

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

		Zarafa.settings.ui.QuotaBar.superclass.constructor.call(this, config);

		this.quotaTemplate = new Ext.XTemplate(this.quotaTemplate, {
			compiled: true
		});

		this.on('afterrender', this.onQuotaBarRender, this);
		if(this.userStore) {
			this.mon(this.userStore.getStore(), 'update', this.onUpdateHierarchyStore, this);
		}
	},

	/**
	 * Function will overwrites the quotaTemplate the content of el with the new node(s).
	 * @private
	 */
	onQuotaBarRender: function() {
		this.quotaTemplate.overwrite(Ext.get(this.el));
		this.updateQuotaBar();
	},

	/**
	 * Function will count the quota size from the user store,
	 * and will update/show the quotabar accordingly.
	 * @private
	 */
	updateQuotaBar: function() {
		if(this.userStore) {
			if(this.el && this.el.child('div.zarafa-quotabar')) {

				// Get quota information
				var storeSize = this.userStore.get('store_size');
				var softQuota = this.userStore.get('quota_soft');
				var hardQuota = this.userStore.get('quota_hard');
				var warnQuota = this.userStore.get('quota_warning');

				// If quota information is not set then return.
				if(!softQuota && !hardQuota && !warnQuota) {
					return;
				}

				/*
				 * Just add necessary quota information in quota variable,
				 * and remove inappropriate info like softQuota > hardQuota.
				 * There might be the case where if hardQuota is not set
				 * then in that case add soft quota to information variable.
				 */
				var quota = [];
				if(warnQuota && (!softQuota || warnQuota < softQuota) && (!hardQuota || warnQuota < hardQuota)) {
					quota.push({size: warnQuota, element: 'div.zarafa-quotabar-warn'});
				}

				if(softQuota && (!hardQuota || softQuota < hardQuota)) {
					quota.push({size: softQuota, element: 'div.zarafa-quotabar-soft'});
				}

				if(hardQuota) {
					quota.push({size: hardQuota, element: 'div.zarafa-quotabar-hard'});
				}


				var maxLimit = hardQuota || softQuota || warnQuota;

				if(storeSize > maxLimit) {
					// If store size has exceeded the hard_quota then set it as maxLimit
					maxLimit = storeSize;
					quota.push({size: storeSize});
				}

				// Count the factor by 'total width of qouta bar'/'max qouta limit'
				var maxAvailableWidth = this.el.child('div.zarafa-quotabar').getWidth(true);
				var factor = maxAvailableWidth/maxLimit;

				// Set blockSize
				var blockSize, totalSize = 0;
				var element = 'div.zarafa-quotabar-normal';
				for (var i = 0; i < quota.length ; i++)
				{
					blockSize = quota[i].size;
					if(storeSize <= blockSize) {
						blockSize = storeSize;
						storeSize = 0;
					}

					/*
					 * get absolute difference between qouta levels in blockSize
					 *
					 * |--------|                           first
					 * |-------------------|                second
					 * |------------------------------|     third
					 *
					 * absolute difference
					 * |--------|                           first
					 *          |----------|                second
					 *                     |----------|     third
					 */
					blockSize -= totalSize;
					totalSize += blockSize;

					if(element) {
						// Set width of the current block.
						var elementWidth = Math.round(blockSize*factor);
						elementWidth = Math.max(elementWidth, 0);

						/*
						 * Math.round sometime gives extra 1 pixel while setting width
						 * of the quotabar elements, because of the layouting is spoiled,
						 * so checked whether it doesn't exceed max limit.
						 */
						if(maxAvailableWidth < elementWidth) {
							elementWidth = maxAvailableWidth;
						}

						this.el.child(element).setWidth(elementWidth);
						maxAvailableWidth -= elementWidth;
					}

					/*
					 * Update element according to which quota is started in quotabar.
					 * This variable will maintain that which quota is till now displayed,
					 * e.g. we have set soft quota and hard quota only, then first it will
					 * draw normal green block till soft-quota limit, then it will get
					 * element of soft quota i.e. orange element, then it will draw ornage
					 * element till hard-quota and now it reached hard quota so it will draw
					 * red element.
					 */
					element = quota[i].element;

					// set default size of every block to zero so that previous block size calculation is cleared.
					if (element) {
						this.el.child(element).setWidth(0);
					}
				}
			}
		}
	},

	/**
	 * Function is called when data is update in {@link Zarafa.hierarchy.data.HierarchyStore}.
	 * If user's default store is changed then it will update ui.
	 * @param {Zarafa.hierarchy.data.HierarchyStore} store, the hierarchy store
	 * @param {Zarafa.hierarchy.data.MAPIStoreRecord} storeRecord, record of the hierarchy store
	 * @param {String} op, operation string
	 * @private
	 */
	onUpdateHierarchyStore: function(store, storeRecord, op) {
		// Check whether default store is changed or not.
		if(storeRecord.isDefaultStore()){
			this.updateQuotaBar();
		}
	}
});
Ext.reg('zarafa.quotabar', Zarafa.settings.ui.QuotaBar);
Ext.namespace('Zarafa.settings.ui');

/**
 * @class Zarafa.settings.ui.SettingsCategory
 * @extends Ext.Container
 * @xtype zarafa.settingscategory
 *
 * The main category object which defines the {@link #title name} of the category
 * and all the {@link Zarafa.settings.ui.SettingsWidget widgets} ({@link #items})
 * which should be displayed in the {@link Zarafa.settings.ui.SettingsCategoryWidgetPanel widget panel}.
 *
 * This object should be used for registering new categories through the
 * {@link Zarafa.settings.ui.SettingsMainPanel#context.settings.categories insertion point}.
 */
Zarafa.settings.ui.SettingsCategory = Ext.extend(Ext.Container, {
	/**
	 * @cfg {Array} items The array of configuration
	 * objects for the {@link Zarafa.settings.ui.SettingsWidget Settings Widgets}
	 * which should be shown in the center panel of the
	 * {@link Zarafa.settings.ui.SettingsMainPanel}.
	 */
	items: undefined,

	/**
	 * @cfg {Zarafa.settings.SettingsContext} settingsContext
	 */
	settingsContext: undefined,

	/**
	 * @cfg {String} title The title of the category, this string
	 * will be displayed in the left panel of the
	 * {@link Zarafa.settings.ui.SettingsMainPanel}.
	 */
	title: '',

	/**
	 * @cfg {Number} categoryIndex The index of the category
	 * in the {@link Zarafa.settings.ui.SettingsCategoryPanel category list}
	 */
	categoryIndex: 100,

	/**
	 * The reference as returned by {@link Zarafa.core.ui.notifier.Notifier#notify} to reference the
	 * message in order to remove the message as soon as the save was completed.
	 * @property
	 * @type Ext.Element
	 * @private
	 */
	savingEl: undefined,

	/**
	 * Each widget can have different saving methods (saving settings through the {@link Zarafa.settings.SettingsModel settings}
	 * or perhaps through an alternative {@link Ext.data.Store}. This counter keeps track how many saving methods have been
	 * {@link #displaySavingMask started} and will only {@link #hideSavingMask hide} it when all saving methods have completed.
	 * @property
	 * @type Number
	 * @private
	 */
	savingElCounter: 0,

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

		Ext.applyIf(config, {
			autoScroll: true
		});

		Zarafa.settings.ui.SettingsCategory.superclass.constructor.call(this, config);
	},

	/**
	 * Called by superclass when the Category has been selected and is shown to the user,
	 * this will register the {@link #onBeforeSaveSettings} event handler.
	 * @private
	 */
	onShow: function()
	{
		Zarafa.settings.ui.SettingsCategory.superclass.onShow.apply(this, arguments);

		this.mon(container.getCurrentContext().getModel(), 'beforesavesettings', this.onBeforeSaveSettingsModel, this);
	},

	/**
	 * Called by superclass when the Category has been deselected and is hidden from the user,
	 * this will unregister the {@link #onBeforeSaveSettings} event handler.
	 * @private
	 */
	onHide: function()
	{
		Zarafa.settings.ui.SettingsCategory.superclass.onHide.apply(this, arguments);

		this.mun(container.getCurrentContext().getModel(), 'beforesavesettings', this.onBeforeSaveSettingsModel, this);

		// Unregister the 'beforesave' event. This could be lingering when
		// 'savesettings' was fired but it was cancelled by one of the
		// event handlers.
		this.mun(container.getSettingsModel(), 'beforesave', this.onBeforeSettingsSave, this);
	},

	/**
	 * Event handler for the
	 * {@link Zarafa.settings.SettingsContextModel ContextModel}#{@link Zarafa.settings.SettingsContextModel#beforesavesettings beforesavesettings}
	 * event. It will reset the {@link #savingElCounter} and his will register the event handler for
	 * {@link Zarafa.settings.SettingsModel#beforesave beforesave} event.
	 * @private
	 */
	onBeforeSaveSettingsModel: function()
	{
		// Listen to the settings model to determine when the saving mask can be removed.
		var model = container.getSettingsModel();
		this.mon(model, 'beforesave', this.onBeforeSettingsSave, this, { single: true });

		// We will start saving, however each category can have multiple
		// saving methods (not just settings). So we keep track of those
		// using a counter if this counter reaches 0 we know that all
		// saving methods have completed.
		this.savingElCounter = 0;
	},

	/**
	 * Event handler which is fired when the {@link Zarafa.settings.SettingsModel SettingsModel} fires
	 * the {@link Zarafa.settings.SettingsModel#beforesave beforesave} event.
	 * This will {@link #displaySavingMask Display a notifier} to the user, and will wait until the save was completed.
	 * @param {Zarafa.settings.SettingsModel} model The settings model which fired the event
	 * @private
	 */
	onBeforeSettingsSave: function(model)
	{
		this.displaySavingMask();

		// Unregister the event handler, we will listen for it again when the ContextModel
		// is about to save the settings again.
		this.mun(model, 'beforesave', this.onBeforeSettingsSave, this);

		// We know the settingsmodel is going to be saved, so we can listen for the event.
		this.mon(model, 'save', this.onSettingsSave, this);
		this.mon(model, 'exception', this.onSettingsException, this);
	},

	/**
	 * Event handler which is fired when the {@link Zarafa.settings.SettingsModel SettingsModel}
	 * fires the {@link Zarafa.settings.SettingsModel#save save} event. This will update the
	 * notification messages.
	 * @param {Zarafa.settings.SettingsModel} model The settings model which fired the event
	 * @private
	 */
	onSettingsSave: function(model)
	{
		// Remove event handlers
		this.mun(model, 'save', this.onSettingsSave, this);
		this.mun(model, 'exception', this.onSettingsException, this);

		this.hideSavingMask(true);
	},

	/**
	 * Called when the {@link Zarafa.settings.SettingsModel} fires the {@link Zarafa.settings.SettingsModel#exception exception}
	 * event to indicate the settings were not successfully saved.
	 * @param {Zarafa.settings.SettingsModel} model The settings model which fired the event
	 * @private
	 */
	onSettingsException: function(model)
	{
		// Remove event handlers
		this.mun(model, 'save', this.onSettingsSave, this);
		this.mun(model, 'exception', this.onSettingsException, this);

		this.hideSavingMask(false);
	},

	/**
	 * Show a {@link Zarafa.core.ui.notifier.Notifier Notification} to the user indicating that the
	 * settings are being saved. This will initialize {@link #savingEl}.
	 * This uses the {@link #savingElCounter} counter to determine if this was the first Saving method
	 * to show the notification.
	 * @private
	 */
	displaySavingMask: function()
	{
		// Increase the counter for savingEl, as we have a new saving method
		// which will handle saving.
		this.savingElCounter++;

		// If we don't have the notifier yet, show it now.
		if (!this.savingEl) {
			this.savingEl = container.getNotifier().notify('info.saving', '', _('Saving') + '...', {
				container: container.getContentPanel().getEl(),
				persistent: true
			});
		}
	},

	/**
	 * Hide the {@link Zarafa.core.ui.notifier.Notifier Notification} from {@link #displaySavingMask}
	 * and optionally show a new Notification indicating that the settings were saved successfully.
	 * This uses the {@link #savingElCounter} counter to determine if this was the last Saving method
	 * in order to update the notifications.
	 * @param {Boolean} success True if a notification should be shown that the settings were saved.
	 * @private
	 */
	hideSavingMask: function(success)
	{
		var contentEl = container.getContentPanel().getEl();

		// Reduce the counter for the savingEl, as one of the used
		// saving methods has completed.
		this.savingElCounter--;

		// If this was the last saving method, we can complete it.
		// If the save was not successful we immediately hide the
		// notification as well, to prevent other saving methods
		// from falsy reporting success.
		if (this.savingElCounter === 0 || !success) {
			if (this.savingEl) {
				container.getNotifier().notify('info.saving', null, null, {
					container: contentEl,
					destroy: true,
					reference: this.savingEl
				});

				delete this.savingEl;

				if (success) {
					container.getNotifier().notify('info.saved', _('Saved'), _('Saved successfully'), {
						container: contentEl
					});
				}
			}
		}
	},

	/**
	 * Called by the {@link Zarafa.settings.ui.SettingsCategoryWidgetPanel widget panel}
	 * to load the latest version of the settings from the
	 * {@link Zarafa.settings.SettingsModel} into the UI of this category.
	 *
	 * By default this will call {@link Zarafa.settings.ui.SettingsWidget#update} on all
	 * {@link #items}
	 *
	 * @param {Zarafa.settings.SettingsModel} settingsModel The settings to load
	 */
	update: function(settingsModel)
	{
		var widgets = this.findByType('zarafa.settingswidget');
		for (var i = 0, len = widgets.length; i < len; i++) {
			widgets[i].update.apply(widgets[i], arguments);
		}
	},

	/**
	 * Called by the {@link Zarafa.settings.ui.SettingsCategoryWidgetPanel widget panel}
	 * to update the settings from the UI into the {@link Zarafa.settings.SettingsModel settings model}.
	 *
	 * By default this will call {@link Zarafa.settings.ui.SettingsWidget#updateSettings} on all
	 * {@link #items}
	 *
	 * @param {Zarafa.settings.SettingsModel} settingsModel The settings to update
	 */
	updateSettings: function(settingsModel)
	{
		var widgets = this.findByType('zarafa.settingswidget');
		for (var i = 0, len = widgets.length; i < len; i++) {
			widgets[i].updateSettings.apply(widgets[i], arguments);
		}
	}
});

Ext.reg('zarafa.settingscategory', Zarafa.settings.ui.SettingsCategory);
Ext.namespace('Zarafa.settings.ui');

/**
 * @class Zarafa.settings.ui.SettingsCategoryPanel
 * @extends Ext.Container
 * @xtype zarafa.settingscategorypanel
 *
 * The extra container which displays the {@link Zarafa.settings.ui.SettingsCategoryTab tabs}
 * for the various registered {@link Zarafa.settings.ui.SettingsCategory categories}.
 */
Zarafa.settings.ui.SettingsCategoryPanel = Ext.extend(Ext.Container, {
	/**
	 * @cfg {Zarafa.settings.SettingsContext} context The context which
	 * is using this tab panel
	 */
	context: undefined,

	/**
	 * The currently marked {@link Zarafa.settings.ui.SettingsCategoryTab#setActive active}
	 * {@link Zarafa.settings.ui.SettingsCategoryTab tab}. This is updated during {@link #onViewChange}
	 * @property
	 * @type Zarafa.settings.ui.SettingsCategoryTab
	 */
	activeTab: undefined,

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

		Ext.applyIf(config, {
			cls:'zarafa-settings-category-panel',
			listeners: {
				afterrender: function() {
					this.getEl().set({ 'role': 'tablist', 'aria-label': _('Settings categories') });
				}
			}
		});

		Zarafa.settings.ui.SettingsCategoryPanel.superclass.constructor.call(this, config);

		if (this.context) {
			this.mon(this.context, 'viewchange', this.onViewChange, this);
		}
	},

	/**
	 * Event handler for the {@link Zarafa.settings.SettingsContext#viewchange} event on
	 * the {@link Zarafa.settings.SettingsContext context}. This will mark the new
	 * {@link Zarafa.settings.ui.SettingsCategoryTab tab} as
	 * {@link Zarafa.settings.ui.SettingsCategoryTab#setActive}.
	 * @param {Zarafa.settings.SettingsContext} context The context which fired the event
	 * @param {Mixed} viewId The view id which should be activated
	 * @param {Mixed} oldViewId the old view which was previously activated
	 * @private
	 */
	onViewChange: function(context, viewId, oldViewId)
	{
		// The viewId is the ID which matches the Zarafa.settings.ui.SettingsCategory
		// as loaded in the Zarafa.settings.ui.SettingsCategoryWidgetPanel. Search
		// through all tabs to find the tab which corresponds with it.
		var item = this.findBy(function(tab) {
			return tab.isTabForId(viewId);
		});

		if (this.activeTab) {
			this.activeTab.setActive(false);
			delete this.activeTab;
		}
		if (!Ext.isEmpty(item)) {
			this.activeTab = item[0];
			this.activeTab.setActive(true);
		}
	}
});

Ext.reg('zarafa.settingscategorypanel', Zarafa.settings.ui.SettingsCategoryPanel);
Ext.namespace('Zarafa.settings.ui');

/**
 * @class Zarafa.settings.ui.SettingsCategoryTab
 * @extends Ext.Container
 * @xtype zarafa.settingscategorytab
 *
 * Special category option to be rendered into the
 * {@link Zarafa.settings.ui.SettingsCategoryPanel SettingsCategoryPanel}
 */
Zarafa.settings.ui.SettingsCategoryTab = Ext.extend(Ext.Container, {
	/**
	 * @cfg {Zarafa.settings.SettingsContext} context The settings context
	 * for which these tabs are shown.
	 */
	context: undefined,

	/**
	 * @cfg {String} title The title of the category, this string
	 * will be displayed in the left panel of the
	 * {@link Zarafa.settings.ui.SettingsMainPanel}.
	 */
	title: '',

	/**
	 * @cfg {Number} categoryIndex The index of the category
	 * in the {@link Zarafa.settings.ui.SettingsCategoryPanel category list}
	 */
	categoryIndex: 100,

	/**
	 * @cfg {Zarafa.settings.ui.SettingsCategory} category The category
	 * to which this tab has been connected to. When this tab is clicked
	 * that category will be activated through
	 * {@link Zarafa.settings.SettingsContext#setView}
	 */
	category: undefined,

	/**
	 * @cfg {String} iconCls The CSS classname which should be
	 * applied to the tab to display an icon next to the {@link #title}.
	 */
	iconCls: 'zarafa-settings-category-tab-icon',

	/**
	 * @cfg {String} activeCls The CSS classname which should be
	 * applied to the tab when it has been marked {@link #setActive active}.
	 */
	activeCls: 'zarafa-settings-category-tab-active',

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

		Ext.applyIf(config, {
			cls: 'zarafa-settings-category-tab',
			html: config.title || this.title,
			listeners: {
				afterrender: this.onAfterRender,
				scope: this
			}
		});

		Zarafa.settings.ui.SettingsCategoryTab.superclass.constructor.call(this, config);
	},

	/**
	 * Check if the given {@link Ext.Component#id} matches the id of the
	 * {@link #category} which is linked to this tab.
	 * @param {String} id The ID to check
	 * @return {Boolean} True if the given ID is handled by this tab
	 */
	isTabForId: function(id)
	{
		return (this.category && this.category.getId() === id);
	},

	/**
	 * Activate the tab by applying the {@link #activeCls} to the {@link #getEl element}
	 * @param {Boolean} active False to deactivate the tab
	 */
	setActive: function(active)
	{
		if (active !== false) {
			this.getEl().addClass(this.activeCls);
			this.getEl().set({ 'aria-selected': 'true' });
		} else {
			this.getEl().removeClass(this.activeCls);
			this.getEl().set({ 'aria-selected': 'false' });
		}
	},

	/**
	 * Called when the Tab is being rendered, this will apply the {@link Ext.Element#addClassOnClick},
	 * {@link Ext.Element#addClassOnFocus} and {@link Ext.Element#addClassOnOver} for the {@link #el mail element}.
	 * @private
	 */
	onRender: function()
	{
		Zarafa.settings.ui.SettingsCategoryTab.superclass.onRender.apply(this, arguments);

		var el = this.getEl();

		if (!Ext.isEmpty(this.cls)) {
			el.addClassOnClick(this.cls + '-click');
			el.addClassOnFocus(this.cls + '-focus');
			el.addClassOnOver(this.cls + '-over');
		}

		el.set({
			'role': 'tab',
			'tabindex': '0'
		});
		this.mon(el, 'click', this.onClick, this);
		this.mon(el, 'keydown', function(e) {
			if (e.getKey() === e.ENTER || e.getKey() === e.SPACE) {
				e.stopEvent();
				this.onClick();
			}
		}, this);
	},

	/**
	 * Called after the Tab has been rendered, this will add the icon
	 * @private
	 */
	onAfterRender: function()
	{
		var el = this.getEl();

		if (!Ext.isEmpty(this.iconCls)) {
			el.addClass('k-with-icon');

			// Create an element for the icon
			el.createChild(
				{
					tag: 'img',
					cls: this.iconCls + ' k-settings-category-icon',
					src: Ext.BLANK_IMAGE_URL,
					'aria-hidden': 'true',
					alt: ''
				},
				Ext.fly(el.dom.firstChild)
			);
		}
	},

	/**
	 * Event handler which is fired when the tab is being clicked,
	 * this will call the {@link #handler} function.
	 * @private
	 */
	onClick: function()
	{
		if (this.context && this.category) {
			this.context.setView(this.category.id);
		}
	}
});

Ext.reg('zarafa.settingscategorytab', Zarafa.settings.ui.SettingsCategoryTab);
Ext.namespace('Zarafa.settings.ui');

/**
 * @class Zarafa.settings.ui.SettingsCategoryWidgetPanel
 * @extends Ext.Container
 * @xtype zarafa.settingscategorywidgetpanel
 *
 * The main container in which the {@link Zarafa.settings.ui.SettingsCategory categories}
 * will display all their settings. This panel has a {@link Ext.layout.CardLayout} to
 * ensure only a single category will be shown at the same time.
 */
Zarafa.settings.ui.SettingsCategoryWidgetPanel = Ext.extend(Ext.Container, {
	/**
	 * @cfg {Zarafa.settings.SettingsContext} context The context which
	 * is using this widget panel
	 */
	context: undefined,

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

		Ext.applyIf(config, {
			layout: 'card',
			cls: 'zarafa-settings-category-widget-panel'
		});

		Zarafa.settings.ui.SettingsCategoryWidgetPanel.superclass.constructor.call(this, config);

		if (this.context) {
			var model = this.context.getModel();

			this.mon(this.context, {
				viewchange: this.onViewChange,
				viewmodechange: this.onViewModeChange,
				scope: this
			});

			this.mon(model, {
				beforesavesettings: this.onBeforeSaveSettings,
				savesettings: this.onSaveSettings,
				discardsettings: this.onDiscardSettings,
				scope: this
			});
		}

		this.on('afterlayout', this.onAfterFirstLayout, this, { single: true });
		this.mon(container.getTabPanel(), 'beforetabchange', this.onTabChangeHandler, this);
	},

	/**
	 * Event handler fired when this component is being {@link #doLayout laid out} for the
	 * first time. This will activate the first registered category.
	 * @private
	 */
	onAfterFirstLayout: function()
	{
		var item = this.get(this.getActiveTab());

		// scroll the scroll bar to signature tab.
		if (Ext.isDefined(this.context.scrollToSignatureWidget)) {
			item.scrollToSignatureWidget = this.context.scrollToSignatureWidget;
			delete this.context.scrollToSignatureWidget;
		}

		this.context.setView(item.id);
	},

	/**
	 * Helper function which used to get the index of {@link Zarafa.settings.ui.SettingsCategoryTab activeTab}.
	 *
	 * @returns {Number} return the index of active tab.
	 */
	getActiveTab: function()
	{
		var activeTab = 0;
		if (Ext.isDefined(this.context.defaultActiveTab)) {
			activeTab = this.context.defaultActiveTab;
			delete this.context.defaultActiveTab;
		}
		return activeTab;
	},

	/**
	 * Event handler triggers when content tab panel is changed,
	 * It will internally call {@link #onAfterFirstLayout} function.
	 * Which select the {@link Zarafa.mail.settings.SettingsMailCategory SettingsMailCategory}
	 * and set the focus on {@link Zarafa.mail.settings.SettingsSignaturesWidget SettingsSignaturesWidget}
	 */
	onTabChangeHandler: function()
	{
		if (Ext.isDefined(this.context.defaultActiveTab) && Ext.isDefined(this.context.scrollToSignatureWidget)){
			this.onAfterFirstLayout();
		}
	},

	/**
	 * Event handler for the {@link Zarafa.settings.SettingsContext#viewchange} event on
	 * the {@link Zarafa.settings.SettingsContext context}. This will
	 * {@link Ext.layout.CardLayout#setActiveItem activate} the view} in the panel.
	 * @param {Zarafa.settings.SettingsContext} context The context which fired the event
	 * @param {Mixed} viewId The view id which should be activated
	 * @param {Mixed} oldViewId the old view which was previously activated
	 * @private
	 */
	onViewChange: function(context, viewId, oldViewId)
	{
		var layout = this.getLayout();

		layout.setActiveItem(viewId);
		layout.activeItem.doLayout();
		layout.activeItem.update(context.getModel().getEditableSettingsModel());
	},

	/**
	 * Event handler for the {@Link Zarafa.settings.SettingsContext#viewmodechange} event on
	 * the {@link Zarafa.settings.SettingsContext context}. This will {@link Ext.Element#scrollChildIntoView scroll}
	 * the corresponding {@link Zarafa.settings.ui.SettingsWidget} into the current view.
	 * @param {Zarafa.settings.SettingsContext} context The context which fired the event
	 * @param {Mixed} viewMod The viewMode which should be activated
	 * @param {Mixed} oldViewId The old viewMode
	 * @private
	 */
	onViewModeChange: function(context, viewMode, oldViewMode)
	{
		var activeItem = this.getLayout().activeItem;
		if (activeItem) {
			var widget = activeItem.get(viewMode);

			if (widget) {
				activeItem.el.scrollChildIntoView(widget.el);
			}
		}
	},

	/**
	 * Event handler for the {@link Zarafa.settings.SettingsContextModel#beforesavesettings}
	 * event on the {@link Zarafa.settings.SettingsContextModel}. This will call
	 * {@link Zarafa.settings.ui.SettingsCategory#updateSettings} to update the settings
	 * from the {@link Zarafa.settings.ui.SettingsCategory category} into the
	 * {@link Zarafa.settings.ui.SettingsModel model} so the latest settings can be saved.
	 * @param {Zarafa.settings.SettingsContextModel} model The context model which fired the event
	 * @param {Zarafa.settings.SettingsModel} settingsModel The settings model which will be saved
	 * @private
	 */
	onBeforeSaveSettings: function(model, settingsModel)
	{
		var layout = this.getLayout();

		layout.activeItem.updateSettings(settingsModel);
	},

	/**
	 * Event handler for the {@link Zarafa.settings.SettingsContextModel#savesettings}
	 * event on the {@link Zarafa.settings.SettingsContextModel}. This will call
	 * {@link Zarafa.settings.ui.SettingsCategory#update} to load the new settings
	 * into the {@link Zarafa.settings.ui.SettingsCategory category}.
	 * @param {Zarafa.settings.SettingsContextModel} model The context model which fired the event
	 * @param {Zarafa.settings.SettingsModel} settingsModel The settings model which has been saved
	 * @private
	 */
	onSaveSettings: function(model, settingsModel)
	{
		var layout = this.getLayout();

		layout.activeItem.update(settingsModel);
	},

	/**
	 * Event handler for the {@link Zarafa.settings.SettingsContextModel#discardsettings}
	 * event on the {@link Zarafa.settings.SettingsContextModel}. This will call
	 * {@link Zarafa.settings.ui.SettingsCategory#update} to load the reverted settings
	 * into the {@link Zarafa.settings.ui.SettingsCategory category}.
	 * @param {Zarafa.settings.SettingsContextModel} model The context model which fired the event
	 * @param {Zarafa.settings.SettingsModel} settingsModel The settings model which has been reverted
	 * @private
	 */
	onDiscardSettings: function(model, settingsModel)
	{
		var layout = this.getLayout();

		layout.activeItem.update(settingsModel);
	}
});

Ext.reg('zarafa.settingscategorywidgetpanel', Zarafa.settings.ui.SettingsCategoryWidgetPanel);
Ext.namespace('Zarafa.settings.ui');

/**
 * @class Zarafa.settings.ui.SettingsTreeNode
 * @extends Ext.tree.AsyncTreeNode
 *
 * Special treeNode to be used in the {@link Zarafa.settings.ui.SettingsTreePanel SettingsTree}.
 */
Zarafa.settings.ui.SettingsTreeNode = Ext.extend(Ext.tree.AsyncTreeNode, {
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		this.addEvents(
			/**
			 * @event valuechange
			 * Fires when the value for a node is changed
			 * @param {Node} node The node
			 * @param {Mixed} value the value
			 * @param {Mixed} oldValue The old value
			 */
			'valuechange'
		);

		Zarafa.settings.ui.SettingsTreeNode.superclass.constructor.call(this, config);
	},

	/**
	 * Enable the editor for this Treenode
	 */
	startEdit: function()
	{
		this.ownerTree.startEdit(this, 1);
	},

	/**
	 * @return {Boolean} True if this node can be edited
	 */
	isEditable: function()
	{
		return this.leaf && !Array.isArray(this.attributes.value);
	},

	/**
	 * Sets the value for this node
	 * @param {Mixed} value
	 */
	setValue: function(value)
	{
		var oldValue = this.attributes['value'];
		this.attributes['value'] = value;
		if (this.rendered) {
			this.ui.onValueChange(this, value, oldValue);
		}
		this.fireEvent('valuechange', this, value, oldValue);
	}
});

Ext.tree.TreePanel.nodeTypes.setting = Zarafa.settings.ui.SettingsTreeNode;
Ext.namespace('Zarafa.settings.ui');

/**
 * @class Zarafa.settings.ui.SettingsTreeNodeUI
 * @extends Ext.ux.tree.TreeGridNodeUI
 *
 * Extension of the default {@link Ext.ux.tree.TreeGridNodeUI} class,
 * to add some helper functionality for editing the settings value.
 */
Zarafa.settings.ui.SettingsTreeNodeUI = Ext.extend(Ext.ux.tree.TreeGridNodeUI, {
	/**
	 * The {@link Ext.Element} which contains the text (or setting name).
	 * @property
	 * @type Ext.Element
	 */
	textNode: undefined,

	/**
	 * The {@link Ext.Element} which contains the value (or setting value).
	 * @property
	 * @type Ext.Element
	 */
	valueNode: undefined,

	/**
	 * Main renderer for the different elements within the node
	 * @private
	 */
	renderElements: function()
	{
		Zarafa.settings.ui.SettingsTreeNodeUI.superclass.renderElements.apply(this, arguments);

		this.valueNode = this.elNode.childNodes[1].childNodes[0];
	},

	/**
	 * Change the value inside the {@link #valueNode}.
	 * @param {Zarafa.settings.ui.SettingsTreeNode} node The node which was changed
	 * @param {Mixed} value The new value which must be applied to the node
	 * @param {Mixed} oldValue The old value which was previously on the node
	 */
	onValueChange: function(node, value, oldValue)
	{
		if (this.rendered) {
			this.valueNode.innerHTML = Ext.util.Format.htmlEncode(value);
		}
	}
});
Ext.namespace('Zarafa.settings.ui');

/**
 * @class Zarafa.settings.ui.SettingsWidget
 * @extends Ext.Panel
 * @xtype zarafa.settingswidget
 *
 * A widget which is placed in a {@link Zarafa.settings.ui.SettingsCategory Settings Category}
 * and will be rendered into the {@link Zarafa.settings.ui.SettingContentPanel}. Each
 * widget is a collection of settings which logically belong to each other and allows the
 * user to edit them nicely.
 */
Zarafa.settings.ui.SettingsWidget = Ext.extend(Ext.Panel, {
	/**
	 * @cfg {Zarafa.settings.SettingsContext} settingsContext
	 */
	settingsContext: undefined,

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

		Ext.applyIf(config, {
			cls: 'zarafa-settings-widget',
			layout: 'form',
			labelWidth: 200
		});

		Zarafa.settings.ui.SettingsWidget.superclass.constructor.call(this, config);
	},

	/**
	 * Called by the {@link Zarafa.settings.ui.SettingsCategory Category} when
	 * it has been called with {@link zarafa.settings.ui.SettingsCategory#update}.
	 * This is used to load the latest version of the settings from the
	 * {@link Zarafa.settings.SettingsModel} into the UI of this category.
	 * @param {Zarafa.settings.SettingsModel} settingsModel The settings to load
	 */
	update: Ext.emptyFn,

	/**
	 * Called by the {@link Zarafa.settings.ui.SettingsCategory Category} when
	 * it has been called with {@link zarafa.settings.ui.SettingsCategory#updateSettings}.
	 * This is used to update the settings from the UI into the {@link Zarafa.settings.SettingsModel settings model}.
	 * @param {Zarafa.settings.SettingsModel} settingsModel The settings to update
	 */
	updateSettings: Ext.emptyFn
});

Ext.reg('zarafa.settingswidget', Zarafa.settings.ui.SettingsWidget);
Ext.namespace('Zarafa.task');

/**
 * @class Zarafa.task.Actions
 * Common actions which can be used within {@link Ext.Button buttons}
 * or other {@link Ext.Component components} with action handlers.
 * @singleton
 */
Zarafa.task.Actions = {
	/**
	 * Open a Panel in which a new {@link Zarafa.core.data.IPMRecord record} can be
	 * further edited.
	 *
	 * @param {Zarafa.task.TaskContextModel} model Context Model object that will be used
	 * to {@link Zarafa.task.TaskContextModel#createRecord create} the Task.
	 * @param {Object} config (optional) Configuration object used to create
	 * the Content Panel.
	 */
	openCreateTaskContent: function(model, config)
	{
		var record = model.createRecord();
		Zarafa.core.data.UIFactory.openCreateRecord(record, config);
	},

	/**
	 * Open a Panel in which a new {@link Zarafa.core.data.IPMRecord record} can be
	 * further edited.
	 *
	 * @param {Zarafa.task.TaskContextModel} model Context Model object that will be used
	 * to {@link Zarafa.task.TaskContextModel#createRecord create} the Task.
	 * @param {Object} config (optional) Configuration object used to create
	 * the Content Panel.
	 */
	openCreateTaskRequestContent: function(model, config)
	{
		var record = model.createRecord();
		record.convertToTaskRequest();
		Zarafa.core.data.UIFactory.openCreateRecord(record, config);
	},

	/**
	 * Converts record to an task and calls {@link Zarafa.core.data.UIFactory.openCreateRecord}
	 * to open the newly created task as editable record. The original record isn't removed.
	 *
	 * @param {Zarafa.core.data.IPMRecord} records The record which will be converted to a task
	 * @param {Zarafa.task.TaskContextModel} model Used to create a new task record
	 */
	createTaskFromMail: function(records, model)
	{
		var record;

		if (Array.isArray(records) && !Ext.isEmpty(records)) {
			record = records[0];
		} else {
			return;
		}

		if (record.isOpened()) {
			var newTaskRecord = record.convertToTask(model.getDefaultFolder());
			Zarafa.core.data.UIFactory.openCreateRecord(newTaskRecord);
		} else {
			// If record is not opened, then we need to reopen it to get the body. (For example when the selected records store reloads)
			record.getStore().on('open', this.openHandler.createDelegate(this, [model], 2), this, {single: true});
			record.open();
		}
	},

	/**
	 * Handler for {@link Zarafa.core.data.IPMStore store} open event. Converts the opened record
	 * to a task and opens it as editable record.
	 *
	 * @param {Zarafa.core.data.IPMStore} store The store of the record.
	 * @param {Zarafa.core.data.IPMRecord} record The record which will be converted to a task
	 * @param {Zarafa.task.TaskContextModel} model Used to create a new task record
	 */
	openHandler: function(store, record, model)
	{
		var newTaskRecord = record.convertToTask(model.getDefaultFolder());
		Zarafa.core.data.UIFactory.openCreateRecord(newTaskRecord);
	},

	/**
	 * Opens a {@link Zarafa.task.dialogs.SendTaskRequestConfirmationContentPanel}.
	 *
	 * @param {Zarafa.task.TaskRecord} record The record, or records, for which the task conformation will be send.
	 * @param {Object} config (optional) Configuration object used to create the ContentPanel
	 */
	openSendConfirmationContent: function(record, config)
	{
		config = Ext.applyIf(config || {}, {
			record: record,
			modal: true
		});
		var componentType = Zarafa.core.data.SharedComponentType['task.dialogs.sendtaskrequestconfirmation'];
		Zarafa.core.data.UIFactory.openLayerComponent(componentType, record, config);
	},

	/**
	 * Opens a {@link Zarafa.addressbook.dialogs.ABMultiUserSelectionContentPanel ABMultiUserSelectionContentPanel}
	 * for configuring the recipient of the given {@link Zarafa.core.data.IPMRecord records}.
	 *
	 * @param {Zarafa.core.data.IPMRecord} records The record, or records for which the recipient
	 * must be configured
	 * @param {Object} config (optional) Configuration object used to create
	 * the Content Panel.
	 */
	openRecipientSelectionContent: function(records, config)
	{
		if (Ext.isArray(records) && !Ext.isEmpty(records)) {
			records = records[0];
		}

		// Create a copy of the record, we don't want the changes
		// to be activated until the user presses the Ok button.
		var copy = records.copy();
		var store = copy.getSubStore('recipients');

		Zarafa.common.Actions.openABUserMultiSelectionContent({
			callback: function() {
				records.applyData(copy);
			},
			convert: function(user, field) {
				return user.convertToRecipient(field ? field.defaultRecipientType : config.defaultRecipientType);
			},
			store: store,
			selectionCfg: [{
				xtype: 'zarafa.recipientfield',
				fieldLabel: _('To') + ':',
				boxStore: store,
				filterRecipientType: Zarafa.core.mapi.RecipientType.MAPI_TO,
				defaultRecipientType: Zarafa.core.mapi.RecipientType.MAPI_TO,
				flex: 1
			}]
		});
	},

	/**
	 * Deletes all passed {@link Zarafa.core.data.IPMRecord records}. A
	 * {@link Zarafa.common.dialogs.MessageBox.show MessageBox} will be shown to explain that the records will be
	 * deleted from their original folder.
	 *
	 * @param {Array} records The array of {@link Zarafa.core.data.IPMRecord records} that must be deleted.
	 */
	deleteRecordsFromTodoList: function(records)
	{
		// It would be nice if we could use the state functionality for this, but the MessageBox is not an
		// Ext.Component and this context menu is already gone when the MessageBox is displayed, so this is
		// not possible.
		var settingsKey = 'zarafa/v1/contexts/task/todolist/dialogs/delete_item_confirmation/disabled';
		var disableDeleteConfirmationDialog = container.getSettingsModel().get(settingsKey) === true;

		if ( disableDeleteConfirmationDialog ){
			Zarafa.common.Actions.doDeleteRecords(records);
			return;
		}

		Zarafa.common.dialogs.MessageBox.addCustomButtons({
			title: _('Delete confirmation'),
			minWidth: 351,
			customButton: [{
				text: _('Delete'),
				name: 'delete'
			}, {
				text: _('Cancel'),
				name: 'cancel'
			}],
			checkbox: true,
			fn: function(btnId, text, checked){
				if ( btnId !== 'delete' ){
					return;
				}

				container.getSettingsModel().set(settingsKey, checked);
				Zarafa.common.Actions.doDeleteRecords(records);
			},
			msg: _('Deleting the item(s) will also delete the original item(s).') + '<br />' + _('Do you want to delete the item(s)?')
		});
	},

	/**
	 * Opens the task options dialog for the given record.
	 *
	 * @param {Zarafa.core.data.IPMRecord|Zarafa.core.data.IPMRecord[]} records
	 * The record(s) for which the options are requested.
	 * @param {Object} config (optional) Configuration object used to create the Content Panel.
	 */
	openOptionsContent: function(records, config)
	{
		if (Array.isArray(records)) {
			records = records[0];
		}

		if (!records) {
			return;
		}

		config = Ext.applyIf(config || {}, {
			modal: true
		});

		var componentType = Zarafa.core.data.SharedComponentType['task.dialog.options'];
		Zarafa.core.data.UIFactory.openLayerComponent(componentType, records, config);
	},

	/**
	 * Opens the {@link Zarafa.task.ui.TaskFlagsMenu FlagsMenu} for
	 * the given {@link Zarafa.core.data.IPMRecord records}.
	 *
	 * @param {Zarafa.task.TaskRecord} records The record, or records for which the flags
	 * menu will be shown.
	 * @param {Object} options An Object Which contains configuration options to
	 * open {@link Zarafa.task.ui.TaskFlagsMenu FlagsMenu}.
	 */
	openFlagsMenu: function(records, options)
	{
		if (!Ext.isArray(records)) {
			records = [ records ];
		}

		var component = Zarafa.core.data.SharedComponentType['task.contextmenu.flags'];
		Zarafa.core.data.UIFactory.openContextMenu(component, records, options);
	}
};
Ext.namespace('Zarafa.task.data');

/**
 * @class Zarafa.task.data.SearchFields
 * Search fields for search bar
 * @singleton
 */
Zarafa.task.data.SearchFields = [{
	name: _('All text fields'),
	value: 'subject body owner companies'
}, {
	name: _('Subject'),
	value: 'subject'
}, {
	name: _('Categories'),
	value: 'categories'
}, {
	name: _('Body'),
	value: 'body'
}, {
	name: _('Owner'),
	value: 'owner'
}, {
	name: _('Companies'),
	value: 'companies'
}, {
	name: _('Due Date'),
	value: 'duedate'
}, {
	name: _('Start Date'),
	value: 'startdate'
}];
Ext.namespace('Zarafa.task.dialogs');

/**
 * @class Zarafa.task.dialogs.SendTaskRequestConfirmationPanel
 * @extends Ext.form.FormPanel
 * @xtype 'zarafa.sendtaskrequestconfirmationpanel'
 */
Zarafa.task.dialogs.SendTaskRequestConfirmationPanel = Ext.extend(Ext.form.FormPanel, {
	/**
	 * @cfg {Zarafa.core.mapi.ResponseStatus} responseType The response type value selected by user.
	 */
	responseType: undefined,

	/**
	 * Info string that will be shown for assignee that is going to accept task request.
	 * @property
	 * @type String
	 */
	acceptInfoString: _('This task will be accepted and moved to your task folder. Do you want to include comments with your response?'),

	/**
	 * Info string that will be shown for assignee that is going to decline task request.
	 * @property
	 * @type String
	 */
	declineInfoString: _('This task will be declined and moved into the Deleted Items folder. Do you want to edit the response before sending it?'),

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

		Ext.applyIf(config, {
			// Override from Ext.Component
			xtype: 'zarafa.sendtaskrequestconfirmationpanel',
			cls: 'k-send-taskrequest-confirmation-panel',
			layout: {
				type: 'vbox',
				align: 'stretch'
			},
			border: false,
			hideLabels: true,
			items: [{
				xtype: 'displayfield',
				value: this.getDisplayText(config.responseType),
				autoHeight: true
			},{
				xtype:'radio',
				boxLabel: _('Edit the response before sending'),
				name: 'sendtaskconfirmation',
				autoHeight: true,
				listeners: {
					check: this.onEditResponseChecked,
					scope: this
				}
			},{
				xtype: 'textarea',
				name: 'responseText',
				fieldLabel: _('Response message'),
				ref: 'responseTextField',
				disabled: true,
				flex: 1
			},{
				xtype:'radio',
				boxLabel: _('Send the response now'),
				name: 'sendtaskconfirmation',
				checked: true,
				autoHeight: true
			}]
		});

		Zarafa.task.dialogs.SendTaskRequestConfirmationPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Checks the {@link Zarafa.core.mapi.ResponseStatus responsetype} and either uses the {@link #acceptInfoString}
	 * or {@link #declineString} for generating the displaytext which should be shown on top of this panel.
	 * @param {Zarafa.core.mapi.ResponseStatus} responseType The response type for which this dialog is shown.
	 * @private
	 */
	getDisplayText: function(responseType)
	{
		switch(responseType)
		{
			case Zarafa.core.mapi.TaskMode.ACCEPT:
				return this.acceptInfoString;
			case Zarafa.core.mapi.TaskMode.DECLINE:
				return this.declineInfoString;
		}
	},

	/**
	 * Event handler for the {@link Ext.form.RadioButton#check check} event, this will
	 * enable/disable the responseTextField accordingly.
	 * @param {Ext.form.RadioButton} rb The radio button which was selected
	 * @param {Boolean} checked True if the radio button was checked
	 * @private
	 */
	onEditResponseChecked: function(rb, checked)
	{
		this.responseTextField.setDisabled(!checked);
	},

	/**
	 * Called by the dialog check the settings from the user and either
	 * send the accept or decline message to the organizer.
	 * @param {Zarafa.core.data.IPMRecord} record The record which is being
	 * accepted or declined.
	 */
	updateRecord: function(record)
	{
		var values = this.getForm().getFieldValues();

		this.editResponse = (values.sendtaskconfirmation[1] !== true);
		record.respondToTaskRequest(this.responseType, values.responseText, this.editResponse);
	}
});

Ext.reg('zarafa.sendtaskrequestconfirmationpanel', Zarafa.task.dialogs.SendTaskRequestConfirmationPanel);
Ext.namespace('Zarafa.task.dialogs');

/**
 * @class Zarafa.task.dialogs.TaskDetailTab
 * @extends Ext.form.FormPanel
 * @xtype zarafa.taskdetailtab
 *
 * details tab in the {@link Zarafa.task.dialogs.TaskEditPanel}
 * that is used to create Tasks.
 */
Zarafa.task.dialogs.TaskDetailTab = Ext.extend(Ext.form.FormPanel, {
	/**
	 * @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, {
			xtype: 'zarafa.taskdetailtab',
			cls: 'k-taskdetailtab',
			title: _('Details'),
			border: false,
			labelWidth: 120,
			labelAlign: 'left',
			defaults: {
				border: false,
				xtype: 'panel'
			},
			items: [
				this.createTaskInfoPanel(),
				this.createDatePanel(),
				this.createWorkPanel(),
				this.createCompanyPanel(),
				this.createUpdateListPanel()
			]
		});

		Zarafa.task.dialogs.TaskDetailTab.superclass.constructor.call(this, config);

	},

	/**
	 * Create the {@link Ext.DataView DataView} containing the information about assigned task request
	 * from assigner/assignee.
	 *
	 * @return {Object} Configuration object for the panel containing the fields
	 * @private
	 */
	createTaskInfoPanel: function ()
	{
		return {
			xtype: 'zarafa.taskinfo',
			ref: 'taskInfoPanel',
			hidden: true
		};
	},

	/**
	 * Create the {@link Ext.Panel panel} containing the form elements
	 * to set the date complete for this Task
	 * @return {Object} Configuration object for the panel containing the fields
	 * @private
	 */
	createDatePanel: function()
	{
		return{
			layout: 'form',
			ref: 'datePanel',
			cls: 'k-datepanel',
			items: [{
				xtype: 'datefield',
				// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
				format: ('d/m/Y'),
				fieldLabel:_('Date Complete'),
				anchor: '35%',
				name: 'date_completed',
				listeners:{
					'change': this.onCompleteDateChange,
					scope: this
				}
			}]
		};
	},

	/**
	 * Create the {@link Ext.Panel panel} containing the form elements
	 * to set the work properties for this Task
	 * @return {Object} Configuration object for the panel containing the fields
	 * @private
	 */
	createWorkPanel: function()
	{
		return{
			layout: 'column',
			ref: 'workPanel',
			cls: 'k-workpanel',
			defaults:{
					layout:'form',
					border:false,
					xtype:'panel'
			},
			items: [{//1 col
				columnWidth: 0.35,
				cls: 'k-col0',
				defaults:{
					anchor:'100%'
				},
				items:[{
					xtype: 'zarafa.durationfield',
					fieldLabel:_('Total Work'),
					name: 'totalwork',
					listeners:{
						'change': this.onPropertyChange,
						scope: this
					}
				},{
					xtype: 'zarafa.durationfield',
					fieldLabel:_('Actual Work'),
					name: 'actualwork',
					listeners:{
						'change': this.onPropertyChange,
						scope: this
					}
				}]
			},{//2 col
				columnWidth: 0.65,
				cls: 'k-col1',
				labelWidth: 160,
				defaults:{
					anchor:'100%'
				},
				items: [{
					xtype: 'textfield',
					fieldLabel:_('Mileage'),
					name: 'mileage',
					listeners:{
						'change': this.onPropertyChange,
						scope: this
					}
				},{
					xtype: 'textfield',
					fieldLabel:_('Billing Information'),
					name: 'billing_information',
					listeners:{
						'change': this.onPropertyChange,
						scope: this
					}
				}]
			}]
		};
	},

	/**
	 * Create the {@link Ext.Panel panel} containing the form elements
	 * to set the companies for this Task
	 * @return {Object} Configuration object for the panel containing the fields
	 * @private
	 */
	createCompanyPanel: function()
	{
		return {
			layout: 'form',
			ref: 'companyPanel',
			cls: 'k-companypanel',
			items: [{
				xtype: 'textfield',
				fieldLabel: _('Companies'),
				anchor: '100%',
				emptyText : _('Multiple companies name must be separated by') + ' \';\'.',
				name: 'companies',
				beforeBlur: this.onBeforeBlurCompanies,
				scope: this,
				listeners: {
					'change': this.onPropertyChange,
					scope: this
				}
			}]
		};
	},

	/**
	 * Function called before the 'blur' event triggred. It will process the companies name
	 * to '; ' separated and set the value to {@link Ext.form.Filed companies} text field.
	 */
	onBeforeBlurCompanies : function()
	{
		/**
		 * Helper function which process the companies names.
		 *
		 * @param {String} value The companies names.
		 * @return {String} return the companies names by '; ' separated.
		 */
		function processCompaniesValue(value) {
			value = value.trim();

			if (Ext.isEmpty(value)) {
				return value;
			}

			if (/;/.test(value.slice(-1)) === false){
				value +=";";
			}

			var result = value.split(";").reduce(function(acc, item, index){
				if (index == 0) {
					acc = item.trim() + "; ";
				} else if (Ext.isEmpty(item) === false) {
					acc += item.trim() + "; ";
				}
				return acc;
			},"");
			return result;
		}

		this.setValue(processCompaniesValue(this.getValue()));
	},

	/**
	 * Create the {@link Ext.Panel panel} containing the form elements
	 * to set the User List for this Assigned Task.
	 * @return {Object} Configuration object for the panel containing the fields
	 * @private
	 */
	createUpdateListPanel: function()
	{
		return {
			layout: 'form',
			cls: 'k-updatelistpanel',
			items: [{
				xtype: 'textfield',
				fieldLabel:_('Update List'),
				ref: '../updateList',
				anchor: '100%',
				readOnly: true,
				name: 'updatelist'
			},{
				xtype: 'button',
				width: 150,
				ref: '../createUnassignedCopy',
				handler: this.onCreateUnassignedCopy,
				text: _('Create Unassigned Copy'),
				name: 'create_unassigned_copy',
				scope: this
			}]
		};
	},
	/**
	 * Enable/disable/hide/unhide all {@link Ext.Component Components} within the {@link Ext.Panel Panel}
	 * using the given {@link Zarafa.core.data.IPMRecord IPMRecord}.
	 * @param {Zarafa.core.data.IPMRecord} record The record to update the panel with
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 * @private
	 */
	updateUI: function(record, contentReset)
	{
		var isTaskAssignerCopy = (record.get('taskhistory') === Zarafa.core.mapi.TaskHistory.DECLINED || record.isTaskAssigned() || record.isTaskOrganized());

		this.taskInfoPanel.setVisible(isTaskAssignerCopy);

		this.datePanel.setVisible(!isTaskAssignerCopy);
		this.workPanel.setVisible(!isTaskAssignerCopy);
		this.companyPanel.setVisible(!isTaskAssignerCopy);
		this.updateList.setVisible(!isTaskAssignerCopy);

		if (contentReset === true || record.isModified('taskstate')) {
			switch (record.get('taskstate')) {
				case Zarafa.core.mapi.TaskState.ACCEPT:
					// If task have taskupdates property to false or user(as assignee) decline the task
					// then disable "Create unassigned copy" button. because in this case we don't have
					// associated task in task folder so we can't able to create unassigned copy of task.
					this.createUnassignedCopy.setDisabled(!record.get('taskupdates') || record.isTaskDeclined());
					break;
				default:
					this.createUnassignedCopy.setDisabled(true);
					break;
			}
			this.doLayout();
		}
	},

	/**
	 * Function is used to update values of form fields when ever
	 * an updated {@link Zarafa.core.data.IPMRecord record} is received
	 * @param {Zarafa.core.data.IPMRecord} record The record update the panel with.
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 * @private
	 */
	update: function(record, contentReset)
	{
		this.record = record;
		this.updateUI(record, contentReset);
		this.getForm().loadRecord(record);
	},

	/**
	 * Function is used to update values of form fields when ever
	 * an updated {@link Zarafa.core.data.IPMRecord record} is received
	 * @param {Zarafa.core.data.IPMRecord} record The record update the panel with.
	 */
	updateRecord: function(record)
	{
		this.getForm().updateRecord(record);
	},

	/**
	 * Event handler triggered when 'Create unassigned copy' button has been
	 * pressed. it will create new unassigned task copy from selected assigned task.
	 *
	 * @param {Ext.button} button The button which has been pressed
	 */
	onCreateUnassignedCopy: function (button)
	{
		var hierarchyStore = container.getHierarchyStore();
		var folder = hierarchyStore.getFolder(this.record.get('parent_entryid'));
		var record = this.record.convertToTask(folder);
		record.setUpdateModificationsTracking(true);
		record.beginEdit();
		record.set('subject', this.record.get('conversation_topic') +" ("+ _("copy") +")");
		record.set('taskstate', Zarafa.core.mapi.TaskState.OWNER_NEW);
		record.set('taskmode', Zarafa.core.mapi.TaskMode.NOTHING);
		record.set('taskhistory', Zarafa.core.mapi.TaskHistory.NONE);
		record.set('ownership', Zarafa.core.mapi.TaskOwnership.NEWTASK);
		record.set('task_acceptance_state', Zarafa.core.mapi.TaskAcceptanceState.NOT_DELEGATED);
		record.set('date_completed', null);
		record.set('updatecount', 1);
		record.set('icon_index', Zarafa.core.mapi.IconIndex['task']);
		record.set('taskfcreator', true);
		record.set('tasklastdelegate', '');
		record.set('task_goid', '');
		record.set('entryid', '');
		var store = container.getHierarchyStore().getById(record.get('store_entryid'));
		if(store) {
			record.set('owner', store.get('display_name'));
		}
		Zarafa.core.data.UIFactory.openCreateRecord(record);
		// We need to record endEdit after creating tab panel because
		// it will remove the updateModifications object
		// and because of that record is not trite as dirty so it will
		// not ask for the "Save change..." message box when user
		// trying to close that tab.
		record.endEdit();
		this.dialog.close();
	},

	/**
	 * Event handler which is triggered when the Completion Date has been
	 * changed. This will check the value and will either mark the task
	 * as {@link Zarafa.core.mapi.TaskStatus#COMPLETE complete} or
	 * {@link Zarafa.core.mapi.TaskStatus#NOT_STARTED not started}.
	 * @param {Ext.form.Field} field The {@link Ext.form.Field field} which was changed.
	 * @param {Mixed} newValue The new value
	 * @param {Mixed} oldValue The old value
	 * @private
	 */
	onCompleteDateChange: function(field, newValue, oldValue)
	{
		this.record.beginEdit();
		if (Ext.isDate(newValue)) {
			this.record.set('status', Zarafa.core.mapi.TaskStatus.COMPLETE);
			this.record.set('complete', true);
			this.record.set('percent_complete', 1);
			this.record.set('date_completed', newValue);
		} else {
			this.record.set('status', Zarafa.core.mapi.TaskStatus.NOT_STARTED);
			this.record.set('complete', false);
			this.record.set('percent_complete', 0);
			this.record.set('date_completed', null);
		}
		this.record.endEdit();
	},

	/**
	 * Event handler which is triggered when one of the Input fields
	 * has been changed by the user. It will validate the new value,
	 * and if correct, will apply it to the {@link Zarafa.core.data.IPMRecord record}.
	 * @param {Ext.form.Field} field The {@link Ext.form.Field field} which was changed.
	 * @param {Mixed} newValue The new value
	 * @param {Mixed} oldValue The old value
	 * @private
	 */
	onPropertyChange: function(field, newValue, oldValue)
	{
		if (!Ext.isEmpty(field.name) && field.validateValue(field.processValue(newValue))) {
			var record = this.record;
			record.set(field.name, newValue);
		}
	}
});

Ext.reg('zarafa.taskdetailtab', Zarafa.task.dialogs.TaskDetailTab);
Ext.namespace('Zarafa.task.dialogs');

/**
 * @class Zarafa.task.dialogs.TaskGeneralTab
 * @extends Ext.form.FormPanel
 * @xtype zarafa.taskgeneraltab
 *
 * Main tab in the {@link Zarafa.task.dialogs.TaskPanel}
 * that is used to create Tasks and also Assign Task
 */
Zarafa.task.dialogs.TaskGeneralTab = Ext.extend(Ext.form.FormPanel, {
	/**
	 * @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, {
			xtype: 'zarafa.taskgeneraltab',
			cls: 'k-taskgeneraltab',
			title: _('Task'),
			layout: {
				type: 'vbox',
				align: 'stretch'
			},
			border: false,
			items: [
				this.createExtraInfoPanel(),
				this.createTaskInfoPanel(),
				this.createAttachmentInfoPanel(),
				this.createRecipientPanel(),
				this.createSubjectPanel(),
				this.createDateTimePanel(),
				this.createTaskRequestSettingPanel(),
				this.createAttachmentPanel(),
				this.createBodyPanel()
			]
		});

		Zarafa.task.dialogs.TaskGeneralTab.superclass.constructor.call(this, config);
	},

	/**
	 * Create the {@link Ext.Container container} containing the information about request/response
	 * from assigner/assignee and some extra information regarding assigned task item.
	 *
	 * @return {Object} Configuration object for the panel containing the fields
	 * @private
	 */
	createExtraInfoPanel: function()
	{
		return {
			xtype: 'container',
			cls: 'k-extrainfopanel',
			hidden: true,
			ref: 'taskExtraInfo',
			autoHeight: true,
			items: [{
				xtype:'zarafa.extrainfolinks'
			}]
		};
	},

	/**
	 * Create the {@link Ext.DataView DataView} containing the information about assigned task request
	 * from assigner/assignee.
	 *
	 * @return {Object} Configuration object for the panel containing the fields
	 * @private
	 */
	createTaskInfoPanel: function ()
	{
		return {
			xtype: 'zarafa.taskinfo',
			cls: 'k-taskinfopanel',
			ref: 'taskInfoPanel',
			autoHeight: true,
			hidden: true
		};
	},

	/**
	 * Create the {@link Ext.Panel panel} containing the information about attachments
	 * which contains by the {@link Zarafa.task.TaskRecord record}.
	 *
	 * @return {Object} Configuration object for the panel containing the fields
	 * @private
	 */
	createAttachmentInfoPanel: function ()
	{
		return {
			xtype: 'panel',
			autoScroll: true,
			cls: 'k-task-attachment-info-panel',
			anchor: '100%',
			border: false,
			hidden: true,
			autoHeight: true,
			ref: 'taskAttachInfo',
			items: [{
				xtype: 'zarafa.attachmentlinks'
			}]
		};
	},

	/**
	 * Create the {@link Zarafa.common.ui.RecipientField RecipientField}
	 * where the recipients for the Meeting requests can be selected
	 * @return {Object} Configuration object for the panel containing the composite field
	 * @private
	 */
	createRecipientPanel: function()
	{
		return {
			xtype: 'zarafa.resizablecompositefield',
			cls: 'k-field-to',
			ref: 'recipientPanel',
			anchor: '100%',
			autoHeight: false,
			items: [{
				xtype: 'button',
				autoHeight: true,
				text: _('To') + ':',
				handler: function() {
					Zarafa.task.Actions.openRecipientSelectionContent(this.record, {
						defaultRecipientType: Zarafa.core.mapi.RecipientType.MAPI_TO
					});
				},
				scope: this
			},{
				xtype: 'zarafa.recipientfield',
				fieldLabel: _('To'),
				plugins: [ 'zarafa.recordcomponentupdaterplugin' ],
				flex: 1
			}]

		};
	},

	/**
	 * Create the {@link Ext.Panel panel} containing the form elements
	 * to set the subject
	 * @return {Object} Configuration object for the panel containing the fields
	 * @private
	 */
	createSubjectPanel: function()
	{
		return {
			xtype: 'panel',
			cls: 'k-subject-panel',
			layout: 'form',
			ref: 'subjectPanel',
			labelWidth: 79,
			labelAlign: 'left',
			autoHeight: true,
			border: false,
			items: [{
				xtype: 'textfield',
				fieldLabel: _('Subject'),
				anchor: '100%',
				name: 'subject',
				listeners:{
					'change': this.onPropertyChange,
					scope: this
				}
			}]
		};
	},

	/**
	 * Create the {@link Ext.Panel panel} containing the following elements
	 * in a table layout: the date panel, recurrence panel, busy status panel,
	 * reminder panel, and the label panel.
	 * @return {Object} Configuration object for the panel containing the fields
	 * @private
	 */
	createDateTimePanel: function()
	{
		return {
			xtype: 'panel',
			cls: 'k-datetime-panel',
			ref: 'datetimePanel',
			border: false,
			autoHeight: true,
			layout: {
				type: 'table',
				columns: 2
			},
			items: [
				this.createDatePanel(),
				this.createStatusPanel(),

				this.createReminderPanel(),
				this.createPriorityCompletePanel()
			]
		};
	},

	/**
	 * Create the {@link Ext.Panel Panel} containing the
	 * {@link Zarafa.common.ui.DateTimePeriodField DateTimePeriodField}.
	 * @return {Object} Configuration object for the panel with time selection fields
	 * @private
	 */
	createDatePanel: function()
	{
		return {
			xtype: 'panel',
			cls: 'k-date-panel',
			ref: '../datePanel',
			autoHeight: true,
			autoWidth: true,
			border: false,
			items: [{
				xtype: 'zarafa.dateperiodfield',
				ref: '../../dateField',
				allowBlank: true,
				defaultPeriod: container.getSettingsModel().get('zarafa/v1/contexts/task/default_task_period'),
				width: 480,
				layout: 'hbox',
				listeners: {
					change: this.onDateRangeFieldChange,
					scope: this
				},
				startFieldConfig: {
					fieldLabel: _('Start date'),
					labelWidth: 79,
					labelAlign: 'left',
					cls: 'from-field',
					width: 220
				},
				endFieldConfig: {
					fieldLabel: _('Due date'),
					labelWidth: 110,
					labelAlign: 'left',
					cls: 'to-field',
					width: 250
				}
			}]
		};
	},

	/**
	 * Create the {@link Ext.Panel Panel} containing the status of the task.
	 * @return {Object} Configuration object for the panel
	 * @private
	 */
	createStatusPanel: function()
	{
		var statusStore = {
			xtype: 'jsonstore',
			fields: ['name', 'value'],
			data: Zarafa.task.data.TaskStatus.status
		};

		return {
			xtype: 'panel',
			cls: 'k-status-panel',
			layout: 'form',
			autoHeight: true,
			border: false,
			labelAlign: 'left',
			items: [{
				xtype: 'combo',
				width: 285,
				fieldLabel:_('Status'),
				editable: false,
				mode: 'local',
				triggerAction: 'all',
				autoSelect: true,
				store: statusStore,
				displayField: 'name',
				valueField: 'value',
				value: statusStore.data[0].value,
				lazyInit: false,
				name: 'status',
				listeners: {
					scope: this,
					'select': this.onStatusSelect
				}
			}]
		};
	},

	/**
	 * Create the {@link Ext.Panel Panel} containing the priority and the
	 * complete percentage of the task.
	 * @return {Object} Configuration object for the panel
	 * @private
	 */
	createPriorityCompletePanel: function()
	{
		var importanceStore = {
			xtype: 'jsonstore',
			fields: ['name', 'value'],
			data: Zarafa.common.data.ImportanceFlags.flags
		};

		return {
			xtype: 'panel',
			cls: 'k-priority-complete-panel',
			layout: 'hbox',
			anchor: '100%',
			border: false,
			items: [{
				xtype: 'combo',
				plugins: [ 'zarafa.fieldlabeler' ],
				fieldLabel:_('Priority'),
				labelAlign: 'left',
				width: 100,
				editable: false,
				mode: 'local',
				triggerAction: 'all',
				autoSelect: true,
				store: importanceStore,
				displayField: 'name',
				valueField: 'value',
				value: importanceStore.data[1].value,
				name: 'importance',
				listeners:{
					select: this.onImportanceSelect,
					scope: this
				}
			},{
				xtype: 'spacer',
				width: 10
			},{
				xtype: 'zarafa.spinnerfield',
				plugins: [ 'zarafa.fieldlabeler', 'zarafa.percentspinner' ],
				fieldLabel: _('% Complete'),
				labelWidth: 100,
				name: 'percent_complete',
				width: 70,
				minValue: 0,
				defaultValue: 0,
				incrementValue: 0.25,
				maxValue: 1,
				listeners:{
					spin: this.onCompleteSpin,
					change: this.onCompleteSpin,
					scope: this
				}
			}]
		};
	},

	/**
	 * Create the {@link Ext.Panel Panel} containing the form fields
	 * for setting the reminder information.
	 * @return {Object} Configuration object for the panel with reminder fields
	 * @private
	 */
	createReminderPanel: function()
	{
		return {
			xtype: 'panel',
			cls: 'k-reminder-panel',
			ref: '../reminderPanel',
			autoHeight: true,
			border: false,
			items: [{
				xtype: 'zarafa.compositefield',
				autoHeight: true,
				items: [{
					xtype: 'label',
					text: _('Reminder') + ':',
					width: 84,
					cls: 'k-reminder-label'
				},{
					xtype: 'checkbox',
					ref: '../../../reminderCheckbox',
					name: 'reminder',
					width: 20,
					handler: this.onToggleReminder,
					scope: this
				},{
					xtype:'zarafa.datetimefield',
					ref: '../../../reminderTimeField',
					name: 'reminder_time',
					disabled: true,
					width: 217,
					timeIncrement: container.getSettingsModel().get('zarafa/v1/contexts/task/reminder_time_stepping'),
					listeners:{
						'change': this.onPropertyChange,
						scope: this
					},
					dateFieldConfig: {
						flex: 0
					}
				}]
			}]
		};
	},

	/**
	 * Create the {@link Ext.Panel Panel} containing the form fields
	 * for setting progress tracking and owner of the task request.
	 * @return {Object} Configuration object for the panel
	 * @private
	 */
	createTaskRequestSettingPanel: function()
	{
		return{
			xtype: 'panel',
			layout: 'hbox',
			border: false,
			ref: 'taskRequestSettingPanel',
			cls: 'k-taskrequestsettings',
			autoHeight: true,
			items: [{
				xtype: 'checkbox',
				boxLabel: _('Track progress'),
				tooltip: _('Keep updated copy of task and receive automated status reports'),
				plugins: 'zarafa.formfieldtooltipplugin',
				name: 'taskupdates',
				width: 200,
				listeners:{
					'change': this.onPropertyChange,
					scope: this
				}
			}, {
				hideLabel: true,
				xtype: 'textfield',
				cls: 'k-ownerfield',
				ref: '../ownerField',
				plugins: [ 'zarafa.fieldlabeler' ],
				fieldLabel: _('Owner'),
				labelWidth: 78,
				autoWidth: true,
				readOnly: true,
				flex: 1,
				name: 'owner',
				listeners:{
					'change': this.onPropertyChange,
					scope: this
				}
			}]
		};
	},

	/**
	 * Create the {@link Ext.Panel Panel} which contains all attachment selection fields
	 * @return {Object} configuration object for the panel containing the attachment fields
	 * @private
	 */
	createAttachmentPanel: function()
	{
		return {
			xtype: 'zarafa.resizablecompositefield',
			hideLabel: true,
			anchor: '100%',
			cls: 'k-field-attachments',
			autoHeight: true,
			ref: 'attachmentPanel',
			items: [{
				xtype: 'zarafa.attachmentbutton',
				plugins: [ 'zarafa.recordcomponentupdaterplugin' ],
				width: 100,
				text: _('Attachments') + ':',
				autoHeight: true
			},{
				xtype: 'zarafa.attachmentfield',
				plugins: [ 'zarafa.recordcomponentupdaterplugin' ],
				flex: 1,
				hideLabel: true
			}]
		};
	},

	/**
	 * Create the {@link Ext.Panel Panel} containing the
	 * {@link Zarafa.common.ui.htmleditor.HtmlEditor HtmlEditor} form field.
	 * @return {Object} Configuration object containing the HtmlEditor
	 * @private
	 */
	createBodyPanel: function()
	{
		return {
			xtype: 'panel',
			cls: 'k-body-panel',
			layout: 'fit',
			border: false,
			flex: 1,
			items: [{
				xtype: 'zarafa.editorfield',
				ref: '../editorField',
				hideLabel: true,
				flex: 1,
				useHtml: true,
				readOnly: false,
				listeners: {
					change: this.onBodyChange,
					scope: this
				}
			}]
		};
	},

	/**
	 * Enable/disable/hide/unhide all {@link Ext.Component Components} within the {@link Ext.Panel Panel}
	 * using the given {@link Zarafa.core.data.IPMRecord IPMRecord}.
	 * @param {Zarafa.core.data.IPMRecord} record The record to update the panel with
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 * @private
	 */
	updateUI: function(record, contentReset)
	{
		var taskHistory = record.get('taskhistory');
		var taskState = record.get('taskstate');
		var taskMode = record.get('taskmode');
		var layout = false;
		// Don't show extra info link if task is normal task.
		this.taskExtraInfo.setVisible(taskHistory !== Zarafa.core.mapi.TaskHistory.NONE);

		if ((taskHistory === Zarafa.core.mapi.TaskHistory.ASSIGNED ||
			taskHistory === Zarafa.core.mapi.TaskHistory.DECLINED ||
			taskMode === Zarafa.core.mapi.TaskMode.DECLINE ||
			record.isTaskOrganized()) && contentReset && record.isOpened()) {

			this.taskInfoPanel.setVisible(true);
			this.taskAttachInfo.setVisible(record.get('hasattach'));

			this.editorField.setReadOnly(taskMode !== Zarafa.core.mapi.TaskMode.DECLINE);

			this.recipientPanel.setVisible(false);
			this.taskRequestSettingPanel.setVisible(false);
			this.attachmentPanel.setVisible(false);
			this.datetimePanel.setVisible(false);
			this.subjectPanel.setVisible(false);
			layout = true;
		} else {
			if (contentReset && taskState === Zarafa.core.mapi.TaskState.OWNER_NEW && !record.isTaskRequest()) {
				this.taskExtraInfo.setVisible(false);
				this.taskInfoPanel.setVisible(false);
				this.taskAttachInfo.setVisible(false);
				this.taskRequestSettingPanel.setVisible(false);
				this.recipientPanel.setVisible(false);
				this.editorField.setReadOnly(false);
				this.attachmentPanel.setVisible(true);
				this.datetimePanel.setVisible(true);
				this.subjectPanel.setVisible(true);
				layout = true;
			}
			var startDate = record.get('startdate');
			var startDateUpdate = contentReset || record.isModifiedSinceLastUpdate('startdate') || record.isModifiedSinceLastUpdate('taskhistor');
			if (Ext.isDate(startDate)) {
				startDate = startDate.toUTC(); // The startdate is an UTC representation
			}

			var dueDate = record.get('duedate');
			var dueDateUpdate = contentReset || record.isModifiedSinceLastUpdate('duedate') || record.isModifiedSinceLastUpdate('taskhistory');
			if (Ext.isDate(dueDate)) {
				dueDate = dueDate.toUTC(); // The duedate is an UTC representation
			}

			if (startDateUpdate || dueDateUpdate) {
				this.dateField.getValue().set(startDate, dueDate);
			}

			if (contentReset === true || record.isModifiedSinceLastUpdate('taskmode')) {
				switch (record.get('taskmode')) {
					case Zarafa.core.mapi.TaskMode.REQUEST:
						this.recipientPanel.setVisible(true);
						this.taskRequestSettingPanel.setVisible(true);
						break;
					case Zarafa.core.mapi.TaskMode.NOTHING:
					/* falls through */
					default:
						this.recipientPanel.setVisible(false);
						this.taskRequestSettingPanel.setVisible(false);
						this.reminderPanel.setVisible(true);
				}
				layout = true;
			}


			if (contentReset === true) {
				// Check if the store in which the record is located is the public store
				// if it is, then the owner field should be made editable.
				var store = container.getHierarchyStore().getById(record.get('store_entryid'));
				if (store) {
					this.ownerField.setReadOnly(!store.isPublicStore());
				}
			}
		}

		if ( this.editorField.readOnly ){
			this.editorField.getEditor().getEl().set({placeholder: ''});
		} else {
			this.editorField.getEditor().getEl().set({placeholder: _('Type your message here…')});
		}

		if(layout) {
			this.doLayout();
		}
	},

	/**
	 * Function is used to update values of form fields when ever
	 * an updated {@link Zarafa.core.data.IPMRecord record} is received.
	 * @param {Zarafa.core.data.IPMRecord} 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;
		this.updateUI(record, contentReset);

		if (contentReset && record.isOpened()) {
			this.editorField.setValue(record.getBody(this.editorField.isHtmlEditor()));
		}

		if(contentReset || record.isModifiedSinceLastUpdate('reminder_time')) {
			// Update reminder
			record.set('flag_due_by', record.get('reminder_time'));
		}

		if (contentReset || record.isModifiedSinceLastUpdate('reminder')) {
			this.reminderTimeField.setDisabled(!record.get('reminder'));
		}

		this.getForm().loadRecord(record);
	},

	/**
	 * Function is used to update values of form fields when ever
	 * an updated {@link Zarafa.core.data.IPMRecord record} is received.
	 * @param {Zarafa.core.data.IPMRecord} record The record update the panel with.
	 */
	updateRecord: function(record)
	{
		var taskHistory = record.get('taskhistory');
		if (taskHistory === Zarafa.core.mapi.TaskHistory.ASSIGNED ||
			taskHistory === Zarafa.core.mapi.TaskHistory.DECLINED ||
			record.isTaskOrganized()) {
			return;
		}

		record.beginEdit();

		this.getForm().updateRecord(record);

		// Update the start & due date
		this.updateStartDueDate(record, this.dateField.getValue());

		// Update the body
		this.onBodyChange(this.editorField.getEditor(), this.editorField.getValue());

		// If user is task owner and task is accepted or updated then
		// set the message action for sending task update notification to assigner.
		if (record.dirty && record.isTaskOwner() && (record.isTaskAccepted() || record.isTaskUpdated())) {
			record.addMessageAction('response_type', Zarafa.core.mapi.TaskMode.UPDATE);
		}

		if (!record.phantom && record.isModified('subject')) {
			record.set('conversation_topic', record.get('subject'));
		}
		record.endEdit();
	},

	/**
	 * Event handler which is triggered when one of the Input fields
	 * has been changed by the user. It will validate the new value,
	 * and if correct, will apply it to the {@link Zarafa.core.data.IPMRecord record}.
	 * @param {Ext.form.Field} field The {@link Ext.form.Field field} which was changed.
	 * @param {Mixed} newValue The new value
	 * @param {Mixed} oldValue The old value
	 * @private
	 */
	onPropertyChange: function(field, newValue, oldValue)
	{
		if (!Ext.isEmpty(field.name) && field.validateValue(field.processValue(newValue))) {
			this.record.set(field.name, newValue);
		}
	},

	/**
	 * Event handler which is triggered when priority field
	 * has been changed by the user. It will validate the new value,
	 * and if correct, will apply it to the {@link Zarafa.core.data.IPMRecord record}.
	 * @param {Ext.form.Field} field The {@link Ext.form.Field field} which was changed.
	 * @param {Ext.data.Record} record The data record returned from the underlying store
	 * @param {Number} index The index of the selected item in the dropdown list
	 * @private
	 */
	onImportanceSelect: function(field, record, selectedIndex)
	{
		var newValue = record.get(field.valueField);
		if (!Ext.isEmpty(field.name) && field.validateValue(field.processValue(newValue))) {
			this.record.set(field.name, newValue);
		}
	},

	/**
	 * Event handler which is triggered when one of the Input fields
	 * has been changed by the user. It will validate the new value,
	 * and if correct, will apply it to the {@link Zarafa.core.data.IPMRecord record}.
	 * @param {Ext.form.Field} field The {@link Ext.form.Field field} which was changed.
	 * @param {Mixed} newValue The new value
	 * @param {Mixed} oldValue The old value
	 * @private
	 */
	onBodyChange: function(field, newValue, oldValue)
	{
		var record = this.record;

		record.beginEdit();
		record.setBody(this.editorField.getValue(), this.editorField.isHtmlEditor());
		record.endEdit();
	},

	/**
	 * A function called when the checked value changes for the
	 * reminder checkbox.
	 * @param {Ext.form.Checkbox} checkbox The Checkbox being toggled.
	 * @param {Boolean} checked The new checked state of the checkbox.
	 * @private
	 */
	onToggleReminder: function(checkbox, checked)
	{
		this.reminderTimeField.setDisabled(!checked);
		this.record.beginEdit();
		this.record.set('reminder', checked);
		if (checked) {
			if (!Ext.isDate(this.record.get('reminder_time'))) {
				var dueDate = this.record.get('duedate');
				var time = container.getSettingsModel().get('zarafa/v1/contexts/task/default_reminder_time');

				if (!Ext.isDate(dueDate)) {
					dueDate = new Date().add(Date.DAY, 1);
				}

				this.record.set('reminder_time', dueDate.clearTime(true).add(Date.MINUTE, time));
			}
		} else {
			this.record.set('reminder_time', null);
		}
		this.record.endEdit();
	},

	/**
	 * Event handler which is triggered when status fields
	 * has been changed by the user. It will validate the new value,
	 * and if correct, will apply it to the {@link Zarafa.core.data.IPMRecord record}.
	 * @param {Ext.form.Field} field The {@link Ext.form.Field field} which was changed.
	 * @param {Ext.data.Record} record The data record returned from the underlying store
	 * @param {Number} index The index of the selected item in the dropdown list
	 * @private
	 */
	onStatusSelect: function(field, record, number)
	{
		var newValue = record.get(field.valueField);

		this.record.beginEdit();
		this.record.set(field.name, newValue);

		var complete = newValue === Zarafa.core.mapi.TaskStatus.COMPLETE;
		if(complete){
			this.record.set('complete', true);
			this.record.set('percent_complete', 1);
			this.record.set('date_completed', new Date());
		} else if (newValue === Zarafa.core.mapi.TaskStatus.NOT_STARTED) {
			this.record.set('complete', false);
			this.record.set('percent_complete', 0);
			this.record.set('date_completed', null);
		} else {
			this.record.set('complete', false);

			// When the status is in progress, we reset the percent_complete
			// counts to 75% when it was previously marked as complete,
			// and we set it to 25% when it was previously marked as not started.
			var curComplete = this.record.get('percent_complete');
			if (curComplete === 0) {
				this.record.set('percent_complete', 0.25);
			} else if (curComplete === 1) {
				this.record.set('percent_complete', 0.75);
			}

			this.record.set('date_completed', null);
		}

		// Set flags related properties.
		this.setFlagsProperties(complete);
		this.record.endEdit();
	},

	/**
	 * Event handler which is triggered when status fields
	 * has been changed by the user. It will validate the new value,
	 * and if correct, will apply it to the {@link Zarafa.core.data.IPMRecord record}.
	 * @param {Ext.form.Field} field The {@link Ext.form.Field field} which was spinned.
	 * @private
	 */
	onCompleteSpin: function(field)
	{
		var newValue = field.getValue();

		this.record.beginEdit();
		if(newValue >= 0 && newValue < 0.1){
			this.record.set('status', Zarafa.core.mapi.TaskStatus.NOT_STARTED);
			this.record.set('complete', false);
			this.record.set('percent_complete', newValue);
			this.record.set('date_completed', null);
		} else if(newValue >= 0.1 && newValue < 1){
			this.record.set('status', Zarafa.core.mapi.TaskStatus.IN_PROGRESS);
			this.record.set('complete', false);
			this.record.set('percent_complete', newValue);
			this.record.set('date_completed', null);
		} else if(newValue === 1 || newValue > 1){
			this.record.set('status', Zarafa.core.mapi.TaskStatus.COMPLETE);
			this.record.set('complete', true);
			this.record.set('percent_complete', 1); // 100%
			this.record.set('date_completed', new Date());
		}
		this.setFlagsProperties(newValue === 1 || newValue > 1);
		this.record.endEdit();
	},

	/**
	 * Set flags related properties to task record when
	 * percentage complete or status was changed.
	 *
	 * @param {Boolean} complete true if selected record marked as a completed.
	 */
	setFlagsProperties: function (complete)
	{
		this.record.set('flag_icon', complete ? Zarafa.core.mapi.FlagIcon.clear : Zarafa.core.mapi.FlagIcon.red);
		this.record.set('flag_complete_time', complete ? new Date() : null);
		this.record.set('flag_request', complete ? '' : 'Follow up');
		this.record.set('flag_status', complete ? Zarafa.core.mapi.FlagStatus.completed : Zarafa.core.mapi.FlagStatus.flagged);
	},

	/**
	 * Event handler which is fired when the {@link Zarafa.common.ui.DateRangeField} has been changed.
	 * This will update the start and due date inside the {@link #record} accordingly.
	 * @param {Ext.form.Field} field The field which has changed
	 * @param {Mixed} newValue The new value for the field
	 * @param {Mixed} oldValue The original value for the field
	 * @private
	 */
	onDateRangeFieldChange: function(field, newRange, oldRange)
	{
		this.updateStartDueDate(this.record, newRange);
	},

	/**
	 * Update the 'startdate' and 'duedate' in the given record from
	 * the given daterange.
	 * @param {Zarafa.core.data.MAPIRecord} record the Record to update
	 * @param {Zarafa.core.DateRange} daterange the Daterange to apply
	 * @private
	 */
	updateStartDueDate: function(record, daterange)
	{
		var startDate = daterange.getStartDate();
		var dueDate = daterange.getDueDate();

		record.beginEdit();

		if(Ext.isDate(startDate)) {
			// The startDate is represented in UTC time,
			// so convert it to local to get the time for the property
			record.set('startdate', startDate.fromUTC());
			record.set('commonstart', startDate.clone());
		} else {
			record.set('startdate', null);
			record.set('commonstart', null);
		}

		if(Ext.isDate(dueDate)) {
			// The dueDate is represented in UTC time,
			// so convert it to local to get the time for the property
			record.set('duedate', dueDate.fromUTC());
			record.set('commonend', dueDate.clone());
		} else {
			record.set('duedate', null);
			record.set('commonend', null);
		}

		record.endEdit();
	}
});

Ext.reg('zarafa.taskgeneraltab', Zarafa.task.dialogs.TaskGeneralTab);
Ext.namespace('Zarafa.task.dialogs');

/**
 * @class Zarafa.task.dialogs.TaskOptionsPanel
 * @extends Ext.Panel
 * @xtype zarafa.taskoptionspanel
 */
Zarafa.task.dialogs.TaskOptionsPanel = Ext.extend(Ext.Panel, {
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

		config = Ext.applyIf(config, {
			xtype: 'zarafa.taskoptionspanel',
			layout: {
				type: 'vbox',
				align: 'stretch'
			},
			border: false,
			defaults: {
				bodyStyle: 'padding-top: 5px; padding-left: 6px; padding-right: 5px; background-color: inherit;',
				border: false
			},
			items: [{
				xtype: 'zarafa.recordpropertiespanel',
				flex: 1
			}]
		});

		Zarafa.task.dialogs.TaskOptionsPanel.superclass.constructor.call(this, config);
	}
});

Ext.reg('zarafa.taskoptionspanel', Zarafa.task.dialogs.TaskOptionsPanel);
Ext.namespace('Zarafa.task.dialogs');

/**
 * @class Zarafa.task.dialogs.TaskPanel
 * @extends Ext.Panel
 * @xtype zarafa.taskpanel
 *
 * This class is used as wrapper class for all tabs, individual tab will have its own class,
 * extra tabs can be added using insertion point in this dialog.
 */
Zarafa.task.dialogs.TaskPanel = Ext.extend(Ext.Panel, {
	// Insertion points for this class
	/**
	 * @insert context.task.taskeditcontent.tabs
	 * can be used to add extra tabs to taskeditcontentpanel by 3rd party plugins
	 * @param {Zarafa.task.dialogs.TaskPanel} panel This panel
	 */

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

		config = Ext.applyIf(config, {
			xtype: 'zarafa.taskpanel',
			bodyStyle: 'background-color: inherit;',
			border: false,
			layout: 'fit',
			items: this.createTabPanel()
		});

		Zarafa.task.dialogs.TaskPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Initialize {@link Ext.TabPanel tabpanel) that will contain all the fields and forms
	 * @return {Object} Configuration object for the form panel
	 * @private
	 */
	createTabPanel: function()
	{
		return [{
			xtype: 'tabpanel',
			activeTab: 0,
			layoutOnTabChange: true,
			border: false,
			items: [{
				xtype: 'zarafa.taskgeneraltab'
			},{
				xtype: 'zarafa.taskdetailtab'
			},
			container.populateInsertionPoint('context.task.taskcontentpanel.tabs', this)
			]
		}];
	}
});

Ext.reg('zarafa.taskpanel', Zarafa.task.dialogs.TaskPanel);
Ext.namespace('Zarafa.task.ui');

/**
 * @class Zarafa.task.ui.TaskInfo
 * @extends Ext.Container
 * @xtype zarafa.taskinfo
 *
 * If the {@link Zarafa.core.plugins.RecordComponentUpdaterPlugin} is installed
 * in the {@link #plugins} array of this component, this component will automatically
 * load the {@link Zarafa.core.data.MAPIRecord record} into the component.
 * Otherwise the user of this component needs to call {@link #setRecord}.
 */
Zarafa.task.ui.TaskInfo = Ext.extend(Ext.DataView, {

	/**
	 * @cfg {String} taskInfoCls is the CSS class which should be applied to {@link #taskInfoTpl task info} Template
	 */
	taskInfoCls: 'preview-header-task',

	/**
	 * @cfg {HTMLElement/Ext.Element} HTML structure for the whole task information
	 * and get references to the different elements in it.
	 * @property
	 */
	taskInfoElem: undefined,

	/**
	 * @cfg {Ext.Template/String} taskGeneralTabTpl The template which must be applied
	 * on {@link Zarafa.task.dialogs.TaskGeneralTab TaskGeneralTab} when task is assigned task.
	 * The arguments of this template will be the {@link Zarafa.core.data.IPMRecord#data record.data} field.
	 */
	taskGeneralTabTpl:
		'<div class="preview-from">' +
			'<div class="task-info-container">' +
				'<span class="label">'+_('Subject')+': </span>' +
				'<span>' +
					'<tpl if="!Ext.isEmpty(values.conversation_topic)">' +
						'{conversation_topic:htmlEncode}' +
					'</tpl>' +
					'<tpl if="Ext.isEmpty(values.conversation_topic)">' +
						'{subject:htmlEncode}' +
					'</tpl>' +
				'</span>'+
			'</div>'+
			'<div class="task-info-container">' +
				'<span class="label">'+ _('Start date') +': </span>' +
				'<tpl if="Ext.isDate(values.startdate)">' +
					'<span class="minwidth120">' +
						'{startdate:date(_("d-m-Y"))}' +
					'</span>'+
				'</tpl>' +
				'<tpl if=" !Ext.isDate(values.startdate)">' +
					'<span class="minwidth120">' +
						_('None') +
					'</span>'+
				'</tpl>' +
				'<span class="label">'+ _('Due date') +': </span>' +
				'<tpl if="Ext.isDate(values.duedate)">' +
					'<span class="minwidth120">' +
					'{duedate:date(_("d-m-Y"))}' +
					'</span>'+
				'</tpl>' +
				'<tpl if="!Ext.isDate(values.duedate)">' +
					'<span>' +
						_('None') +
					'</span>'+
				'</tpl>' +
			'</div>'+
			'<div class="task-info-container">' +
				'<span class="label">'+ _('Status') +': </span>' +
				'<span class="minwidth120">' +
					'{status:this.getStatus}' +
				'</span>'+
				'<span class="label">'+ _('Priority') +': </span>' +
				'<span class="minwidth120">' +
					'{importance:this.getPriority}' +
				'</span>'+
				'<span class="label">'+ _('% Complete') +': </span>' +
				'<span>' +
					'{percent_complete:this.getPercentComplete}' +
				'</span>'+
			'</div>'+
			'<div class="task-info-container">' +
				'<span class="label">'+ _('Owner') +': </span>' +
				'<span>' +
					'{owner:htmlEncode}' +
				'</span>'+
			'</div>'+
		'</div>',

	/**
	 * @cfg {Ext.Template/String} taskDetailsTabTpl The template which must be applied
	 * on {@link Zarafa.task.dialogs.TaskDetailTab TaskDetailTab} when task is assigned task.
	 * The arguments of this template will be the {@link Zarafa.core.data.IPMRecord#data record.data} field.
	 */
	taskDetailsTabTpl:
		'<div class="preview-from">' +
			'<div class="task-info-container">' +
				'<span class="label minwidth120">'+ _('Date completed') +': </span>' +
				'<span>' + '{date_completed:this.getFormattedDate}' + '</span>'+
			'</div>'+
			'<div class="task-info-container">' +
				'<span class="label minwidth120">'+ _('Total work') +': </span>' +
				'<span>' + '{totalwork:htmlEncode} '+_('hours')+ '</span>'+
				'<span class="label">'+ _('Mileage') +': </span>' +
				'<span>' + '{mileage:htmlEncode}' + '</span>'+
			'</div>'+
			'<div class="task-info-container">' +
				'<span class="label minwidth120">'+ _('Actual work') +': </span>' +
				'<span>' + '{actualwork:htmlEncode} '+_('hours') + '</span>'+
				'<span class="label">'+ _('Billing information') +': </span>' +
				'<span>' + '{billing_information:htmlEncode}' + '</span>'+
			'</div>'+
			'<div class="task-info-container">' +
				'<span class="label minwidth120">'+ _('Company') +': </span>' +
				'<span>' + '{companies:htmlEncode}' + '</span>'+
			'</div>'+
			'<div class="task-info-container">' +
				'<span class="label minwidth120">'+ _('Update list') +': </span>' +
				'<span>' + '{tasklastuser:htmlEncode}' + '</span>'+
			'</div>'+
		'</div>',

	/**
	 * @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,{
			xtype: 'zarafa.taskinfo',
			border: false,
			autoScroll:true,
			anchor: '100%'
		});

		Zarafa.task.ui.TaskInfo.superclass.constructor.call(this, config);

		if (Ext.isString(this.taskGeneralTabTpl) && Ext.isString(this.taskDetailsTabTpl)) {
			this.taskGeneralTabTpl = new Ext.XTemplate(this.taskGeneralTabTpl, {
				compiled: true,
				getStatus: function (value)
				{
					return Zarafa.core.mapi.TaskStatus.getDisplayName(value);
				},
				getPriority: function (value)
				{
					return Zarafa.core.mapi.Importance.getDisplayName(value);
				},
				getPercentComplete: function (value)
				{
					return value * 100 + '%';
				}
			});
			this.taskDetailsTabTpl = new Ext.XTemplate(this.taskDetailsTabTpl, {
				compiled: true,
				getFormattedDate: function (value) {
					if (Ext.isDate(value)) {
						return value.format(_("D d-m-Y"));
					} else {
						return _('None');
					}
				}
			});
		}
	},

	/**
	 * Render the template and set the references to HTML Elements.
	 * @protected
	 */
	onRender: function()
	{
		Zarafa.task.ui.TaskInfo.superclass.onRender.apply(this, arguments);
		this.taskInfoElem = Ext.DomHelper.append(this.el.dom,{tag:'div', cls:this.taskInfoCls});
	},

	/**
	 * Update the {@link Zarafa.common.ui.messagepanel.SentInfo header} with the data
	 * from the {@link Zarafa.core.data.IPMRecord record}. Updates the panel
	 * by loading data from the record data into the template.
	 * Attach mouse handlers on the anchors
	 * @param {Zarafa.core.data.IPMRecord} record to update the header panel with
	 */
	update: function(record)
	{
		if (this.taskInfoElem){
			var taskInfoElem = Ext.get(this.taskInfoElem);

			if (!Ext.isDefined(record)) {
				this.taskInfoElem.innerHTML = '';
			} else {
				var ownerComponent = this.ownerCt;
				if (ownerComponent.isXType("zarafa.taskgeneraltab") || ownerComponent.isXType("zarafa.messageheader")) {
					this.taskGeneralTabTpl.overwrite(taskInfoElem, record.data);
				} else if(ownerComponent.isXType("zarafa.taskdetailtab")) {
					this.taskDetailsTabTpl.overwrite(taskInfoElem, record.data);
				}
			}
		}
		this.record = record;
	}
});
Ext.reg('zarafa.taskinfo', Zarafa.task.ui.TaskInfo);
Ext.namespace('Zarafa.task.ui');

/**
 * @class Zarafa.task.ui.TaskPreviewPanel
 * @extends Ext.Panel
 * @xtype zarafa.taskpreviewpanel
 *
 * Panel that previews the contents of task.
 */
Zarafa.task.ui.TaskPreviewPanel = Ext.extend(Ext.Panel, {
	/**
	 * @constructor
	 * @param {Object} config configuration object.
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			xtype: 'zarafa.taskpreviewpanel',
			border: false,
			layout: 'zarafa.collapsible',
			items: [{
				xtype: 'zarafa.messageheader'
			},{
				xtype: 'zarafa.messagebody'
			}]
		});

		Zarafa.task.ui.TaskPreviewPanel.superclass.constructor.call(this, config);
	}
});

Ext.reg('zarafa.taskpreviewpanel', Zarafa.task.ui.TaskPreviewPanel);
Ext.namespace('Zarafa.task.ui');

/**
 * @class Zarafa.task.ui.TaskRequestButton
 * @extends Ext.Button
 * @xtype zarafa.taskrequestbutton
 *
 * Singleton Base class for all task request buttons which can be added to {@link Ext.Toolbar Toolbar}.
 * It contains common functions of all task request buttons and handlers for all task request buttons.
 */
Zarafa.task.ui.TaskRequestButton = Ext.extend(Ext.Button, {
	/**
	 * The record for which this ButtonGroup is shown.
	 * @property
	 * @type Ext.data.Record
	 */
	record: undefined,

	/**
	 * This property will keep track whether this component was visible or hidden last.
	 * it will be set in {@link update} function.
	 * @property
	 * @type Boolean
	 */
	visible: false,

	/**
	 * @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, {
			hidden: true,
			forceLayout: true,
			cls: 'k-tr-buttons',
			ref: 'taskRequestButtons',
			handler: this.openSendConfirmationContent,
			scope: this,

			/**
			 * Note: This is a fix for a bug: when Ext.js converts buttons to menuitems,
			 * it takes button component's initial configs into consideration.
			 * Initially this button component is hidden so we need to show/hide menuitem
			 * on the basis of visible config which is being set.
			 * in {@link #update} function.
			 */
			beforeShow: function(item) {
				item.setVisible(this.visible);
			}
		});

		Zarafa.task.ui.TaskRequestButton.superclass.constructor.call(this, config);

		/**
		 * We need to remove 'zarafa.recordcomponentupdaterplugin' plugin from initialConfig.
		 * Because Ext uses initialConfig to make a menu item from button component.
		 * This 'zarafa.recordcomponentupdaterplugin' plugin will call update function
		 * which will not be found in newly created menuitem.
		 * So it will call Ext's update function instead
		 * and because Ext's update function will get called with wrong parameters,
		 * text of menu item will be changed.
		 */
		this.initialConfig.plugins = [];
	},

	/**
	 * Update the components with the given record.
	 *
	 * @param {Zarafa.core.data.MAPIRecord} record The record to update in this component
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 * @private
	 */
	update: function(record, contentReset)
	{
		if (!(record instanceof Zarafa.task.TaskRecord)) {
			this.setVisible(false);
			return;
		}

		this.record = record;
		this.visible = false;
		var isAcceptbtn = this.name === Zarafa.task.data.TaskRequestButtonNames.ACCEPT;
		var isDeclineBtn = this.name === Zarafa.task.data.TaskRequestButtonNames.DECLINE;

		if (contentReset) {
			var isMessageTaskResponse = Zarafa.core.MessageClass.isClass(record.get('message_class'), ['IPM.TaskRequest.Accept', 'IPM.TaskRequest.Update', 'IPM.TaskRequest.Decline', 'IPM.TaskRequest.Complete']);
			var isTaskReceived = false;
			var isTaskAccepted = false;

			// Check if we are having a sub message, if then don't display any of the buttons
			var isSubMessage = record.isSubMessage();

			// If record is instance of TaskRequestRecord, message_class property is IPM.TaskRequest and record is received task
			// then show the Accept and Decline button in preview panel toolbar
			if (record instanceof Zarafa.task.TaskRequestRecord && record.isMessageClass('IPM.TaskRequest')) {
				var iskAssignedTR = record.isReceivedTaskRequestFromAssigner();
				this.visible = iskAssignedTR && !isSubMessage;
			} else if(!isMessageTaskResponse) {
				// Condition gets true if record is other then 'IPM.TaskRequest/.Accept/.Update/.Decline'.
				// isTaskOwner gets true if user is owner of this task else false
				var isTaskOwner = record.isTaskOwner();
				// isTaskAssigned gets true if task was assigned to login user else false.
				var isTaskAssigned = record.isTaskAssigned();
				// isTaskReceived gets true if task is received task else false.
				isTaskReceived = record.isTaskReceived();
				// isTaskReceived gets true if task is Accepted by user else false.
				isTaskAccepted = record.isTaskAccepted();

				// showAcceptButton gets true if task is received task, task is assigned to user, user is owner of the task
				// and task yet not accepted else false.
				if (isAcceptbtn) {
					this.visible = (isTaskReceived && isTaskAssigned && isTaskOwner && !isTaskAccepted && !isSubMessage);
				} else if (isDeclineBtn) {
					// showDeclineButton gets true if task is received task, user is owner of the task and
					// task is assigned to user and task is accepted else false.
					this.visible = (!isSubMessage && isTaskReceived && isTaskOwner && (isTaskAssigned || isTaskAccepted || record.isTaskUpdated()));
				}
			}
			this.setVisible(this.visible);
		} else if (record.isModifiedSinceLastUpdate('taskmode')) {
			this.setVisible(this.visible);
		}

		if (isAcceptbtn && this.isVisible()) {
			this.getEl().addClass('zarafa-action');
		}
	},

	/**
	 * Opens a {@link Zarafa.task.dialogs.SendTaskRequestConfirmationContentPanel SendTaskRequestConfirmationContentPanel}
	 *
	 * @param {Ext.Button} button button object.
	 * @param {EventObject} eventObject The click event object.
	 * @private
	 */
	openSendConfirmationContent: function(button, eventObject)
	{
		if (this.record.isTaskOwner()) {
			Zarafa.task.Actions.openSendConfirmationContent(this.record, { responseType: button.responseStatus });
		}
	}
});

Ext.reg('zarafa.taskrequestbutton', Zarafa.task.ui.TaskRequestButton);
Ext.namespace('Zarafa.calendar.ui');

/**
 * @class Zarafa.task.ui.TaskRequestButtons
 * @extends Ext.ButtonGroup
 * @xtype zarafa.taskrequestbuttons
 *
 * Container for a group of buttons contains functions create {@link Ext.Button Buttons} that
 * can be added to {@link Ext.Toolbar Toolbar}. It also contains handler functions to handle
 * functionality of buttons.
 */
Zarafa.task.ui.TaskRequestButtons = Ext.extend(Ext.ButtonGroup, {
	/**
	 * The record for which this ButtonGroup is shown.
	 * @property
	 * @type Ext.data.Record
	 */
	record: undefined,

	/**
	 * @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, {
			hidden: true,
			forceLayout: true,
			cls: 'k-tr-buttons',
			ref : 'taskRequestButtons',
			items: [
				this.createAcceptButton(),
				this.createDeclineButton()
			]
		});

		Zarafa.task.ui.TaskRequestButtons.superclass.constructor.call(this, config);
	},

	/**
	 * Update the components with the given record.
	 *
	 * @param {Zarafa.core.data.MAPIRecord} record The record to update in this component
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 * @private
	 */
	update : function(record, contentReset)
	{
		if (!(record instanceof Zarafa.task.TaskRecord)) {
			this.setVisible(false);
			return;
		}

		this.record = record;

		// Check if we are having a sub message, if then don't display any of the buttons
		var isSubMessage = record.isSubMessage();

		var showAcceptButton = false;
		var showDeclineButton = false;
		if (contentReset) {
			var isMessageTaskResponse = Zarafa.core.MessageClass.isClass(record.get('message_class'), ['IPM.TaskRequest.Accept', 'IPM.TaskRequest.Update', 'IPM.TaskRequest.Decline', 'IPM.TaskRequest.Complete']);
			var isTaskReceived = false;
			var isTaskAccepted = false;

			// If record is instance of TaskRequestRecord, message_class property is IPM.TaskRequest and record is received task
			// then show the Accept and Decline button in preview panel toolbar
			if (record instanceof Zarafa.task.TaskRequestRecord && record.isMessageClass('IPM.TaskRequest')) {
				var iskAssignedTR = record.isReceivedTaskRequestFromAssigner();
				showAcceptButton = iskAssignedTR && !isSubMessage;
				showDeclineButton = iskAssignedTR && !isSubMessage;
			} else if(!isMessageTaskResponse) {
				// Condition gets true if record is other then 'IPM.TaskRequest/.Accept/.Update/.Decline'.
				// isTaskOwner gets true if user is owner of this task else false
				var isTaskOwner = record.isTaskOwner();
				// isTaskAssigned gets true if task was assigned to login user else false.
				var isTaskAssigned = record.isTaskAssigned();
				// isTaskReceived gets true if task is received task else false.
				isTaskReceived = record.isTaskReceived();
				// isTaskReceived gets true if task is Accepted by user else false.
				isTaskAccepted = record.isTaskAccepted();
				// showAcceptButton gets true if task is received task, task is assigned to user, user is owner of the task
				// and task yet not accepted else false.
				showAcceptButton = (isTaskReceived && isTaskAssigned && isTaskOwner && !isTaskAccepted && !isSubMessage);
				// showDeclineButton gets true if task is received task, user is owner of the task and
				// task is assigned to user and task is accepted else false.
				showDeclineButton = (!isSubMessage && isTaskReceived && isTaskOwner && (isTaskAssigned || isTaskAccepted || record.isTaskUpdated()));
			}
			this.acceptButton.setVisible(showAcceptButton);
			this.declineButton.setVisible(showDeclineButton);
		}

		if (!contentReset && record.isModifiedSinceLastUpdate('taskmode')) {
			this.acceptButton.setVisible(showAcceptButton);
			this.declineButton.setVisible(showDeclineButton);
		}

		if ( this.acceptButton.isVisible() ) {
			this.acceptButton.getEl().addClass('zarafa-action');
		}
		// Determine if there are any visible buttons, if that is not the case,
		// lets hide the entire button group.
		var visible = false;
		this.items.each(function(btn) {
			if (btn.hidden !== true) {
				visible = true;
				return false;
			}
		});

		this.setVisible(visible);
	},

	/**
	 * Function used to create "Accept" button. which used to
	 * accept the task request.
	 *
	 * @return {Ext.Button} element config which should be
	 * added in the {@link Ext.Toolbar Toolbar}.
	 * @private
	 */
	createAcceptButton : function()
	{
		return {
			xtype: 'button',
			ref : 'acceptButton',
			text: _('Accept'),
			iconCls: 'icon_calendar_appt_accept',
			responseStatus : Zarafa.core.mapi.TaskMode.ACCEPT,
			handler: this.openSendConfirmationContent,
			scope: this
		};
	},

	/**
	 * Function used to create "Decline" button. which used to
	 * decline the task request.
	 *
	 * @return {Ext.Button} element config which should be
	 * added in the {@link Ext.Toolbar Toolbar}.
	 * @private
	 */
	createDeclineButton : function()
	{
		return {
			xtype : 'button',
			ref : 'declineButton',
			text : _('Decline'),
			iconCls : 'icon_calendar_appt_cancelled',
			responseStatus : Zarafa.core.mapi.TaskMode.DECLINE,
			handler: this.openSendConfirmationContent,
			scope: this
		};
	},

	/**
	 * Opens a {@link Zarafa.task.dialogs.SendTaskRequestConfirmationContentPanel SendTaskRequestConfirmationContentPanel}
	 *
	 * @param {Ext.Button} button button object.
	 * @param {EventObject} eventObject The click event object.
	 * @private
	 */
	openSendConfirmationContent : function(button, eventObject)
	{
		if (this.record.isTaskOwner()) {
			Zarafa.task.Actions.openSendConfirmationContent(this.record, { responseType : button.responseStatus });
		}
	}
});

Ext.reg('zarafa.taskrequestbuttons', Zarafa.task.ui.TaskRequestButtons);
/*
 * #dependsFile client/zarafa/core/MessageClass.js
 */
Ext.namespace('Zarafa.core');

/**
 * @class Zarafa.core.ContainerClass
 *
 * Special utility class for manipulation and comparisons
 * of Container Class Strings
 *
 * @singleton
 */
Zarafa.core.ContainerClass = {

	/**
	 * Check if the given containerclass property on this record matches the given 'expectedClass'.
	 * This comparison is done in a case-insensite way. This function can be used
	 * for comparing the value of 'container_class' safely.
	 *
	 * @param {String} className The className which should be checked,
	 * @param {String|Array} expectedClass The expected class against which the className
	 * must be compared
	 * @param {Boolean} baseOnly (optional) Only compare the start of the className,
	 * this can be used for partial comparison (e.g. record.isClass('message_class', 'IPM', true)
	 * will return true when the actual message_class is 'IPM.Note'). Defaults to false.
	 * @return {Boolean} True when the className matches.
	 */
	// Implementation is exactly that of the Zarafa.core.MessageClass...
	isClass: Zarafa.core.MessageClass.isClass,

	/**
	 * Test if the containerClass is compatible with the give messageClass.
	 * An IPF.Note is for example compatible with IPM.Note messageClass.
	 *
	 * @param {String} containerClass The Container Class to compare
	 * @param {String} messageClass The Message Class to compare
	 * @return {Boolean} True when the containerClass is compatible with the given messageClass
	 */
	isMessageClassCompatible: function(containerClass, messageClass)
	{
		// Implementation is exact inverse of Zarafa.core.MessageClass, so simply swap arguments.
		return Zarafa.core.MessageClass.isContainerClassCompatible(messageClass, containerClass);
	},

	/**
	 * Function will return default folder type from the hierarchy
	 * for the supplied container_class to the function.
	 *
	 * @param {String} containerClass The container_class of the mapi record.
	 * @return {String} The foldertype of the default folder for the supplied message_class
	 */
	getDefaultFolderTypeFromContainerClass: function(containerClass)
	{
		containerClass = containerClass.toUpperCase();

		switch (containerClass) {
			case 'IPF.APPOINTMENT':
				return 'calendar';
			case 'IPF.STICKYNOTE':
				return 'note';
			case 'IPF.CONTACT':
				return 'contact';
			case 'IPF.TASK':
				return 'task';
			case 'IPF.NOTE':
				return 'inbox';
		}

		// Our fallthrough, the switch didn't catch any container class,
		// so perhaps we should cut of everything after the last dot (.)
		// and then we just retry by calling this function recursively.
		var index = containerClass.lastIndexOf('.');
		if (index > 0) {
			containerClass = containerClass.substr(0, index);
			return this.getDefaultFolderTypeFromContainerClass(containerClass);
		}

		return '';
	}
};
Ext.namespace('Zarafa.core');

/**
 * @class Zarafa.core.ContextMetaData
 * @extends Zarafa.core.PluginMetaData
 *
 * The Meta Data object containing the registration details
 * of a {@link Zarafa.core.Context}. An instance of this object
 * must be passed to {@link Zarafa.core.Container#registerContext}.
 */
Zarafa.core.ContextMetaData = Ext.extend(Zarafa.core.PluginMetaData, {

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

		Ext.applyIf(config, {
			// By default Contexts cannot be disabled
			allowUserDisable: false
		});

		Zarafa.core.ContextMetaData.superclass.constructor.call(this, config);
	}
});
Ext.namespace('Zarafa.core');

/**
 * @class Zarafa.core.ContextModel
 * @extends Zarafa.core.data.StatefulObservable
 *
 * A context model is the main class containing
 * the data which is being used within a
 * {@link Zarafa.core.Context context}
 */
Zarafa.core.ContextModel = Ext.extend(Zarafa.core.data.StatefulObservable, {
	/**
	 * The currently active datamode, this is updated through {@link #setDataMode} and when
	 * this field changes, the {@link #datamodechange} event will be fired.
	 * When this context is {@link #stateful stateful}, this option will be
	 * saved in the settings.
	 * @property
	 * @type Mixed
	 */
	current_data_mode: undefined,

	/**
	 * @cfg {Zarafa.core.data.IPMStore} store The store
	 * which contains all {@link Zarafa.core.data.IPMRecord records}
	 * which must be shown within this {@link Zarafa.core.Context context},
	 */
	store: undefined,

	/**
	 * @cfg {Zarafa.hierarchy.data.MAPIFolderRecord[]} folders The folders
	 * which have been loaded by this context model.
	 */
	folders: undefined,

	/**
	 * The object containing all entryids to folders which are opened by the
	 * context. This object is used to save to the settings so it can be restored
	 * in a new session.
	 * When this context is {@link #stateful stateful}, this option will be
	 * saved in the settings.
	 * @property
	 * @type Object
	 */
	last_used_folders: undefined,

	/**
	 * @cfg {Zarafa.core.data.IPMRecord[]} selectedRecords The records
	 * which are currently selected within this {@link Zarafa.core.Context context}.
	 */
	selectedRecords: undefined,

	/**
	 * @cfg {Zarafa.hierarchy.data.MAPIFolderRecord[]} defaultFolder default folder for the contextModel.
	 */
	defaultFolder: undefined,

	/**
	 * @cfg {Boolean} statefulRecordSelection True if per-folder the last selected records
	 * must be stored ({@link #lastSelectedRecord} and {@link #lastPreviewedRecord}), which will be
	 * automatically selected when the given folder has been selected again.
	 */
	statefulRecordSelection: false,

	/**
	 * A key-value array of {@link Zarafa.hierarchy.data.MAPIFolderRecord Folder} and {@link Zarafa.core.data.IPMRecord Record} entryids.
	 * In this array we store the last record which is being {@link #setPreviewRecord previewed} in this Context.
	 * @property
	 * @type Object
	 */
	lastPreviewedRecord: {},

	/**
	 * A key-value array of {@link Zarafa.hierarchy.data.MAPIFolderRecord Folder} and {{@link Zarafa.core.data.IPMRecord Records} entryids.
	 * In this array we store the last record selection which was selected in the Context.
	 * @property
	 * @type Object
	 */
	lastSelectedRecord: {},

	/**
	 * True if the model is currently busy in live scrolling. This is updated during
	 * {@link #startLiveScroll} and {@link #stopLiveScroll}.
	 * @property
	 * @type Boolean
	 * @private
	 */
	isBusyScrolling: false,

	/**
	 * True if the Model is currently {@link #enable enabled}.
	 * While true, calls to {@link #load} can operate (unless {@link #suspended suspended}.
	 * @property
	 * @type Boolean
	 */
	enabled: false,

	/**
	 * True if the Model is currently {@link #suspendLoading suspended} from loading.
	 * While true, calls to {@link #load} will not do anything.
	 * @property
	 * @type Boolean
	 */
	suspended: false,

	/**
	 * If {@link #suspendLoading suspended} then this indicates if {@link #load}
	 * has been called. This is used during {@link #resumeLoading resuming} to
	 * determine if a call to {@link #load} should be made. The options for the
	 * {@link #load} call will have been saved in {@link #suspendData}.
	 */
	suspendLoad: false,

	/**
	 * If {@link #suspendLoading suspended} then this object contains the properties
	 * which were last given to {@link #load}. When {@link #resumeLoading resuming}
	 * this object will be used as argument to {@link #load}.
	 * @property
	 * @type Object
	 */
	suspendData: undefined,

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

		Ext.applyIf(config, {
			stateful: true
		});

		this.addEvents(
			/**
			 * @event previewrecordchange
			 * Fires when a mail record is selected for preview.
			 * @param {Zarafa.core.ContextModel} model this model.
			 * @param {Zarafa.core.data.IPMRecord} record the record.
			 */
			'previewrecordchange',
			/**
			 * @event recordselectionchange
			 * Fires when the selected records within the context has been changed.
			 * @param {Zarafa.core.ContextModel} model this context model.
			 * @param {Zarafa.core.data.IPMRecord[]} records The selected records
			 */
			'recordselectionchange',
			/**
			 * @event folderchange
			 * Fires when the list of selected folders has changed.
			 * @param {Zarafa.core.ContextModel} model this context model.
			 * @param {Array} folders selected folders as an array of {Zarafa.hierarchy.data.MAPIFolderRecord Folder} objects.
			 */
			'folderchange',
			/**
			 * @event beforefolderchange
			 * Fires before the list of selected folders has changed.
			 * @param {Array} folders Selected folders as an array of {Zarafa.hierarchy.data.MAPIFolderRecord Folder} objects.
			 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder Folder to remove.
			 */
			'beforefolderchange',
			/**
			 * @event modechange
			 * Fires when the view mode is changed.
			 * @param {Zarafa.core.ContextModel} model this model.
			 * @param {Number} mode new data mode (view modes are set by every context).
			 * @param {Number} oldMode previous data mode (view modes are set by every context).
			 */
			'datamodechange',
			/**
			 * @event beforelivescrollstart
			 * Fires before the live scroll is being {@link #startLiveScroll started}.
			 * @param {Zarafa.core.ContextModel} model The model which fired the event
			 * @param {Number} cursor the cursor contains the last index of record in grid.
			 * @return {Boolean} false to prevent the live scroll from being started
			 */
			'beforelivescrollstart',
			/**
			 * @event livescrollstart
			 * Fires when the live scroll is being {@link #startLiveScroll started}.
			 * @param {Zarafa.core.ContextModel} model The model which fired the event
			 * @param {Number} cursor the cursor contains the last index of record in grid.
			 */
			'livescrollstart',
			/**
			 * @event beforelivescrollstop
			 * Fired before the live scroll is being {@link #stopLiveScroll stopped}.
			 * @param {Zarafa.core.ContextModel} model The model which fired the event
			 * @return {Boolean} False to prevent the live scroll from being stopped
			 */
			'beforelivescrollstop',
			/**
			 * @event livescrollstop
			 * Fired when the live scroll is being {@link #stopLiveScroll stopped}.
			 * @param {Zarafa.core.ContextModel} model The model which fired the event
			 */
			'livescrollstop',
			/**
			 * @event beforesearchstart
			 * Fires before the Search is being {@link #startSearch started}.
			 * @param {Zarafa.core.ContextModel} model The model which fired the event
			 * @param {Object} restriction The restriction which should be applied for the search
			 * @param {Boolean} subfolders True if searching should also include the subfolders
			 * @return {Boolean} false to prevent the search from being started
			 */
			'beforesearchstart',
			/**
			 * @event searchstart
			 * Fires when the Search is being {@link #startSearch started}.
			 * @param {Zarafa.core.ContextModel} model The model which fired the event
			 * @param {Object} restriction The restriction which should be applied for the search
			 * @param {Boolean} subfolders True if searching should also include the subfolders
			 */
			'searchstart',
			/**
			 * @event searchupdate
			 * Fired when the Search results have been {@link #onSearchUpdate updated}.
			 * @param {Zarafa.core.ContextModel} model The model which fired the event
			 * @param {Ext.data.Store} store the Store in which the search takes place
			 * @param {Object} searchInfo The searchInfo sent by the server
			 */
			'searchupdate',
			/**
			 * @event beforesearchstop
			 * Fired before the search is being {@link #stopSearch stopped}.
			 * @param {Zarafa.core.ContextModel} model The model which fired the event
			 * @return {Boolean} False to prevent the search from being stopped
			 */
			'beforesearchstop',
			/**
			 * @event searchstop
			 * Fired when the search is being {@link #stopSearch stopped}.
			 * @param {Zarafa.core.ContextModel} model The model which fired the event
			 */
			'searchstop',
			/**
			 * @event searchfinish
			 * Fired when the search is finished, this could come shortly after the
			 * {@link #searchupdate} event.
			 * @param {Zarafa.core.ContextModel} model The model which fired the event
			 */
			'searchfinish',
			/**
			 * @event searchexception
			 * Fired when the server encountered an exception during the search
			 * @param {Zarafa.core.ContextModel} model The model which fired the event
			 * @param {Zarafa.core.data.IPMProxy} proxy object that received the error.
			 * @param {String} type 'request' if an invalid response from server received,
			 * '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
			 */
			'searchexception'
		);

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

		if (this.statefulRecordSelection === true) {
			this.store.on('beforeload', this.onBeforeLoad, this);
			this.store.on('load', this.onLoad, this);
		}

		if (this.stateful) {
			this.initState();
		}

		// Hook an event handler to the 'load' event of the
		// hierarchyStore. Call the event handler directly
		// in case the hierarchy has already been loaded.
		var hierarchyStore = container.getHierarchyStore();
		hierarchyStore.on('load', this.onHierarchyLoad, this);
		this.onHierarchyLoad(hierarchyStore);

		Zarafa.core.data.IPFStoreMgr.on('afterrecordupdate', this.afterRecordUpdate, this);
	},

	/**
	 * Called during the {@link Zarafa.core.Context#enable enabling} of the {@link Zarafa.core.Context context}.
	 * Secondly it will {@link #setFolders set the} {@link #folders folder} to this object to {@link #load} the {@link #store}.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder MAPI folder to show.
	 * @param {Boolean} suspended True to enable the ContextModel {@link #suspendLoading suspended}
	 */
	enable: function(folder, suspended)
	{
		// Enable ContextModel
		this.enabled = true;

		// By default suspend loading, both setFolders() and setDataMode()
		// might trigger a call to load() and we want to defer that to a single
		// call only.
		this.suspendLoading(true);

		// apply the folder, don't reload, as setDataMode will do that for us.
		if (folder) {
			this.setFolders(folder);
		}
		this.setDataMode(this.getCurrentDataMode(), true);

		if (suspended !== true) {
			this.resumeLoading();
		}
	},

	/**
	 * Called during the {@link Zarafa.core.Context#disable disabling} of the {@link Zarafa.core.Context context}.
	 * This will {@link #stopSearch stop the search} and clear all data in the {@link #store}.
	 */
	disable: function()
	{
		// Disable ContextModel
		this.enabled = false;

		this.stopLiveScroll();
		this.store.cancelLoadRequests();
		this.store.removeAll(true);
	},

	/**
	 * When a {@link #store} is about to be {@link Ext.data.Store#load load},
	 * we remove the currently previewed record. This is required because
	 * during a store load, all records will be purged and thus the reference
	 * from the previewrecord to the store will be lost.
	 *
	 * @param {Zarafa.core.data.IPMStore} store the Store which is going to be loaded
	 * @param {Object} options The options object which is used for loading the store
	 * @private
	 */
	onBeforeLoad: function (store, options)
	{
		if (options && (options.actionType === Zarafa.core.Actions['updatesearch'] || options.actionType === Zarafa.core.Actions['updatelist'])) {
			// don't do anything here, as we are just updating the search results or updating list using infinite scrolling.
			// so selection shouldn't be changed
			return;
		}

		// When reloading, we don't want to reset the previewrecord,
		// as we already have the desired record loaded in the previewpanel.
		if (options.reload !== true) {
			this.setPreviewRecord(undefined, false);
		}
	},

	/**
	 * When a {@link #store} has completed a {@link Ext.data.Store#load load} action,
	 * we check if a record has been {@link #lastPreviewedRecord previewed} for this
	 * folder before. If so, we automatically select that record.
	 *
	 * @param {Zarafa.core.data.IPMStore} store The store which has loaded
	 * @param {Zarafa.core.data.IPMRecord/Array} records The records which have loaded
	 * @param {Object} options The options object used for loading the store.
	 * @private
	 */
	onLoad: function(store, records, options)
	{
		if(options && options.actionType === Zarafa.core.Actions['updatesearch']) {
			// don't do anything here, as we are just updating the search results
			// so selection shouldn't be changed
			return;
		}

		var previewRecord;
		var selectedRecords = [];

		if (!Ext.isEmpty(records)) {
			if (options && options.params) {
				var lastPreviewed = this.lastPreviewedRecord[options.params.entryid];
				if (lastPreviewed) {
					previewRecord = store.getById(lastPreviewed);
				}

				var lastSelected = this.lastSelectedRecord[options.params.entryid];
				if (lastSelected) {
					for (var i = 0, len = lastSelected.length; i < len; i++) {
						var record = store.getById(lastSelected[i]);
						if (record) {
							selectedRecords.push(record);
						}
					}
				}
			}

			/*
			 * We are already selecting record which was selected earlier
			 * so no need to save that in lastSelectedRecord.
			 */
			if (!Ext.isEmpty(selectedRecords)) {
				this.setSelectedRecords(selectedRecords, false);
			}

			/*
			 * We are already selecting record which was previewed earlier
			 * so no need to save that in lastPriviewedRecord.
			 */
			if (Ext.isDefined(previewRecord)) {
				this.setPreviewRecord(previewRecord, false);
			}
		}
	},

	/**
	 * Sets {@link #defaultFolder default folder} for the particular {@link Zarafa.core.Context context}.
	 * This will help while opening new item dialog from other contexts
	 * e.g. Create new Contact from Inbox, at this moment we need {@link #defaultFolder} to create the item.
	 *
	 * When the {@link Zarafa.core.Context context} was opened without any folders,
	 * this also means we can now {@link #addFolder load} the {@link #defaultFolder}.
	 *
	 * @param {Zarafa.core.hierarchyStore} store that holds hierarchy data.
	 * @private
	 */
	onHierarchyLoad: function(hierarchyStore)
	{
		// only continue when hierarchyStore has data
		if (hierarchyStore.getCount() === 0) {
			return;
		}

		// Check whether we have foldertype of the model and defaultFolders data or not.
		if (Ext.isEmpty(this.store) || Ext.isEmpty(this.store.preferredMessageClass) || !Ext.isEmpty(this.folders)) {
			return;
		}

		// assign default folder of this context model based on preferred message class from store, so getDefaultFolder can use this
		var folderType = Zarafa.core.MessageClass.getDefaultFolderTypeFromMessageClass(this.store.preferredMessageClass);
		this.defaultFolder = hierarchyStore.getDefaultFolder(folderType);

		// If we haven't any folders yet. We should obtain
		// the previously used folders or the default folder.
		var openfolders = [];

		if (!Ext.isEmpty(this.last_used_folders)) {
			for (var key in this.last_used_folders) {
				var store = hierarchyStore.getById(key);
				if (!store) {
					continue;
				}

				var folders = store.getSubStore('folders');
				var statefolders = this.last_used_folders[key];
				for (var i = 0; i < statefolders.length; i++) {
					var folder = folders.getById(statefolders[i]);
					if (!folder) {
						continue;
					}

					openfolders.push(folder);
				}
			}
		}

		if (Ext.isEmpty(openfolders) && this.defaultFolder) {
			openfolders.push(this.defaultFolder);
		}

		this.setFolders(openfolders);
	},

	/**
	 * Event handler which is triggered when a {@link Zarafa.core.data.IPFRecord record} has been
	 * updated from the server. This function will check whether action on the
	 * {@link Zarafa.core.data.IPFRecord record} does affect on the current context model i.e.
	 * {@link Zarafa.core.Actions.emptyFolder}, {@link Zarafa.core.Actions.readAllMsgs}, if it
	 * affects then we should reload current context model.
	 *
	 * @param {Zarafa.core.data.IPFStore} IPFStore
	 * @param {Zarafa.core.data.IPFRecord} record The Record which has been updated
	 * @param {String} operation The update operation being performed.
	 * ({@link Ext.data.Record#EDIT}, {@link Ext.data.Record#REJECT}, {@link Ext.data.Record#COMMIT}).
	 * @private
	 */
	afterRecordUpdate: function(IPFStore, record, operation)
	{
		if(this.enabled) {
			var action = record.getMessageAction('action_type');
			if (operation == 'commit' && Ext.isString(action)) {
				switch (action.toLowerCase()) {
					case 'emptyfolder':
					case 'readflags':
						// Reload store
						Ext.each(this.getFolders(), function(folder) {
							// If any folder of the current context model is updated
							// then reload the context model.
							if (record.equals(folder)) {
								this.reload();
								return false;
							}
						}, this);
						break;
				}
			}
		}
	},

	/**
	 * Gets the store.
	 * @return {Zarafa.core.data.IPMStore} store object.
	 */
	getStore: function()
	{
		return this.store;
	},

	/**
	 * Adds a folder to the selected folder list.
	 * This function automatically causes the store to
	 * reload its contents. This method triggers the
	 * {@link #folderchange} event if the folder was not already in the
	 * selected folder list.
	 *
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder folder to add.
	 * @return {Zarafa.hierarchy.data.MAPIFolderRecord} the folder if it was added,
	 * or undefined otherwise (i.e. it was already in the folder list).
	 */
	addFolder: function(folder)
	{
		var localFolder = this.getFolder(folder.get('entryid'));

		if (Ext.isDefined(localFolder)) {
			return undefined;
		}

		// Add the folder to the current folder list
		this.folders.push(folder);

		this.onFolderChange(this, this.folders);

		// Fire 'folderchange' event.
		this.fireEvent('folderchange', this, this.folders);

		this.load();

		return folder;
	},

	/**
	 * Removes a folder from the selected folder list.
	 * This function automatically causes the store to
	 * reload its contents. This method fires
	 * {@link #beforefolderchange} and {@link #folderchange} event
	 * if the folder was previously in the list.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder folder to remove.
	 * @return {Zarafa.hierarchy.data.MAPIFolderRecord} the folder if it was removed,
	 * or undefined otherwise (i.e. it was not in the folder list).
	 */
	removeFolder: function(folder)
	{
		var localFolder = this.getFolder(folder.get('entryid'));

		if (!Ext.isDefined(localFolder)) {
			return undefined;
		}

		// Remove the folder from the list.
		this.folders.remove(localFolder);

		if (this.fireEvent('beforefolderchange', this.folders, folder) !== false) {
			// A ContextModel must always have at least
			// 1 folder loaded. When the last folder is
			// being unloaded, load the default one again.
			if (Ext.isEmpty(this.folders)) {
				this.folders.push(this.defaultFolder);
			}
		}

		this.onFolderChange(this, this.folders);

		// Fire 'folderchange' event.
		this.fireEvent('folderchange', this, this.folders);

		this.load();

		return localFolder;
	},

	/**
	 * Sets the selected folder list directly.
	 * Fires the {@link #folderchange} event.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord[]} folders selected folders as an array of
	 * {@link Zarafa.hierarchy.data.MAPIFolderRecord MAPIFolder} objects.
	 */
	setFolders: function(folders)
	{
		var saveState = true;
		// Skip saving state when initial folders are set
		if (this.folders == folders || this.folders === undefined) {
			saveState = false;
		}

		if (Array.isArray(folders)) {
			this.folders = folders.clone();
		} else if (Ext.isDefined(folders)) {
			this.folders = [ folders ];
		} else {
			this.folders = [];
		}

		this.onFolderChange(this, this.folders);

		// Fire 'folderchange' event.
		this.fireEvent('folderchange', this, this.folders, saveState);

		this.load();
	},

	/**
	 * Event handler which is executed right before the {@link #folderchange}
	 * event is fired. This allows subclasses to update the folders.
	 *
	 * @param {Zarafa.core.ContextModel} model The model which fired the event.
	 * @param {Array} folders selected folders as an array of {@link Zarafa.hierarchy.data.MAPIFolderRecord Folder} objects.
	 * @private
	 */
	onFolderChange: function(model, folders)
	{
		// Stop the search

		// Stop the live scroll.
		this.stopLiveScroll();

		// Load the sorting for the new folders
		if (this.stateful && !Ext.isEmpty(folders)) {
			var folder = folders[0];
			var state = container.getHierarchyStore().getState(folder, 'list');
			var sort = this.store.defaultSortInfo;

			if (state && state.sort) {
				sort = state.sort;
			}

			if (sort) {
				this.store.setDefaultSort(sort.field, sort.direction);
			}
		}
	},

	/**
	 * Returns a list of currently selected folders.
	 * @return {Zarafa.hierarchy.data.MAPIFolderRecord[]} selected folders as an array of
	 * {@link Zarafa.hierarchy.data.MAPIFolderRecord MAPIFolder} objects.
	 */
	getFolders: function()
	{
		return this.folders || [];
	},

	/**
	 * Returns the default {@link Zarafa.hierarchy.data.MAPIFolderRecord folder} which is
	 * used within the current selection of folders. If this {@link Zarafa.core.ContextModel ContextModel} is not enabled
	 * then this function will return default folder of this context, and if not enabled then it will return
	 * currently selected folder in the ContextModel.
	 * @return {Zarafa.hierarchy.data.MAPIFolderRecord} The default folder
	 */
	getDefaultFolder: function()
	{
		if(this.enabled) {
			// ContextModel is enabled return currently selected folder
			var folders = this.getFolders();

			// For ContextModel which doesn't handle multiple folder selection, folders will always be an array with one folder
			// so get that first folder
			if(!Ext.isEmpty(folders)) {
				return folders[0];
			}
		}

		// ContextModel is not enabled so return default folder
		return this.defaultFolder;
	},

	/**
	 * Gets a folder from the selected folder list.
	 * @param {String} id folder MAPI Id.
	 * @return {Zarafa.hierarchy.data.MAPIFolderRecord} a folder object if found, undefined otherwise.
	 * @private
	 */
	getFolder: function(id)
	{
		var ret;
		Ext.each(this.getFolders(), function(folder) {
			if (Zarafa.core.EntryId.compareEntryIds(folder.get('entryid'), id)) {
				ret = folder;
				return false;
			}
		});
		return ret;
	},

	/**
	 * Checks if a folder exists in the selected folder list.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord/String} folder Either the MAPIFolder, or
	 * the id from the MAPIFolder to be used as id.
	 * @return {Boolean} true iff the given folder is selected.
	 */
	hasFolder: function(folder)
	{
		if (folder instanceof Zarafa.core.data.MAPIRecord) {
			folder = folder.get('entryid');
		}

		return Ext.isDefined(this.getFolder(folder));
	},

	/*
	 * Create a new {@link Zarafa.core.data.IPMRecord IPMRecord} record which is associated to this context.
	 * this is a base function to create record. each context will overwrite this function to
	 * create record of that specific context.
	 * @param {Zarafa.core.IPMFolder} folder (optional) The target folder in which the new record must be
	 * created. If this is not provided the default folder will be used.
	 * @return {Zarafa.core.data.IPMRecord} IPMRecord which is associated to this context.
	 */
	createRecord: Ext.emptyFn,

	/*
	 * Check if user has enough permissions to create record in given folder.
	 * A confirmation message will be raised when user don't have enough permissions
	 * asking to use default folder instead.
	 * @param {Function} callback The callback function which will be called once the record gets created.
	 * @param {Zarafa.core.IPMFolder} folder The target folder in which the new record will be created.
	 * @param {Zarafa.core.DateRange} dateRange The {@link Zarafa.core.DateRange DateRange} object
	 * @return {Boolean} true if user has enough permissions, false otherwise.
	 */
	checkCreateRights: Ext.emptyFn,

	/**
	 * Set the {@link Zarafa.core.data.IPMRecord records} which have been
	 * selected within the {@link Zarafa.core.Context context}. Raise the
	 * {@link #recordselectionchange} to inform any listeners about the update.
	 *
	 * @param {Zarafa.core.data.IPMRecord[]} records The selected records
	 * @param {Boolean} stateful (optional) false to prevent the selected records to be saved
	 * in the {@link #lastSelectedRecords}.
	 */
	setSelectedRecords: function(records, stateful)
	{
		if (!this.selectedRecords || !records || !this.selectedRecords.equals(records)) {
			this.selectedRecords = records;

			if (this.statefulRecordSelection === true && stateful !== false && !Ext.isEmpty(records)) {
				this.lastSelectedRecord[records[0].get('parent_entryid')] = Ext.pluck(records, 'id');
			}

			this.fireEvent('recordselectionchange', this, records);
		}
	},

	/**
	 * Get the currently selected {@link Zarafa.core.data.IPMRecord records}
	 * from this {@link Zarafa.core.Context context}.
	 *
	 * @return {Zarafa.core.data.IPMRecord[]} The selected records
	 */
	getSelectedRecords: function()
	{
		if (!Ext.isDefined(this.selectedRecords)) {
			return;
		}

		var store = this.getStore();
		var records = this.selectedRecords;

		if (!Ext.isArray(records)) {
			if (records && Ext.isFunction(records.getRange)) {
				records = records.getRange();
			} else if (records && Ext.isDefined(records.items)) {
				records = records.items.slice();
			} else if (records) {
				records = [records];
			} else {
				records = [];
			}
		}

		return Zarafa.common.Actions.resolveRecords(records, store);
	},

	/**
	 * Suspend all calls to {@link #load}. This will prevent the {@link #store}
	 * to send load requests to the server until the model has been
	 * {@link #resumeLoading resumed} again.
	 *
	 * This can be used to configure multiple options in the ContextModel
	 * and defer the loading until all actions have been completed.
	 */
	suspendLoading: function()
	{
		this.suspended = true;
	},

	/**
	 * Resume the loading actions again if {@link #suspendLoading}.
	 * This will allow the {@link #load} function to use the {@link #store}
	 * again to send requests to the server. This will also call {@link #load}
	 * with the last options that were given to it while being suspended.
	 *
	 * @param {Boolean} discard Discard the calls to load while being suspended.
	 */
	resumeLoading: function(discard)
	{
		if (this.suspended === true) {
			this.suspended = false;
			if (discard !== false && this.suspendLoad === true) {
				this.load(this.suspendData);
			}
			delete this.suspendData;
		}
	},

	/**
	 * Load the store using the given (optional) restriction.
	 * @param {Object} data The data object to load the store with
	 * @private
	 */
	load: function(data)
	{
		if (!this.enabled) {
			return;
		}

		if (this.suspended) {
			this.suspendLoad = true;
			this.suspendData = data;
			return;
		}

		if (Ext.isEmpty(this.folders)) {
			// No folders can be loaded, empty the store
			this.setPreviewRecord(undefined, false);
			this.store.removeAll(true);
		} else {
			// Load a new set of folders from the store.
			data = Ext.applyIf(data || {}, {
				folder: this.folders
			});

			this.store.load(data);
		}
	},

	/**
	 * Reload the store using the previous configured options/folders.
	 * but it check that action type is {@link Zarafa.core.Actions#updatelist 'updatelist'} then
	 * call {@link #stopLiveScroll}
	 */
	reload: function()
	{
		var lastOptions = this.store.lastOptions;

		// If live scroll is performed and search is not then stop the live scroll.
		if(!this.isSearching() && this.isBusyScrolling){
			this.stopLiveScroll();
		}

		if (this.suspended) {
			this.suspendLoad = true;
			this.suspendData = lastOptions;
		} else {
			this.store.reload();
		}
	},

	/**
	 * Update the current preview {@link Zarafa.core.data.IPMRecord}
	 * This will fire the event {@link #previewrecordchange}.
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record which is set as preview
	 * @param {Boolean} stateful (optional) false to prevent the previewrecord to be saved
	 * in the {@link #lastPreviewedRecord}.
	 */
	setPreviewRecord: function(record, stateful)
	{
		if (this.previewRecord !== record) {
			this.previewRecord = record;

			if (this.statefulRecordSelection === true && stateful !== false && Ext.isDefined(record)) {
				this.lastPreviewedRecord[record.get('parent_entryid')] = record.get('entryid');
			}

			this.fireEvent('previewrecordchange', this, record);
		}
	},

	/**
	 * Obtain the currently selected {@link Ext.data.Record} which is
	 * set as the preview record.
	 *
	 * @return {Zarafa.core.data.IPMRecord}
	 */
	getPreviewRecord: function()
	{
		return this.previewRecord;
	},

	/**
	 * Sets the current mode from the available data modes.
	 *
	 * Fires the {@link #datamodechange} event.
	 * @param {Number} mode view mode (context should define modes and its numeric values).
	 * @param {Boolean} init (optional) True when this function is called during initialization
	 * and it should force the change of the data mode.
	 */
	setDataMode: function(mode, init)
	{
		if (init === true || this.current_data_mode !== mode) {
			var oldMode = this.current_data_mode;
			this.current_data_mode = mode;

			this.onDataModeChange(this, this.current_data_mode, oldMode);

			// fire mode change event
			this.fireEvent('datamodechange', this, this.current_data_mode, oldMode);
		}
	},

	/**
	 * Event handler which is executed right before the {@link #datamodechange}
	 * event is fired. This allows subclasses to initialize the {@link #store}.
	 *
	 * @param {Zarafa.core.ContextModel} model The model which fired the event.
	 * @param {Mixed} newMode The new selected DataMode.
	 * @param {Mixed} oldMode The previously selected DataMode.
	 * @private
	 */
	onDataModeChange: Ext.emptyFn,

	/**
	 * Returns the currently active {@link #current_data_mode datamode} as configured
	 * through {@link #setDataMode}.
	 * @return {Mixed} The datamode id of the currently active datamode.
	 */
	getCurrentDataMode: function()
	{
		return this.current_data_mode;
	},

	/**
	 * {@link Ext.data.Store#clearGrouping clear the grouping} on the
	 * {@link #store}.
	 */
	clearGrouping: function()
	{
		// Only clear grouping, when grouping was applied
		// in the first place.
		if (!Ext.isEmpty(this.store.groupField)) {
			this.store.clearGrouping();
		}
	},

	/**
	 * {@link Ext.data.Store#groupBy group the properties} on the
	 * {@link #store}.
	 */
	groupBy: function(property)
	{
		this.store.groupBy(property);
	},

	/**
	 * Check if the store is currently using searching.
	 * @return {Boolean} True if the store is currently searching
	 */
	isSearching: function()
	{
		if(Ext.isDefined(this.getStore()) && Ext.isDefined(this.getStore().isBusySearching) && this.getStore().isBusySearching) {
			return true;
		}
		return false;
	},

	/**
	 * Check if the {@link Zarafa.hierarchy.data.MAPIStoreRecord store}
	 * of the {@link #getDefaultFolder folder} in which we intend to search has support for
	 * {@link Zarafa.hierarchy.data.MAPIStoreRecord#hasSearchSupport Search folders}.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder (optional) If passed, this folder's store
	 * will be checked for search folder support instead of the default folder's
	 * @return {Boolean} True if search folders are supported
	 */
	supportsSearchFolder: function(folder)
	{
		folder = folder || this.getDefaultFolder();

		if (Ext.isEmpty(folder)) {
			return false;
		}

		var mapiStore = folder.getMAPIStore();
		if (!mapiStore) {
			return false;
		}

		return mapiStore.hasSearchSupport();
	},

	/**
	 * Check if the model is currently using live scroll.
	 * @return {Boolean} True if the model is currently searching
	 */
	isLiveScrolling: function()
	{
		return this.isBusyScrolling;
	},

	/**
	 * Fire the {@link #livescrollstart} event and set {@link #isBusyScrolling} to true.
	 * @param {Number} cursor the cursor contains the last index of record in grid.
	 * @param {Ext.data.Store} store The store on which live scroll is going to perform.
	 */
	startLiveScroll: function(cursor, store)
	{
		/*
		 * don't do anything if live scroll is not enabled just simply return.
		 */
		var isEnableLiveScroll = container.getSettingsModel().get('zarafa/v1/contexts/mail/enable_live_scroll');
		if(!isEnableLiveScroll) {
			return;
		}

		if (this.fireEvent('beforelivescrollstart', this, cursor) !== false) {
			this.isBusyScrolling = true;
			store = Ext.isDefined(store) ? store : this.store;
			store.on('exception', this.onLiveScrollException, this);
			if(this.fireEvent('livescrollstart', this, cursor) !== false) {
				var options = {
					restriction: {
						start: cursor,
						limit: container.getSettingsModel().get('zarafa/v1/main/page_size')
					}
				};

				// If filter was applied already then set the
				// filter restriction in params.
				if (store.hasFilterApplied) {
					var filter = store.getFilterRestriction(Zarafa.common.data.Filters.UNREAD);
					if (store.hasFilterApplied) {
						options.restriction['filter'] = filter;
					}
				}
				store.liveScroll({
					folder: [this.getDefaultFolder()],
					params: options,
					add: true
				});
			}
		}
	},

	/**
	 * Stop {@link Zarafa.core.data.ListModuleStore#stopLiveScroll stopLiveScroll} in the {@link #store}
	 * and fire the {@link #livescrollstop} event.
	 * @param {Ext.data.Store} store The store on which live scroll is performed.
	 */
	stopLiveScroll: function(store)
	{
		if (this.isBusyScrolling && this.fireEvent('beforelivescrollstop', this) !== false) {
			store = Ext.isDefined(store) ? store : this.store;
			store.stopLiveScroll();

			store.un('exception', this.onLiveScrollException, this);
			this.isBusyScrolling = false;
			this.fireEvent('livescrollstop', this);
		}
	},

	/**
	 * Event handler for the {@link Zarafa.core.data.ListModuleStore#exception} event
	 * of the {@link #store} which is fired when the server failed to update the live scroll.
	 *
	 * @param {Zarafa.core.data.IPMProxy} proxy object that received the error
	 * and which fired exception event.
	 * @param {String} type 'request' if an invalid response from server received,
	 * '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
	 * @private
	 */
	onLiveScrollException: function(proxy, type, action, options, response, args)
	{
		this.stopLiveScroll();
	},

	/**
	 * Fire the {@link #searchstart} event and start
	 * {@link Zarafa.core.data.ListModuleStore#search searching} in the {@link #store}.
	 * @param {Object} restriction The restriction which should be applied for the search
	 * @param {Boolean} subfolders True if searching should also include the subfolders
	 * this will only be used when searchfolders are supported
	 */
	startSearch: function(restriction, subfolders, options)
	{
		var useSearchFolder = this.supportsSearchFolder(options.folder);

		// We don't support subfolders when searchfolders are disabled
		if (!useSearchFolder) {
			subfolders = false;
		}

		if (this.fireEvent('beforesearchstart', this, restriction, subfolders) !== false) {
			this.store.isBusySearching = true;
			this.fireEvent('searchstart', this, restriction, subfolders);

			this.store.on('exception', this.onSearchException, this);
			if (useSearchFolder) {
				// only required when using a search folder
				this.store.on('beforeupdatesearch', this.onSearchUpdate, this);
			} else {
				// if we are not using search folder then also after completion of search we need to fire 'searchfinished' event
				this.store.on('load', function() {
					this.fireEvent('searchfinished', this);
				}, this, {single: true});
			}

			var store = this.getActiveStore();
			var forceCreateSearchFolder = false;
			if (Ext.isDefined(store.searchFolder[store.searchStoreUniqueId])) {
				var searchFolderEntryid = store.searchFolder[store.searchStoreUniqueId].get('entryid');
				forceCreateSearchFolder = Zarafa.core.EntryId.compareEntryIds(searchFolderEntryid, store.searchFolderEntryId);
			}

			// send request to store to start search
			this.store.search({
				// search in multiple folders not supported at the moment
				folder: options.folder,
				forceCreateSearchFolder: forceCreateSearchFolder,
				useSearchFolder: useSearchFolder,
				subfolders: subfolders,
				searchRestriction: restriction
			});
		}
	},

	/**
	 * Stop {@link Zarafa.core.data.ListModuleStore#stopSearch searching} in the {@link #store}
	 * and fire the {@link #searchstop} event.
	 */
	stopSearch: function()
	{
		if (this.isSearching() && this.fireEvent('beforesearchstop', this) !== false) {
			// send request to store to stop search
			this.store.stopSearch({});

			this.store.un('exception', this.onSearchException, this);
			this.store.un('beforeupdatesearch', this.onSearchUpdate, this);

			this.store.isBusySearching = false;
			this.isBusyScrolling = false;
			this.fireEvent('searchstop', this);
		}
	},

	/**
	 * Event handler for the {@link Zarafa.core.data.ListModuleStore#beforeupdatesearch} event
	 * of the {@link #store}. This will fire the {@link #searchupdate} event and will
	 * check if the {@link Zarafa.core.mapi.Search#isSearchRunning search is still running},
	 * otherwise the {@link #searchfinished} event will be fired.
	 * @param {Ext.data.Store} store the Store which fired the event
	 * @param {Object} searchInfo The searchInfo sent by the server
	 * @private
	 */
	onSearchUpdate: function(store, searchInfo)
	{
		this.fireEvent('searchupdate', this, store, searchInfo);

		if (!Zarafa.core.mapi.Search.isSearchRunning(searchInfo['searchState'])) {

			store.un('exception', this.onSearchException, this);
			store.un('beforeupdatesearch', this.onSearchUpdate, this);

			this.fireEvent('searchfinished', this);
		}
	},

	/**
	 * Event handler for the {@link Zarafa.core.data.ListModuleStore#exception} event
	 * of the {@link #store} which is fired when the server failed to update the search.
	 * This will fire the {@link #searchexception} event and stop the search.
	 *
	 * @param {Zarafa.core.data.IPMProxy} proxy object that received the error
	 * and which fired exception event.
	 * @param {String} type 'request' if an invalid response from server received,
	 * '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
	 * @private
	 */
	onSearchException: function(proxy, type, action, options, response, args)
	{
		this.store.un('exception', this.onSearchException, this);
		this.store.un('beforeupdatesearch', this.onSearchUpdate, this);

		this.stopSearch();

		this.fireEvent('searchexception', this, proxy, type, action, options, response, args);
	},

	/**
	 * Obtain the path in which the {@link #getState state} must be saved.
	 * This option is only used when the {@link Zarafa.core.data.SettingsStateProvider SettingsStateProvider} is
	 * used in the {@link Ext.state.Manager}. This returns {@link #statefulName} if provided, or else generates
	 * a custom name.
	 * @return {String} The unique name for this component by which the {@link #getState state} must be saved.
	 */
	getStateName: function()
	{
		var name = this.statefulName;
		if (!name) {
			if (this.store) {
				name = this.store.preferredMessageClass.match(/(?:IPM\.)?(.*)/)[1].toLowerCase();
			}
		}

		return 'models/' + name;
	},

	/**
	 * Register the {@link #stateEvents state events} to the {@link #saveFolderChangeState} and the
	 * {@link #saveDataModeState} callback functions.
	 * @protected
	 */
	initStateEvents: function()
	{
		Zarafa.core.ContextModel.superclass.initStateEvents.call(this);
		this.on('datamodechange', this.saveDataModeState, this, { delay: 100 });
		this.on('folderchange', this.saveFolderChangeState, this, { delay: 100 });
	},

	/**
	 * Handler for 'folderchange' event, which calls {@link #saveState} only
	 * when save is not false.
	 *
	 * @param {Zarafa.core.ContextModel} contextModel the contextModel which states needs to be saved.
	 * @param {Array} folders the folders which are changed.
	 * @param {Boolean} save determines if state should be saved.
	 */
	saveFolderChangeState: function(contextModel, folders, save)
	{
		if (save !== false) {
			this.saveState();
		}
	},

	/*
	 * Handler for 'datamodechange' event, which calls {@link #saveState} only
	 * when the datamode has been changed.
	 *
	 * @param {Zarafa.core.ContextModel} contextModel the contextModel which states needs to be saved.
	 * @param {Number} current_data_mode the current data mode.
	 * @param {Number} oldMode the old data mode.
	 */
	saveDataModeState: function(context, current_data_mode, oldMode)
	{
		if (current_data_mode != oldMode) {
			this.saveState();
		}
	},

	/**
	 * When {@link #stateful} the State object which should be saved into the
	 * {@link Ext.state.Manager}.
	 * @return {Object} The state object
	 * @protected
	 */
	getState: function()
	{
		var state = Zarafa.core.ContextModel.superclass.getState.call(this) || {};
		var searching = this.isSearching();

		// If a folders list exists, it should be used to update
		// the last_used_folders object. Otherwise we keep the value
		// as we don't want to override it with an empty object.
		if (this.folders) {
			this.last_used_folders = {};

			for (var i = 0, len = this.folders.length; i < len; i++) {
				var folder = this.folders[i];
				var storeentryid = folder.get('store_entryid');
				var entryid = folder.get('entryid');

				if (!Ext.isDefined(this.last_used_folders[storeentryid])) {
					this.last_used_folders[storeentryid] = [];
				}

				this.last_used_folders[storeentryid].push(entryid);
			}
		}

		return Ext.apply(state, searching ? {} : {
			current_data_mode: this.current_data_mode,
			last_used_folders: this.last_used_folders
		});
	},

	/**
	 * Apply the given state to this object activating the properties which were previously saved in
	 * {@link Ext.state.Manager}. Overridden to check if the state is valid. When this ContextModel
	 * is not a {@link Zarafa.core.MultiFolderContextModel MultiFolderContextModel} it can never
	 * have more than 1 folder set as 'last used folder', so when this is the case we will remove
	 * this property from the state.
	 *
	 * @param {Object} state The state object
	 */
	applyState: function(state)
	{
		if ( !(this instanceof Zarafa.core.MultiFolderContextModel) ){
			// Check if the state is valid
			if ( Ext.isDefined(state.last_used_folders) ){
				if ( Object.keys(state.last_used_folders).length > 1 ){
						delete state.last_used_folders;
				} else {
					Ext.iterate(state.last_used_folders, function(storeEntryid, folders, lastUsedFoldersObj){
						if ( Array.isArray(folders) && folders.length>1 ){
							delete lastUsedFoldersObj[storeEntryid];
						}
					}, this);
				}
			}
		}

		Zarafa.core.ContextModel.superclass.applyState.call(this, state);
	}
});
Ext.namespace('Zarafa.core');

/**
 * @class Zarafa.core.ErrorType
 * @extends Zarafa.core.Enum
 *
 * @singleton
 */
Zarafa.core.ErrorType = Zarafa.core.Enum.create({
	/**
	 * Denotes type of error is MAPI which is generated from an erroneous mapi call.
	 * @property
	 * @type Number
	 */
	'MAPI': 1,

	/**
	 * Denotes type of error is ZARAFA which is generated by our PHP code.
	 * @property
	 * @type Number
	 */
	'ZARAFA': 2,

	/**
	 * Denotes type of error is GENERAL which is generated by PHP's error handler.
	 * @property
	 * @type Number
	 */
	'GENERAL': 3
});

Ext.namespace('Zarafa.core');

/**
 * @class Zarafa.core.Plugin
 * @extends Zarafa.core.data.StatefulObservable
 *
 * A pluggable component. It can be used to implement UI plug-ins
 * or content contexts such as the email context, notes context, etc.
 * <p>
 * Each plugin should be registered manually with the global Container object.
 * The container can then be used to enumerate the registered plugins.
 * <p>
 * This class was intended to be overridden.
 */
Zarafa.core.Plugin = Ext.extend(Zarafa.core.data.StatefulObservable, {
	/**
	 * @cfg {Zarafa.core.PluginMetaData} info The Plugin MetaData object
	 * which was used to {@link Zarafa.core.Container#registerPlugin register}
	 * this Plugin to the {@link Zarafa.core.Container container}.
	 */
	info: undefined,

	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		Zarafa.core.Plugin.superclass.constructor.call(this, config);

		this.initPlugin();
	},

	/**
	 * Function to be implemented by Plugin subclasses to initialize the plugin.
	 * @protected
	 */
	initPlugin: function()
	{
		var about = this.info.getAbout();

		// If the about text is provided, automatically register it
		if (!Ext.isEmpty(about)) {
			this.registerAboutText(this.getDisplayName(), about);
		}
	},

	/**
	 * Returns the name of the plugin.
	 * @return {String} name context name
	 */
	getName: function() {
		return this.info.getName();
	},

	/**
	 * Return the display name for this plugin.
	 * @return {String} Display name as string
	 */
	getDisplayName: function()
	{
		return this.info.getDisplayName();
	},

	/**
	 * Obtain the CSS classname for this plugin
	 * @return {String} The CSS classname for this plugin
	 */
	getIconCls: function()
	{
		return this.info.getIconCls();
	},

	/**
	 * Obtain the base path for the {@link Zarafa.settings.SettingsModel settings} in which the settings
	 * for this plugin can be found.
	 * @return {String} The settings path
	 */
	getSettingsBase: function()
	{
		return this.info.getSettingsBase();
	},

	/**
	 * Add a About/Copyright notice to grommunio Web, to the {@link Zarafa.settings.ui.SettingsCopyrightCategory About Category}
	 * inside the {@link Zarafa.settings.SettingsContext Settings}. This is a convenience function which will use the
	 * {@link Zarafa.settings.ui.SettingsCopyrightCategory#context.settings.category.copyright context.settings.category.copyright}
	 * insertion point.
	 * @param {String} title The title for the About widget
	 * @param {String} text The text which should be shown in the About text (may contain HTML)
	 * @protected
	 */
	registerAboutText: function(title, text)
	{
		this.registerInsertionPoint('context.settings.category.copyright', function() {
			return {
				xtype: 'zarafa.settingscopyrightwidget',
				title: title,
				about: text
			};
		});
	},

	/**
	 * Registers a function with an insertion point. The function will be called when the insertion point
	 * is populated using <code>container.populateInsertionPoint</code> and is expected to return one or
	 * more instances of {@link Ext.Component Component}.
	 * <p>
	 * The 'match' parameter can be either a string denoting the name of the insertion point, or a
	 * regular expression if the function should be called for more than one insertion point (i.e. /toolbar.* /
	 * will match all insertion points starting with 'toolbar').
	 * <p>
	 * The function will be called exactly once for each matched insertion point.
	 * @param {Mixed} match a string or regexp
	 * @param {Function} createFunction function that creates one or more ExtJS components at the specified insertion point
	 * @param {Object} scope (optional) scope in which to run the create function (defaults to undefined).
	 */
	registerInsertionPoint: function(match, createFunction, scope)
	{
		if (!this.insertionPoints) {
			this.insertionPoints = [];
		}

		if (!scope) {
			scope = this;
		}

		this.insertionPoints.push({
			match: match,
			createFunction: createFunction,
			scope: scope
		});
	},

	/**
	 * Returns a set of components for a given insertion point.
	 * @param {String} insertionPointName the name of the insertion point
	 * @return {Mixed} either undefined or an array of {Ext.Component} objects.
	 */
	getComponents: function(insertionPointName)
	{
		var ret = [];

		// convert arguments object to a real array
		var args = Ext.toArray(arguments);

		// check if we have a registered handler for this insertionPoint
		Ext.each(this.insertionPoints, function(insertionPoint) {
			if ((typeof insertionPoint.match=='string' && insertionPoint.match==insertionPointName) ||
				(insertionPoint.match.constructor && insertionPoint.match.constructor==RegExp && insertionPoint.match.test(insertionPointName)))
			{
				var components = insertionPoint.createFunction.apply(insertionPoint.scope, args);

				if (components === undefined || components === null) {
					components = [];
				}

				if (!Array.isArray(components)) {
					components = [ components ];
				}

				ret.push.apply(ret, components);
			}
		}, this);

		return ret;
	},

	/**
	 * Produces a bid on the shared component. A negative bid (-1) indicates
	 * that this plug-in can return a shared component. A positive bid (1)
	 * indicates that it can, and a higher bid (>1) can be used to override
	 * default plug-ins. The context that bids the highest is selected to return
	 * the shared component through the {@link #getSharedComponent} function.
	 * @param {Zarafa.core.data.SharedComponentType} type Type of component a context can bid for.
	 * @param {Ext.data.Record} record (optional) Passed record.
	 * @return {Number} a bid on the shared component
	 */
	bidSharedComponent: function(type, record)
	{
		return -1;
	},

	/**
	 * When the Plugin has returned the highest {@link #bidSharedComponent bid} for the
	 * given type and record, it must return the desired {@link Ext.Component component}
	 * with this function.
	 *
	 * @param {Zarafa.core.data.SharedComponentType} type Type of component which is requested.
	 * @param {Ext.data.Record} record (optional) Passed record.
	 * @return {Constructor} The class constructor of the Shared Component.
	 */
	getSharedComponent: function(type, record)
	{
		return undefined;
	}
});
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.AbstractNotificationResponseHandler
 * @extends Zarafa.core.data.AbstractResponseHandler
 *
 * This class specializes in handling notifications received by the PHP-side,
 * and is to be used by the {@link Zarafa.core.ResponseRouter ResponseRouter} to
 * process these notifications.
 */
Zarafa.core.data.AbstractNotificationResponseHandler = Ext.extend(Zarafa.core.data.AbstractResponseHandler, {
	/**
	 * @cfg {Ext.data.Store} store The Store in which the {@link Ext.data.Record records} are located
	 * which can be updated by this Notification Response. Note that this is only used to obtain the
	 * affected {@link Ext.data.Record records}, and the actual updating will be done by the {@link #notifyObject}.
	 */
	store: undefined,

	/**
	 * @cfg {Ext.data.Reader} reader
	 * Reader that will be used to parse the records
	 */
	reader: undefined,

	/**
	 * @cfg {Object} notifyObject The object which must be called when notifications have been received.
	 * This object must contain the function 'onNotify' which accepts the following arguments:
	 * <div class="mdetail-params"><ul>
	 * <li><b>action</b>: Zarafa.core.data.Notifications<p class="sub-desc">The notification action</p></li>
	 * <li><b>records</b>: Ext.data.Record/Array<p class="sub-desc">The record or records which have been
	 * affected by the notification</p></li>
	 * <li><b>data</b>: Object <p class="sub-desc">The data which has been received from the PHP-side which must be applied
	 * to the given records.</p></li>
	 * <li><b>success</b>: Boolean<p class="sub-desc">The success status, True if the notification was successfully received</p></li>
	 * </ul></div>
	 */
	notifyObject: undefined,

	/**
	 * Collection of Notifications which were found inside the Response object.
	 * This object contains <action, notification> pairs, where the action is used
	 * as key on the 'notifications' object, and the notification is either an object
	 * or an array of objects which have been collected for the given action. Each
	 * object contains the records with all the {@link Ext.data.Record records} which
	 * is being affected by this notification, and the data which has been received
	 * from the PHP-side.
	 *
	 * Adding new notifications can be done using the {@link #addNotification} function.
	 * They will automatically be reported to the {@link #notifyObject} during the {@link #done} function.
	 * @property
	 * @type Object
	 */
	notifications: undefined,

	/**
	 * The {@link Date#getTime timestamp} of the date on which the notifications were received, this
	 * value is provided during {@link #start}.
	 * @property
	 * @type Number
	 */
	receivedTime: undefined,

	/**
	 * The main handler to begin a Response processing transaction.
	 * @param {String} moduleName The name of the PHP module from which this response originated.
	 * @param {String} moduleId The unique identifier for the PHP response.
	 * @param {Object} data The entire response object which will be processed during this transaction.
	 * @param {Number} timestamp The {@link Date#getTime timestamp} on which the response was received
	 * @return {Boolean} False when the given data object cannot be handled by this response handler,
	 * and the transaction must be canceled.
	 */
	start: function(moduleName, moduleId, data, timestamp)
	{
		// If the data is not a valid response, then we cannot handle it.
		// The same counts for the notifyObject, which must be a valid object,
		// which contains our callback function for reporting the notifications.
		if (!Ext.isObject(data) || Ext.isEmpty(this.notifyObject) || !Ext.isFunction(this.notifyObject.onNotify)) {
			return false;
		}

		// Prepare the notifications list
		this.notifications = {};

		// Update the received time
		this.receivedTime = timestamp;
	},

	/**
	 * The main handler to complete a Response processing transaction.
	 * @param {Boolean} success True if no errors were returned from the PHP-side.
	 */
	done: function(success)
	{
		Ext.iterate(this.notifications, function(action, actionData) {
			Ext.each(actionData, function(notification) {
				this.notifyObject.onNotify(action, notification.records, notification.data, this.receivedTime, success);
			}, this);
		}, this);
	},

	/**
	 * Add a new notification to the {@link #notifications} object. This will
	 * check if the given action already has a notification attached to it.
	 * If this is the case, the notification is converted into an array, in order
	 * to be able to deliver multiple notifications for the same action type.
	 * @param {Zarafa.core.data.Notifications} action The notification action.
	 * @param {Array} records The records for which the notification is intended
	 * @param {Array} data The notification data for each record
	 * @private
	 */
	addNotification: function(action, records, data)
	{
		var notification = { records: records, data: data };

		if (Ext.isDefined(this.notifications[action])) {
			if (Array.isArray(this.notifications[action])) {
				this.notifications[action].push(notification);
			} else {
				this.notifications[action] = [ this.notifications[action], notification ];
			}
		} else {
			this.notifications[action] = [ notification ];
		}
	}
});
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.CompositeResponseHandler
 * @extends Zarafa.core.data.AbstractResponseHandler
 *
 * This class specializes in wrapping multiple {@link Zarafa.core.data.AbstractResponseHandler AbstractResponseHandler}
 * objects into a single interface to be used by the {@link Zarafa.core.ResponseRouter ResponseRouter}.
 *
 * Each call into this class, will result in the same call into each {@link Zarafa.core.data.AbstractResponseHandler sub-handler}.
 */
Zarafa.core.data.CompositeResponseHandler = Ext.extend(Zarafa.core.data.AbstractResponseHandler, {
	/**
	 * @cfg {Zarafa.core.data.AbstractResponseHandler[]} handlers The array of notification handlers
	 * which have been wrapped by this composite handler.
	 */
	handlers: undefined,

	/**
	 * Array of objects containing the {@link Zarafa.core.data.AbstractResponseHandler ResponseHandlers} which
	 * are active for the current transaction. This is a subset of {@link #handlers}. Any ResponseHandler not in this list,
	 * has returned false during {@link #start}. For each ResponseHandler we also keep track of the success status, this
	 * indicates the value for the 'success' argument to {@link #done}.
	 * @property
	 * @type Array
	 */
	activeHandlers: undefined,

	/**
	 * The main handler to begin a Response processing transaction.
	 * @param {String} moduleName The name of the PHP module from which this response originated.
	 * @param {String} moduleId The unique identifier for the PHP response.
	 * @param {Object} data The entire response object which will be processed during this transaction.
	 * @param {Number} timestamp The {@link Date#getTime timestamp} on which the response was received
	 * @return {Boolean} False when the given data object cannot be handled by this response handler,
	 * and the transaction must be canceled.
	 */
	start: function(moduleName, moduleId, data, timestamp)
	{
		this.activeHandlers = [];

		for (var i = 0, len = this.handlers.length; i < len; i++) {
			var handler = this.handlers[i];
			if (handler.start(moduleName, moduleId, data, timestamp) !== false) {
				this.activeHandlers.push({ handler: handler, success: true });
			}
		}

		return this.activeHandlers.length > 0;
	},

	/**
	 * The handler for handling the given command from a Response.
	 * @param {Zarafa.core.Actions} action The action which must be executed.
	 * @param {Object} data The response object belonging to the given command.
	 * @return {Boolean} False when action could not be handled successfully. This will
	 * not cancel the transaction itself, but rather causes the 'success' argument for the
	 * {@link #done} function to be false.
	 */
	handle: function(action, data)
	{
		for (var i = 0, len = this.activeHandlers.length; i < len; i++) {
			var handler = this.activeHandlers[i];
			handler.success = handler.handler.handle(action, data);
		}
	},

	/**
	 * The main handler to complete a Response processing transaction.
	 * @param {Boolean} success True if no errors were returned from the PHP-side.
	 */
	done: function(success)
	{
		for (var i = 0, len = this.activeHandlers.length; i < len; i++) {
			var handler = this.activeHandlers[i];
			handler.handler.done(handler.success);
		}
	}
});
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.IPFRecord
 * @extends Zarafa.core.data.MAPIRecord
 *
 */
Zarafa.core.data.IPFRecord = Ext.extend(Zarafa.core.data.MAPIRecord, {
	/**
	 * Compare this {@link Zarafa.core.data.MAPIRecord record} instance with another one to see
	 * if they are the same item from the server (i.e. The entryid matches).
	 *
	 * @param {Zarafa.core.data.MAPIRecord} record The MAPIRecord to compare with
	 * @return {Boolean} True if the records are the same.
	 */
	equals: function(record)
	{
		// Simplest case, do we have the same object...
		if (this === record) {
			return true;
		}

		// @FIXME move this to MAPIRecord
		// When only one of the 2 records is a phantom, then the
		// records can impossibly be equal...
		if (this.phantom !== record.phantom) {
			return false;
		}

		// For phantom records, no entryid exists. Since both objects thus only
		// contain on the client-side and cannot be represented by 2 different
		// objects, comparison on the id-field only is sufficient.
		if (this.phantom) {
			return this.id == record.id;
		} else {
			return Zarafa.core.EntryId.compareEntryIds(this.get('entryid'), record.get('entryid'));
		}
	},

	/**
	 * This will determine if the provided className matches the container_class
	 * on this record. This comparison is done case-insensitive. See {@link Zarafa.core.ContainerClass#isClass}
	 * for further details.
	 *
	 * @param {String/Array} className The class name which must be compared to the container_class
	 * @param {Boolean} baseOnly (optional) True when only the start of the container_class needs
	 * to match with the className (So isContainerClass('IPF', true) will return true when the
	 * actual container_class is 'IPF.Note'). Defaults to false.
	 * @return True when the given className matches the container_class.
	 */
	isContainerClass: function(className, baseOnly)
	{
		return Zarafa.core.ContainerClass.isClass(this.get('container_class'), className, baseOnly);
	}
});
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.IPFStore
 * @extends Zarafa.core.data.MAPIStore
 * @xtype zarafa.ipfstore
 *
 * The {@link Zarafa.core.data.IPFStore IPFStore} represents the collection of MAPIStores.
 */
Zarafa.core.data.IPFStore = Ext.extend(Zarafa.core.data.MAPIStore, {
	/**
	 * @cfg {Boolean} standalone If true, the {@link Zarafa.core.data.IPFStore IPFStore}
	 * will not be hooked into the {@link Zarafa.core.data.IPFStoreMgr IPFStoreMgr}. This will prevent
	 * listening to events coming from the {@link Zarafa.core.data.IPFStoreMgr IPFStoreMgr}.
	 * Defaults to false.
	 */
	standalone: false,
	/**
	 * @cfg {Boolean} serveronly If true, the {@link Zarafa.core.data.IPFStore IPFStore}
	 * will be hooked into the {@link Zarafa.core.data.IPFStoreMgr IPFStoreMgr}, but will
	 * only do that for events which are triggered by a serverside change (e.g. a write
	 * event coming from the server, after a successful save).
	 * Defaults to false.
	 */
	serveronly: false,

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

		this.addEvents(
			/**
			 * @event beforenotify
			 * Event fired before a {@link #onNotify notification} is handled.
			 *
			 * @param {Zarafa.core.data.IPFStore} store The store which fired the event
			 * @param {Zarafa.core.data.Notifications} notification 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 received 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 received.
			 * @return {Boolean} false to cancel the notification handling
			 */
			'beforenotify',
			/**
			 * @event notify
			 * Event fired after a {@link #onNotify notification} is handled.
			 *
			 * @param {Zarafa.core.data.IPFStore} store The store which fired the event
			 * @param {Zarafa.core.data.Notifications} notification 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 received 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 received.
			 */
			'notify'
		);

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

		// Register store with the store manager
		if (!this.standalone) {
			Zarafa.core.data.IPFStoreMgr.register(this, this.serveronly);
		}
	},

	/**
	 * Initialize all events which Zarafa.core.data.IPFStore IPFStore} will listen to.
	 * @private
	 */
	initEvents: function()
	{
		Zarafa.core.data.IPFStore.superclass.initEvents.call(this);

		if (!this.standalone) {
			if (!this.serveronly) {
				Zarafa.core.data.IPFStoreMgr.on('beforerecordsave', this.onExternalSave, this);
			}
			Zarafa.core.data.IPFStoreMgr.on('afterrecordwrite', this.onExternalWrite, this);
		}
	},

	/**
	 * Filter a list of {@link Zarafa.core.data.IPFRecord records} by checking if the record
	 * belongs to this Store. This comparison is based on checking if the entryid of the given
	 * records match the entryid of the records inside the store. If the records are being
	 * {@link Zarafa.core.data.JsonReader#realize realized} by the {@link Zarafa.core.data.JsonReader JsonReader}
	 * then we check if the parent_entryid matches this store.
	 *
	 * What will be returned is an object containing the records as present inside the store,
	 * and the data objects which should be applied to them (the data from the records of the
	 * store that triggered the event).
	 *
	 * @param {Zarafa.core.data.IPFRecord|Array} records The record or records to filter
	 * @param {Ext.data.Api.actions} action The API action for which the updates are looked for.
	 * @return {Object} Object containing the key 'records' containing the array of records inside
	 * this store, and the key 'updatedRecords' containing the array of records which should be
	 * applied to those records.
	 * @private
	 */
	getRecordsForUpdateData: function(records, action)
	{
		var results = { records: [], updatedRecords: [] };

		if (Ext.isDefined(records)) {
			if (!Array.isArray(records)) {
				records = [ records ];
			}

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

			if (action === Ext.data.Api.actions.create) {
				for (var i = 0, len = records.length; i < len; i++) {
					var rec = records[i];
					var storeRec = this.containsStoreInLastLoad(rec.get('store_entryid'));

					// Notify Zarafa.hierarchy.data.MAPIFavoritesSubStore when record/folder is Favorites folder.
					if (rec.isFavoritesFolder()) {
						// Requires separate check here because there are many cases
						// where record was favorites marked but store is not favorites store in that case don't do
						// anything.
						if (isFavoritesStoreInstance) {
							results.records.push(rec.copy());
							results.updatedRecords.push(rec);
						}
					} else if (rec.store !== this && storeRec && !isFavoritesStoreInstance) {
						results.records.push(rec.copy());
						results.updatedRecords.push(rec);
					}
				}
			} else {
				for (var i = 0, len = records.length; i < len; i++) {
					var rec = records[i];
					var storeRec = this.getById(rec.get('entryid'));

					if (storeRec !== this && storeRec && !rec.isFavoritesFolder() && !isFavoritesStoreInstance) {
						results.records.push(storeRec);
						results.updatedRecords.push(rec);
					}
				}
			}
		}

		return results;
	},

	/**
	 * Event handler which is raised when a {@link Zarafa.core.data.IPFRecord IPFRecord} is about
	 * to be saved by a different {@link Zarafa.core.data.IPFStore IPFStore}. However the same
	 * {@link Zarafa.core.data.IPFRecord IPFRecord} may have been listed in multiple
	 * {@link Zarafa.core.data.IPFStore IPFStores} at the same time. Thus check if the saved
	 * {@link Zarafa.core.data.IPFRecord IPFRecord} is also listed in this store, and if that is the
	 * case, update it here as well. This way we don't have to wait until a reload from the
	 * server occurs, and the {@link Zarafa.core.data.IPFRecord IPFRecord} is updated on all stores
	 * instantly.
	 *
	 * @param {Zarafa.core.data.IPFStore} store The {@link Zarafa.core.data.IPFStore store} to which the
	 * {@link Zarafa.core.data.IPFRecord record} belongs.
	 * @param {Object} data An object containing the data that is to be saved.
	 * The object will contain a key for each appropriate action, with an array
	 * of updated data for each record.
	 */
	onExternalSave: function(store, data)
	{
		var result;

		if (store === this) {
			return;
		}

		result = this.getRecordsForUpdateData(data[Ext.data.Api.actions.open], Ext.data.Api.actions.open);
		if (!Ext.isEmpty(result.records)) {
			this.onNotify(Zarafa.core.data.Notifications.objectModified, result.records, result.updatedRecords);
		}

		result = this.getRecordsForUpdateData(data[Ext.data.Api.actions.update], Ext.data.Api.actions.update);
		if (!Ext.isEmpty(result.records)) {
			this.onNotify(Zarafa.core.data.Notifications.objectModified, result.records, result.updatedRecords);
		}

		result = this.getRecordsForUpdateData(data[Ext.data.Api.actions.create], Ext.data.Api.actions.create);
		if (!Ext.isEmpty(result.records)) {
			this.onNotify(Zarafa.core.data.Notifications.objectCreated, result.records, result.updatedRecords);
		}

		result = this.getRecordsForUpdateData(data[Ext.data.Api.actions.destroy], Ext.data.Api.actions.destroy);
		if (!Ext.isEmpty(result.records)) {
			this.onNotify(Zarafa.core.data.Notifications.objectDeleted, result.records, result.updatedRecords);
		}
	},

	/**
	 * Event handler which is raised when a {@link Zarafa.core.data.IPFRecord IPFRecord} has been
	 * updated from the server. This {@link Zarafa.core.data.IPFRecord IPFRecord} may have been listed in
	 * multiple {@link Zarafa.core.data.IPFStore IPFStores} at the same time. Thus we need to
	 * check if this {@link Zarafa.core.data.IPFRecord IPFRecord} is also listed in this store, and if that
	 * is the case, update it here as well. This way we don't have to wait until a reload from the
	 * server occurs for this specific store.
	 *
	 * @param {Zarafa.core.data.IPFStore} store The {@link Zarafa.core.data.IPFStore store} to which the
	 * {@link Zarafa.core.data.IPFRecord record} belongs.
	 * @param {String} action [Ext.data.Api.actions.create|update|destroy|open]
	 * @param {Object} data The 'data' picked-out out of the response for convenience.
	 * @param {Ext.Direct.Transaction} res
	 * @param {Zarafa.core.data.IPFrecord} record The most recent version of the record
	 * which came from the server.
	 */
	onExternalWrite: function(store, action, data, res, record)
	{
		if (store === this) {
			return;
		}

		var obj = {};
		if (Array.isArray(record)) {
			obj[action] = record;
		} else {
			obj[action] = [ record ];
		}

		this.onExternalSave(store, obj);
	},

	/**
	 * Checks whether any of the stores that were included in the parameters during the last load,
	 * matches the supplied entryid argument.
	 *
	 * The IPFStore can currently not load data directly from the server, hence this
	 * function will always return false as it cannot determine which store it is currently
	 * displaying. Subclasses should override this function to return accurate information.
	 *
	 * @param {String|Array} entryidList Entryid of the folder
	 * @return {Boolean} Returns true when entryid matches, false when it does not.
	 */
	containsStoreInLastLoad: function(entryidList)
	{
		return false;
	},

	/**
	 * Notification handler which is called automatically by the
	 * {@link Zarafa.hierarchy.data.HierarchyNotificationResponseHandler NotificationResponseHandler}
	 * when a notification has been received for the hierarchy.
	 *
	 * @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 received 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 received.
	 */
	onNotify: function(action, records, data, timestamp, success)
	{
		if (this.fireEvent('beforenotify', this, action, records, data, timestamp, success) !== false) {
			var handler = this['onNotify' + Ext.util.Format.capitalize(action)];
			if (Ext.isFunction(handler)) {
				handler.call(this, action, records, data, timestamp, success);
			}
			this.fireEvent('notify', this, action, records, data, timestamp, success);
		}
	},

	/**
	 * Destroys the store.
	 */
	destroy: function()
	{
		if (!this.standalone) {
			Zarafa.core.data.IPFStoreMgr.unregister(this, this.serveronly);

			Zarafa.core.data.IPFStoreMgr.un('beforerecordsave', this.onExternalSave, this);
			Zarafa.core.data.IPFStoreMgr.un('afterrecordwrite', this.onExternalWrite, this);
		}
		Zarafa.core.data.IPFStore.superclass.destroy.call(this);
	}
});

Ext.reg('zarafa.ipfstore', Zarafa.core.data.IPFStore);
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.IPMAttachmentProxy
 * @extends Zarafa.core.data.MAPIProxy
 *
 * Special Proxy for the {@link Zarafa.core.data.IPMAttachmentStore Attachment Store}.
 */
Zarafa.core.data.IPMAttachmentProxy = Ext.extend(Zarafa.core.data.MAPIProxy, {
	/**
	 * This will create a {@link Zarafa.core.data.ProxyResponseHandler ProxyResponseHandler} object
	 * which will be used by the {@link Zarafa.core.data.ResponseRouter ResponseRouter} when the
	 * response for the given request has returned.
	 *
	 * @param {String} modulename The modulename which is being accessed with this request
	 * @param {Zarafa.core.Actions} serverAction The action to perform on the server.
	 * @param {Ext.data.Api.action} action name of the action to perform.
	 * @param {Ext.data.Record[]} records list of records to operate on.
	 * @param {Object} parameters object containing user parameters such as range (pagination) information, sorting information, etc.
	 * @param {Ext.data.DataReader} reader data reader. Converts raw JavaScript objects (in our case) to instances of {@link Ext.data.Record}
	 * @param {Function} callback call back function to call when the request has finished successfully.
	 * @param {Object} scope scope for the call back function.
	 * @param {Object} args arguments object. This will be passed to the call back function on successful read.
	 * @return {Object} An instance of the {@link Zarafa.core.data.ProxyResponseHandler ProxyResponseHandler}
	 * which should be used for this request.
	 * @private
	 */
	getResponseHandlerForRequest: function(modulename, serverAction, action, records, parameters, reader, callback, scope, args)
	{
		return new Zarafa.core.data.IPMAttachmentResponseHandler({
			proxy: this,
			action: action,
			reader: reader,
			sendRecords: records,
			options: args,
			callback: callback,
			scope: scope
		});
	},

	/**
	 * @param {Ext.data.Api.action} action name of the action to perform. Either 'create' or 'update'.
	 * @param {Ext.data.Record[]} records list of records to operate on.
	 * @param {Object} parameters object containing user parameters such as range (pagination) information, sorting information, etc.
	 * @param {Ext.data.DataReader} reader data reader. Converts raw JavaScript objects (in our case) to instances of {@link Ext.data.Record}
	 * @param {Function} callback call back function to call when the request has finished successfully.
	 * @param {Object} scope scope for the call back function.
	 * @param {Object} args arguments object. This will be passed to the call back function on successful read.
	 * @private
	 */
	createUpdateAction: function(action, records, parameters, reader, callback, scope, args)
	{
		this.doRequests(args.actionType || Zarafa.core.Actions['upload'], action, records, parameters, reader, callback, scope, args);
	},

	/**
	 * Performs a destroy action on one or more records.
	 * @param {Ext.data.Api.action} action name of the action to perform. Always 'destroy'.
	 * @param {Ext.data.Record[]} records list of records to operate on.
	 * @param {Object} parameters object containing user parameters such as range (pagination) information, sorting information, etc.
	 * @param {Ext.data.DataReader} reader data reader. Converts raw JavaScript objects (in our case) to instances of {@link Ext.data.Record}
	 * @param {Function} callback call back function to call when the request has finished successfully.
	 * @param {Object} scope scope for the call back function.
	 * @param {Object} args arguments object. This will be passed to the call back function on successful read.
	 * @private
	 */
	destroyAction: function(action, records, parameters, reader, callback, scope, args)
	{
		this.doRequests(args.actionType || Zarafa.core.Actions['delete'], action, records, parameters, reader, callback, scope, args);
	},

	/**
	 * Initialize the the {@link Zarafa.core.Request request} structure. The initial
	 * {@link Zarafa.core.Request request} will be reset and new requests will be added
	 * for each individual {@link Zarafa.core.date.MAPIRecord record} which was provided.
	 * @param {Zarafa.core.Actions} serverAction The action to perform on the server.
	 * @param {Ext.data.Api.action} action name of the action to perform.
	 * @param {Ext.data.Record[]} records list of records to operate on.
	 * @param {Object} parameters object containing user parameters such as range (pagination) information, sorting information, etc.
	 * @param {Ext.data.DataReader} reader data reader. Converts raw JavaScript objects (in our case) to instances of {@link Ext.data.Record}
	 * @param {Function} callback call back function to call when the request has finished successfully.
	 * @param {Object} scope scope for the call back function.
	 * @param {Object} args arguments object. This will be passed to the call back function on successful read.
	 * @private
	 */
	doRequests: function(serverAction, action, records, parameters, reader, callback, scope, args)
	{
		var request = container.getRequest();

		var record = records;
		if (Array.isArray(records)) {
			record = records[0];
		}

		// Just initialize based on a single record
		var module = this.getItemModuleName(record);
		var handler = this.getResponseHandlerForRequest(module, serverAction, action, records, parameters, reader, callback, scope, args);

		var data = new FormData();

		// Convert the parameters into FormData
		for (var key in parameters) {
			var value = parameters[key];
			if (Array.isArray(value) || value instanceof FileList) {
				for (var i = 0, len = value.length; i < len; i++) {
					data.append(key + '[]', value[i]);
				}
			} else {
				data.append(key, value);
			}
		}

		request.reset();
		var requestId = request.addDataRequest(module, serverAction, data, handler);

		// Add the module and moduleid to the url
		var requestUrl = args.requestUrl;
		requestUrl = Ext.urlAppend(requestUrl, 'module=' + module);
		requestUrl = Ext.urlAppend(requestUrl, 'moduleid=' + requestId);

		record.requestId = requestId;
		request.send(requestUrl, {});
	}
});
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.IPMExpandDistlistProxy
 * @extends Zarafa.core.data.MAPIProxy
 *
 * The IPMExpandDistlistProxy communicates directly with a ExpandDistlistModule on server.
 * It supports listing member items of a DistList, and is supposed to work in conjunction
 * with a {@link Zarafa.core.data.IPMRecipientStore IPMRecipientStore} and JSON reader.
 */
Zarafa.core.data.IPMExpandDistlistProxy = Ext.extend(Zarafa.core.data.MAPIProxy, {
	/**
	 * @cfg {String} listModuleName Name of the listModule on the server.
	 */
	listModuleName: 'expanddistlistmodule',

	/**
	 * This will create a {@link Zarafa.core.data.ProxyResponseHandler ProxyResponseHandler} object
	 * which will be used by the {@link Zarafa.core.data.ResponseRouter ResponseRouter} when the
	 * response for the given request has returned.
	 *
	 * @param {String} modulename The modulename which is being accessed with this request
	 * @param {Zarafa.core.Actions} serverAction The action to perform on the server.
	 * @param {Ext.data.Api.action} action name of the action to perform.
	 * @param {Ext.data.Record[]} records list of records to operate on.
	 * @param {Object} parameters object containing user parameters such as range (pagination) information, sorting information, etc.
	 * @param {Ext.data.DataReader} reader data reader. Converts raw JavaScript objects (in our case) to instances of {@link Ext.data.Record}
	 * @param {Function} callback call back function to call when the request has finished successfully.
	 * @param {Object} scope scope for the call back function.
	 * @param {Object} args arguments object. This will be passed to the call back function on successful read.
	 * @return {Object} An instance of the {@link Zarafa.core.data.ProxyResponseHandler ProxyResponseHandler}
	 * which should be used for this request.
	 * @private
	 */
	getResponseHandlerForRequest: function(modulename, serverAction, action, records, parameters, reader, callback, scope, args)
	{

		return new Zarafa.core.data.IPMExpandDistlistResponseHandler({
			proxy: this,
			action: Ext.data.Api.actions['read'],
			reader: reader,
			sendRecords: records,
			options: args,
			callback: callback,
			scope: scope
		});
	},

	/**
	 * Performs a request for a store. This will direct the expand action to the readAction method.
	 * @param {Ext.data.Api.action} action name of the action to perform. One of 'create', 'destroy', 'update', and 'read'.
	 * @param {Ext.data.Record[]} records list of records to operate on. In case of 'read' this will be ignored.
	 * @param {Object} parameters object containing user parameters such as range (pagination) information, sorting information, etc.
	 * @param {Ext.data.DataReader} reader data reader. Converts raw JavaScript objects (in our case) to instances of {@link Ext.data.Record}
	 * @param {Function} callback call back function to call when the request has finished successfully.
	 * @param {Object} scope scope for the call back function.
	 * @param {Object} args arguments object. This will be passed to the call back function on successful read.
	 */
	request: function(action, records, parameters, reader, callback, scope, args)
	{
		switch (action)
		{
			case Zarafa.core.Actions['expand']:
				this.readAction(action, records, parameters, reader, callback, scope, args);
				break;
		}
	}
});
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.IPMProxy
 * @extends Zarafa.core.data.MAPIProxy
 *
 * The IPMProxy communicates directly with a ListModule on server. It supports listing items,
 * pagination, etc, and is supposed to work in conjunction with a list store and JSON reader.
 * <p>
 * Each instance of IPMProxy corresponds to a single MAPI folder and needs to be created with
 * a MAPI store id and a MAPI folder id.
 */
Zarafa.core.data.IPMProxy = Ext.extend(Zarafa.core.data.MAPIProxy, {
	/**
	 * This will create a {@link Zarafa.core.data.ProxyResponseHandler ProxyResponseHandler} object
	 * which will be used by the {@link Zarafa.core.data.ResponseRouter ResponseRouter} when the
	 * response for the given request has returned.
	 *
	 * @param {String} modulename The modulename which is being accessed with this request
	 * @param {Zarafa.core.Actions} serverAction The action to perform on the server.
	 * @param {Ext.data.Api.action} action name of the action to perform.
	 * @param {Ext.data.Record[]} records list of records to operate on.
	 * @param {Object} parameters object containing user parameters such as range (pagination) information, sorting information, etc.
	 * @param {Ext.data.DataReader} reader data reader. Converts raw JavaScript objects (in our case) to instances of {@link Ext.data.Record}
	 * @param {Function} callback call back function to call when the request has finished successfully.
	 * @param {Object} scope scope for the call back function.
	 * @param {Object} args arguments object. This will be passed to the call back function on successful read.
	 * @return {Object} An instance of the {@link Zarafa.core.data.ProxyResponseHandler ProxyResponseHandler}
	 * which should be used for this request.
	 * @private
	 */
	getResponseHandlerForRequest: function(modulename, serverAction, action, records, parameters, reader, callback, scope, args)
	{
		return new Zarafa.core.data.IPMResponseHandler({
			proxy: this,
			action: action,
			reader: reader,
			sendRecords: records,
			options: args,
			callback: callback,
			scope: scope
		});
	},

	/**
	 * Implementation of {@link Zarafa.core.data.MAPIProxy.readAction}
	 * @param {Ext.data.Api.action} action name of the action to perform. Always 'read'.
	 * @param {Ext.data.Record[]} records list of records to operate on. In case of 'read' this will be ignored.
	 * @param {Object} parameters object containing user parameters such as range (pagination) information, sorting information, etc.
	 * @param {Ext.data.DataReader} reader data reader. Converts raw JavaScript objects (in our case) to instances of{@link Ext.data.Record}
	 * @param {Function} callback call back function to call when the request has finished successfully.
	 * @param {Object} scope scope for the call back function.
	 * @param {Object} args arguments object. This will be passed to the call back function on successful read.
	 * @private
	 */
	readAction: function(action, records, parameters, reader, callback, scope, args)
	{
		// Extjs puts the pagination into the parameters, move it into the restriction
		if (Ext.isDefined(parameters.start)) {
			parameters.restriction = parameters.restriction || {};
			parameters.restriction.start = parameters.start;
			delete parameters.start;
		}

		// FIXME: This is currently hardcoded by PHP, but it should actually listen
		// to the actual request.
		if (Ext.isDefined(parameters.limit)) {
			parameters.restriction = parameters.restriction || {};
			parameters.restriction.limit = parameters.limit;
			delete parameters.limit;
		}

		// Sorting must be an array
		if (Ext.isDefined(parameters.sort) && !Array.isArray(parameters.sort)) {
			parameters.sort = [{
				field: parameters.sort,
				direction: parameters.dir
			}];
			delete parameters.dir;
		}

		Zarafa.core.data.IPMProxy.superclass.readAction.call(this, action, records, parameters, reader, callback, scope, args);
	}
});
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.IPMRecipientResolveProxy
 * @extends Zarafa.core.data.MAPIProxy
 *
 * The IPMProxy communicates directly with a ListModule on server. It supports listing items,
 * pagination, etc, and is supposed to work in conjunction with a list store and JSON reader.
 * <p>
 * Each instance of IPMProxy corresponds to a single MAPI folder and needs to be created with
 * a MAPI store id and a MAPI folder id.
 */
Zarafa.core.data.IPMRecipientResolveProxy = Ext.extend(Zarafa.core.data.MAPIProxy, {
	/**
	 * @cfg {String} listModuleName Name of the listModule on the server.
	 */
	listModuleName: 'resolvenamesmodule',

	/**
	 * This will create a {@link Zarafa.core.data.ProxyResponseHandler ProxyResponseHandler} object
	 * which will be used by the {@link Zarafa.core.data.ResponseRouter ResponseRouter} when the
	 * response for the given request has returned.
	 *
	 * @param {String} modulename The modulename which is being accessed with this request
	 * @param {Zarafa.core.Actions} serverAction The action to perform on the server.
	 * @param {Ext.data.Api.action} action name of the action to perform.
	 * @param {Ext.data.Record[]} records list of records to operate on.
	 * @param {Object} parameters object containing user parameters such as range (pagination) information, sorting information, etc.
	 * @param {Ext.data.DataReader} reader data reader. Converts raw JavaScript objects (in our case) to instances of {@link Ext.data.Record}
	 * @param {Function} callback call back function to call when the request has finished successfully.
	 * @param {Object} scope scope for the call back function.
	 * @param {Object} args arguments object. This will be passed to the call back function on successful read.
	 * @return {Object} An instance of the {@link Zarafa.core.data.ProxyResponseHandler ProxyResponseHandler}
	 * which should be used for this request.
	 * @private
	 */
	getResponseHandlerForRequest: function(modulename, serverAction, action, records, parameters, reader, callback, scope, args)
	{

		return new Zarafa.core.data.IPMRecipientResolveResponseHandler({
			proxy: this,
			action: Ext.data.Api.actions['read'],
			reader: reader,
			sendRecords: records,
			options: args,
			callback: callback,
			scope: scope
		});
	},

	/**
	 * Performs a request for a store. This will direct the checknames action to the readAction method.
	 * @param {Ext.data.Api.action} action name of the action to perform. One of 'create', 'destroy', 'update', and 'read'.
	 * @param {Ext.data.Record[]} records list of records to operate on. In case of 'read' this will be ignored.
	 * @param {Object} parameters object containing user parameters such as range (pagination) information, sorting information, etc.
	 * @param {Ext.data.DataReader} reader data reader. Converts raw JavaScript objects (in our case) to instances of {@link Ext.data.Record}
	 * @param {Function} callback call back function to call when the request has finished successfully.
	 * @param {Object} scope scope for the call back function.
	 * @param {Object} args arguments object. This will be passed to the call back function on successful read.
	 */
	request: function(action, records, parameters, reader, callback, scope, args)
	{
		switch (action)
		{
			case Zarafa.core.Actions['checknames']:
				this.readAction(action, records, parameters, reader, callback, scope, args);
				break;
		}
	}
});
Ext.namespace('Zarafa.core.data');

Ext.data.Api.actions.open = 'open';

/**
 * @class Zarafa.core.data.IPMStore
 * @extends Zarafa.core.data.MAPIStore
 * @xtype zarafa.ipmstore
 *
 * The {@link Zarafa.core.data.IPMStore IPMStore} represents the collection of messages.
 */
Zarafa.core.data.IPMStore = Ext.extend(Zarafa.core.data.MAPIStore, {
	/**
	 * @cfg {Boolean} standalone If true, the {@link Zarafa.core.data.IPMStore IPMStore}
	 * will not be hooked into the {@link Zarafa.core.data.IPMStoreMgr IPMStoreMgr}. This will prevent
	 * listening to events coming from the {@link Zarafa.core.data.IPMStoreMgr IPMStoreMgr}.
	 * Defaults to false.
	 */
	standalone: false,

	/**
	 * @cfg {Boolean} serveronly If true, the {@link Zarafa.core.data.IPMStore IPMStore}
	 * will be hooked into the {@link Zarafa.core.data.IPMStoreMgr IPMStoreMgr}, but will
	 * only do that for events which are triggered by a serverside change (e.g. a write
	 * event coming from the server, after a successful save).
	 * Defaults to false.
	 */
	serveronly: false,

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

		Ext.applyIf(config, {
			remoteGroup: true
		});

		this.addEvents(
			/**
			 * @event beforenotify
			 * Event fired before a {@link #onNotify notification} is handled.
			 *
			 * @param {Zarafa.core.data.IPMStore} store The store which fired the event
			 * @param {Zarafa.core.data.Notifications} notification 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 received 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 received.
			 * @return {Boolean} false to cancel the notification handling
			 */
			'beforenotify',
			/**
			 * @event notify
			 * Event fired after a {@link #onNotify notification} is handled.
			 *
			 * @param {Zarafa.core.data.IPMStore} store The store which fired the event
			 * @param {Zarafa.core.data.Notifications} notification 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 received 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 received.
			 */
			'notify'
		);

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

		// Register store with the store manager
		if (!this.standalone) {
			Zarafa.core.data.IPMStoreMgr.register(this, this.serveronly);
		}
	},

	/**
	 * Initialize all events which Zarafa.core.data.IPMStore IPMStore} will listen to.
	 * @private
	 */
	initEvents: function()
	{
		Zarafa.core.data.IPMStore.superclass.initEvents.call(this);

		if (!this.standalone) {
			if (!this.serveronly) {
				Zarafa.core.data.IPMStoreMgr.on('beforerecordsave', this.onExternalSave, this);
			}
			Zarafa.core.data.IPMStoreMgr.on('afterrecordwrite', this.onExternalWrite, this);
		}
	},

	/**
	 * Filter a list of {@link Zarafa.core.data.IPMRecord records} by checking if the record
	 * belongs to this Store. This comparison is based on checking if the entryid of the given
	 * records match the entryid of the records inside the store. If the records are being
	 * {@link Zarafa.core.data.JsonReader#realize realized} by the {@link Zarafa.core.data.JsonReader JsonReader}
	 * then we check if the parent_entryid matches this store.
	 *
	 * What will be returned is an object containing the records as present inside the store,
	 * and the data objects which should be applied to them (the data from the records of the
	 * store that triggered the event).
	 *
	 * @param {Zarafa.core.data.IPMRecord|Array} records The record or records to filter
	 * @param {Ext.data.Api.actions} action The API action for which the updates are looked for.
	 * @return {Object} Object containing the key 'records' containing the array of records inside
	 * this store, and the key 'updatedRecords' containing the array of records which should be
	 * applied to those records.
	 * @private
	 */
	getRecordsForUpdateData: function(records, action)
	{
		var results = { records: [], updatedRecords: [] };

		if (Ext.isDefined(records)) {
			if (!Array.isArray(records)) {
				records = [ records ];
			}

			if (action === Ext.data.Api.actions.create) {
				for (var i = 0, len = records.length; i < len; i++) {
					var rec = records[i];
					var storeRec = this.containsFolderInLastLoad(rec.get('parent_entryid'));

					if (rec.store !== this && storeRec) {
						results.records.push(rec.copy());
						results.updatedRecords.push(rec);
					}
				}
			} else {
				for (var i = 0, len = records.length; i < len; i++) {
					var rec = records[i];
					var storeRec = this.findBy(function (record, id) {
						if(rec.equals(record)) {
							return true;
						}
					});

					if(storeRec < 0) {
						continue;
					}

					storeRec = this.getAt(storeRec);

					if (rec.store !== this && storeRec) {
						results.records.push(storeRec);
						results.updatedRecords.push(rec);
					}
				}
			}
		}

		return results;
	},

	/**
	 * Event handler which is raised when a {@link Zarafa.core.data.IPMRecord IPMRecord} is about
	 * to be saved by a different {@link Zarafa.core.data.IPMStore IPMStore}. However the same
	 * {@link Zarafa.core.data.IPMRecord IPMRecord} may have been listed in multiple
	 * {@link Zarafa.core.data.IPMStore IPMStores} at the same time. Thus check if the saved
	 * {@link Zarafa.core.data.IPMRecord IPMRecord} is also listed in this store, and if that is the
	 * case, update it here as well. This way we don't have to wait until a reload from the
	 * server occurs, and the {@link Zarafa.core.data.IPMRecord IPMRecord} is updated on all stores
	 * instantly.
	 *
	 * @param {Zarafa.core.data.IPMStore} store The {@link Zarafa.core.data.IPMStore store} to which the
	 * {@link Zarafa.core.data.IPMRecord record} belongs.
	 * @param {Object} data An object containing the data that is to be saved.
	 * The object will contain a key for each appropriate action, with an array
	 * of updated data for each record.
	 */
	onExternalSave: function(store, data)
	{
		var result;

		if (store === this) {
			return;
		}

		result = this.getRecordsForUpdateData(data[Ext.data.Api.actions.open], Ext.data.Api.actions.open);
		if (!Ext.isEmpty(result.records)) {
			this.onNotify(Zarafa.core.data.Notifications.objectModified, result.records, result.updatedRecords);
		}

		result = this.getRecordsForUpdateData(data[Ext.data.Api.actions.update], Ext.data.Api.actions.update);
		if (!Ext.isEmpty(result.records)) {
			this.onNotify(Zarafa.core.data.Notifications.objectModified, result.records, result.updatedRecords);
		}

		result = this.getRecordsForUpdateData(data[Ext.data.Api.actions.create], Ext.data.Api.actions.create);
		if (!Ext.isEmpty(result.records)) {
			this.onNotify(Zarafa.core.data.Notifications.objectCreated, result.records, result.updatedRecords);
		}

		result = this.getRecordsForUpdateData(data[Ext.data.Api.actions.destroy], Ext.data.Api.actions.destroy);
		if (!Ext.isEmpty(result.records)) {
			this.onNotify(Zarafa.core.data.Notifications.objectDeleted, result.records, result.updatedRecords);
		}
	},

	/**
	 * Event handler which is raised when a {@link Zarafa.core.data.IPMRecord IPMRecord} has been
	 * updated from the server. This {@link Zarafa.core.data.IPMRecord IPMRecord} may have been listed in
	 * multiple {@link Zarafa.core.data.IPMStore IPMStores} at the same time. Thus we need to
	 * check if this {@link Zarafa.core.data.IPMRecord IPMRecord} is also listed in this store, and if that
	 * is the case, update it here as well. This way we don't have to wait until a reload from the
	 * server occurs for this specific store.
	 *
	 * @param {Zarafa.core.data.IPMStore} store The {@link Zarafa.core.data.IPMStore store} to which the
	 * {@link Zarafa.core.data.IPMRecord record} belongs.
	 * @param {String} action [Ext.data.Api.actions.create|update|destroy|open]
	 * @param {Object} data The 'data' picked-out out of the response for convenience.
	 * @param {Ext.Direct.Transaction} res
	 * @param {Zarafa.core.data.IPMrecord} record The most recent version of the record
	 * which came from the server.
	 */
	onExternalWrite: function(store, action, data, res, record)
	{
		if (store === this) {
			return;
		}

		var obj = {};
		if (Array.isArray(record)) {
			obj[action] = record;
		} else {
			obj[action] = [ record ];
		}

		this.onExternalSave(store, obj);
	},

	/**
	 * Checks whether any of the folders 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.
	 */
	containsFolderInLastLoad: function(entryidList)
	{
		if (!this.lastOptions) {
			return false;
		}

		if (!Array.isArray(entryidList)) {
			entryidList = [ entryidList ];
		}

		var folderList = this.lastOptions.folder;
		if (Ext.isDefined(folderList)) {
			if (!Array.isArray(folderList)) {
				folderList = [ folderList ];
			}

			for (var i = 0, len = folderList.length; i < len; i++) {
				for (var j = 0, jlen = entryidList.length; j < jlen; j++) {
					if (Zarafa.core.EntryId.compareEntryIds(folderList[i].get('entryid'), entryidList[j])) {
						return true;
					}
				}
			}
		}

		return false;
	},

	/**
	 * Notification handler which is called automatically by the
	 * {@link Zarafa.core.data.IPMNotificationResponseHandler NotificationResponseHandler}
	 * when a notification has been received for a {@link Zarafa.core.data.IPMRecord record} which
	 * has been loaded by this 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 received 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 received.
	 */
	onNotify: function(action, records, data, timestamp, success)
	{
		if (this.fireEvent('beforenotify', this, action, records, data, timestamp, success) !== false) {
			var handler = this['onNotify' + Ext.util.Format.capitalize(action)];
			if (Ext.isFunction(handler)) {
				handler.call(this, action, records, data, timestamp, success);
			}

			/*
			 * Fire the notify event only for record of this store.
			 * Notify event is only for records, not for store it self.
			 */
			if (!Ext.isEmpty(records) && records[0] instanceof Ext.data.Record) {
				this.fireEvent('notify', this, action, records, data, timestamp, success);
			}
		}
	},

	/**
	 * Notification handler called by {@link #onNotify} when
	 * a {@link Zarafa.core.data.Notifications#objectDeleted objectDeleted}
	 * notification has been received.
	 *
	 * This will remove the message 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 received 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 received.
	 * @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 received.
	 *
	 * This will update the message 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 received 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 received.
	 * @private
	 */
	onNotifyObjectmodified: function(action, records, data, timestamp, success)
	{
		var reloadStore = false;

		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);

			var recordMessageClass = record.get('message_class');
			if (singleData instanceof Ext.data.Record) {
				// whenever message class is changed that means we will need to recreate record as the old record
				// will be having different set of fields, for that we simply reload the store
				if(recordMessageClass !== singleData.get('message_class')) {
					// If changed message class is part of original message class, then original message record
					// will already have all the properties, so for that case we don't need reload
					if(recordMessageClass.indexOf(singleData.get('message_class')) !== 0) {
						reloadStore = true;
					}
				}

				// Merge the changes into the record without using the JSONReader.
				record.applyData(singleData);
				// Commit the changes, it is a merge, so there are no local changes.
				record.commit();
			} else if(Object.keys(singleData).length !== 0) {
				// whenever message class is changed that means we will need to recreate record as the old record
				// will be having different sets of fields, for that we simply reload the store
				var updateMessageClass = singleData['message_class'] || singleData['props']['message_class'];
				if(recordMessageClass !== updateMessageClass) {
					// If changed message class is part of original message class, then original message record
					// will already have all the properties, so for that case we don't need reload
					if(recordMessageClass.indexOf(updateMessageClass) !== 0) {
						reloadStore = true;
					}
				}

				// 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);
		}

		if(reloadStore) {
			this.reload();
		}
	},

	/**
	 * Notification handler called by {@link #onNotify} when
	 * a {@link Zarafa.core.data.Notifications#objectCreated objectCreated}
	 * notification has been received.
	 *
	 * Because it is unknown if the added record must be visible, or where
	 * in the Store the record must be shown, we simply reload the entire
	 * store to get all updates.
	 *
	 * @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 received 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 received.
	 * @private
	 */
	onNotifyObjectcreated: function(action, records, data, timestamp, success)
	{
		// If this notification came at the same time as we reloaded this folder,
		// we already obtained created item, and we don't need to reload.
		if (!timestamp || this.lastExecutionTime(Zarafa.core.Actions['list']) < timestamp) {
			this.reload();
		}
	},

	/**
	 * Destroys the store.
	 */
	destroy: function()
	{
		if (!this.standalone) {
			Zarafa.core.data.IPMStoreMgr.un('afterrecordwrite', this.onExternalWrite, this);
			if (!this.serveronly) {
				Zarafa.core.data.IPMStoreMgr.un('beforerecordsave', this.onExternalSave, this);
			}
			Zarafa.core.data.IPMStoreMgr.unregister(this, this.serveronly);
		}
		Zarafa.core.data.IPMStore.superclass.destroy.call(this);
	}
});

Ext.reg('zarafa.ipmstore', Zarafa.core.data.IPMStore);
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.JsonAttachmentReader
 * @extends Zarafa.core.data.JsonReader
 */
Zarafa.core.data.JsonAttachmentReader = Ext.extend(Zarafa.core.data.JsonReader, {
	/**
	 * @constructor
	 * @param {Object} meta Metadata configuration options.
	 * @param {Object} recordType (optional) Optional Record type matches the type
	 * which must be read from response. If no type is given, it will use the
	 * record type for the {@link Zarafa.core.mapi.ObjectType#MAPI_ATTACH}.
	 */
	constructor: function(meta, recordType)
	{
		meta = Ext.applyIf(meta || {}, {
			id: 'attach_id',
			idProperty: 'attach_id'
		});

		// If no recordType is provided, force the type to be an attachment
		if (!Ext.isDefined(recordType)) {
			recordType = Zarafa.core.data.RecordFactory.getRecordClassByObjectType(Zarafa.core.mapi.ObjectType.MAPI_ATTACH);
		}

		Zarafa.core.data.JsonAttachmentReader.superclass.constructor.call(this, meta, recordType);
	}
});
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.JsonAttachmentWriter
 * @extends Zarafa.core.data.JsonWriter
 */
Zarafa.core.data.JsonAttachmentWriter = Ext.extend(Zarafa.core.data.JsonWriter, {
	/**
	 * Similar to {@link Ext.data.JsonWriter#toHash}
	 *
	 * Convert attachments into a hash. Attachments exists as
	 * {@link Zarafa.core.data.IPMAttachmentRecord IPMAttachmentRecord} within
	 * a {@link Zarafa.core.data.IPMRecord IPMRecord} and thus must be serialized
	 * separately into the hash object.
	 *
	 * @param {Ext.data.Record} record The record to hash
	 * @return {Object} The hashed object
	 * @override
	 * @private
	 */
	toPropHash: function(record)
	{
		var attachmentStore = record.getAttachmentStore();
		var hash = {};

		if (!Ext.isDefined(attachmentStore)) {
			return hash;
		}

		// @FIXME currently only inline attachments are handled,
		// normal attachments will be handled separately

		// Overwrite previous definition to something we can work with.
		hash.attachments = {};
		hash.attachments.dialog_attachments = attachmentStore.getId();

		var modified = attachmentStore.getModifiedRecords();
		var removed = attachmentStore.getRemovedRecords();

		Ext.each(modified, function(attach) {
			if (attach.isInline()) {
				if (!Ext.isDefined(hash.attachments.add)) {
					hash.attachments.add = [];
				}

				// FIXME: serialize?
				var data = attach.data;

				hash.attachments.add.push(data);
			}
		}, this);

		Ext.each(removed, function(attach) {
			if (attach.isInline()) {
				if (!Ext.isDefined(hash.attachments.remove)) {
					hash.attachments.remove = [];
				}

				// FIXME: serialize?
				var data = attach.data;

				hash.attachments.remove.push(data);
			}
		}, this);

		return hash;
	}
});
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.JsonRecipientWriter
 * @extends Zarafa.core.data.JsonWriter
 */
Zarafa.core.data.JsonRecipientWriter = Ext.extend(Zarafa.core.data.JsonWriter, {
	/**
	 * Similar to {@link Ext.data.JsonWriter#toHash}
	 *
	 * Convert recipients into a hash. Recipients exists as
	 * {@link Zarafa.core.data.IPMRecipientRecord IPMRecipientRecord} within
	 * a {@link Zarafa.core.data.IPMRecord IPMRecord} and thus must be serialized
	 * separately into the hash object.
	 *
	 * @param {Ext.data.Record} record The record to hash
	 * @return {Object} The hashed object
	 * @override
	 * @private
	 */
	toPropHash: function(record)
	{
		var recipientStore = record.getRecipientStore();
		var hash = {};

		if (!Ext.isDefined(recipientStore)) {
			return hash;
		}

		// Get list of modified (modified and newly added) records
		var modifiedRecords = recipientStore.getModifiedRecords();
		// Get list of removed records
		var deletedRecords = recipientStore.getRemovedRecords();
		var serializeAllRecipients = Ext.isFunction(record.isUnsent) &&
			record.isUnsent() &&
			!record.phantom &&
			(modifiedRecords.length > 0 || deletedRecords.length > 0);
		var changedRecords = serializeAllRecipients ? recipientStore.getRange() : modifiedRecords;

		if(changedRecords.length > 0 || deletedRecords.length > 0) {
			hash.recipients = {};

			// Existing unsent items need the retained recipient rows as well, otherwise
			// moving one recipient between fields can drop the untouched rows on save.
			for (var i = 0; i < changedRecords.length; i++) {
				var recipient = changedRecords[i];

				// FIXME: serialize?
				var data = recipient.data;

				if(recipient.isMeetingOrganizer()) {
					// organizer information shouldn't be passed in recipient table at all
					continue;
				}

				var isModifiedRecipientInDeletedList = function (item) {
					return item.get('entryid') === recipient.get('entryid') && item.phantom !== recipient.phantom;
				};

				if (deletedRecords.some(isModifiedRecipientInDeletedList)) {
					continue;
				}

				if (Ext.isEmpty(recipient.get('rowid'))) {
					// If recipient does not have a rowid the recipient is new
					if(!Ext.isDefined(hash.recipients.add)) {
						hash.recipients.add = [];
					}

					hash.recipients.add.push(data);
				} else {
					// Recipient already exists and needs to be updated
					if(!Ext.isDefined(hash.recipients.modify)) {
						hash.recipients.modify = [];
					}

					hash.recipients.modify.push(data);
				}
			}

			// Adding the removed records to the remove part of the recipients bit
			for (var i = 0; i < deletedRecords.length; i++) {
				var recipient = deletedRecords[i];
				var data = recipient.data;

				if(recipient.isMeetingOrganizer()) {
					// organizer information shouldn't be passed in recipient table at all
					continue;
				}

				var isDeletedRecipientInModifiedList = function (item) {
					return item.get('entryid') === recipient.get('entryid') && item.phantom !== recipient.phantom;
				};

				if (changedRecords.some(isDeletedRecipientInModifiedList)) {
					continue;
				}

				if (!Ext.isDefined(hash.recipients.remove)) {
					hash.recipients.remove = [];
				}

				hash.recipients.remove.push(data);
			}
		}

		return hash;
	}
});
/**
 * #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);
	}
});
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.ParalyzeReason
 * @extends Zarafa.core.Enum
 * @singleton
 *
 * The various reasons why grommunio Web can be {@link Zararafa.core.Request#paralyze paralyzed}.
 */
Zarafa.core.data.ParalyzeReason = Zarafa.core.Enum.create({
	/**
	 * Denotes that grommunio Web is paralyzed because the browser
	 * window is reloading or otherwise unloading.
	 * @property
	 * @type Number
	 */
	BROWSER_RELOADING: 0,

	/**
	 * Denotes that grommunio Web is paralyzed because the session
	 * has been expired and the user is no longer logged in.
	 * @property
	 * @type Number
	 */
	SESSION_EXPIRED: 1,

	/**
	 * Denotes that grommunio Web is paralyzed because the session
	 * was altered (cookie session id doesn't match expected id)
	 * @property
	 * @type Number
	 */
	SESSION_INVALID: 2
});
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.PresenceStatus
 * @extends Zarafa.core.Enum
 * @singleton
 *
 * The various presence statuses that a user can have.
 * {@link Zarafa.core.PresencePlugin PresencePlugins} should return
 * these statuses when requested through the
 * {@link Zarafa.core.PresencePlugin#getPresence getPresence} function.
 */
Zarafa.core.data.PresenceStatus = Zarafa.core.Enum.create({
	/**
	 * Denotes that the presence status of the user is currently unknown.
	 * @property
	 * @type Number
	 */
	UNKNOWN: 0,

	/**
	 * Denotes that the user is currently active.
	 * @property
	 * @type Number
	 */
	OFFLINE: 1,

	/**
	 * Denotes that the user has not been active for some time
	 * @property
	 * @type Number
	 */
	ONLINE: 2,

	/**
	 * Returns a css class name for the given presence status
	 * @param {Zarafa.core.data.PresenceStatus} presenceStatus The presence status for
	 * which a css class name is requested.
	 */
	getCssClass: function(presenceStatus)
	{
		var name = this.getName(presenceStatus);
		name = name ? name.toLowerCase() : 'unknown';
		return 'zarafa-presence-status-' + name;
	}
});
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.ProxyResponseHandler
 * @extends Zarafa.core.data.AbstractResponseHandler
 *
 * A Simple implementation for a {@link Zarafa.core.data.AbstractResponseHandler ResponseHandler}.
 * This one can only be used by {@link Ext.data.DataProxy proxies} which wish to handle a Response
 * to their Request.
 *
 * This implementation limits itself to firing an {@link Ext.data.DataProxy#exception exception}
 * on error, and calling a callback function when all processing has been completed.
 */
Zarafa.core.data.ProxyResponseHandler = Ext.extend(Zarafa.core.data.AbstractResponseHandler, {
	/**
	 * @cfg {Ext.data.DataProxy} proxy The proxy which send out the Request
	 * for which the Response is being handled by this handler.
	 */
	proxy: undefined,

	/**
	 * @cfg {Ext.data.Reader} reader
	 * Reader that will be used to parse the records
	 */
	reader: undefined,

	/**
	 * @cfg {Ext.data.Api.actions} action The {@link Ext.data.Api#actions action}
	 * type of the Request for which the Response is being handled by this handler.
	 */
	action: undefined,

	/**
	 * @cfg {Function} callback
	 * Callback function that will be called when the handling of the request is done.
	 */
	callback: undefined,

	/**
	 * @cfg {Object} scope
	 * Scope that will be used when calling the callback function.
	 */
	scope: undefined,

	/**
	 * @cfg {Object} options The options as passed to the {@link Ext.data.DataProxy#request request}
	 * function.
	 */
	options: undefined,

	/**
	 * @cfg {Ext.data.Record/Array} sendRecords The {@link Ext.data.Record records} which have
	 * been send to the PHP-side if {@link #action} was a 'write' action
	 * (Ext.data.Api.actions.create|update|destroy)
	 */
	sendRecords: undefined,

	/**
	 * The deserialized results from the Response. Depending on the action (it depends for example
	 * if the {@link Zarafa.core.data.JsonReader JsonReader} is used, and which function is called.
	 * See {@link Zarafa.core.data.JsonReader#readResponse readResponse} and
	 * {@link Zarafa.core.data.JsonReader#readRecords readRecords} for possible return values)
	 * this could be an Array or an {@link Ext.data.Response object}.
	 * @property
	 * @type Mixed
	 */
	receivedRecords: undefined,

	/**
	 * The {@link Date#getTime timestamp} of the date on which the response was received, this
	 * value is provided during {@link #start}.
	 * @property
	 * @type Number
	 */
	receivedTime: undefined,

	/**
	 * The meta data that is received with response that will be sent to the {#callback} function.
	 * this can contain pagination information or search information.
	 * @property
	 * @type Mixed
	 */
	metaData: undefined,

	/**
	 * The handler which is invoked when no valid response was returned
	 * for the Request. This could be the PHP-side returned an invalid object which could not be
	 * parsed by a {@link Ext.data.DataReader DataReader}.
	 * @param {Object} responseObject The raw browser response object (e.g.: XMLHttpRequest)
	 * @param {Object} args (optional) A Javascript error object if the response could not
	 * have been parsed by a {@link Ext.data.DataReader DataReader}.
	 */
	responseFailure: function(responseObject, args)
	{
		if (Ext.isDefined(this.proxy)) {
			// Create the args object containing the sendRecords and the JS Error to be used when
			// handling the exception event
			args = {
				error: args,
				sendRecords: this.sendRecords
			};
			this.proxy.fireEvent('exception', this.proxy, 'response', this.action, this.options, responseObject, args);
		}
	},

	/**
	 * The main handler to begin a Response processing transaction. Checks the whether the {@link #proxy}
	 * is defined and if not returns false to cancel all processing by this handler.
	 * @param {String} moduleName The name of the PHP module from which this response originated.
	 * @param {String} moduleId The unique identifier for the PHP response.
	 * @param {Object} data The entire response object which will be processed during this transaction.
	 * @param {Number} timestamp The {@link Date#getTime timestamp} on which the response was received
	 * @return {Boolean} False when the given data object cannot be handled by this response handler,
	 * and the transaction must be canceled.
	 * @override
	 */
	start: function(moduleName, moduleId, data, timestamp)
	{
		if(!Ext.isDefined(this.proxy)){
			return false;
		}

		// Inform the proxy the response for a given request has been returned.
		if (Ext.isFunction(this.proxy.deleteRequestId)) {
			this.proxy.deleteRequestId(moduleId);
		}

		// Prepare the collectedItems list so the doList is able to handle multiple list actions.
		this.receivedRecords = [];

		// Update the received time
		this.receivedTime = timestamp;

		// Force sendRecords to be an array
		if (!Ext.isEmpty(this.sendRecords) && !Array.isArray(this.sendRecords)) {
			this.sendRecords = [ this.sendRecords ];
		}

		// prepare object to store meta data information
		this.metaData = {};
	},

	/**
	 * The handler for handling the given command from a Response.
	 * @param {Zarafa.core.Actions} action The action which must be executed.
	 * @param {Object} data The response object belonging to the given command.
	 * @return {Boolean} False when action could not be handled successfully. This will
	 * not cancel the transaction itself, but rather causes the 'success' argument for the
	 * {@link #done} function to be false.
	 */
	handle: function(action, data)
	{
		var ret;
		try {
			ret = Zarafa.core.data.ProxyResponseHandler.superclass.handle.call(this, action, data);
		} catch(e) {
			// Create the args object containing the sendRecords and the thrown exception to be used
			// when handling the exception event
			var args = {
				error: e,
				sendRecords: this.sendRecords
			};
			this.proxy.fireEvent('exception', this.proxy, 'response', this.action, this.options, data, args);
			ret = false;
		}

		if (ret !== false) {
			if (this.proxy && Ext.isFunction(this.proxy.updateExecutionTimestamp)) {
				this.proxy.updateExecutionTimestamp(action, this.receivedTime);
			}
		}

		return ret;
	},

	/**
	 * Handles the 'error' response. This means that the Request has failed
	 * due to a problem on the PHP-side. This will fire the {@link Ext.data.DataProxy#exception exception}
	 * event on the {@link #proxy} object.
	 * @param {Object} data The response object belonging to the given command.
	 * @return {Boolean} False when action could not be handled successfully. This will
	 * not cancel the transaction itself, but rather causes the 'success' argument for the
	 */
	doError: function(response)
	{
		// Create the args object containing the sendRecords to be used when handling the exception
		var args = { sendRecords: this.sendRecords };
		response = { error: response };

		this.proxy.fireEvent('exception', this.proxy, 'remote', this.action, this.options, response, args);
		return false;
	},

	/**
	 * The main handler to complete a Response processing transaction. This will
	 * call the {@link #callback} function with the required arguments.
	 * @param {Boolean} success True if no errors were returned from the PHP-side.
	 * @override
	 */
	done: function(success)
	{
		if (Ext.isFunction(this.callback)) {
			// @FIXME do we have better way to pass meta data to callback function ?
			this.callback.call(this.scope, this.receivedRecords, this.options, success, this.metaData);
		}
	},

	/**
	 * Reads all {@link Zarafa.core.data.MAPIRecord records} from the response data from the server,
	 * and correlates them to the {@link #sendRecords} list.
	 * @param {Object} response The response data from the server containing the
	 * {@link Zarafa.core.data.MAPIRecord records}
	 * @private
	 */
	correlateRecordFromResponse: function(response)
	{
		var responseObj = this.reader.readResponse(Ext.data.Api.actions.read, response);
		var sendRecords = this.sendRecords.clone();
		var records = [];

		if (!Ext.isEmpty(responseObj.data) && !Ext.isEmpty(sendRecords)) {
			for (var i = 0, len = responseObj.data.length; i < len; i++) {
				for (var j = 0, len2 = sendRecords.length; j < len2; j++) {
					if (this.compareResponseDataToRecord(responseObj.data[i], sendRecords[j])) {
						records.push(responseObj.data[i]);
						sendRecords.splice(j, 1);
						break;
					}
				}
			}
		}

		return records;
	},

	/**
	 * Used by {@link #correlateRecordFromResponse} to determine if the given item data
	 * matches the record which was send to the server. This is used to determine if the
	 * given data can be applied to the record.
	 * @param {Object} data The response data
	 * @param {Zarafa.core.data.MAPIRecord} record The record which was send to the server
	 * @protected
	 */
	compareResponseDataToRecord: function(data, record)
	{
		// FIXME: Record comparison by entryid alone is not sufficient.
		// FIXME: probably first we should check type of entryid and then use appropriate compare functions
		return record.phantom || Zarafa.core.EntryId.compareEntryIds(data.entryid, record.get('entryid'));
	},

	/**
	 * Reads all {@link Zarafa.core.data.MAPIRecord records} from the response data from the server.
	 * @param {Object} response The response data from the server containing the
	 * {@link Zarafa.core.data.MAPIRecord records}
	 * @param {String} field The fieldname of the array of items in the Response object which
	 * must be converted into {@link Zarafa.core.data.MAPIRecord records}.
	 * @return {Zarafa.core.data.MAPIRecord[]} The {@link Zarafa.core.data.MAPIRecord records}
	 * read from the server response.
	 * @private
	 */
	readRecordsFromResponse: function(response, field)
	{
		var items = response[field] || [];
		if (!Array.isArray(items)) {
			items = [ items ];
		}

		// Item count is the number of items in the server-side store, not the number of items in
		// the returned record list. This is used for pagination.
		var itemCount = response.page ? response.page.totalrowcount : items.length;

		var o = { count: itemCount };
		// Add 'field' property to response data, this will fix this.getRoot(o) in JSONReader
		o[field] = items;
		if (response.error) {
			o.error = response.error;
		}

		// Use the reader to turn the raw JavaScript objects into a set of Ext.data.Record instances.
		return this.reader.readRecords(o);
	}
});
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.RecordCustomObjectType
 * @extends Zarafa.core.Enum
 *
 * Extension to the MAPI definitions for the
 * {@link Zarafa.core.data.ObjectType}. This can be used
 * by plugins to register new Object Types which can be
 * handled by the {@link Zarafa.core.data.RecordFactory}.
 *
 * New types should be registered using {@link #addProperty}
 * to register their new type, after which the value can be
 * used with the {@link Zarafa.core.data.RecordFactory} to
 * configure the {@link Zarafa.core.data.RecordFactory#addFieldToCustomType fields}
 * for example.
 *
 * @singleton
 */
Zarafa.core.data.RecordCustomObjectType = Zarafa.core.Enum.create({
	/**
	 * Denotes the BASE value from where to start counting
	 * new Custom Type. Because the Custom Types will be used
	 * by the {@link Zarafa.core.data.RecordFactory RecordFactory}
	 * together with {@link Zarafa.core.mapi.ObjectType ObjectType}
	 * the values in this enumeration should be higher then the
	 * highest value of the ObjectType enumeration.
	 * @property
	 * @type Number
	 */
	BASE_TYPE: 1000
});
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.ShadowProxy
 * @extends Zarafa.core.data.MAPIProxy
 *
 * The ShadowProxy is an extension of the {@link Zarafa.core.data.MAPIProxy MAPIProxy}, The
 * {@link Zarafa.core.data.ShadowProxy ShadowProxy} works by dynamically detecting the
 * names for the listmodule and itemmodule by which we are communication with the
 * PHP side. The names are determined based on the {@link Zarafa.core.data.IPMRecords records}
 * which are being send to the server. This implies that the proxy only works while
 * directly working with {@link Zarafa.core.data.IPMRecord records}, as such listing items
 * is not possible. For that purpose the {@link Zarafa.core.data.IPMProxy} must be
 * used with the corresponding list module names.
 */
Zarafa.core.data.ShadowProxy = Ext.extend(Zarafa.core.data.MAPIProxy, {
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			listModuleName: Zarafa.core.ModuleNames.getListName('IPM.Note', true),
			itemModuleName: Zarafa.core.ModuleNames.getItemName('IPM.Note', true)
		});

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

	/**
	 * This will create a {@link Zarafa.core.data.ProxyResponseHandler ProxyResponseHandler} object
	 * which will be used by the {@link Zarafa.core.data.ResponseRouter ResponseRouter} when the
	 * response for the given request has returned.
	 * @param {String} modulename The modulename which is being accessed with this request
	 * @param {Zarafa.core.Actions} serverAction The action to perform on the server.
	 * @param {Ext.data.Api.action} action name of the action to perform.
	 * @param {Ext.data.Record[]} records list of records to operate on.
	 * @param {Object} parameters object containing user parameters such as range (pagination) information, sorting information, etc.
	 * @param {Ext.data.DataReader} reader data reader. Converts raw JavaScript objects (in our case) to instances of {@link Ext.data.Record}
	 * @param {Function} callback call back function to call when the request has finished successfully.
	 * @param {Object} scope scope for the call back function.
	 * @param {Object} args arguments object. This will be passed to the call back function on successful read.
	 * @return {Object} An instance of the {@link Zarafa.core.data.ProxyResponseHandler ProxyResponseHandler}
	 * which should be used for this request.
	 * @private
	 */
	getResponseHandlerForRequest: function(modulename, serverAction, action, records, parameters, reader, callback, scope, args)
	{
		var objectType;
		var ResponseHandler;

		if (Array.isArray(records)) {
			objectType = records[0].get('object_type');
		} else {
			objectType = records.get('object_type');
		}

		switch (objectType) {
			case Zarafa.core.mapi.ObjectType.MAPI_ABCONT:
			case Zarafa.core.mapi.ObjectType.MAPI_MAILUSER:
			case Zarafa.core.mapi.ObjectType.MAPI_DISTLIST:
				ResponseHandler = Zarafa.addressbook.AddressBookResponseHandler;
				break;
			case Zarafa.core.mapi.ObjectType.MAPI_STORE:
			case Zarafa.core.mapi.ObjectType.MAPI_FOLDER:
				ResponseHandler = Zarafa.hierarchy.data.HierarchyResponseHandler;
				break;
			default:
				ResponseHandler = Zarafa.core.data.IPMResponseHandler;
				break;
		}

		return new ResponseHandler({
			proxy: this,
			action: action,
			reader: reader,
			sendRecords: records,
			options: args,
			callback: callback,
			scope: scope
		});
	},

	/**
	 * Implementation of {@link Zarafa.core.data.MAPIProxy#getListModuleName} which returns
	 * the listModuleName for the given {@link Zarafa.core.data.IPMRecord record}. If no moduleName
	 * could be detected, it defaults to the configured {@link #listModuleName}.
	 * @param {Zarafa.core.data.IPMRecord} record the record for which the listModuleName is requested
	 * @return {String} the listModuleName
	 * @private
	 */
	getListModuleName: function(record)
	{
		var objectType = record.get('object_type');
		var moduleName;

		switch (objectType) {
			case Zarafa.core.mapi.ObjectType.MAPI_ABCONT:
			case Zarafa.core.mapi.ObjectType.MAPI_MAILUSER:
			case Zarafa.core.mapi.ObjectType.MAPI_DISTLIST:
				moduleName = Zarafa.core.ModuleNames.getListName('addressbook');
				break;
			case Zarafa.core.mapi.ObjectType.MAPI_STORE:
			case Zarafa.core.mapi.ObjectType.MAPI_FOLDER:
				moduleName = Zarafa.core.ModuleNames.getListName('hierarchy');
				break;
			default:
				var messageClass = record.get('message_class');
				if (!Ext.isEmpty(messageClass)) {
					moduleName = Zarafa.core.ModuleNames.getListName(messageClass, true);
				}
				break;
		}

		return moduleName || this.listModuleName;
	},

	/**
	 * Implementation of {@link Zarafa.core.data.MAPIProxy#getItemModuleName} which returns
	 * the itemModuleName for the given {@link Zarafa.core.data.IPMRecord record}. If no moduleName
	 * could be detected, it defaults to the configured {@link #itemModuleName}.
	 * @param {Zarafa.core.data.IPMRecord} record the record for which the itemModuleName is requested
	 * @return {String} the itemModuleName
	 * @private
	 */
	getItemModuleName: function(record)
	{
		var objectType = record.get('object_type');
		var moduleName;

		switch (objectType) {
			case Zarafa.core.mapi.ObjectType.MAPI_ABCONT:
			case Zarafa.core.mapi.ObjectType.MAPI_MAILUSER:
			case Zarafa.core.mapi.ObjectType.MAPI_DISTLIST:
				moduleName = Zarafa.core.ModuleNames.getItemName('addressbook');
				break;
			case Zarafa.core.mapi.ObjectType.MAPI_STORE:
			case Zarafa.core.mapi.ObjectType.MAPI_FOLDER:
				moduleName = Zarafa.core.ModuleNames.getListName('hierarchy');
				break;
			default:
				var messageClass = record.get('message_class');
				if (!Ext.isEmpty(messageClass)) {
					moduleName = Zarafa.core.ModuleNames.getItemName(messageClass, true);
				}
				break;
		}

		return moduleName || this.itemModuleName;
	}
});
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.ShadowStore
 * @extends Zarafa.core.data.MAPIStore
 * @xtype zarafa.shadowstore
 *
 * A store which holds all items which are being created or edited within a {@link Zarafa.core.ui.ContentPanel}
 * This store only contains references of {@link Zarafa.core.data.IPMRecord} elements which have
 * been retrieved from the server by a regular {@link Zarafa.core.data.ListModuleStore}.
 * <p>
 * Each {@link Zarafa.core.ui.ContentPanel} will register the {@link Zarafa.core.data.MAPIRecord} on which it is working
 * to this {@link Zarafa.core.data.ShadowStore}
 *
 * A store that communicates with a list module on the php side. It supports listing items,
 * pagination, etc.
 * <p>
 * Pagination is not properly supported since there is no way to pass the desired page size
 * to the server side. Therefore the page size has to be hard-coded to 50 items.
 */
Zarafa.core.data.ShadowStore = Ext.extend(Zarafa.core.data.MAPIStore, {
	/**
	 * @cfg {Boolean} standalone If true, the {@link Zarafa.core.data.ShadowStore ShadowStore}
	 * will not be hooked into the {@link Zarafa.core.data.IPMStoreMgr IPMStoreMgr} and
	 * {@link Zarafa.core.data.IPFStoreMgr IPFStoreMgr}. This will prevent listening to events
	 * coming from the {@link Zarafa.core.data.IPMStoreMgr IPMStoreMgr} or
	 * {@link Zarafa.core.data.IPFStoreMgr IPFStoreMgr}.
	 * Defaults to false.
	 */
	standalone: false,

	/**
	 * @cfg {Boolean} serveronly If true, the {@link Zarafa.core.data.ShadowStore ShadowStore}
	 * will be hooked into the {@link Zarafa.core.data.IPMStoreMgr IPMStoreMgr} and
	 * {@link Zarafa.core.data.IPFStoreMgr IPFStoreMgr}, but will only do that for events which
	 * are triggered by a serverside change (e.g. a write event coming from the server, after a successful save).
	 * Defaults to false.
	 */
	serveronly: true,

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

		Ext.applyIf(config, {
			batch: false,
			proxy: new Zarafa.core.data.ShadowProxy(),
			writer: new Zarafa.core.data.JsonWriter(),
			reader: new Zarafa.core.data.JsonReader()
		});

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

		// Register store with the store manager
		if (!this.standalone) {
			Zarafa.core.data.IPMStoreMgr.register(this, this.serveronly);
			Zarafa.core.data.IPFStoreMgr.register(this, this.serveronly);
		}
	},

	/**
	 * For the ShadowStore it is possible that any record can be entered
	 * into this store multiple times. To ensure that the {@link #data MixedCollecton}
	 * will not become upset when adding and removing those records, we
	 * generate a fresh unique for each new record which will be added
	 * to the store. The ID generation has been taken from {@link Ext.data.Record#id}.
	 * @param {Ext.data.Record} o The record for which the key is requested
	 * @return {String} The key by which the record must be saved into the {@link Ext.util.MixedCollection}.
	 * @protected
	 */
	getRecordKey: function(o)
	{
		var objectKey;

		// First search if the given record is already present
		// in this store, and by which key it was added.
		this.eachKey(function(key, item) {
			if (item === o) {
				objectKey = key;
				return false;
			}
		});

		// either return the foud key, or generate the new ID
		return objectKey || [Ext.data.Record.PREFIX, '-', Ext.data.Record.AUTO_ID++].join('');
	},

	/**
	 * remap record ids in MixedCollection after records have been realized.
	 * @see Store#onCreateRecords, @see DataReader#realize
	 * Because in {@link #getRecordKey} we always generate a new ID for the record,
	 * remapping is not needed (and even hurts the mappings in this store}. Hence
	 * we simply delete the {@link Ext.data.Record#_phid} and don't do anything
	 * else.
	 * @param {Ext.data.Record} record The record which was realized
	 * @protected
	 */
	reMap: function(record)
	{
		if (Array.isArray(record)) {
			for (var i = 0, len = record.length; i < len; i++) {
				this.reMap(record[i]);
			}
		} else {
			delete record._phid;
		}
	},

	/**
	 * Remove Records from the Store and fires the {@link #remove} event.
	 * @param {Ext.data.Record/Ext.data.Record[]} record The record object or array of records to remove from the cache.
	 * @param {Boolean} silent [false] Defaults to <tt>false</tt>. Set <tt>true</tt> to not fire remove event.
	 */
	remove: function(record, silent)
	{
		// FIXME: This function is completely copied from
		// Ext.data.store.remove(). Is there a nicer way we can
		// add the 'silent' argument without blunt code copying?
		if(Array.isArray(record)){
			Ext.each(record, function(r){
				this.remove(r, silent);
			}, this);

			return;
		}

		var index = this.data.indexOf(record);
		if(index > -1){
			record.join(null);
			this.data.removeAt(index);
		}

		if(this.pruneModifiedRecords){
			this.modified.remove(record);
		}
		if(this.snapshot){
			this.snapshot.remove(record);
		}
		if(index > -1 && silent !== true){
			this.fireEvent('remove', this, record, index);
		}
	},

	/**
	 * Remove a Record from the Store at the specified index. Fires the {@link #remove} event.
	 * @param {Number} index The index of the record to remove.
	 * @param {Boolean} silent [false] Defaults to <tt>false</tt>. Set <tt>true</tt> to not fire remove event.
	 */
	removeAt: function(index, silent)
	{
		this.remove(this.getAt(index), silent);
	},

	/**
	 * Checks whether any of the folders that were included in the parameters during the last load,
	 * matches the supplied entryid argument.
	 *
	 * Implemented for the {@link Zarafa.core.data.IPMStoreMgr IPMStoreMgr} which calls this function
	 * on {@link Zarafa.core.data.IPMStore IPMStores} to determine if a given folder has been loaded.
	 * Since the ShadowStore will never load data from the server, this function always returns false.
	 *
	 * @param {String|Array} entryidList Entryid of the folder
	 * @return {Boolean} Returns true when entryid matches, false when it does not.
	 */
	containsFolderInLastLoad: function(entryidList)
	{
		return false;
	},

	/**
	 * Checks whether any of the stores that were included in the parameters during the last load,
	 * matches the supplied entryid argument.
	 *
	 * Implemented for the {@link Zarafa.core.data.IPFStoreMgr IPFStoreMgr} which calls this function
	 * on {@link Zarafa.core.data.IPFStore IPFStores} to determine if a given store has been loaded.
	 * Since the ShadowStore will never load data from the server, this function always returns false.
	 *
	 * @param {String|Array} entryidList Entryid of the folder
	 * @return {Boolean} Returns true when entryid matches, false when it does not.
	 */
	containsStoreInLastLoad: function(entryidList)
	{
		return false;
	},

	/**
	 * Destroys the store.
	 */
	destroy: function()
	{
		if (!this.standalone) {
			Zarafa.core.data.IPMStoreMgr.unregister(this, this.serveronly);
			Zarafa.core.data.IPFStoreMgr.unregister(this, this.serveronly);
		}
		Zarafa.core.data.IPMStore.superclass.destroy.call(this);
	}
});

Ext.reg('zarafa.shadowstore', Zarafa.core.data.ShadowStore);
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.SharedComponentType
 * @extends Zarafa.core.Enum
 *
 * Used in the bidSharedComponent method in the {@link Zarafa.core.Container Container}
 * to indicate what type of component is requested by the initiator of the bidding round.
 *
 * @singleton
 */
Zarafa.core.data.SharedComponentType = Zarafa.core.Enum.create({
	/**
	 * A dialog that creates a new record/message
	 * @property
	 * @type Number
	 */
	'common.create': 1,

	/**
	 * A dialog that views a record
	 * @property
	 * @type Number
	 */
	'common.view': 2,

	/**
	 * A whole previewpanel
	 * @property
	 * @type Number
	 */
	'common.preview': 3,

	/**
	 * A context menu
	 * @property
	 * @type Number
	 */
	'common.contextmenu': 4,

	/**
	 * A dialog that views search result
	 */
	'common.search': 5,

	/**
	 * A hover card
	 * @property
	 * @type Number
	 */
	'common.hovercard': 6
});

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

/**
 * @class Zarafa.core.data.UIFactoryBrowserWindowLayer
 * @extends Zarafa.core.data.UIFactoryLayer
 *
 * This layer supports placing {@link Zarafa.core.ui.ContentPanel Content Panels}
 * inside a separate browser window.
 */
Zarafa.core.data.UIFactoryBrowserWindowLayer = Ext.extend(Zarafa.core.data.UIFactoryLayer, {
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			type: 'separateWindows',
			index: 20,
			plugins: [ 'zarafa.contentlayerplugin' ]
		});

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

	/**
	 * The create function which is invoked when a component needs to be added to the Container using
	 * this Layer.
	 * @param {Function} component The constructor of the component which has to be created in the container layer.
	 * @param {Object} config The configuration object which must be
	 * passed to the constructor when creating the component
	 * @protected
	 */
	create: function(component, config)
	{
		// Generate unique name to assign to a separate window, this unique name will be then
		// extensively used by the BrowserWindowMgr
		var uniqueWindowName = Ext.id(null, 'WebApp'+ new Date().getTime());

		var url = container.getBaseURL();
		url = Ext.urlAppend(url, 'load=separate_window');

		if ( Ext.isDefined(window.deskappOpenWindow) ){
			// DeskApp needs to open the external window itself because we want to open a new DeskApp window
			// and not a new browser window. So we must check if we are using DeskApp or not. We will do so by
			// checking if the function deskappOpenWindow exists, because this gets injected in grommunio Web by
			// DeskApp.
			var options = {
				width: config.width || 950,
				height: config.height || 600
			};
			var me = this;
			window.deskappOpenWindow(url, function(separateWindowInstance){
				if ( separateWindowInstance ){
					separateWindowInstance.window.name = uniqueWindowName;
					separateWindowInstance.window.deskappWindow = separateWindowInstance;

					me.registerNewlyCreatedWindow(separateWindowInstance.window, component, config);
				}
			}, options);
		} else {
			// If record is already opened then switch focus to the browser window.
			var browserWindow = Zarafa.core.BrowserWindowMgr.getOpenedWindow(config.record);
			if (browserWindow) {
				browserWindow.focus();
			} else {
				// Create and open a separate browser window.
				var separateWindowInstance = window.open(url, uniqueWindowName, this.getBrowserWindowPosition(config));
				this.registerNewlyCreatedWindow(separateWindowInstance, component, config);
			}
		}
	},

	/**
	 * A helper function which registers newly created window if we get the instance of the same.
	 * @param {Object} separateWindowInstance Newly created browser window object.
	 * @param {Function} component The constructor of the component which has to be created in the container layer.
	 * @param {Object} config The configuration object which must be
	 * passed to the constructor when creating the component
	 */
	registerNewlyCreatedWindow: function(separateWindowInstance, component, config)
	{
		if (separateWindowInstance) {
			Zarafa.core.BrowserWindowMgr.register(separateWindowInstance, component, config);
		} else {
			// Seems popup is blocked, just remember that it is blocked
			Zarafa.core.BrowserWindowMgr.isPopupsBlocked = true;

			// Keep the inner components and config details of blocked popouts,
			// So that, this can be rendered in case if user manually allows blocked popup windows.
			Zarafa.core.BrowserWindowMgr.blockedPopupsContent.push({
				component: component,
				config: config
			});
		}
	},

	/**
	 * A helper function which prepare a config string dynamically to render popout window properly in a stacked manner.
	 * The first opened window will be centered over grommunio Web main window. The following windows will be stacked
	 * over the previous opened window.
	 *
	 * @param {Object} config A configuration object for the window that should be created. It can contain the
	 * following properties:
	 * - width
	 * - height
	 * - toolbar
	 * - location
	 * - status
	 * - menubar
	 * - scrollbars
	 *
	 * See {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/open} for more information on these
	 * properties.
	 */
	getBrowserWindowPosition: function(config)
	{
		var availableBrowserWindows = Zarafa.core.BrowserWindowMgr.browserWindows;
		var browserWindowHeight = config.width || 600;
		var browserWindowWidth = config.height || 950;
		var left;
		var top;

		if(Ext.isDefined(availableBrowserWindows)) {
			if(availableBrowserWindows.getCount() === 1) {
				var win = availableBrowserWindows.itemAt(0);
				left = win.screenX + (win.outerWidth - browserWindowWidth)/2;
				top = win.screenY + (win.outerHeight - browserWindowHeight)/2;
			} else {
				var lastBrowserWindow = availableBrowserWindows.last();
				left = lastBrowserWindow.screenX;
				top = lastBrowserWindow.screenY;
				left += 30;
				top += 30;
			}
		}

		var toolbar = config.toolbar && config.toolbar !== 'no' ? 'yes' : 'no';
		var location = config.location && config.location !== 'no' ? 'yes' : 'no';
		var status = config.status && config.status !== '0' ? 1: 0;
		var menubar = config.menubar && config.menubar !== '0' ? 1: 0;
		var scrollbars = config.scrollbars && config.scrollbars !== '0' ? 1: 0;
		var resizable = config.resizable && config.resizable !== '0' ? 1: 0;

		return (
			'toolbar=' + toolbar + ', ' +
			'location=' + location +', ' +
			'status=' + status + ', ' +
			'menubar=' +menubar +', ' +
			'scrollbars=' + scrollbars + ', ' +
			'resizable=' + resizable + ', ' +
			'width=' + browserWindowWidth + ', ' +
			'height=' + browserWindowHeight + ', ' +
			'top=' + top + ', ' +
			'left=' + left
		);
	}
});

Zarafa.core.data.UIFactory.registerLayer(new Zarafa.core.data.UIFactoryBrowserWindowLayer());
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.UIFactoryTabLayer
 * @extends Zarafa.core.data.UIFactoryLayer
 *
 * This layer supports placing {@link Zarafa.core.ui.ContentPanel Content Panels}
 * to be placed inside a {@link Ext.TabPanel} instance.
 */
Zarafa.core.data.UIFactoryTabLayer = Ext.extend(Zarafa.core.data.UIFactoryLayer, {
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			type: 'tabs',
			index: 1,
			allowModal: false,
			plugins: [ 'zarafa.contenttablayerplugin' ]
		});

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

	/**
	 * The create function which is invoked when a component needs to be added to the Container using
	 * this Layer.
	 * @param {Function} Constructor The constructor of the component which has to be created in the container layer.
	 * @param {Object} config The configuration object which must be
	 * passed to the constructor when creating the component
	 * @protected
	 */
	create: function(component, config)
	{
		config = config || {};

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

		var tabContainer = new component(config);
		container.getTabPanel().add(tabContainer);
	}
});

Zarafa.core.data.UIFactory.registerLayer(new Zarafa.core.data.UIFactoryTabLayer());
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.UIFactoryWindowLayer
 * @extends Zarafa.core.data.UIFactoryLayer
 *
 * This layer supports placing {@link Zarafa.core.ui.ContentPanel Content Panels}
 * to be placed inside a {@link Ext.Window} instance.
 */
Zarafa.core.data.UIFactoryWindowLayer = Ext.extend(Zarafa.core.data.UIFactoryLayer, {
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			type: 'dialogs',
			index: 10,
			allowModal: true,
			manager: Ext.WindowMgr,
			plugins: [ 'zarafa.contentwindowlayerplugin' ]
		});

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

	/**
	 * The create function which is invoked when a component needs to be added to the Container using
	 * this Layer.
	 * @param {Function} Constructor The constructor of the component which has to be created in the container layer.
	 * @param {Object} config The configuration object which must be
	 * passed to the constructor when creating the component
	 * @protected
	 */
	create: function(component, config)
	{
		Ext.applyIf(config, {
			statefulRelativeDimensions: false
		});

		var panel = new component(config);

		var sm = container.getSettingsModel();
		var stateWidth = sm.get('zarafa/v1/state/' + panel.getStateName() + '/width');
		if ( stateWidth && stateWidth<1 ){
			// dimensions are stored relatively, but we changed it to absolutely,
			// so we will delete these state settings and recreate the panel with
			// the default size.
			sm.remove('zarafa/v1/state/' + panel.getStateName() + '/width');
			sm.remove('zarafa/v1/state/' + panel.getStateName() + '/height');
			panel = new component(config);
		}

		var windowCfg = {
			modal: panel.modal,
			manager: panel.manager,
			iconCls: Ext.isDefined(config.iconCls) ? config.iconCls : '',
			constrainHeader: true,
			resizable: Ext.isDefined(panel.resizable)? panel.resizable : true,
			minimizable: Ext.isDefined(panel.minimizable)? panel.minimizable : false,
			closable: config.closable,
			headerCfg: {
				tag: 'div',
				cls: 'zarafa-window-header x-window-header x-unselectable x-window-draggable'
			},
			layout: 'fit',
			items: [
				panel
			],

			listeners: {
				// We want to give fade effect when subjecttext is longer than window width
				// NOTE: Removing this breaks logon when webmeetings is enabled
				'afterrender': function(win) {
					if (Ext.isDefined(win.header) && Ext.isElement(win.header.dom)) {
						Ext.DomHelper.append(win.header.dom, "<span class='fade'>&nbsp;</span>");
					}
				}
			},
			title: panel.title
		};

		// If the component has listeners defined add them to the containing window
			Ext.applyIf(windowCfg.listeners, config.listeners);

		if(panel.closeAction) {
			// add a custom closeAction config to properly fire close events when closing
			// dialogs using closeAction config
			windowCfg.closeAction = panel.closeAction;
			windowCfg[panel.closeAction] = panel[panel.closeAction].createDelegate(panel);
		}

		// Get active focus element before opening the window,
		// So we will set focus on that element after the window close
		var activeElement = document.activeElement;
		var window = new Ext.Window(windowCfg);
		window.on('close', this.onWindowClose.createDelegate(this, [activeElement]));

		// Verify if window should open on main browser window then,
		// Render it on main browser window first.
		if (panel.forceFullyOpenInMainWindow && !Zarafa.core.BrowserWindowMgr.isMainWindowActive()) {
			window.render(Zarafa.core.BrowserWindowMgr.getMainBrowserWindowBody());
		}

		window.show();
	},

	/**
	 * Event handler for the close event of the {@link Ext.Window}
	 * It will set the focus on element which was previously focused before window open
	 * @param {Ext.Element} activeElement which was previously focused
   */
	onWindowClose: function(activeElement)
	{
		if (activeElement) {
			activeElement.focus();
		}
	}
});

Zarafa.core.data.UIFactory.registerLayer(new Zarafa.core.data.UIFactoryWindowLayer());
Ext.namespace('Zarafa.core.mapi');

/**
 * @class Zarafa.core.mapi.Access
 * @extends Zarafa.core.Enum
 * 
 * Enumerates the different access flags
 * 
 * @singleton
 */
Zarafa.core.mapi.Access = Zarafa.core.Enum.create({
	/**
	 * Denotes that write access is given
	 * @property
	 * @type Number
	 */
	ACCESS_MODIFY					: 0x00000001,

	/**
	 * Denotes that read access is given
	 * @property
	 * @type Number
	 */
	ACCESS_READ 					: 0x00000002,

	/**
	 * Denotes that delete access is given
	 * @property
	 * @type Number
	 */
	ACCESS_DELETE					: 0x00000004,

	/**
	 * Denotes that access is given to create subfolders in the folder hierarchy
	 * @property
	 * @type Number
	 */
	ACCESS_CREATE_HIERARCHY			: 0x00000008,

	/**
	 * Denotes that access is given to create content messages
	 * @property
	 * @type Number
	 */
	ACCESS_CREATE_CONTENTS			: 0x00000010,

	/**
	 * Denotes that access is given to create associated content messages
	 * @property
	 * @type Number
	 */
	ACCESS_CREATE_ASSOCIATED		: 0x00000020
});Ext.namespace('Zarafa.core.mapi');

/**
 * @class Zarafa.core.mapi.AppointmentAuxiliaryFlags
 * @extends Zarafa.core.Enum
 *
 * Enumerates the different bit field that describes the auxiliary state of the object.
 *
 * @singleton
 */
Zarafa.core.mapi.AppointmentAuxiliaryFlags = Zarafa.core.Enum.create({
	/**
	 * This flag indicates that the calendar object was copied from another calendar folder.
	 *
	 * @property
	 * @type Number
	 */
	auxApptFlagCopied: 0x00000001,

	/**
	 * This flag on a meeting request indicates that the client or server should send a
	 * meeting response back to the organizer when a response is chosen.
	 *
	 * @property
	 * @type Number
	 */
	auxApptFlagForceMtgResponse: 0x00000002,

	/**
	 * This flag on a meeting request indicates that it was forwarded (including being forwarded
	 * by the organizer), rather than being an invitation from the organizer.
	 *
	 * @property
	 * @type Number
	 */
	auxApptFlagForwarded: 0x00000004
});
Ext.namespace('Zarafa.core.mapi');

/**
 * @class Zarafa.core.mapi.AppointmentLabels
 * @extends Zarafa.core.Enum
 *
 * Enumerates the different appointment labels
 *
 * @singleton
 */
Zarafa.core.mapi.AppointmentLabels = Zarafa.core.Enum.create({

	/**
	 * Default label for appointments
	 *
	 * @property
	 * @type Number
	 */
	NONE: 0,

	/**
	 * Mark appointment as important
	 *
	 * @property
	 * @type Number
	 */
	IMPORTANT: 1,

	/**
	 * Mark appointment as work
	 *
	 * @property
	 * @type Number
	 */
	WORK: 2,

	/**
	 * Mark appointment as personal
	 *
	 * @property
	 * @type Number
	 */
	PERSONAL: 3,

	/**
	 * Mark appointment as holiday
	 *
	 * @property
	 * @type Number
	 */
	HOLIDAY: 4,

	/**
	 * Mark appointment as required
	 *
	 * @property
	 * @type Number
	 */
	REQUIRED: 5,

	/**
	 * Mark appointment as travel required
	 *
	 * @property
	 * @type Number
	 */
	TRAVEL_REQUIRED: 6,

	/**
	 * Mark appointment as prepare required
	 *
	 * @property
	 * @type Number
	 */
	PREPARATION_REQUIRED: 7,

	/**
	 * Mark appointment as birthday
	 *
	 * @property
	 * @type Number
	 */
	BIRTHDAY: 8,

	/**
	 * Mark appointment as special day
	 *
	 * @property
	 * @type Number
	 */
	SPECIAL_DATE: 9,

	/**
	 * Mark appointment as phone interview
	 *
	 * @property
	 * @type Number
	 */
	PHONE_INTERVIEW: 10,

	/**
	 * Return the display name for the given Appointment label
	 * @param {Zarafa.core.mapi.AppointmentLabels} label The given appointment label
	 * @return {String} The display name for the appointment label
	 */
	getDisplayName: function(label)
	{
		switch(label) {
			case Zarafa.core.mapi.AppointmentLabels.NONE:
				return _("None");
			case Zarafa.core.mapi.AppointmentLabels.IMPORTANT:
				return _("Important");
			case Zarafa.core.mapi.AppointmentLabels.WORK:
				return _("Work");
			case Zarafa.core.mapi.AppointmentLabels.PERSONAL:
				return _("Personal");
			case Zarafa.core.mapi.AppointmentLabels.HOLIDAY:
				return _("Holiday");
			case Zarafa.core.mapi.AppointmentLabels.REQUIRED:
				return _("Required");
			case Zarafa.core.mapi.AppointmentLabels.TRAVEL_REQUIRED:
				return _("Travel Required");
			case Zarafa.core.mapi.AppointmentLabels.PREPARATION_REQUIRED:
				return _("Preparation Required");
			case Zarafa.core.mapi.AppointmentLabels.BIRTHDAY:
				return _("Birthday");
			case Zarafa.core.mapi.AppointmentLabels.SPECIAL_DATE:
				return _("Special Date");
			case Zarafa.core.mapi.AppointmentLabels.PHONE_INTERVIEW:
				return _("Phone Interview");
		}
	}
});
Ext.namespace('Zarafa.core.mapi');

/**
 * @class Zarafa.core.mapi.AttachMethod
 * @extends Zarafa.core.Enum
 *
 * @singleton
 */
Zarafa.core.mapi.AttachMethod = Zarafa.core.Enum.create({
	/**
	 * Denotes that the attachment has just been created.
	 * @property
	 * @type Number
	 */
	NO_ATTACHMENT: 0,

	/**
	 * Denotes that the PR_ATTACH_DATA_BIN (PidTagAttachDataBinary) property contains the attachment data.
	 * @property
	 * @type Number
	 */
	ATTACH_BY_VALUE: 1,

	/**
	 * Denotes that the PR_ATTACH_PATHNAME (PidTagAttachPathname) or PR_ATTACH_LONG_PATHNAME (PidTagAttachLongPathname)
	 * property contains a fully-qualified path identifying the attachment to recipients with access to
	 * a common file server.
	 * @property
	 * @type Number
	 */
	ATTACH_BY_REFERENCE: 2,

	/**
	 * Denotes that the PR_ATTACH_PATHNAME or PR_ATTACH_LONG_PATHNAME property contains
	 * a fully-qualified path identifying the attachment.
	 * @property
	 * @type Number
	 */
	ATTACH_BY_REF_RESOLVE: 3,

	/**
	 * Denotes that the PR_ATTACH_PATHNAME or PR_ATTACH_LONG_PATHNAME property contains
	 * a fully-qualified path identifying the attachment.
	 * @property
	 * @type Number
	 */
	ATTACH_BY_REF_ONLY: 4,

	/**
	 * Denotes that the PR_ATTACH_DATA_OBJ (PidTagAttachDataObject) property contains an
	 * embedded object that supports the IMessage interface.
	 * @property
	 * @type Number
	 */
	ATTACH_EMBEDDED_MSG: 5,

	/**
	 * Denotes that the attachment is an embedded OLE object.
	 * @property
	 * @type Number
	 */
	ATTACH_OLE: 6
});Ext.namespace('Zarafa.core.mapi');

/**
 * @class Zarafa.core.mapi.BusyStatus
 * @extends Zarafa.core.Enum
 *
 * Enumerates the different busy status types.
 *
 * @singleton
 */
Zarafa.core.mapi.BusyStatus = Zarafa.core.Enum.create({
	/**
	 * Denotes that the owner/participants don't have freebusy information for the
	 * given time.
	 * @property
	 * @type Number
	 */
	UNKNOWN: -1,

	/**
	 * Denotes that the owner/participants is/are free for the duration of the appointment.
	 * @property
	 * @type Number
	 */
	FREE: 0,

	/**
	 * Denotes that the appointment has been tentatively accepted.
	 * @property
	 * @type Number
	 */
	TENTATIVE: 1,

	/**
	 * Denotes that the owner/participants is/are busy for the duration of the appointment.
	 * @property
	 * @type Number
	 */
	BUSY: 2,

	/**
	 * Denotes that the owner/participants is/are out of office for the duration of the appointment.
	 * @property
	 * @type Number
	 */
	OUTOFOFFICE: 3,

	/**
	 * Denotes that the owner/participants is/are working elsewhere for the duration of the appointment.
	 * @property
	 * @type Number
	 */
	WORKINGELSEWHERE: 4,

	/**
	 * Denotes that there is no data regarding the owner/participants status for the duration of appointment.
	 * @property
	 * @type Number
	 */
	BLUR: 5,

	/**
	 * Return the display name for the given busy status
	 * @param {Zarafa.core.mapi.BusyStatus} busystatus The given busy Status
	 * @return {String} The display name for the busy status
	 */
	getDisplayName: function(busystatus)
	{
		switch (busystatus) {
			case Zarafa.core.mapi.BusyStatus.UNKNOWN:
				return _('Unknown');
			case Zarafa.core.mapi.BusyStatus.FREE:
				return _('Free');
			case Zarafa.core.mapi.BusyStatus.TENTATIVE:
				return _('Tentative');
			case Zarafa.core.mapi.BusyStatus.BUSY:
				return _('Busy');
			case Zarafa.core.mapi.BusyStatus.OUTOFOFFICE:
				return _('Out of Office');
			case Zarafa.core.mapi.BusyStatus.WORKINGELSEWHERE:
				return _('Working Elsewhere');
			case Zarafa.core.mapi.BusyStatus.BLUR:
				return _('No Information');
		}
	}
});
Ext.namespace('Zarafa.core.mapi');

/**
 * @class Zarafa.core.mapi.DisplayType
 * @extends Zarafa.core.Enum
 *
 * Enumerates the different Display types.
 *
 * @singleton
 */
Zarafa.core.mapi.DisplayType = Zarafa.core.Enum.create({
	/*********** For address book contents tables ***********/

	/**
	 * Denotes a typical messaging user.
	 * @property
	 * @type Number
	 */
	DT_MAILUSER: 0x00000000,

	/**
	 * Denotes a distribution list.
	 * @property
	 * @type Number
	 */
	DT_DISTLIST: 0x00000001,

	/**
	 * Denotes a forum, such as a bulletin board service or a public or shared folder.
	 * @property
	 * @type Number
	 */
	DT_FORUM: 0x00000002,

	/**
	 * Denotes an automated agent, such as Quote-Of-The-Day or a weather chart display.
	 * @property
	 * @type Number
	 */
	DT_AGENT: 0x00000003,

	/**
	 * Denotes a special alias defined for a large group, such as helpdesk, accounting, or blood-drive coordinator.
	 * @property
	 * @type Number
	 */
	DT_ORGANIZATION: 0x00000004,

	/**
	 * Denotes a private, personally administered distribution list.
	 * @property
	 * @type Number
	 */
	DT_PRIVATE_DISTLIST: 0x00000005,

	/**
	 * Denotes a recipient known to be from a foreign or remote messaging system.
	 * @property
	 * @type Number
	 */
	DT_REMOTE_MAILUSER: 0x00000006,

	/*********** For folder hierarchy tables ***********/

	/**
	 * Display default folder icon adjacent to folder.
	 * @property
	 * @type Number
	 */
	DT_FOLDER: 0x01000000,

	/**
	 * Display default folder link icon adjacent to folder rather than the default folder icon.
	 * @property
	 * @type Number
	 */
	DT_FOLDER_LINK: 0x02000000,

	/**
	 * Display icon for a folder with an application-specific distinction, such as a special type of public folder.
	 * @property
	 * @type Number
	 */
	DT_FOLDER_SPECIAL: 0x04000000,

	/*********** For address book hierarchy tables ***********/

	/**
	 * The container should be denoted as modifiable in the user interface.
	 * @property
	 * @type Number
	 */
	DT_MODIFIABLE: 0x00010000,

	/**
	 * Denotes a global address book.
	 * @property
	 * @type Number
	 */
	DT_GLOBAL: 0x00020000,

	/**
	 * Denotes a local address book that you share with a small workgroup.
	 * @property
	 * @type Number
	 */
	DT_LOCAL: 0x00030000,

	/**
	 * Denotes a wide area network address book.
	 * @property
	 * @type Number
	 */
	DT_WAN: 0x00040000,

	/**
	 * Does not match any of the other settings.
	 * @property
	 * @type Number
	 */
	DT_NOT_SPECIFIC: 0x00050000
});
Ext.namespace('Zarafa.core.mapi');

/**
 * @class Zarafa.core.mapi.DisplayTypeEx
 * @extends Zarafa.core.Enum
 *
 * @singleton
 */
Zarafa.core.mapi.DisplayTypeEx = Zarafa.core.Enum.create({
	/**
	 * Denotes a flag for remote user/group.
	 * @property
	 * @type Number
	 */
	DTE_FLAG_REMOTE_VALID: 0x80000000,

	/**
	 * Denotes a flag for permission user/group.
	 * @property
	 * @type Number
	 */
	DTE_FLAG_ACL_CAPABLE: 0x40000000,

	/**
	 * ???
	 * @property
	 * @type Number
	 */
	DTE_MASK_REMOTE: 0x0000FF00,

	/**
	 * ???
	 * @property
	 * @type Number
	 */
	DTE_MASK_LOCAL: 0x000000FF,

	/**
	 * Denotes a room as a resource
	 * @property
	 * @type Number
	 */
	DT_ROOM	: 0x00000007,

	/**
	 * Denotes an equipment as a resource
	 * @property
	 * @type Number
	 */
	DT_EQUIPMENT: 0x00000008,

	/**
	 * Denotes a permission group
	 * @property
	 * @type Number
	 */
	DT_SEC_DISTLIST: 0x00000009,

	/**
	 * Function to check if any DisplayTypeEx value denotes a remote user.
	 * @param {Number} value value of DisplayTypeEx to check.
	 * @return {Boolean} Returns true if value denotes a remote user else false.
	 */
	DTE_IS_REMOTE_VALID: function(value)
	{
		if(!Ext.isNumber(value)) {
			return false;
		}

		return !!(value & Zarafa.core.mapi.DisplayTypeEx.DTE_FLAG_REMOTE_VALID);
	},

	/**
	 * Function to check if any DisplayTypeEx value denotes a user/groups that can have permissions.
	 * @param {Number} value value of DisplayTypeEx to check.
	 * @return {Boolean} Returns true if value denotes a user/groups that can have permissions else false.
	 */
	DTE_IS_ACL_CAPABLE: function(value)
	{
		if(!Ext.isNumber(value)) {
			return false;
		}

		return !!(value & Zarafa.core.mapi.DisplayTypeEx.DTE_FLAG_ACL_CAPABLE);
	},

	/**
	 * Function to strip {@link Zarafa.core.mapi.DisplayTypeEx#DTE_MASK_REMOTE} flag from DisplayTypeEx property value.
	 * @param {Number} value value of DisplayTypeEx.
	 * @return {Number} Returns value of DisplayTypeEx property value after removing
	 * {@link Zarafa.core.mapi.DisplayTypeEx#DTE_MASK_REMOTE} flag.
	 */
	DTE_REMOTE: function(value)
	{
		if(!Ext.isNumber(value)) {
			return value;
		}

		return ((value & Zarafa.core.mapi.DisplayTypeEx.DTE_MASK_REMOTE) >> 8);
	},

	/**
	 * Function to strip {@link Zarafa.core.mapi.DisplayTypeEx#DTE_MASK_LOCAL} flag from DisplayTypeEx property value.
	 * @param {Number} value value of DisplayTypeEx.
	 * @return {Number} Returns value of DisplayTypeEx property value after removing
	 * {@link Zarafa.core.mapi.DisplayTypeEx#DTE_MASK_LOCAL} flag.
	 */
	DTE_LOCAL: function(value)
	{
		if(!Ext.isNumber(value)) {
			return value;
		}

		return (value & Zarafa.core.mapi.DisplayTypeEx.DTE_MASK_LOCAL);
	}
});Ext.namespace('Zarafa.core.mapi');

/**
 * @class Zarafa.core.mapi.DistlistType
 * @extends Zarafa.core.Enum
 *
 * @singleton
 */
Zarafa.core.mapi.DistlistType = Zarafa.core.Enum.create({

	/**
	 * Denotes contact members in distlist from contact folders.
	 * @property
	 * @type Number
	 */
	'DL_USER': 0x000000C3,

	/**
	 * Denotes contact members in distlist from contact folders.
	 * @property
	 * @type Number
	 */
	'DL_USER2': 0x000000D3,

	/**
	 * Denotes contact members in distlist from contact folders.
	 * @property
	 * @type Number
	 */
	'DL_USER3': 0x000000E3,

	/**
	 * Denotes External members in distlist.
	 * @property
	 * @type Number
	 */
	'DL_EXTERNAL_MEMBER': 0x00000000,

	/**
	 * Denotes distribution list from contact folders.
	 * @property
	 * @type Number
	 */
	'DL_DIST': 0x000000B4,

	/**
	 * Denotes addressbook users.
	 * @property
	 * @type Number
	 */
	'DL_USER_AB': 0x000000B5,

	/**
	 * Denotes addressbook user groups.
	 * @property
	 * @type Number
	 */
	'DL_DIST_AB': 0x000000B6
});

Ext.namespace("Zarafa.core.mapi");

/**
 * @class Zarafa.core.mapi.FlagStatus
 * @extends Zarafa.core.Enum
 * @singleton
 */
Zarafa.core.mapi.FlagStatus = Zarafa.core.Enum.create({

	/**
	 * @property
	 * @type Number
	 * Indicates that the flag status property is cleared (neither flagged or completed).
	 */
	cleared: 0,

	/**
	 * @property
	 * @type Number
	 * Indicates that an item is completed.
	 */
	completed: 1,

	/**
	 * @property
	 * @type Number
	 * Indicates that an item is flagged.
	 */
	flagged: 2

});

/**
 * @class Zarafa.core.mapi.FlagIcon
 * @extends Zarafa.core.Enum
 * @singleton
 */
Zarafa.core.mapi.FlagIcon = Zarafa.core.Enum.create({
	/**
	 * @property
	 * @type Number
	 * The item should not be displayed with any flag.
	 */
	clear: 0,

	/**
	 * @property
	 * @type Number
	 * The item should be displayed with a purple flag.
	 */
	purple: 1,

	/**
	 * @property
	 * @type Number
	 * The item should be displayed with a orange flag.
	 */
	orange: 2,

	/**
	 * @property
	 * @type Number
	 * The item should be displayed with a green flag.
	 */
	green: 3,

	/**
	 * @property
	 * @type Number
	 * The item should be displayed with a yellow flag.
	 */
	yellow: 4,

	/**
	 * @property
	 * @type Number
	 * The item should be displayed with a blue flag.
	 */
	blue: 5,

	/**
	 * @property
	 * @type Number
	 * The item should be displayed with a red flag.
	 */
	red: 6

});
Ext.namespace('Zarafa.core.mapi');

/**
 * @class Zarafa.core.mapi.FlavorFlags
 * @extends Zarafa.core.Enum
 *
 * Enumerates possible 'flavor' values for RuleActions.
 * The exact value depends on the selected
 * {@link Zarafa.core.mapi.RuleActions Rule Action}.
 *
 * @singleton
 */
Zarafa.core.mapi.FlavorFlags = Zarafa.core.Enum.create({
	/**
	 * Flavor for {@link Zarafa.core.mapi.RuleActions#OP_FORWARD OP_FORWARD}
	 * which indicates that the original sender must be preserved.
	 * @property
	 * @type Number
	 */
	FWD_PRESERVE_SENDER: 1,

	/**
	 * Flavor for {@link Zarafa.core.mapi.RuleActions#OP_FORWARD OP_FORWARD}.
	 * Forwards the message without making any changes to the message
	 * @property
	 * @type Number
	 */
	FWD_DO_NOT_MUNGE_MSG: 2,

	/**
	 * Flavor for {@link Zarafa.core.mapi.RuleActions#OP_FORWARD OP_FORWARD}
	 * which indicates that the messages is forwarded as attachment.
	 * @property
	 * @type Number
	 */
	FWD_AS_ATTACHMENT: 4
});
Ext.namespace('Zarafa.core.mapi');

/**
 * @class Zarafa.core.mapi.FolderExtendedFlags
 * @extends Zarafa.core.Enum
 *
 * @singleton
 */
Zarafa.core.mapi.FolderExtendedFlags = Zarafa.core.Enum.create({
	/**
	 * Denotes default implementation
	 * @property
	 * @type Number
	 */
	DEFAULT: 0,

	/**
	 * Denotes that the unread count should be used
	 * @property
	 * @type Number
	 */
	USE_UNREAD_COUNT: 1,

	/**
	 * Denotes that the total count should be used
	 * @property
	 * @type Number
	 */
	USE_TOTAL_COUNT: 3
});Ext.namespace("Zarafa.core.mapi");

/**
 * @class Zarafa.core.mapi.IconIndex
 * @extends Zarafa.core.Enum
 *
 * Enumerates all Icon Index values
 *
 * @singleton
 */
Zarafa.core.mapi.IconIndex = Zarafa.core.Enum.create({
	// mail
	'mail_stubbed': 0x00000002,
	'mail_read': 0x00000100,
	'mail_unread': 0x00000101,
	'mail_submitted': 0x00000102,
	'mail_unsent': 0x00000103,
	'mail_receipt': 0x00000104,
	'mail_replied': 0x00000105,
	'mail_forwarded': 0x00000106,
	'mail_remote': 0x00000107,
	'mail_delivery_receipt': 0x00000108,
	'mail_read_receipt': 0x00000109,
	'icon_mail_report_ndr': 0x0000010a,
	'mail_nonread_receipt': 0x0000010b,
	'mail_recall_s': 0x0000010c,
	'mail_recall_f': 0x0000010d,
	'mail_tracking': 0x0000010e,
	'mail_oof': 0x0000011b,
	'mail_recall': 0x0000011c,
	'mail_tracked': 0x00000130,

	// contact
	'contact_user': 0x00000200,
	'contact_distlist': 0x00000202,

	// sticky notes
	'note_blue': 0x00000300,
	'note_green': 0x00000301,
	'note_pink': 0x00000302,
	'note_yellow': 0x00000303,
	'note_white': 0x00000304,

	// appointment
	'appt_appointment': 0x00000400,
	'appt_recurring': 0x00000401,
	'calendar_mr': 0x00000402,
	'appt_meeting_recurring': 0x00000403,
	'appt_meeting_request': 0x00000404,
	'appt_meeting_accept': 0x00000405,
	'appt_meeting_decline': 0x00000406,
	'appt_meeting_tentative': 0x00000407,
	'appt_meeting_cancel': 0x00000408,
	'appt_meeting_outofdate': 0x00000409,
	'appt_meeting_forward': 0x0000040b,

	// task
	'task': 0x00000500,
	'task_recurring': 0x00000501,
	'task_assignee': 0x00000502,
	'task_assigned': 0x00000503,
	'task_declined': 0x00000506,

	// journal
	'journal_conversation': 0x00000601,
	'journal_document': 0x00000612,
	'journal_email': 0x00000602,
	'journal_fax': 0x00000609,
	'journal_letter': 0x0000060c,
	'journal_meeting': 0x00000613,
	'journal_meeting_cancel': 0x00000614,
	'journal_meeting_request': 0x00000603,
	'journal_meeting_response': 0x00000604,
	'journal_access': 0x00000610,
	'journal_excel': 0x0000060e,
	'journal_powerpoint': 0x0000060f,
	'journal_word': 0x0000060d,
	'journal_note': 0x00000608,
	'journal_phone_call': 0x0000060a,
	'journal_remote_session': 0x00000615,
	'journal_task': 0x0000060b,
	'journal_task_request': 0x00000606,
	'journal_task_response': 0x00000607,
	'journal_other': 0x00000003,

	/**
	 * Gets icon class based on icon index value
	 * @param {Number} id icon index value
	 * @param {String} prefix prefix to add before class name
	 * @return {String} icon class
	 */
	getClassName: function(id, prefix)
	{
		if (Ext.isEmpty(prefix)) {
			prefix = 'icon';
		}

		// only allow numbers
		if(!Ext.isNumber(id)) {
			id = parseInt(id, 10);
		}

		if (!Ext.isNumber(id)) {
			return '';
		}

		var className = this.getName(id);

		if(!Ext.isEmpty(className)) {
			return prefix + '_' + className;
		}

		return '';
	}
});
Ext.namespace('Zarafa.core.mapi');

/**
 * @class Zarafa.core.mapi.Priority
 * @extends Zarafa.core.Enum
 *
 * @singleton
 */
Zarafa.core.mapi.Importance = Zarafa.core.Enum.create({
	/**
	 * Denotes that the message has low importance.
	 * @property
	 * @type Number
	 */
	'NONURGENT': 0x00000000,

	/**
	 * Denotes that the message has normal importance.
	 * @property
	 * @type Number
	 */
	'NORMAL': 0x00000001,

	/**
	 * Denotes that the message has high importance.
	 * @property
	 * @type Number
	 */
	'URGENT': 0x00000002,

	/**
	 * Return the display name for the given importance
	 * @param {Zarafa.core.mapi.Importance} importance The given importance
	 * @return {String} The display name for the importance
	 */
	getDisplayName: function(importance)
	{
		switch (importance) {
			case Zarafa.core.mapi.Importance.NONURGENT:
				return _('Low');
			case Zarafa.core.mapi.Importance.NORMAL:
				return _('Normal');
			case Zarafa.core.mapi.Importance.URGENT:
				return _('High');
		}
		return '';
	},

	/**
	 * Gets icon class based on importance level
	 * @param {Number} level importance level
	 * @param {String} prefix prefix to add before class name
	 * @return {String} icon class
	 */
	getClassName: function(level, prefix)
	{
		if(Ext.isEmpty(prefix)) {
			prefix = 'icon_importance';
		}

		// only allow numbers
		if(!Ext.isNumber(level)) {
			level = parseInt(level, 10);
		}

		// invalid values should be handled as normal priority
		if(!Ext.isNumber(level)) {
			return prefix + '_normal';
		}

		var className = this.getName(level).toLowerCase();

		if(!Ext.isEmpty(className)) {
			return prefix + '_' + className;
		}

		return prefix + '_normal';
	}
});
Ext.namespace('Zarafa.core.mapi');

/**
 * @class Zarafa.core.mapi.MAPIFolderType
 * @extends Zarafa.core.Enum
 *
 * Enumerates that indicates the folder type.
 * @singleton
 */
Zarafa.core.mapi.MAPIFolderType = Zarafa.core.Enum.create({
	/**
	 * The root folder of the folder hierarchy table,
	 * that is, a folder that has no parent folder.
	 * @property
	 * @type Number
	 */
	FOLDER_ROOT: 0x00000000,

	/**
	 * A generic folder that contains messages and other folders.
	 * @property
	 * @type Number
	 */
	FOLDER_GENERIC: 0x00000001,

	/**
	 * A folder that contains the results of a search,
	 * in the form of links to messages that meet search criteria.
	 * @property
	 * @type Number
	 */
	FOLDER_SEARCH: 0x00000002
});
Ext.namespace('Zarafa.core.mapi');

/**
 * @class Zarafa.core.mapi.MDBProvider
 * @extends Zarafa.core.Enum
 * 
 * Enumerates the different store types
 * 
 * @singleton
 */
Zarafa.core.mapi.MDBProvider = Zarafa.core.Enum.create({
	/**
	 * Denotes that the MAPI Store is a default store.
	 * @property
	 * @type String
	 */
	ZARAFA_SERVICE_GUID			: '5494a1c0297f101ba58708002b2a2517',
	/**
	 * Denotes that the MAPI Store is a public store.
	 * @property
	 * @type String
	 */
	ZARAFA_STORE_PUBLIC_GUID		: '78b2fa70aff711cd9bc800aa002fc45a',
	/**
	 * Denotes that the MAPI Store is a delegated store.
	 * @property
	 * @type String
	 */
	ZARAFA_STORE_DELEGATE_GUID		: '9eb4770074e411ce8c5e00aa004254e2',
	/**
	 * Denotes that the MAPI Store is a archive store.
	 * @property
	 * @type String
	 */
	ZARAFA_STORE_ARCHIVER_GUID		: 'ad5389bc3f2e72419404896ff459870f'
});
Ext.namespace('Zarafa.core.mapi');

/**
 * @class Zarafa.core.mapi.MeetingStatus
 * @extends Zarafa.core.Enum
 *
 * @singleton
 */
Zarafa.core.mapi.MeetingStatus = Zarafa.core.Enum.create({
	/**
	 * Denotes that the message is no meeting
	 * @property
	 * @type Number
	 */
	NONMEETING	: 0,

	/**
	 * Denotes that the message is a meeting
	 * @property
	 * @type Number
	 */
	MEETING		: 1,

	/**
	 * Denotes that the meeting was received by the recipients
	 * @property
	 * @type Number
	 */
	MEETING_RECEIVED	: 3,

	/**
	 * Denotes that the meeting is canceled
	 * @property
	 * @type Number
	 */
	MEETING_CANCELED	: 5,
	/**
	 * Denotes that the scheduled meeting has been canceled but still appears on the user's calendar.
	 * @property
	 * @type Number
	 */
	MEETING_RECEIVED_AND_CANCELED	: 7,

	/**
	 * Return the display name for the given Meeting Status
	 * @param {Zarafa.core.mapi.MeetingStatus} meetingstatus The given meeting status
	 * @return {String} The display name for the meeting status
	 */
	getDisplayName: function(meetingstatus)
	{
		switch (meetingstatus) {
			case Zarafa.core.mapi.MeetingStatus.NONMEETING:
				return '';
			case Zarafa.core.mapi.MeetingStatus.MEETING:
				return _('Meeting');
			case Zarafa.core.mapi.MeetingStatus.MEETING_RECEIVED:
				return _('Received');
			case Zarafa.core.mapi.MeetingStatus.MEETING_CANCELED:
			case Zarafa.core.mapi.MeetingStatus.MEETING_RECEIVED_AND_CANCELED:
				return _('Canceled');
		}
		return '';
	}
});
Ext.namespace('Zarafa.core.mapi');

/**
 * @class Zarafa.core.mapi.MeetingType
 * @extends Zarafa.core.Enum
 * 
 * @singleton
 */
Zarafa.core.mapi.MeetingType = Zarafa.core.Enum.create({
	/**
	 * Denotes that the client has not set this property for the message.
	 * @property
	 * @type Number
	 */
	MEETING_NONE	: 0x00000000,

	/**
	 * Denotes that the message is a new meeting request.
	 * @property
	 * @type Number
	 */
	MEETING_REQUEST	: 0x00000001,

	/**
	 * Denotes that the message is a full update of a prior meeting request.
	 * @property
	 * @type Number
	 */
	MEETING_FULL_UPDATE	: 0x00010000,		// 65536

	/**
	 * Denotes that the message is an informational update of a prior meeting request.
	 * @property
	 * @type Number
	 */
	MEETING_INFO_UPDATE	: 0x00020000,		// 131072

	/**
	 * Denotes that the message is out-of-date and a more recent update has been sent to the recipient.
	 * @property
	 * @type Number
	 */
	MEETING_OUT_OF_DATE	: 0x00080000,		// 524288

	/**
	 * Denotes that the message is a copy of a meeting request,
	 * or a copy of an update of a prior meeting request received by the principal recipient.
	 * The original meeting request or the update has been sent to a delegate of the principal.
	 * @property
	 * @type Number
	 */
	MEETING_DELEGATED	: 0x00100000		// 1048576
});Ext.namespace('Zarafa.core.mapi');

/**
 * @class Zarafa.core.mapi.Sensitivity
 * @extends Zarafa.core.Enum
 *
 * @singleton
 */
Zarafa.core.mapi.MessageFlags = Zarafa.core.Enum.create({
	/**
	 * Denotes that the message is marked as having been read.
	 * @property
	 * @type Number
	 */
	MSGFLAG_READ: 0x00000001,

	/**
	 * Denotes that the outgoing message has not been modified since the first time that it was saved,
	 * the incoming message has not been modified since it was delivered.
	 * @property
	 * @type Number
	 */
	MSGFLAG_UNMODIFIED: 0x00000002,

	/**
	 * Denotes that the message is marked for sending as a result of a call to IMessage::SubmitMessage.
	 * @property
	 * @type Number
	 */
	MSGFLAG_SUBMIT: 0x00000004,

	/**
	 * Denotes that the message is saved but has not been sent.
	 * @property
	 * @type Number
	 */
	MSGFLAG_UNSENT: 0x00000008,

	/**
	 * Denotes that the message has at least one attachment.
	 * @property
	 * @type Number
	 */
	MSGFLAG_HASATTACH: 0x00000010,

	/**
	 * Denotes that the message sender and receiver both are same.
	 * @property
	 * @type Number
	 */
	MSGFLAG_FROMME: 0x00000020,

	/**
	 * Denotes that the message is an associated message of a folder.
	 * @property
	 * @type Number
	 */
	MSGFLAG_ASSOCIATED: 0x00000040,

	/**
	 * Denotes that the message includes a request for a resend operation with a nondelivery report.
	 * @property
	 * @type Number
	 */
	MSGFLAG_RESEND: 0x00000080,

	/**
	 * Denotes that a read report needs to be sent for the message.
	 * @property
	 * @type Number
	 */
	MSGFLAG_RN_PENDING: 0x00000100,

	/**
	 * Denotes that a nonread report needs to be sent for the message.
	 * @property
	 * @type Number
	 */
	MSGFLAG_NRN_PENDING: 0x00000200
});Ext.namespace("Zarafa.core.mapi");

/**
 * @class Zarafa.core.mapi.NoteColor
 * @extends Zarafa.core.Enum
 *
 * Enumerates to Note Color values
 *
 * @singleton
 */
Zarafa.core.mapi.NoteColor = Zarafa.core.Enum.create({

	// sticky notes
	'note_blue': 0,
	'note_green': 1,
	'note_pink': 2,
	'note_yellow': 3,
	'note_white': 4,

	/**
	 * Function returns color name as a string.
	 *
	 * @param {Object} value The data value for the cell.
	 * @return {String} return the color name.
	 */
	getColorText: function(value)
	{
		var colorValue = "";

		switch(parseInt(value, 10))
		{
			case this.note_blue:
				colorValue = _('Blue');
				break;
			case this.note_green:
				colorValue = _('Green');
				break;
			case this.note_pink:
				colorValue = _('Pink');
				break;
			case this.note_white:
				colorValue = _('White');
				break;
			case this.note_yellow:
			/* falls through */
			default:
				colorValue = _('Yellow');
				break;
		}

		return colorValue;
	},

	/**
	 * Function is used to get the color value based on icon index.
	 *
	 * @param{Number} icon index value.
	 * @return return the color value.
	 */
	getColorValue: function(iconIndex)
	{
		var color = Zarafa.core.mapi.IconIndex.getName(iconIndex);
		return this[color];
	}
});
Ext.namespace('Zarafa.core.mapi');

/**
 * @class Zarafa.core.mapi.ObjectType
 * @extends Zarafa.core.Enum
 * 
 * Enumerates the different object types
 * 
 * @singleton
 */
Zarafa.core.mapi.ObjectType = Zarafa.core.Enum.create({
	/**
	 * Denotes that the MAPI Store
	 * @property
	 * @type Number
	 */
	MAPI_STORE		: 1,
	/**
	 * Denotes that the MAPI Address Book
	 * @property
	 * @type Number
	 */
	MAPI_ADDRBOOK	: 2,
	/**
	 * Denotes that the MAPI Folder
	 * @property
	 * @type Number
	 */
	MAPI_FOLDER		: 3,
	/**
	 * Denotes that the MAPI Address Book Container
	 * @property
	 * @type Number
	 */
	MAPI_ABCONT		: 4,
	/**
	 * Denotes that the MAPI Message
	 * @property
	 * @type Number
	 */
	MAPI_MESSAGE	: 5,
	/**
	 * Denotes that the MAPI Address Book MailUser
	 * @property
	 * @type Number
	 */
	MAPI_MAILUSER	: 6,
	/**
	 * Denotes that the MAPI Attachment
	 * @property
	 * @type Number
	 */
	MAPI_ATTACH		: 7,
	/**
	 * Denotes that the MAPI Address Book Distribution List
	 * @property
	 * @type Number
	 */
	MAPI_DISTLIST	: 8,
	/**
	 * Denotes that the MAPI Profile Section
	 * @property
	 * @type Number
	 */
	MAPI_PROFSECT	: 9,
	/**
	 * Denotes that the MAPI Status
	 * @property
	 * @type Number
	 */
	MAPI_STATUS		: 10,
	/**
	 * Denotes that the MAPI Session
	 * @property
	 * @type Number
	 */
	MAPI_SESSION	: 11,
	/**
	 * Denotes that the MAPI Form Information
	 * @property
	 * @type Number
	 */
	MAPI_FORMINFO	: 12
});
Ext.namespace('Zarafa.core.mapi');

/**
 * @class Zarafa.core.mapi.RecipientFlags
 * @extends Zarafa.core.Enum
 *
 * Enumerates the different recipient flags.
 * any combination of these flags can be saved recipient_flags property.
 *
 * @singleton
 */
Zarafa.core.mapi.RecipientFlags = Zarafa.core.Enum.create({
	/**
	 * Denotes that the recipient is a sendable attendee of the meeting request.
	 * @property
	 * @type Number
	 */
	recipSendable: 1,

	/**
	 * Denotes that the recipient is an organizer of the meeting request.
	 * @property
	 * @type Number
	 */
	recipOrganizer: 2,

	/**
	 * Denotes that the recipient gave a response for the exception of the meeting request.
	 * @property
	 * @type Number
	 */
	recipExceptionalResponse: 16,

	/**
	 * Denotes that the recipient is deleted from the exception of the meeting request.
	 * @property
	 * @type Number
	 */
	recipExceptionalDeleted: 32,

	/**
	 * Denotes that the recipient is deleted from the exception of the meeting request.
	 * @property
	 * @type Number
	 */
	recipOriginal: 256,

	/**
	 * Reserved Flag.
	 * @property
	 * @type Number
	 */
	recipReserved: 512
});Ext.namespace('Zarafa.core.mapi');

/**
 * @class Zarafa.core.mapi.RecipientTrackStatus
 * @extends Zarafa.core.Enum
 * 
 * @singleton
 */
Zarafa.core.mapi.RecipientTrackStatus = Zarafa.core.Enum.create({
	/**
	 * Denotes that attendee has not replied to meeting request yet.
	 * @property
	 * @type Number
	 */
	RECIPIENT_TRACKSTATUS_NONE		: 0,

	/**
	 * Denotes that attendee has tentatively accepted the meeting request
	 * @property
	 * @type Number
	 */
	RECIPIENT_TRACKSTATUS_TENTATIVE	: 2,

	/**
	 * Denotes that attendee has accepted the meeting request
	 * @property
	 * @type Number
	 */
	RECIPIENT_TRACKSTATUS_ACCEPTED	: 3,
	/**
	 * Denotes that attendee has declined the meeting request
	 * @property
	 * @type Number
	 */
	RECIPIENT_TRACKSTATUS_DECLINED	: 4
});
Ext.namespace('Zarafa.core.mapi');

/**
 * @class Zarafa.core.mapi.RecipientType
 * @extends Zarafa.core.Enum
 *
 * Enumerates the different recipient types.
 *
 * @singleton
 */
Zarafa.core.mapi.RecipientType = Zarafa.core.Enum.create({
	/**
	 * Denotes that the recipient is the originator of the meeting request.
	 * @property
	 * @type Number
	 */
	MAPI_ORIG: 0,

	/**
	 * Denotes that the recipient is a required attendee of the meeting request.
	 * @property
	 * @type Number
	 */
	MAPI_TO: 1,

	/**
	 * Denotes that the recipient is an optional attendee of the meeting request.
	 * @property
	 * @type Number
	 */
	MAPI_CC: 2,

	/**
	 * Denotes that the recipient is a resource of the meeting request.
	 * @property
	 * @type Number
	 */
	MAPI_BCC: 3
});
Ext.namespace('Zarafa.core.mapi');

/**
 * @class Zarafa.core.mapi.ResponseStatus
 * @extends Zarafa.core.Enum
 *
 * @singleton
 */
Zarafa.core.mapi.ResponseStatus = Zarafa.core.Enum.create({
	/**
	 * Denotes that no response is required
	 * @property
	 * @type Number
	 */
	RESPONSE_NONE		: 0,

	/**
	 * Denotes that the message belongs to meeting organizer
	 * @property
	 * @type Number
	 */
	RESPONSE_ORGANIZED	: 1,

	/**
	 * Denotes that attendee has tentatively accepted the meeting request
	 * @property
	 * @type Number
	 */
	RESPONSE_TENTATIVE	: 2,

	/**
	 * Denotes that attendee has accepted the meeting request
	 * @property
	 * @type Number
	 */
	RESPONSE_ACCEPTED	: 3,
	/**
	 * Denotes that attendee has declined the meeting request
	 * @property
	 * @type Number
	 */
	RESPONSE_DECLINED	: 4,

	/**
	 * Denotes that attendee has not yet responded to the meeting request
	 * @property
	 * @type Number
	 */
	RESPONSE_NOT_RESPONDED	: 5,

	/**
	 * Return the display name for the given Response Status
	 * @param {Zarafa.core.mapi.ResponseStatus} responsestatus The given response status
	 * @return {String} The display name for the response status
	 */
	getDisplayName: function(responsestatus)
	{
		switch (responsestatus) {
			case Zarafa.core.mapi.ResponseStatus.RESPONSE_ORGANIZED:
				return _('Organizer');
			case Zarafa.core.mapi.ResponseStatus.RESPONSE_NONE:
				return _('No response');
			case Zarafa.core.mapi.ResponseStatus.RESPONSE_NOT_RESPONDED:
				return _('Not responded');
			case Zarafa.core.mapi.ResponseStatus.RESPONSE_TENTATIVE:
				return _('Tentative');
			case Zarafa.core.mapi.ResponseStatus.RESPONSE_ACCEPTED:
				return _('Accepted');
			case Zarafa.core.mapi.ResponseStatus.RESPONSE_DECLINED:
				return _('Declined');
		}
		return '';
	}
});
Ext.namespace('Zarafa.core.mapi');

/**
 * @class Zarafa.core.mapi.Restrictions
 * @extends Zarafa.core.Enum
 *
 * Enumerates the different flags used in restriction.
 *
 * @singleton
 */
Zarafa.core.mapi.Restrictions = Zarafa.core.Enum.create({
	/**
	 * Denotes restriction type that applies bitwise AND operation to restrictions.
	 * @property
	 * @type Number
	 */
	RES_AND	: 0x00000000,

	/**
	 * Denotes restriction type that applies bitwise OR operation to restrictions.
	 * @property
	 * @type Number
	 */
	RES_OR: 0x00000001,

	/**
	 * Denotes restriction type that applies bitwise NOT operation to a restriction.
	 * @property
	 * @type Number
	 */
	RES_NOT: 0x00000002,

	/**
	 * Denotes restriction type that is used for fuzzy content search in string properties.
	 * @property
	 * @type Number
	 */
	RES_CONTENT: 0x00000003,

	/**
	 * Denotes restriction type that is used for checking values of properties using logical operators.
	 * @property
	 * @type Number
	 */
	RES_PROPERTY: 0x00000004,

	/**
	 * Denotes restriction type that is used for comparing two properties.
	 * @property
	 * @type Number
	 */
	RES_COMPAREPROPS: 0x00000005,

	/**
	 * Denotes restriction type that is used for comparing value of property using bitmask operators.
	 * @property
	 * @type Number
	 */
	RES_BITMASK: 0x00000006,

	/**
	 * Denotes restriction type that is used for checking size of a property.
	 * @property
	 * @type Number
	 */
	RES_SIZE: 0x00000007,

	/**
	 * Denotes restriction type that is used for checking existence of a property.
	 * @property
	 * @type Number
	 */
	RES_EXIST: 0x00000008,

	/**
	 * Denotes restriction type that is used for checking value of internal properties that are
	 * stored in attachment and recipients table.
	 * @property
	 * @type Number
	 */
	RES_SUBRESTRICTION: 0x00000009,

	/**
	 * @property
	 * @type Number
	 */
	RES_COMMENT	: 0x0000000A,

	/**
	 * Denotes full string search for RES_CONTENT restriction type.
	 * @property
	 * @type Number
	 */
	FL_FULLSTRING: 0x00000000,

	/**
	 * Denotes sub string search for RES_CONTENT restriction type.
	 * @property
	 * @type Number
	 */
	FL_SUBSTRING: 0x00000001,

	/**
	 * Denotes prefix string search for RES_CONTENT restriction type.
	 * @property
	 * @type Number
	 */
	FL_PREFIX:	0x00000002,

	/**
	 * Denotes case insensitive string search for RES_CONTENT restriction type.
	 * @property
	 * @type Number
	 */
	FL_IGNORECASE: 0x00010000,

	/**
	 * Denotes space ignoring string search for RES_CONTENT restriction type.
	 * @property
	 * @type Number
	 */
	FL_IGNORENONSPACE: 0x00020000,

	/**
	 * Denotes loose type string search for RES_CONTENT restriction type.
	 * @property
	 * @type Number
	 */
	FL_LOOSE: 0x00040000,

	/**
	 * Denotes less than (<) operator for RES_PROPERTY restriction type.
	 * @property
	 * @type Number
	 */
	RELOP_LT: 0x00000000,

	/**
	 * Denotes less than or equal (<=) operator for RES_PROPERTY restriction type.
	 * @property
	 * @type Number
	 */
	RELOP_LE: 0x00000001,

	/**
	 * Denotes greater than (>) operator for RES_PROPERTY restriction type.
	 * @property
	 * @type Number
	 */
	RELOP_GT: 0x00000002,

	/**
	 * Denotes greater than or equal (>=) operator for RES_PROPERTY restriction type.
	 * @property
	 * @type Number
	 */
	RELOP_GE: 0x00000003,

	/**
	 * Denotes equal (==) operator for RES_PROPERTY restriction type.
	 * @property
	 * @type Number
	 */
	RELOP_EQ: 0x00000004,

	/**
	 * Denotes not equal (!=) operator for RES_PROPERTY restriction type.
	 * @property
	 * @type Number
	 */
	RELOP_NE: 0x00000005,

	/**
	 * Denotes regular expression (LIKE) for RES_PROPERTY restriction type.
	 * @property
	 * @type Number
	 */
	RELOP_RE: 0x00000006,

	/**
	 * Denotes equality of value (== 0) after applying mask for RES_BITMASK restriction type.
	 * @property
	 * @type Number
	 */
	BMR_EQZ: 0x00000000,

	/**
	 * Denotes non equality of value (!= 0) after applying mask for RES_BITMASK restriction type.
	 * @property
	 * @type Number
	 */
	BMR_NEZ: 0x00000001,

	// internal constants for restrictions
	/**
	 * Denotes propvalue.
	 * @property
	 * @type Number
	 */
	VALUE: 0,

	/**
	 * Denotes relation operator for comparison.
	 * @property
	 * @type Number
	 */
	RELOP: 1,

	/**
	 * Denotes fuzzy level to search in string property.
	 * @property
	 * @type Number
	 */
	FUZZYLEVEL: 2,

	/**
	 * Denotes value of size for comparison.
	 * @property
	 * @type Number
	 */
	CB: 3,

	/**
	 * Denotes type of bitmask (BMR_xxx).
	 * @property
	 * @type Number
	 */
	ULTYPE: 4,

	/**
	 * Denotes value of bitmask to compare after applying bitmask.
	 * @property
	 * @type Number
	 */
	ULMASK: 5,

	/**
	 * Denotes property tag.
	 * @property
	 * @type Number
	 */
	ULPROPTAG: 6,

	/**
	 * Denotes property tag (to use in RES_COMPAREPROPS as 1st property).
	 * @property
	 * @type Number
	 */
	ULPROPTAG1: 7,

	/**
	 * Denotes property tag (to use in RES_COMPAREPROPS as 2nd property).
	 * @property
	 * @type Number
	 */
	ULPROPTAG2: 8,

	/**
	 * Denotes property list structure (to use in RES_COMMENT).
	 * @property
	 * @type Number
	 */
	PROPS: 9,

	/**
	 * Denotes sub restriction (to be used in RES_COMMENT and RES_SUBRESTRICTION).
	 * @property
	 * @type Number
	 */
	RESTRICTION: 10
});Ext.namespace('Zarafa.core.mapi');

/**
 * @class Zarafa.core.mapi.Rights
 * @extends Zarafa.core.Enum
 *
 * Enumerates the different rights
 *
 * @singleton
 */
Zarafa.core.mapi.Rights = Zarafa.core.Enum.create({
	/**
	 * Denotes that no rights are given
	 * @property
	 * @type Number
	 */
	RIGHTS_NONE: 0x00000000,
	/**
	 * Denotes that read rights are given
	 * @property
	 * @type Number
	 */
	RIGHTS_READ_ANY: 0x00000001,
	/**
	 * Denotes that create rights are given
	 * @property
	 * @type Number
	 */
	RIGHTS_CREATE: 0x00000002,
	/**
	 * Denotes that edit rights are given for items owned by the user
	 * @property
	 * @type Number
	 */
	RIGHTS_EDIT_OWNED: 0x00000008,
	/**
	 * Denotes that delete rights are given for items owned by the user
	 * @property
	 * @type Number
	 */
	RIGHTS_DELETE_OWNED: 0x00000010,
	/**
	 * Denotes that edit rights are given for all items
	 * @property
	 * @type Number
	 */
	RIGHTS_EDIT_ANY: 0x00000020,
	/**
	 * Denotes that delete rights are given for all items
	 * @property
	 * @type Number
	 */
	RIGHTS_DELETE_ANY: 0x00000040,
	/**
	 * Denotes that create subfolders rights are given
	 * @property
	 * @type Number
	 */
	RIGHTS_CREATE_SUBFOLDER: 0x00000080,
	/**
	 * Denotes that folder access rights are given
	 * @property
	 * @type Number
	 */
	RIGHTS_CREATE_FOLDER: 0x00000100,
	/**
	 * Denotes that display the folder permissions list rights are given
	 * @property
	 * @type Number
	 */
	RIGHTS_FOLDER_CONTACT: 0x00000200,
	/**
	 * Denotes that folder visibility rights are given
	 * @property
	 * @type Number
	 */
	RIGHTS_FOLDER_VISIBLE: 0x00000400,
	/**
	 * Denotes that simple free/busy visibility rights are given
	 * @property
	 * @type Number
	 */
	RIGHTS_FBSIMPLE: 0x00000800,
	/**
	 * Denotes that detailed free/busy visibility rights are given
	 * @property
	 * @type Number
	 */
	RIGHTS_FBDETAILED: 0x00001000
});

/**
 * Denotes that no rights are granted, the user can only {@link #RIGHTS_FOLDER_VISIBLE see folders}.
 * @property
 * @type Number
 */
Zarafa.core.mapi.Rights.RIGHTS_NO_RIGHTS = Zarafa.core.mapi.Rights.RIGHTS_NONE;

/**
 * Denotes that create rights are granted, this extends {@link #RIGHTS_FOLDER_VISIBLE} with
 * the extra {@link #RIGHTS_CREATE permission}.
 * @property
 * @type Number
 */
Zarafa.core.mapi.Rights.RIGHTS_CONTRIBUTOR =
	Zarafa.core.mapi.Rights.RIGHTS_FOLDER_VISIBLE |
	Zarafa.core.mapi.Rights.RIGHTS_CREATE;

/**
 * Denotes that readonly rights are granted, this extends {@link #RIGHTS_FOLDER_VISIBLE} with
 * the extra {@link #RIGHTS_READ_ANY read permission}.
 * @property
 * @type Number
 */
Zarafa.core.mapi.Rights.RIGHTS_READONLY =
	Zarafa.core.mapi.Rights.RIGHTS_FOLDER_VISIBLE |
	Zarafa.core.mapi.Rights.RIGHTS_READ_ANY;

/**
 * Denotes that readonly and folder visible rights are granted, this extends {@link #RIGHTS_READ_ANY} with
 * the extra {@link #RIGHTS_FOLDER_VISIBLE permission}.
 * @property
 * @type Number
 */
Zarafa.core.mapi.Rights.RIGHTS_REVIEWER =
	Zarafa.core.mapi.Rights.RIGHTS_READ_ANY |
	Zarafa.core.mapi.Rights.RIGHTS_FOLDER_VISIBLE;

/**
 * Denotes that readonly, folder visible, delete own and create items rights are granted, this extends {@link #RIGHTS_REVIEWER} with
 * the extra {@link #RIGHTS_CREATE permission} and {@link #RIGHTS_DELETE_OWNED permission}.
 * @property
 * @type Number
 */
Zarafa.core.mapi.Rights.RIGHTS_NONEDITINGAUTHOR =
	Zarafa.core.mapi.Rights.RIGHTS_REVIEWER |
	Zarafa.core.mapi.Rights.RIGHTS_CREATE |
	Zarafa.core.mapi.Rights.RIGHTS_DELETE_OWNED;

/**
 * Denotes that readonly, folder visible, delete, editing own and create items rights are granted,
 * this extends {@link #RIGHTS_NONEDITINGAUTHOR} with the extra {@link #RIGHTS_EDIT_OWNED permission}.
 * @property
 * @type Number
 */
Zarafa.core.mapi.Rights.RIGHTS_AUTHOR =
	Zarafa.core.mapi.Rights.RIGHTS_NONEDITINGAUTHOR |
	Zarafa.core.mapi.Rights.RIGHTS_EDIT_OWNED;

/**
 * Denotes that readonly, folder visible, delete, editing own, create subfolder and create items rights are granted,
 * this extends {@link #RIGHTS_AUTHOR} with the extra {@link #RIGHTS_CREATE_SUBFOLDER permission}.
 * @property
 * @type Number
 */
Zarafa.core.mapi.Rights.RIGHTS_PUBLISHINGAUTHOR =
	Zarafa.core.mapi.Rights.RIGHTS_AUTHOR |
	Zarafa.core.mapi.Rights.RIGHTS_CREATE_SUBFOLDER;

/**
 * Denotes that readonly, folder visible, deleting, editing and create items rights are granted,
 * this extends {@link #RIGHTS_AUTHOR} with the extra {@link #RIGHTS_DELETE_ANY permission} and {@link #RIGHTS_EDIT_ANY permission}.
 * @property
 * @type Number
 */
Zarafa.core.mapi.Rights.RIGHTS_EDITOR =
	Zarafa.core.mapi.Rights.RIGHTS_AUTHOR |
	Zarafa.core.mapi.Rights.RIGHTS_DELETE_ANY |
	Zarafa.core.mapi.Rights.RIGHTS_EDIT_ANY;

/**
 * Denotes that readonly, folder visible, deleting, editing, create subfolder and create items rights are granted,
 * this extends {@link #RIGHTS_EDITOR} with the extra {@link #RIGHTS_CREATE_SUBFOLDER permission}.
 * @property
 * @type Number
 */
Zarafa.core.mapi.Rights.RIGHTS_PUBLISHINGEDITOR =
	Zarafa.core.mapi.Rights.RIGHTS_EDITOR |
	Zarafa.core.mapi.Rights.RIGHTS_CREATE_SUBFOLDER;

/**
 * Denotes that secretary rights are granted, this extends {@link #RIGHTS_READONLY} with some extra
 * {@link #RIGHTS_CREATE create}, {@link #RIGHTS_EDIT_OWNED edit own}, {@link #RIGHTS_DELETE_OWNED delete own},
 * {@link #RIGHTS_EDIT_ANY edit any}, {@link #RIGHTS_DELETE_ANY delete any} permissions.
 * Note that RIGHTS_SECRETARY IS LEFT FOR COMPATIBILITY AS IT IS NOT RESPECTED WITH ACLS DIRECTLY ANYMORE
 * @property
 * @type Number
 */
Zarafa.core.mapi.Rights.RIGHTS_SECRETARY =
	Zarafa.core.mapi.Rights.RIGHTS_READONLY |
	Zarafa.core.mapi.Rights.RIGHTS_CREATE |
	Zarafa.core.mapi.Rights.RIGHTS_EDIT_OWNED |
	Zarafa.core.mapi.Rights.RIGHTS_DELETE_OWNED |
	Zarafa.core.mapi.Rights.RIGHTS_EDIT_ANY |
	Zarafa.core.mapi.Rights.RIGHTS_DELETE_ANY;

/**
 * Denotes that full control rights are granted, this extends {@link #RIGHTS_SECRETARY} with the
 * extra {@link #RIGHTS_CREATE_SUBFOLDER create subfolder permission}.
 * Note that RIGHTS_FULL_CONTROL IS LEFT FOR COMPATIBILITY AS IT IS NOT RESPECTED WITH ACLS DIRECTLY ANYMORE
 * @property
 * @type Number
 */
Zarafa.core.mapi.Rights.RIGHTS_FULL_CONTROL =
	Zarafa.core.mapi.Rights.RIGHTS_SECRETARY |
	Zarafa.core.mapi.Rights.RIGHTS_CREATE_SUBFOLDER;

/**
 * Denotes that ownership rights are granted, this extends {@link #RIGHTS_PUBLISHINGEDITOR} with
 * the {@link #RIGHTS_CREATE_FOLDER create folder permission} and {@link #RIGHTS_FOLDER_CONTACT permission}.
 * @property
 * @type Number
 */
Zarafa.core.mapi.Rights.RIGHTS_OWNER =
	Zarafa.core.mapi.Rights.RIGHTS_PUBLISHINGEDITOR |
	Zarafa.core.mapi.Rights.RIGHTS_CREATE_FOLDER |
	Zarafa.core.mapi.Rights.RIGHTS_FOLDER_CONTACT;

/**
 * Denotes that simple free busy rights are granted, this extends {@link #RIGHTS_NO_RIGHTS} with
 * the extra {@link #RIGHTS_FBSIMPLE permission}.
 * @property
 * @type Number
 */
Zarafa.core.mapi.Rights.RIGHTS_CAL_FBSIMPLE = Zarafa.core.mapi.Rights.RIGHTS_FBSIMPLE;

/**
 * Denotes that detailed free busy rights are granted, this extends
 * {@link #RIGHTS_FBSIMPLE permission} with the {@link #RIGHTS_FBDETAILED permission}.
 * @property
 * @type Number
 */
Zarafa.core.mapi.Rights.RIGHTS_CAL_FBDETAILED =
	Zarafa.core.mapi.Rights.RIGHTS_FBSIMPLE |
	Zarafa.core.mapi.Rights.RIGHTS_FBDETAILED;

/**
 * Denotes that calendar contributor rights are granted, this extends
 * {@link #RIGHTS_FOLDER_VISIBLE} with the extra {@link #RIGHTS_CREATE permission}
 * and {@link #RIGHTS_FBSIMPLE permission}.
 * @property
 * @type Number
 */
Zarafa.core.mapi.Rights.RIGHTS_CAL_CONTRIBUTOR =
	Zarafa.core.mapi.Rights.RIGHTS_FOLDER_VISIBLE |
	Zarafa.core.mapi.Rights.RIGHTS_FBSIMPLE |
	Zarafa.core.mapi.Rights.RIGHTS_CREATE;

/**
 * Denotes that readonly and folder visible rights are granted, this extends
 * {@link #RIGHTS_READ_ANY} with the extra {@link #RIGHTS_FOLDER_VISIBLE permission},
 * {@link #RIGHTS_FBSIMPLE permission} and {@link #RIGHTS_FBDETAILED permission}.
 * @property
 * @type Number
 */
Zarafa.core.mapi.Rights.RIGHTS_CAL_REVIEWER =
	Zarafa.core.mapi.Rights.RIGHTS_READ_ANY |
	Zarafa.core.mapi.Rights.RIGHTS_FBSIMPLE |
	Zarafa.core.mapi.Rights.RIGHTS_FBDETAILED |
	Zarafa.core.mapi.Rights.RIGHTS_FOLDER_VISIBLE;

/**
 * Denotes that readonly, folder visible, delete own and create items rights are granted,
 * this extends {@link #RIGHTS_REVIEWER} with the extra {@link #RIGHTS_CREATE permission},
 * {@link #RIGHTS_DELETE_OWNED permission}, {@link #RIGHTS_FBSIMPLE permission}
 * and {@link #RIGHTS_FBDETAILED permission}.
 * @property
 * @type Number
 */
Zarafa.core.mapi.Rights.RIGHTS_CAL_NONEDITINGAUTHOR =
	Zarafa.core.mapi.Rights.RIGHTS_REVIEWER |
	Zarafa.core.mapi.Rights.RIGHTS_FBSIMPLE |
	Zarafa.core.mapi.Rights.RIGHTS_FBDETAILED |
	Zarafa.core.mapi.Rights.RIGHTS_CREATE |
	Zarafa.core.mapi.Rights.RIGHTS_DELETE_OWNED;

/**
 * Denotes that readonly, folder visible, delete, editing own and create items rights are granted,
 * this extends {@link #RIGHTS_NONEDITINGAUTHOR} with the extra {@link #RIGHTS_EDIT_OWNED permission},
 * {@link #RIGHTS_FBSIMPLE permission} and {@link #RIGHTS_FBDETAILED permission}.
 * @property
 * @type Number
 */
Zarafa.core.mapi.Rights.RIGHTS_CAL_AUTHOR =
	Zarafa.core.mapi.Rights.RIGHTS_NONEDITINGAUTHOR |
	Zarafa.core.mapi.Rights.RIGHTS_FBSIMPLE |
	Zarafa.core.mapi.Rights.RIGHTS_FBDETAILED |
	Zarafa.core.mapi.Rights.RIGHTS_EDIT_OWNED;

/**
 * Denotes that readonly, folder visible, delete, editing own, create subfolder and create items rights are granted,
 * this extends {@link #RIGHTS_AUTHOR} with the extra {@link #RIGHTS_CREATE_SUBFOLDER permission},
 * {@link #RIGHTS_FBSIMPLE permission} and {@link #RIGHTS_FBDETAILED permission}.
 * @property
 * @type Number
 */
Zarafa.core.mapi.Rights.RIGHTS_CAL_PUBLISHINGAUTHOR =
	Zarafa.core.mapi.Rights.RIGHTS_AUTHOR |
	Zarafa.core.mapi.Rights.RIGHTS_FBSIMPLE |
	Zarafa.core.mapi.Rights.RIGHTS_FBDETAILED |
	Zarafa.core.mapi.Rights.RIGHTS_CREATE_SUBFOLDER;

/**
 * Denotes that readonly, folder visible, deleting, editing and create items rights are granted,
 * this extends {@link #RIGHTS_AUTHOR} with the extra {@link #RIGHTS_DELETE_ANY permission},
 * {@link #RIGHTS_EDIT_ANY permission}, {@link #RIGHTS_FBSIMPLE permission} and
 * {@link #RIGHTS_FBDETAILED permission}.
 * @property
 * @type Number
 */
Zarafa.core.mapi.Rights.RIGHTS_CAL_EDITOR =
	Zarafa.core.mapi.Rights.RIGHTS_AUTHOR |
	Zarafa.core.mapi.Rights.RIGHTS_FBSIMPLE |
	Zarafa.core.mapi.Rights.RIGHTS_FBDETAILED |
	Zarafa.core.mapi.Rights.RIGHTS_DELETE_ANY |
	Zarafa.core.mapi.Rights.RIGHTS_EDIT_ANY;

/**
 * Denotes that readonly, folder visible, deleting, editing, create subfolder and create items rights are granted,
 * this extends {@link #RIGHTS_EDITOR} with the extra {@link #RIGHTS_CREATE_SUBFOLDER permission},
 * {@link #RIGHTS_FBSIMPLE permission} and {@link #RIGHTS_FBDETAILED permission}.
 * @property
 * @type Number
 */
Zarafa.core.mapi.Rights.RIGHTS_CAL_PUBLISHINGEDITOR =
	Zarafa.core.mapi.Rights.RIGHTS_EDITOR |
	Zarafa.core.mapi.Rights.RIGHTS_FBSIMPLE |
	Zarafa.core.mapi.Rights.RIGHTS_FBDETAILED |
	Zarafa.core.mapi.Rights.RIGHTS_CREATE_SUBFOLDER;

/**
 * Denotes that ownership rights are granted, this extends {@link #RIGHTS_PUBLISHINGEDITOR} with
 * the {@link #RIGHTS_CREATE_FOLDER create folder permission},
 * {@link #RIGHTS_FBSIMPLE permission}, {@link #RIGHTS_FBDETAILED permission} and
 * {@link #RIGHTS_FOLDER_CONTACT permission}.
 * @property
 * @type Number
 */
Zarafa.core.mapi.Rights.RIGHTS_CAL_OWNER =
	Zarafa.core.mapi.Rights.RIGHTS_PUBLISHINGEDITOR |
	Zarafa.core.mapi.Rights.RIGHTS_CREATE_FOLDER |
	Zarafa.core.mapi.Rights.RIGHTS_FBSIMPLE |
	Zarafa.core.mapi.Rights.RIGHTS_FBDETAILED |
	Zarafa.core.mapi.Rights.RIGHTS_FOLDER_CONTACT;

/**
 * Denotes that full read rights on a calendar folder are granted, this extends
 * {@link #RIGHTS_READONLY permission} with the {@link #RIGHTS_CAL_FBDETAILED permission}.
 * @property
 * @type Number
 */
Zarafa.core.mapi.Rights.RIGHTS_CAL_READ_FULL =
	Zarafa.core.mapi.Rights.RIGHTS_READONLY |
	Zarafa.core.mapi.Rights.RIGHTS_CAL_FBDETAILED;

/**
 * Denotes that edit all calendar items rights are granted, this extends
 * {@link #RIGHTS_EDIT_OWNED permission} with the {@link #RIGHTS_EDIT_ANY permission}.
 * @property
 * @type Number
 */
Zarafa.core.mapi.Rights.RIGHTS_CAL_EDIT_ALL =
	Zarafa.core.mapi.Rights.RIGHTS_EDIT_OWNED |
	Zarafa.core.mapi.Rights.RIGHTS_EDIT_ANY;

/**
 * Denotes that folder owner rights are granted, this extends
 * {@link #RIGHTS_CREATE_FOLDER permission} with the {@link #RIGHTS_FOLDER_CONTACT permission}
 * and {@link #RIGHTS_FOLDER_VISIBLE permission}.
 * @property
 * @type Number
 */
Zarafa.core.mapi.Rights.RIGHTS_FOLDER_OWNER =
	Zarafa.core.mapi.Rights.RIGHTS_CREATE_FOLDER |
	Zarafa.core.mapi.Rights.RIGHTS_FOLDER_CONTACT |
	Zarafa.core.mapi.Rights.RIGHTS_FOLDER_VISIBLE;Ext.namespace('Zarafa.core.mapi');

/**
 * @class Zarafa.core.mapi.RuleActions
 * @extends Zarafa.core.Enum
 *
 * Enumerates the different rule actions.
 *
 * @singleton
 */
Zarafa.core.mapi.RuleActions = Zarafa.core.Enum.create({
	/**
	 * Rule action which indicates the message should
	 * be moved to a designated folder.
	 * @property
	 * @type Number
	 */
	OP_MOVE: 1,

	/**
	 * Rule action which indicates the message should
	 * be copied to a designated folder.
	 * @property
	 * @type Number
	 */
	OP_COPY: 2,

	/**
	 * @property
	 * @type Number
	 */
	OP_REPLY: 3,

	/**
	 * @property
	 * @type Number
	 */
	OP_OOF_REPLY: 4,

	/**
	 * @property
	 * @type Number
	 */
	OP_DEFER_ACTION: 5,

	/**
	 * @property
	 * @type Number
	 */
	OP_BOUNCE: 6,

	/**
	 * Rule action which indicates that the message should
	 * be forwarded to the designated recipients
	 * @property
	 * @type Number
	 */
	OP_FORWARD: 7,

	/**
	 * @property
	 * @type Number
	 */
	OP_DELEGATE: 8,

	/**
	 * @property
	 * @type Number
	 */
	OP_TAG: 9,

	/**
	 * Rule action which indicates that the message should
	 * be (hard-)deleted from the store
	 * @property
	 * @type Number
	 */
	OP_DELETE: 10,

	/**
	 * Rule action which indicates that the message should
	 * be marked as read
	 * @property
	 * @type Number
	 */
	OP_MARK_AS_READ: 11
});
Ext.namespace('Zarafa.core.mapi');

/**
 * @class Zarafa.core.mapi.RuleStates
 * @extends Zarafa.core.Enum
 *
 * Enumerates the different rule states.
 *
 * @singleton
 */
Zarafa.core.mapi.RuleStates = Zarafa.core.Enum.create({
	/**
	 * Denotes that the rule is disabled for the execution.
	 * @property
	 * @type Number
	 */
	ST_DISABLED: 0,

	/**
	 * Denotes that the rule is enabled for the execution.
	 * @property
	 * @type Number
	 */
	ST_ENABLED: 1,

	/**
	 * Denotes that the server has encountered any nonparsing error processing the rule.
	 * @property
	 * @type Number
	 */
	ST_ERROR: 2,

	/**
	 * Denotes that the rule is executed only when a user sets the Out of Office (OOF) state on the mailbox.
	 * @property
	 * @type Number
	 */
	ST_ONLY_WHEN_OOF: 4,

	/**
	 * Denotes that the rule will manage list of recipients when user has set Out of Office state on the mailbox.
	 * @property
	 * @type Number
	 */
	ST_KEEP_OOF_HIST: 8,

	/**
	 * Denotes that the rule evaluation will terminate after executing this rule.
	 * @property
	 * @type Number
	 */
	ST_EXIT_LEVEL: 16,

	/**
	 * Denotes that the evaluation of this rule will be skipped if the delivered message's
	 * PidTagContentFilterSpamConfidenceLevel property has a value of 0xFFFFFFFF.
	 * @property
	 * @type Number
	 */
	ST_SKIP_IF_SCL_IS_SAFE: 32,

	/**
	 * Denotes that the server has encountered rule data from the client that is in an incorrect format,
	 * which caused an error parsing the rule data.
	 * @property
	 * @type Number
	 */
	ST_RULE_PARSE_ERROR: 64,

	/**
	 * Denotes that the rule will clear list of recipients when user has set Out of Office state on the mailbox.
	 * @property
	 * @type Number
	 */
	ST_CLEAR_OOF_HIST: 2147483648
});Ext.namespace('Zarafa.core.mapi');

/**
 * @class Zarafa.core.mapi.Search
 * @extends Zarafa.core.Enum
 *
 * Enumerates the different search flags used in SetSearchCriteria and GetSearchCriteria.
 *
 * @singleton
 */
Zarafa.core.mapi.Search = Zarafa.core.Enum.create({
	// Flags used by GetSearchCriteria

	/**
	 * @property
	 * @type Number
	 * The search is running means search folder is in active state.
	 */
	SEARCH_RUNNING: 0x00000001,

	/**
	 * @property
	 * @type Number
	 * The search is in the CPU-intensive mode of its operation, trying to locate messages that match the criteria.
	 * If this flag is not set, the CPU-intensive part of the search's operation is over.
	 * This flag only has meaning if the search is active (if the SEARCH_RUNNING flag is set).
	 */
	SEARCH_REBUILD: 0x00000002,

	/**
	 * @property
	 * @type Number
	 * The search is looking in specified folder containers and all their child folder containers for matching entries.
	 */
	SEARCH_RECURSIVE: 0x00000004,

	/**
	 * @property
	 * @type Number
	 * The search is running at a high priority relative to other searches.
	 */
	SEARCH_FOREGROUND: 0x00000008,

	// Flags used by SetSearchCriteria

	/**
	 * @property
	 * @type Number
	 * Request server to abort the search.
	 */
	STOP_SEARCH: 0x00000001,

	/**
	 * @property
	 * @type Number
	 * Request server to restart/start the search.
	 */
	RESTART_SEARCH: 0x00000002,

	/**
	 * @property
	 * @type Number
	 * Request server to search in specified folder containers and all their child folder containers for matching entries.
	 */
	RECURSIVE_SEARCH: 0x00000004,

	/**
	 * @property
	 * @type Number
	 * Request server to search in specified folder containers only and not in child folder containers for matching entries.
	 * defaults for first search call if RECURSIVE_SEARCH or SHALLOW_SEARCH flag is not used.
	 */
	SHALLOW_SEARCH: 0x00000008,

	/**
	 * @property
	 * @type Number
	 * Request the server to run this search at a high priority relative to other searches.
	 */
	FOREGROUND_SEARCH: 0x00000010,

	/**
	 * @property
	 * @type Number
	 * Request the server to run this search at normal priority relative to other searches.
	 * defaults for first search call if FOREGROUND_SEARCH or BACKGROUND_SEARCH flag is not used.
	 */
	BACKGROUND_SEARCH: 0x00000020,

	/**
	 * Convenience method to check if search is active or it has been stopped by user.
	 * @param {Number} searchState value of searchState flag that is returned by GetSearchCriteria.
	 * @return {Boolean} true if search is active or false.
	 */
	isSearchActive: function(searchState)
	{
		if(!Ext.isNumber(searchState)) {
			searchState = parseInt(searchState, 10);
		}

		if(!Ext.isNumber(searchState)) {
			return false;
		}

		if(searchState & this.SEARCH_RUNNING) {
			return true;
		}

		return false;
	},

	/**
	 * Convenience method to check if search is locating messages in folder containers for matching entries.
	 * @param {Number} searchState value of searchState flag that is returned by GetSearchCriteria.
	 * @return {Boolean} true if search is running or false.
	 */
	isSearchRunning: function(searchState)
	{
		if(!Ext.isNumber(searchState)) {
			searchState = parseInt(searchState, 10);
		}

		if(!Ext.isNumber(searchState)) {
			return false;
		}

		if(searchState & this.SEARCH_REBUILD && searchState & this.SEARCH_RUNNING) {
			return true;
		}

		return false;
	}
});
Ext.namespace('Zarafa.core.mapi');

/**
 * @class Zarafa.core.mapi.Sensitivity
 * @extends Zarafa.core.Enum
 *
 * @singleton
 */
Zarafa.core.mapi.Sensitivity = Zarafa.core.Enum.create({
	/**
	 * Denotes that the message has no special sensitivity.
	 * @property
	 * @type Number
	 */
	'NONE': 0x00000000,

	/**
	 * Denotes that the message is personal.
	 * @property
	 * @type Number
	 */
	'PERSONAL': 0x00000001,

	/**
	 * Denotes that the message is private.
	 * @property
	 * @type Number
	 */
	'PRIVATE': 0x00000002,

	/**
	 * Denotes that the message is designated company confidential.
	 * @property
	 * @type Number
	 */
	'COMPANY_CONFIDENTIAL': 0x00000003,

	/**
	 * Return the display name for the given sensitivity
	 * @param {Zarafa.core.mapi.Sensitivity} sensitivity The given sensitivity
	 * @return {String} The display name for the sensitivity
	 */
	getDisplayName: function(sensitivity)
	{
		switch (sensitivity) {
			case Zarafa.core.mapi.Sensitivity.NONE:
				return _("None");
			case Zarafa.core.mapi.Sensitivity.PERSONAL:
				return _("Personal");
			case Zarafa.core.mapi.Sensitivity.PRIVATE:
				return _("Private");
			case Zarafa.core.mapi.Sensitivity.COMPANY_CONFIDENTIAL:
				return _("Confidential");
		}
		return '';
	}
});
Ext.namespace('Zarafa.core.mapi');

/**
 * @class Zarafa.core.mapi.StoreSupportMask
 * @extends Zarafa.core.Enum
 *
 * Enumerates the different flgas used in store's property PR_SUPPORT_MASK
 *
 * @singleton
 */
Zarafa.core.mapi.StoreSupportMask = Zarafa.core.Enum.create({
	/**
	 * @property
	 * @type Number
	 * The {@link Zarafa.core.MAPIStore MAPIStore} supports properties containing ANSI (8-bit) characters.
	 */
	STORE_ANSI_OK: 0x00020000,

	/**
	 * @property
	 * @type Number
	 * The {@link Zarafa.core.MAPIStore MAPIStore} supports attachments (OLE or non-OLE) to messages.
	 */
	STORE_ATTACH_OK: 0x00000020,

	/**
	 * @property
	 * @type Number
	 * The {@link Zarafa.core.MAPIStore MAPIStore} supports categorized views of tables.
	 */
	STORE_CATEGORIZE_OK: 0x00000400,

	/**
	 * @property
	 * @type Number
	 * The {@link Zarafa.core.MAPIStore MAPIStore} supports creation of new messages.
	 */
	STORE_CREATE_OK: 0x00000010,

	/**
	 * @property
	 * @type Number
	 * Entry identifiers for the objects in the {@link Zarafa.core.MAPIStore MAPIStore} are unique,
	 * that is, never reused during the life of the {@link Zarafa.core.MAPIStore MAPIStore}.
	 */
	STORE_ENTRYID_UNIQUE: 0x00000001,

	/**
	 * @property
	 * @type Number
	 * The {@link Zarafa.core.MAPIStore MAPIStore} supports Hypertext Markup Language (HTML) messages,
	 * stored in the PR_BODY_HTML property.
	 */
	STORE_HTML_OK: 0x00010000,

	/**
	 * @property
	 * @type Number
	 * This flag is reserved and should not be used.
	 */
	STORE_LOCALSTORE: 0x00080000,

	/**
	 * @property
	 * @type Number
	 * The {@link Zarafa.core.MAPIStore MAPIStore} supports modification of its existing messages.
	 */
	STORE_MODIFY_OK: 0x00000008,

	/**
	 * @property
	 * @type Number
	 * The {@link Zarafa.core.MAPIStore MAPIStore} supports multivalued properties,
	 * guarantees the stability of value order in a multivalued property throughout a save operation,
	 * and supports instantiation of multivalued properties in tables.
	 */
	STORE_MV_PROPS_OK: 0x00000200,

	/**
	 * @property
	 * @type Number
	 * The {@link Zarafa.core.MAPIStore MAPIStore} supports notifications.
	 */
	STORE_NOTIFY_OK: 0x00000100,

	/**
	 * @property
	 * @type Number
	 * The {@link Zarafa.core.MAPIStore MAPIStore} supports OLE attachments.
	 */
	STORE_OLE_OK: 0x00000040,

	/**
	 * @property
	 * @type Number
	 * The folders in this {@link Zarafa.core.MAPIStore MAPIStore} are public (multi-user),
	 * not private (possibly multi-instance but not multi-user).
	 */
	STORE_PUBLIC_FOLDERS: 0x00004000,

	/**
	 * @property
	 * @type Number
	 * All interfaces for the {@link Zarafa.core.MAPIStore MAPIStore} have a read-only access level.
	 */
	STORE_READONLY: 0x00000002,

	/**
	 * @property
	 * @type Number
	 * The {@link Zarafa.core.MAPIStore MAPIStore} supports restrictions.
	 */
	STORE_RESTRICTION_OK: 0x00001000,

	/**
	 * @property
	 * @type Number
	 * The {@link Zarafa.core.MAPIStore MAPIStore} supports Rich Text Format (RTF) messages,
	 * usually stored compressed, and the {@link Zarafa.core.MAPIStore MAPIStore} itself keeps PR_BODY and PR_RTF_COMPRESSED synchronized.
	 */
	STORE_RTF_OK: 0x00000800,

	/**
	 * @property
	 * @type Number
	 * The {@link Zarafa.core.MAPIStore MAPIStore} supports search-results folders.
	 */
	STORE_SEARCH_OK: 0x00000004,

	/**
	 * @property
	 * @type Number
	 * The {@link Zarafa.core.MAPIStore MAPIStore} supports sorting views of tables.
	 */
	STORE_SORT_OK: 0x00002000,

	/**
	 * @property
	 * @type Number
	 * The {@link Zarafa.core.MAPIStore MAPIStore} supports marking a message for submission.
	 */
	STORE_SUBMIT_OK: 0x00000080,

	/**
	 * @property
	 * @type Number
	 * The {@link Zarafa.core.MAPIStore MAPIStore} supports storage of
	 * Rich Text Format (RTF) messages in uncompressed form.
	 */
	STORE_UNCOMPRESSED_RTF: 0x00008000,

	/**
	 * @property
	 * @type Number
	 * The {@link Zarafa.core.MAPIStore MAPIStore} supports properties containing Unicode characters.
	 */
	STORE_UNICODE_OK: 0x00040000,

	/**
	 * Convenience method to check if {@link Zarafa.core.MAPIStore MAPIStore} supports creation of
	 * search folders.
	 * @param {Number} storeSupportMask value of property PR_STORE_SUPPORT_MASK.
	 * @return {Boolean} true if {@link Zarafa.core.MAPIStore MAPIStore} supports creation of
	 * search folders else false.
	 */
	hasSearchSupport: function(storeSupportMask)
	{
		if (!Ext.isNumber(storeSupportMask)) {
			storeSupportMask = parseInt(storeSupportMask, 10);
		}

		if (!Ext.isNumber(storeSupportMask)) {
			return false;
		}

		if (storeSupportMask & this.STORE_SEARCH_OK) {
			return true;
		}

		return false;
	}
});
Ext.namespace('Zarafa.core.mapi');

/**
 * @class Zarafa.core.mapi.TaskAcceptanceState
 * @extends Zarafa.core.Enum
 * @singleton
 *
 * The TaskAcceptanceState property indicates the acceptance state of the task.
 */
Zarafa.core.mapi.TaskAcceptanceState = Zarafa.core.Enum.create({
	/**
	 * Denotes that the task object is not assigned.
	 * @property
	 * @type Number
	 */
	'NOT_DELEGATED': 0 ,

	/**
	 * Denotes that the Task object's acceptance status is unknown.
	 * @property
	 * @type Number
	 */
	'DELEGATION_UNKNOWN': 1,

	/**
	 * Denotes that the task assignee has accepted the Task object.
	 * This value is set when the client processes a task acceptance.
	 * @property
	 * @type Number
	 */
	'DELEGATION_ACCEPTED': 2,

	/**
	 * Denotes that The task assignee has rejected the Task object.
	 * This value is set when the client processes a task rejection.
	 * @property
	 * @type Number
	 */
	'DELEGATION_DECLINED': 3
});
Ext.namespace('Zarafa.core.mapi');

/**
 * @class Zarafa.core.mapi.TaskHistory
 * @extends Zarafa.core.Enum
 * @singleton
 *
 * The task history property indicates the type of change that was last made to the Task object.
 * When the value of this property is set, the task last update property MUST also be set to
 * the current time.
 */
Zarafa.core.mapi.TaskHistory = Zarafa.core.Enum.create({
	/**
	 * Denotes that No changes were made in task.
	 * @property
	 * @type Number
	 */
	'NONE': 0,

	/**
	 * Denotes the task assignee accepted this Task object.
	 * @property
	 * @type Number
	 */
	'ACCEPTED': 1,

	/**
	 * Denotes the task assignee rejected this Task object.
	 * @property
	 * @type Number
	 */
	'DECLINED': 2,

	/**
	 * Denotes that the another property was changed in task object.
	 * @property
	 * @type Number
	 */
	'UPDATED': 3,

	/**
	 * Denotes that the task due date property changed.
	 * @property
	 * @type Number
	 */
	'DUEDATECHANGED': 4,

	/**
	 * Denotes the Task object has been assigned to a task assignee.
	 * @property
	 * @type Number
	 */
	'ASSIGNED': 5
});
Ext.namespace('Zarafa.core.mapi');

/**
 * @class Zarafa.core.mapi.TaskMode
 * @extends Zarafa.core.Enum
 *
 * @singleton
 */
Zarafa.core.mapi.TaskMode = Zarafa.core.Enum.create({

	/**
	 * Denotes that the task is not assigned.
	 * @property
	 * @type Number
	 */
	'NOTHING': 0,
	/**
	 * Denotes that the task is embedded in a message.
	 * @property
	 * @type Number
	 */
	'REQUEST': 1,
	/**
	 * Denotes that the task has been accepted by assignee.
	 * @property
	 * @type Number
	 */
	'ACCEPT': 2,
	/**
	 * Denotes that the task has been rejected by assignee
	 * @property
	 * @type Number
	 */
	'DECLINE': 3,
	/**
	 * Denotes that the task is embedded in task update.
	 * @property
	 * @type Number
	 */
	'UPDATE': 4,
	/**
	 * Denotes that the task has been assigned to self
	 * @property
	 * @type Number
	 */
	'SELF': 5
});

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

/**
 * @class Zarafa.core.mapi.TaskOwnership
 * @extends Zarafa.core.Enum
 * @singleton
 *
 * The TaskOwnership property indicates the role of the current user relative to the Task object.
 */
Zarafa.core.mapi.TaskOwnership = Zarafa.core.Enum.create({
	/**
	 * Denotes that the task object is not assigned.
	 * @property
	 * @type Number
	 */
	'NEWTASK': 0,

	/**
	 * Denotes that the task object is the task assigner's copy of the Task object.
	 * @property
	 * @type Number
	 */
	'DELEGATEDTASK': 1,

	/**
	 * Denotes that the task object is the task assignee's copy of the Task object.
	 * @property
	 * @type Number
	 */
	'OWNTASK': 2
});

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

/**
 * @class Zarafa.core.mapi.TaskState
 * @extends Zarafa.core.Enum
 *
 * @singleton
 */
Zarafa.core.mapi.TaskState = Zarafa.core.Enum.create({

	/**
	 * Denotes that the task is normal.
	 * @property
	 * @type Number
	 */
	'NORMAL': 0,
	/**
	 * Denotes that a new task is not assigned.
	 * @property
	 * @type Number
	 */
	'OWNER_NEW': 1,
	/**
	 * Denotes that the task is the assignee copy of an assigned task.
	 * @property
	 * @type Number
	 */
	'OWNER': 2,
	/**
	 * Denotes that the task is the assigner copy of an assigned task.
	 * @property
	 * @type Number
	 */
	'ACCEPT': 3,
	/**
	 * Denotes that the task is Assigner version, but assignee has declined.
	 * @property
	 * @type Number
	 */
	'DECLINE': 4
});

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

/**
 * @class Zarafa.core.mapi.TaskStatus
 * @extends Zarafa.core.Enum
 *
 * @singleton
 */
Zarafa.core.mapi.TaskStatus = Zarafa.core.Enum.create({
	/**
	 * Denotes that the user has not started work on the task.
	 * @property
	 * @type Number
	 */
	'NOT_STARTED': 0,

	/**
	 * Denotes that the user work on this task is in progress.
	 * @property
	 * @type Number
	 */
	'IN_PROGRESS': 1,

	/**
	 * Denotes that the user work on this task is complete.
	 * @property
	 * @type Number
	 */
	'COMPLETE': 2,

	/**
	 * Denotes that user is waiting for somebody else.
	 * @property
	 * @type Number
	 */
	'WAIT_FOR_OTHER_PERSON': 3,

	/**
	 * Denotes that the user has deferred work on this task.
	 * @property
	 * @type Number
	 */
	'DEFERRED': 4,

	/**
	 * Return the display name for the given task Status
	 * @param {Zarafa.core.mapi.TaskStatus} tasktatus The given task status
	 * @return {String} The display name for the task status
	 */
	getDisplayName: function(taskstatus)
	{
		switch (taskstatus) {
			case Zarafa.core.mapi.TaskStatus.NOT_STARTED:
				return _('Not Started');
			case Zarafa.core.mapi.TaskStatus.IN_PROGRESS:
				return _('In Progress');
			case Zarafa.core.mapi.TaskStatus.COMPLETE:
				return _('Complete');
			case Zarafa.core.mapi.TaskStatus.WAIT_FOR_OTHER_PERSON:
				return _('Wait for other person');
			case Zarafa.core.mapi.TaskStatus.DEFERRED:
				return _('Deferred');
		}
		return '';
	}
});

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

/**
 * @class Zarafa.core.plugins.ContentTabLayerPlugin
 * @extends Zarafa.core.plugins.ContentLayerPlugin
 * @ptype zarafa.contenttablayerplugin
 *
 * Implementation of the {@link Zarafa.core.plugins.ContentLayerPlugin ConentLayerPlugin}
 * which supports placing the {@link Zarafa.core.ui.ContentPanel Content Panel} inside a Tab
 */
Zarafa.core.plugins.ContentTabLayerPlugin = Ext.extend(Zarafa.core.plugins.ContentLayerPlugin, {
	/**
	 * This will bring focus to the Container by bringing it to the attention
	 * of the user by {@link Zarafa.core.ui.ContextContainer#setActiveTab activating the tab}
	 *
	 * @protected
	 */
	focus: function()
	{
		container.getTabPanel().setActiveTab(this.field);

		// Also move keyboard focus into the panel so its keymap works immediately
		// (e.g. Ctrl+Alt+W to close) without requiring a click.
		if (Ext.isFunction(this.field.focus)) {
			if (this.field.rendered) {
				this.field.focus();
			}
			else {
				this.field.on('afterrender', this.field.focus, this.field, { single: true });
			}
		}
	}
});

Ext.preg('zarafa.contenttablayerplugin', Zarafa.core.plugins.ContentTabLayerPlugin);
Ext.namespace('Zarafa.core.plugins');

/**
 * @class Zarafa.core.plugins.ContentWindowLayerPlugin
 * @extends Zarafa.core.plugins.ContentLayerPlugin
 * @ptype zarafa.contentwindowlayerplugin
 *
 * Implementation of the {@link Zarafa.core.plugins.ContentLayerPlugin ConentLayerPlugin}
 * which supports placing the {@link Zarafa.core.ui.ContentPanel Content Panel} inside a
 * {@link Ext.Window Window}.
 */
Zarafa.core.plugins.ContentWindowLayerPlugin = Ext.extend(Zarafa.core.plugins.ContentLayerPlugin, {
	/**
	 * The {@link Ext.Window Window} object on which the {@link #field} has been installed.
	 * @property
	 * @type Ext.Window
	 * @protected
	 */
	win: undefined,

	/**
	 * @cfg {Array} windowRelays The list of event names which should be {@link Zarafa.core.ui.ContentPanel#relayEvents relayed}
	 * from the {@link #win}. Defaults to 'beforeshow', 'show', 'beforehide' and 'hide'.
	 */
	windowRelays: undefined,

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

		Ext.applyIf(config, {
			windowRelays: ['beforeshow', 'show', 'beforehide', 'hide']
		});

		Zarafa.core.plugins.ContentWindowLayerPlugin.superclass.constructor.call(this, config);
	},

	/**
	 * This is called after {@link #init} when the {@link #field} has been fully initialized,
	 * and the owner of the component is known. This allows the plugin to access the parent.
	 *
	 * @protected
	 */
	initPlugin: function()
	{
		this.win = this.field.findParentByType('window');

		// The Ext.Window itself is not stateful; however, each call to saveState
		// in the window should be redirected to the ContentPanel contained by it
		this.win.saveState = function() { this.field.saveState(); }.createDelegate(this);

		// Relay events from this.win as if they were fired by this
		// The aim is to go full cycle through the panel - e.g. when closing we normally want the panel to initiate the close
		this.field.relayEvents(this.win, this.windowRelays);

		Zarafa.core.plugins.ContentWindowLayerPlugin.superclass.initPlugin.apply(this, arguments);

		this.win.on('render', this.applyTooltip);
	},

	/**
	 * This will apply tooltip on close button ('X') of (@link Ext.Window Window).
	 */
	applyTooltip: function()
	{
		var el = this.el;
		var closeTab = Ext.get(el).child('.x-tool-close', true);
		if(closeTab) {
			closeTab.qtip = _('Close') + Zarafa.core.KeyMapMgr.formatShortcutHint('Ctrl + Alt + W', false);
		}
	},

	/**
	 * This will apply the required title onto the {@link #win Window}
	 *
	 * @param {String} title The title to apply
	 * @protected
	 */
	setTitle: function(title)
	{
		this.win.setTitle(Ext.util.Format.htmlEncode(title));
	},

	/**
	 * Handler for hiding this layer
	 *
	 * This plugin contains an {@link #win Ext.Window}, which is hidden by calling win.hide
	 * @protected
	 */
	hide: function()
	{
		this.win.hide();
	},

	/**
	 * Handler for closing this layer
	 *
	 * This plugin contains an {@link #win Ext.Window}, which is closed by calling win.close
	 * @protected
	 */
	close: function()
	{
		this.win.close();
	},

	/**
	 * This will bring focus to the Container by bringing it to the attention
	 * of the user by {@link Ext.WindowMgr#bringToFront moving the dialog to the top}
	 * @protected
	 */
	focus: function()
	{
		(this.win.manager || Ext.WindowMgr).bringToFront(this.win);
	}
});

Ext.preg('zarafa.contentwindowlayerplugin', Zarafa.core.plugins.ContentWindowLayerPlugin);
Ext.namespace('Zarafa.core.plugins');

/**
 * @class Zarafa.core.plugins.FormFieldTooltipPlugin
 * @extends Zarafa.core.plugins.ComponentTooltipPlugin
 * @ptype zarafa.formfieldtooltipplugin
 *
 * This plugin is use to set the tooltip on {@link Ext.form.Field FormField} component
 * of {@link Ext.BoxComponent BoxComponent}.
 */
Zarafa.core.plugins.FormFieldTooltipPlugin = Ext.extend(Zarafa.core.plugins.ComponentTooltipPlugin,{

	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};
		Ext.apply(this, config);

		Zarafa.core.plugins.FormFieldTooltipPlugin.superclass.constructor.call(this, config);
	},

	/**
	 * Initializes the {@link Ext.Component Component} to which this plugin has been hooked.
	 * @param {Ext.menu.Item} field The field on which the plugin is installed.
	 */
	init: function(field)
	{
		Zarafa.core.plugins.FormFieldTooltipPlugin.superclass.init.apply(this, arguments);
		this.field.on('render', this.onRenderApplyTooltip, this);
	},

	/**
	 * Used to apply the tooltip on {@link Ext.form.Field FormField} components
	 * of {@link Ext.BoxComponent BoxComponent}.
	 * @param {Ext.BoxComponent} component The box component of {@link Ext.Component Component}
	 */
	onRenderApplyTooltip: function(component)
	{
		if (Ext.isDefined(component.tooltip)) {
			// Ext.form.Field are not supported tooltip so we have to
			// manually apply the qtip on components. here we are not
			// apply the qtip on wrap object because we want to show tooltip
			// on components object only and not on wrapper.
			if (Ext.isDefined(component.wrap)) {
				Ext.each(component.wrap.dom.children, function (childComponent) {
					childComponent.qtip = component.tooltip;
				});
			} else {
				component.getEl().dom.qtip = component.tooltip;
			}
		}
	}
});

Ext.preg('zarafa.formfieldtooltipplugin', Zarafa.core.plugins.FormFieldTooltipPlugin);/*
 * #dependsFile client/zarafa/core/ui/Toolbar.js
 */
Ext.namespace("Zarafa.core.ui");

/**
 * @class Zarafa.core.ui.ContentPanelToolbar
 * @extends Zarafa.core.ui.Toolbar
 * @xtype zarafa.contentpaneltoolbar
 */
Zarafa.core.ui.ContentPanelToolbar = Ext.extend(Zarafa.core.ui.Toolbar, {
	/**
	 * @cfg {String} insertionPointBase The base string for Insertion points in
	 * this panel. (i.e. context.mail.showmailcontentpanel). This base name is
	 * used for accessing the insertion points which are defined for the content panel
	 * toolbar. Used insertion points are:
	 *	[insertionPointBase].toolbar.actions
	 *	[insertionPointBase].toolbar.options
	 */
	insertionPointBase: undefined,
	/**
	 * @cfg {Array} actionItems The array of {@link Ext.Component} elements which should be added to the actions {@link Ext.Button buttons} of the
	 * {@link Zarafa.core.ui.Toolbar}. These elements can be extended by the main.dialog.[dialog].toolbar.actions insertion point.
	 */
	actionItems: [],
	/**
	 * @cfg {Array} actionItems The array of {@link Ext.Component} elements which should be added to the options {@link Ext.Button buttons} of the
	 * {@link Zarafa.core.ui.Toolbar}. These elements can be extended by the main.dialog.[dialog].toolbar.options insertion point.
	 */
	optionItems: [],
	/**
	 * @cfg {Array} rightAlignedItems The array of {@link Ext.Component} elements which should be added to the options {@link Ext.Button buttons} of the
	 * {@link Zarafa.core.ui.Toolbar}. These elements can be extended by the main.dialog.[dialog].toolbar.options.right insertion point.
	 */
	rightAlignedItems: [],
	/**
	 * @constructor
	 * @param config Configuration structure
	 */
	constructor: function(config)
	{
		Ext.apply(this, config, {
			// Override from Ext.Component
			xtype: 'zarafa.contentpaneltoolbar',
			cls: 'zarafa-dialogtoolbar'
		});

		// Declare events.
		this.addEvents(
			/**
			 * @event window_before_pop
			 * Fires before a window is popped in or out of the browser window.
			 * @param {Ext.data.Record} record The record which is currently used in the window
			 */
			'window_before_pop'
		);

		Zarafa.core.ui.ContentPanelToolbar.superclass.constructor.call(this, config);

		this.initButtonGroups();
	},

	/**
	 * Add default button groups to toolbar.
	 * @private
	 */
	initButtonGroups: function()
	{
		var namespace = this.insertionPointBase + '.toolbar';

		// Initialize the items list with all buttons which were registered through insertion points.
		this.addItems(this.actionItems, namespace + '.actions');
		this.addItems(this.optionItems, namespace + '.options');

		// It will render all rightAlignedItems to the Right side in toolbar
		if(!Ext.isEmpty(this.rightAlignedItems)) {
			this.rightAlignedItems = ["->"].concat(this.rightAlignedItems);
			this.addItems(this.rightAlignedItems, namespace + '.options.right');
		}
	}
});

Ext.reg('zarafa.contentpaneltoolbar', Zarafa.core.ui.ContentPanelToolbar);
Ext.namespace('Zarafa.core.ui');

/**
 * @class Zarafa.core.ui.ContextContainer
 * @extends Zarafa.core.ui.SwitchViewContentContainer
 *
 * The ContextContainer is a light-weight ExtJS container that holds several child
 * components in a card layout, and is populated using an insertion point.
 * Each child item belongs to a ContentContext, and corresponding children are
 * made visible when contexts are switched. Two examples of ContextContainer objects
 * in the main layout are the top tool bar and the main content panel. Each available
 * content context (mail, tasks, etc) registers exactly one component with each named
 * context container (main.toolbar, main.content). When a folder is selected via
 * the hierarchy panel on the left and a context switch is needed (ie, when a mail
 * folder is selected the mail toolbar and mail content panel need to be shown) the
 * context containers will show the child items that correspond to the newly selected
 * context.
 */
Zarafa.core.ui.ContextContainer = Ext.extend(Zarafa.core.ui.SwitchViewContentContainer, {
	/**
	 * @cfg {String} name The name of the context container.
	 */
	name: undefined,

	/**
	 * @cfg {String} title The title of this component. Generally used only by owner container
	 */
	title: undefined,

	/**
	 * @constructor
	 * @param config configuration object
	 */
	constructor: function(config)
	{
		if (config.name) {
			this.name = config.name;
		}

		// Gather child components through an insertion point.
		var items = [];
		var lazyItems = container.populateInsertionPoint(this.name, config.scope);

		// Go over all plugins which have inserted components, each item needs to
		// be assigned with a unique ID which is made up out of this container's name
		// and the name of the plugins that inserted them. We use this for switching
		// between them.
		// At the same time we will be looking for components that were added by the
		// currently active context, as those will be set as the default component.
		var currentContext = container.getCurrentContext();
		Ext.each(lazyItems, function(item) {
			var pluginName = item.context.getName();

			// Check if this is the default component
			if (currentContext && pluginName === currentContext.getName()) {
				items.push(item);
			}
		}, this);

		// Standard configuration.
		Ext.applyIf(config, {
			autoDestroy: true,
			layout: 'card',
			deferredRender: 'false',
			hideMode: 'offsets',
			size: 'auto',
			border: false,
			lazyItems: lazyItems,
			items: items,
			activeItem: 0,
			// Fix for ExtJS 3.1
			forceLayout: true
		});

		this.addEvents(
			/**
			 * @event titlechange
			 * This event behaves similarly to the one in {@link Ext.Panel} Panel.
			 * Its primary purpose is to notify its parent containers of a title change.
			 * The idea is that this event will be passed on to an adapter that would know how to update the actual container
			 * @param {Ext.Component} this The component that fired the event
			 * @param {String} title New title that is being set on the container
			 * @param {String} oldTitle The previous title of this container
			 */
			 'titlechange',

			/**
			 * @event iconchange
			 * Fired when the icon class of this component is changed.
			 * Its primary purpose is to notify its parent containers of the change.
			 * @param {Ext.Component} this THe component that fired the event
			 * @param {String} iconCls The new icon class
			 * @param {String} oldIconCls The old icon class
			 */
			 'iconchange'
		);

		Zarafa.core.ui.ContextContainer.superclass.constructor.call(this, config);

		this.init();
	},

	/**
	 * Initialises the component.
	 * @private
	 */
	init: function()
	{
		this.mon(container, 'contextswitch', this.onContextSwitch, this);
		this.mon(container, 'folderselect', this.onFolderSelect, this);

		//initialize title
		var model = container.getCurrentContext().getModel(),
			title, iconCls;
		if(model){
			var folder = model.getDefaultFolder();
			if(folder){
				title = folder.getFullyQualifiedDisplayName();
				iconCls = Zarafa.common.ui.IconClass.getIconClass(folder);
			}
		} else {
			title = container.getCurrentContext().getDisplayName();
		}

		this.setTitle(title);
		this.setIcon(iconCls);
	},

	/**
	 * Set the title for this component
	 * The event 'titlechange' is fired to notify owner container of the change
	 * (for instance a tab panel would want to change the tab title, a window - the window title, etc.)
	 * @param {String} title The new title to set
	 */
	setTitle: function(title)
	{
		var oldTitle = this.title;
		this.title = title;

		this.fireEvent('titlechange', this, title, oldTitle);
	},

	/**
	 * Set the icon class for this component
	 * The event 'iconchange' is fired to notify the owner container of the change
	 * tab panels, for instance could place an icon in this component's tab
	 * @param {String} iconCls The new icon class to set
	 */
	setIcon: function(iconCls)
	{
		var oldIconCls = this.iconCls;
		this.iconCls = iconCls;

		this.fireEvent('iconchange', this, iconCls, oldIconCls);
	},

	/**
	 * Handler for the 'contextswitch' event triggered by the Zarafa.core.Container
	 * Switches between child items depending on the context they belong to. Switching
	 * to 'mail' shows the component associated with the mail context and hides all others.
	 * @param {Object} parameters Parameters passed to the context switch
	 * @param {String} oldContext name of the context that was last active
	 * @param String newContext name of the context to be made visible (mail, task, calendar, etc)
	 * @private
	 */
	onContextSwitch: function(parameters, oldContext, newContext)
	{
		var oldItem = this.getActiveItem();
		var newItem = this.findBy(function(item) { return item.context === newContext; });
		var visible = true;

		if (!Ext.isEmpty(newItem)) {
			newItem = newItem[0];
			this.layout.setActiveItem(newItem.id);
		} else {
			visible = false;
		}

		if (this.isVisible() != visible) {
			this.setVisible(visible);
		}

		// TODO: We should enable some timeout mechanism which
		// removes and deletes the oldView after a particular timeout.
		// This should increase performance when switching between 2
		// views often.
		if (this.autoClean === true && oldItem && oldItem != newItem) {
			this.remove(oldItem);
			delete oldItem;
		}
		var model = newContext.getModel();
		var folder, title, iconCls;
		if(model){
			folder = model.getDefaultFolder();
		}
		if(folder){
			title = folder.getFullyQualifiedDisplayName();
			iconCls = Zarafa.common.ui.IconClass.getIconClass(folder);
		} else {
			title = newItem.title;
			iconCls = newItem.iconCls;
		}
		this.setTitle(title);
		this.setIcon(iconCls);
	},

	/**
	 * Handler for the 'folderchange' event, triggered by Zarafa.core.Container when a folder is selected in the hierarchy
	 * @param {Zarafa.core.data.IPFRecord} folders
	 * @private
	 */
	onFolderSelect: function(folders)
	{
		var title, iconCls, folder;
		if(folders){
			if(Array.isArray(folders)){
				folder = folders[0];
			} else {
				folder = folders;
			}
			title = folder.getFullyQualifiedDisplayName();
			iconCls = Zarafa.common.ui.IconClass.getIconClass(folder);
		} else {
			title = container.getCurrentContext().getDisplayName();
		}

		this.setTitle(title);
		this.setIcon(iconCls);
		//FIXME: why is this automatic for context switch but not for folder change?
		this.ownerCt.setActiveTab(0);
	}
});
Ext.namespace('Zarafa.core.ui');

/**
 * @class Zarafa.core.ui.MainToolbar
 * @extends Zarafa.core.ui.Toolbar
 * @xtype zarafa.maintoolbar
 */
Zarafa.core.ui.MainToolbar = Ext.extend(Zarafa.core.ui.Toolbar, {
	// Insertion points for this class
	/**
	 * @insert main.maintoolbar.new.item
	 * Insertion point for populating the "New item" menu. It will be placed in the item part of the
	 * list. Each item inserted to this list is accessible from all contexts.
	 * @param {Zarafa.core.ui.MainToolbar} toolbar This toolbar
	 */
	/**
	 * @insert main.maintoolbar.new.folder
	 * Insertion point for populating the "New item" menu. It will be placed in the folder part of
	 * the list. Each item inserted to this list is accessible from all contexts.
	 * @param {Zarafa.core.ui.MainToolbar} toolbar This toolbar
	 */
	/**
	 * @insert main.toolbar.actions
	 * Insertion point for populating the main toolbar with buttons. It will be added after the
	 * NEW-button and before the VIEW-buttons.
	 */
	/**
	 * @insert main.toolbar.actions.last
	 * Insertion point for populating the main toolbar with buttons. It will be added after the
	 * VIEW-buttons.
	 */

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

		Ext.applyIf(config, {
			// Override from Ext.Component
			xtype: 'zarafa.maintoolbar',
			id: 'zarafa-maintoolbar',
			cls: 'zarafa-maintoolbar',
			ariaLabel: _('Main toolbar')
		});

		Zarafa.core.ui.MainToolbar.superclass.constructor.call(this, config);

		this.initButtonGroups();
	},

	/**
	 * Add default button groups to toolbar
	 * @private
	 */
	initButtonGroups: function()
	{
		// Initialize the items list with all default buttons, and add buttons which were
		// registered through insertion points.
		this.addItems([], 'main.toolbar.actions.first');
		this.addNewItems();
		this.addActionItems();
		this.addPrintButton();
		this.addViewItems();
		this.addItems([], 'main.toolbar.actions.last');
		this.addItems([{xtype: 'tbfill'}], 'main.toolbar.actions.right');
	},

	/**
	 * Creates the "New" {@link Ext.Button}. The NEW-button menu consists of three parts. The first
	 * part is the button that will be considered the default button for the active context. The
	 * second part contains items that deal with folders and the last part will deal with the
	 * creation of items.
	 * The menu will be populated by using the main.maintoolbar.new.folder and
	 * main.maintoolbar.new.item insertion points. The first one is for adding items to the second
	 * (folder) part and the latter one is for adding items to the third (item) part of the menu.
	 *
	 * Based on the newMenuIndex property of each button the items are sorted. Based on the text
	 * property, double items are filtered out of the list. When the list contains items a dummy
	 * button and a separator is added at the top of the list. The dummy button will "replace" the
	 * item of the active context. The other item will not be removed, but it will be hidden and
	 * properties like the icon and the handler are also set on the dummy button. This switch is
	 * done when the contexts are switched by the {@link Zarafa.core.Container Container}.
	 * @private
	 */
	addNewItems: function()
	{
		var menu = [];
		var itemMenu = container.populateInsertionPoint('main.maintoolbar.new.item', this) || [];

		// Make sure the items are properly sorted by priority.
		itemMenu = Zarafa.core.Util.sortArray(itemMenu, 'ASC', 'newMenuIndex');

		// Remove doubles
		itemMenu = Zarafa.core.Util.uniqueArray(itemMenu, 'text');

		// menu.length should practically always be non-zero, but
		// just in case, we check it, and add the divider to separate
		// the default buttons from the rest.
		if (itemMenu.length !== 0) {
			// Hide the first item as we copy that to the default button.
			itemMenu[0].hidden = true;
			itemMenu[0].ref = 'defaultButton';
			menu.push({
				xtype: 'menuitem',
				id: 'zarafa-maintoolbar-newitem-defaultbutton',
				tooltip: itemMenu[0].tooltip,
				plugins: 'zarafa.menuitemtooltipplugin',
				// The following config properties copy the first item for the default button.
				iconCls: itemMenu[0].iconCls,
				text: itemMenu[0].text,
				handler: itemMenu[0].handler,
				scope: itemMenu[0].scope
			},
			'-');
		}

		// Add the item list to to the menu
		if (itemMenu.length !== 0) {
			menu = menu.concat(itemMenu);
		}

		this.addItems({
			xtype: 'splitbutton',
			id: 'zarafa-maintoolbar-newitem',
			cls: 'zarafa-action',
			scale: 'large',
			ref: 'newButton',
			rowspan: 2,
			menu: menu,
			listeners: {
				render: this.onRenderNewButton,
				scope: this
			},
			// The following config properties set the first item (that is now item number
			// three in the menu) as the button in the toolbar.
			iconCls: itemMenu[0] ? itemMenu[0].iconCls : undefined,
			handler: itemMenu[0] ? itemMenu[0].handler : undefined,
			scope: itemMenu[0] ? itemMenu[0].scope : undefined
		});
	},

	/**
	 * Adds an event handler to the NEW-button that is registered to the contextswitch event of the
	 * {@link Zarafa.core.Container Container}. This event handler will set the correct icon and
	 * handler for the {@link Ext.SplitButton SplitButton} in the toolbar. It will also add the icon,
	 * handler and text for the first item: the dummy button that holds the default action for the
	 * active context. This event handler is called within the scope of the SplitButton.
	 * It will loop through all the menu items in the last part of the menu and based on the
	 * activate context it will hide or show the menu items. If the button is marked to belong to
	 * the active context it will be hidden and the dummy button will be changed so it will look
	 * like the button has moved to the top. All other buttons will remain visible.
	 * If no button is marked to belong to the active context the first button in the list, after
	 * the dummy button and the separator, will become the new default button.
	 * @param {Ext.Component} cmp The SplitButton
	 */
	onRenderNewButton: function(cmp)
	{
		cmp.mon(container, 'contextswitch', this.onNewButtonContextSwitch, this);
		this.onNewButtonContextSwitch(null, container.getCurrentContext(), container.getCurrentContext());
	},

	/**
	 * Fired when the {@link Zarafa.core.Container container} fires the {@link Zarafa.core.Container#contextswitch} event.
	 * This will update the default new button to be shown.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder folder that is loaded for the new context
	 * @param {Zarafa.core.Context} oldContext context that was switched out
	 * @param {Zarafa.core.Context} newContext new context that was switched
	 * @private
	 */
	onNewButtonContextSwitch: function(parameters, oldContext, newContext)
	{
		var menu = this.newButton.menu;

		// The dummy button is the first button in the context menu that shows the button first
		// button registered by the active context.
		var dummyButton = menu.items.itemAt(0);
		// The default button is the button that will be set as the SplitButton if the active
		// context has not registered any buttons. The first button in the item-list will become
		// the default button.
		var defaultButton = menu.defaultButton;
		// Find the index of the default button. From this index on the list will be run down to
		// find the button that should be put as the SplitButton/dummy button.
		var itemIndex = menu.items.indexOf(defaultButton);
		// Use this variable to check whether there has already been assigned
		// a button to the default item. It could be that there are two buttons
		// being set by the same context.
		var button = false;

		// Only loop through the list if the default button is found.
		if(itemIndex >= 0){
			// Loop from the itemIndex onwards.
			for(var i=itemIndex,len=menu.items.length;i<len;i++){
				var item = menu.items.itemAt(i);
				// Only get the first button of the context that is active
				if(button===false && item.context == newContext.getName()){
					button = item;
				// Hide the found button of the active context to prevent doubles
					item.setVisible(false);
				} else {
					// Enable all other buttons
					item.setVisible(true);
				}
			}
		}

		// If no button was found, fall back to the default button
		if(button===false && defaultButton){
			button = defaultButton;
			// Hide the defaultButton so it does not show twice in the menu
			defaultButton.setVisible(false);
		}

		// If we have a button set the SplitButton and the dummy button properties
		if(button!==false){
			// Set the icon class and handler for the button in the toolbar
			this.newButton.setIconClass(button.iconCls);
			this.newButton.setHandler(button.handler, button.scope);

			// Set the default item (first item in the dropdown)
			dummyButton.tooltip = button.tooltip;
			dummyButton.setText(button.text);
			dummyButton.setIconClass(button.iconCls);
			dummyButton.setHandler(button.handler, button.scope);
		}
	},

	/**
	 * Creates the "View" {@link Ext.Button buttons}.
	 * For each context a "View" button will be created which will be populated with the
	 * main.maintoolbar.view.[context]. The first item from that list will also be used to set the
	 * properties like iconCls and handler to the {@link Ext.SplitButton SplitButton}.
	 * @private
	 */
	addViewItems: function()
	{
		var menu, menuItems = [];
		var contexts = container.getContexts();
		for(var i = 0,len = contexts.length; i < len; i++){
			var context = contexts[i];
			menu = context.getMainToolbarViewButtons();
			if (Array.isArray(menu) && menu.length) {
				if (context.groupViewBtns) {
					menuItems.push({
						xtype: 'splitbutton',
						id: 'zarafa-maintoolbar-view-'+context.getName(),
						scale: 'large',
						iconCls: 'icon_large_view',
						tooltip: _('Switch view') + Zarafa.core.KeyMapMgr.formatShortcutHint('Ctrl + Alt + 1..9', false),
						hidden: true,
						contextName: context.getName(),
						menu: Zarafa.core.Util.uniqueArray(menu, 'text'),
						listeners: {
							render: this.onRenderViewButton,
							menuhide: this.onSwitchViewMenuHide,
							menushow: this.onSwitchViewMenuShow.createDelegate(this,[context], true),
							scope: this
						}
					});
				} else {
					for (var j = 0, jlen = menu.length; j < jlen; j++) {
						var item = Ext.apply({}, menu[j], {
							xtype: 'button',
							scale: 'large',
							hidden: true,
							contextName: context.getName()
						});

						// Move the 'text' property to the tooltip
						var viewCounter = j + 1;
						item.tooltip = item.text + Zarafa.core.KeyMapMgr.formatShortcutHint('Ctrl + Alt + '+ viewCounter, false);
						delete item.text;

						item.listeners = Zarafa.core.Util.mergeListeners(item.listeners, {
							render: this.onRenderViewButton
						});

						menuItems.push(item);
					}
				}
			}
		}

		this.addItems(menuItems);
	},

	/**
	 * Event handler which is triggered when switch view split button
	 * menu hide. function which remove selection css class from
	 * split button menu item.
	 *
	 * @param {Ext.SplitButton} splitBtn split button which contains highlighted menu item.
	 * @param {Ext.menu.Menu} menu menu contains highlighted menu item.
	 */
	onSwitchViewMenuHide: function (splitBtn, menu)
	{
		menu.find().forEach(function(item){
			var hasClass = item.getEl().hasClass('x-menu-item-selected');
			if(hasClass) {
				item.getEl().removeClass('x-menu-item-selected');
			}
		}, this);
	},

	/**
	 * Event handler which is triggered when switch view split button
	 * menu shows. It will highlight current selected view in switch
	 * view menu by checking {@link Zarafa.core.Context#current_view_mode view mode}
	 * of menu item and for sticky note switch view menu we use
	 * {@link Zarafa.core.Context#current_view view} to highlight the
	 * menu item.
	 *
	 * @param {Ext.SplitButton} splitBtn split button which menu item is going to highlight.
	 * @param {Ext.menu.Menu} menu menu contains the menu item which is going to highlight
	 * @param {Zarafa.core.Context} context which contains the switch view split button.
	 */
	onSwitchViewMenuShow: function(splitBtn, menu, context)
	{
		if(context.getName() !== 'note') {
			var menuItem = menu.find('valueViewMode', context.getCurrentViewMode())[0];
			if (Ext.isDefined(menuItem)) {
				menuItem.addClass('x-menu-item-selected');
			}
		} else {
			var menuItems = menu.find('valueView', context.getCurrentView());
			var currentDataMode = context.getModel().getCurrentDataMode();
			menuItems.forEach(function (menuItem) {
				if(menuItem.valueDataMode === currentDataMode) {
					menuItem.addClass('x-menu-item-selected');
					return;
				}
			}, this);
		}
	},

	/**
	 * Adds an event handler to the VIEW-button that is registered to the contextswitch event of the
	 * {@link Zarafa.core.Container Container}. This event handler will enable or disable the
	 * VIEW-button based on whether this button belong to the active context. This event handler is
	 * called within the scope of the SplitButton/Button.
	 * @param {Ext.Component} cmp The SplitButton/Button
	 */
	onRenderViewButton: function(cmp)
	{
		cmp.mon(container, 'contextswitch', function(parameters, oldContext, newContext) {
			this.setVisible(this.contextName == newContext.getName());
		}, cmp);
		//we need this line in order to set the visibility correctly as the contextswitch event will not have fired yet
		cmp.setVisible(cmp.contextName === container.getCurrentContext().getName());

		// We want the menu to be toggled when the splitbutton is clicked. We cannot use the regular btn handler
		// for this, since an opened menu will already be closed before the handler is executed and there is
		// no way of knowing anymore if we should open it or not. So we will add a handler for an ordinary
		// mousedown event that gets called when the menu is still open.
		if ( cmp.xtype === 'splitbutton' ){
			cmp.btnEl.on('mousedown', function(){
				if ( cmp.hasVisibleMenu() ){
					cmp.hideMenu();
				} else {
					cmp.showMenu();
				}
			});
		}
	},

	/**
	 * Creates the Print {@link Ext.Button buttons}.
	 * This differs for each context, and is populated with
	 * main.toolbar.view.[context].
	 */
	 addPrintButton: function()
	 {
		 var menu, menuItems = [];
		 var contexts = container.getContexts();
		 for(var i=0,len=contexts.length;i<len;i++){
			var context = contexts[i];
			menu = context.getMainToolbarPrintButtons();
			if(Array.isArray(menu) && menu.length){
				menuItems.push({
					xtype: 'splitbutton',
					id: 'zarafa-maintoolbar-print-'+context.getName(),
					name: 'defaultPrintBtn',
					scale: 'large',
					iconCls: 'icon_large_print',
					tooltip: _('Print') + Zarafa.core.KeyMapMgr.formatShortcutHint('Ctrl + P', true),
					handler: menu[0].handler,
					scope: menu[0].scope,
					hidden: true,
					contextName: context.getName(),
					menu: {
						xtype: 'zarafa.conditionalmenu',
						defaults: {
							context: context,

							// Override getRecords to obtain the
							// records from the current selected records
							getRecords: function() {
								return this.context.getModel().getSelectedRecords();
							}
						},
						items: Zarafa.core.Util.uniqueArray(menu, 'text'),
						// Override this function so the menu will not be destroyed when hidden.
						onMenuHide: Ext.emptyFn
					},
					listeners: {
						render: this.onRenderPrintButton
					}
				});
			}
		}

		this.addItems(menuItems);
	 },

	/**
	 * Adds an event handler to the Print button that is registered to the contextswitch event of the
	 * {@link Zarafa.core.Container Container}. This event handler will enable or disable the
	 * VIEW-button based on whether this button belong to the active context. This event handler is
	 * called within the scope of the SplitButton.
	 * @param {Ext.Component} cmp The SplitButton
	 */
	onRenderPrintButton: function(cmp)
	{
		cmp.mon(container, 'contextswitch', function(parameters, oldContext, newContext) {
			this.setVisible(this.contextName == newContext.getName());
		}, cmp);
		//we need this line in order to set the visibility correctly as the contextswitch event will not have fired yet
		cmp.setVisible(cmp.contextName === container.getCurrentContext().getName());
	},

	/**
	 * Adds the action buttons to the main toolbar. It adds a couple of default buttons by default
	 * and allows to populate the toolbar with more buttons by using the main.maintoolbar.actions
	 * insertion point.
	 * @private
	 */
	addActionItems: function()
	{
		var menuItems = [{
			xtype: 'button',
			id: 'zarafa-maintoolbar-addressbook',
			scale: 'large',
			overflowText: _('Address Book'),
			tooltip: _('Address Book'),
			iconCls: 'icon_large_addressbook',
			handler: this.onAddressBook,
			scope: this
		},{
			xtype: 'button',
			id: 'zarafa-maintoolbar-refresh',
			scale: 'large',
			title: _('Refresh'),
			overflowText: _('Refresh'),
			tooltip: _('Refresh') + ' (F5)',
			iconCls: 'icon_large_refresh',
			handler: this.onRefresh,
			ref: 'refreshButton',
			scope: this,
			listeners: {
				render: this.onRenderRefreshButton,
				scope: this
			}
		}];

		this.addItems(menuItems, 'main.toolbar.actions');
	},


	/**
	 * Open the {@link Zarafa.addressbook.dialogs.AddressBookContentPanel AddressBookContentPanel}
	 * @private
	 */
	onAddressBook: function()
	{
		Zarafa.addressbook.Actions.openAddressBook();
	},

	/**
	 * This will Refresh the view and fire {@link Zarafa.core.data.ListModuleStore#reload}
	 * @private
	 */
	onRefresh: function()
	{
		var model = container.getCurrentContext().getModel();
		if (model) {
			model.reload();
		}
	},

	/**
	 * This will print the current view
	 * @private
	 */
	onPrint: function()
	{
		var context = container.getCurrentContext();
		if (context) {
			Zarafa.common.Actions.openPrintDialog(context);
		}
	},

	/**
	 * Adds an event handler to the Refresh button that will register the contextswitch event of the
	 * {@link Zarafa.core.Container Container}. This event handler will hide or unhide the
	 * Refresh button based on whether the active context have store or not. This event handler is
	 * called within the scope of the Refresh Button.
	 * @param {Ext.Component} cmp The {@link #refreshButton}
	 */
	onRenderRefreshButton: function(cmp)
	{
		var context = container.getCurrentContext();
		cmp.mon(container, 'contextswitch', function(parameters, oldContext, newContext) {
			var store = newContext.getModel().getStore();
			this.setVisible(Ext.isDefined(store));
		}, cmp);

		//we need this line in order to set the visibility correctly as the contextswitch event will not have fired yet
		var contextModel = context.getModel();
		cmp.setVisible(Ext.isDefined(contextModel) && Ext.isDefined(contextModel.getStore()));
	}
});

Ext.reg('zarafa.maintoolbar', Zarafa.core.ui.MainToolbar);
Ext.namespace('Zarafa.core.ui');

/**
 * @class Zarafa.core.ui.NavigationPanel
 * @extends Zarafa.core.ui.MainViewSidebar
 * @xtype zarafa.navigationpanel
 *
 * NavigationPanel provides basic navigation through {@link Zarafa.hierarchy.ui.Tree} and
 * {@link Zarafa.core.ui.NavigationButtonPanel} and other components which are provided by different
 * contexts. E.g. DatePicker provided by CalendarContext
 */
Zarafa.core.ui.NavigationPanel = Ext.extend(Zarafa.core.ui.MainViewSidebar, {
	// Insertion points for this class
	/**
	 * @insert navigation.north
	 * Insertion point for adding components to top part of the NavigationPanel like Datepicker for
	 * Calendar. Add the reference to the related Context in the property navigationContext to the
	 * added component. Then this component will only be shown when the related Context is active.
	 * If it is not set it will always show the component.
	 * @param {Zarafa.core.ui.NavigationPanel} panel This panel
	 */
	/**
	 * @insert navigation.center
	 * Insertion point for adding a {@link Zarafa.core.ui.ContextNavigationPanel ContextNavigationPanel}
	 * to the {@link #centerPanel centerPanel}. This hierarchy panel will be shown when the Context it
	 * is related to is active. For that to work the {@link Zarafa.core.ui.ContextNavigationPanel#context context}
	 * property needs to be set on the added ContextNavigationPanel.
	 * @param {Zarafa.core.ui.NavigationPanel} panel This panel
	 */
	/**
	 * @insert navigation.south
	 * Insertion point for adding components to top part of the NavigationPanel like Datepicker for
	 * Calendar. Add the reference to the related Context in the property navigationContext to the
	 * added component. Then this component will only be shown when the related Context is active.
	 * If it is not set it will always show the component.
	 * @param {Zarafa.core.ui.NavigationPanel} panel This panel
	 */

	/**
	 * Reference to the {@link Ext.Container Container} that holds the hierarchy panels for all the
	 * Contexts. It contains a card layout that is able to switch between the different items within
	 * the Container.
	 * @property
	 * @type Ext.Container
	 */
	centerPanel: null,

	/**
	 * This will determine whether the "Show all Folders" Panel will be shown or if
	 * the Context-related {@link Zarafa.core.ui.ContextNavigationPanel ContextNavigationPanel} will
	 * be shown.
	 * @property
	 * @type Boolean
	 */
	showFolderList: false,

	/**
	 * @constructor
	 * @param {Object} config configuration object
	 */
	constructor: function (config)
	{
		this.addEvents([
			/**
			 * @event toggleshowallfolders Fired when the user switches between forcing to show all folders in the hierarchy tree
			 * @param {Boolean} show The showAllFolders state
			 */
			'toggleshowallfolders'
		]);

		config = config || {};

		// Collect components for 'north', 'center' and 'south' from registered plugins
		var northComponents = container.populateInsertionPoint('navigation.north', this);
		var centerComponents = container.populateInsertionPoint('navigation.center', this);
		var southComponents = container.populateInsertionPoint('navigation.south', this);

		for (var i = 0, len = northComponents.length; i < len; i++){
			northComponents[i] = Ext.create(northComponents[i]);
		}

		for (var i = 0, len = centerComponents.length; i < len; i++){
			centerComponents[i] = Ext.create(centerComponents[i]);
		}
		// Add the default ShowAllFoldersPanel to the start of the centerComponents
		centerComponents.unshift(this.getAllFoldersPanel());

		for (var i = 0, len = southComponents.length; i < len; i++){
			southComponents[i] = Ext.create(southComponents[i]);
		}
		var items = [];
		items.push.apply(items, northComponents);

		items.push({
				xtype: 'container',
				ref: 'centerPanel',
				cls: 'zarafa-navigationpanel-centerpanel',
				flex: 1,
				layout: {
					type: 'card',
					deferredRender: true
				},
				activeItem: 0,
				items: centerComponents
			});

		items.push.apply(items, southComponents);

		// Add CSS class to every item
		for(var i=0,len=items.length;i<len;i++){
			// Prevent overriding other css classes
			if(Ext.isEmpty(items[i].cls)){
				items[i].cls = 'zarafa-navigationpanel-item';
			} else {
				items[i].cls += ' zarafa-navigationpanel-item';
			}
		}

		Ext.applyIf(config, {
			border: false,
			layout: {
				type: 'vbox',
				align: 'stretch'
			},
			id: 'zarafa-navigationpanel',
			cls: 'zarafa-navigation zarafa-panel zarafa-context-mainpanel',

			north: northComponents,
			center: centerComponents,
			south: southComponents,

			items: items,
			collapseQuickTip: _('Collapse hierarchy'),
			expandQuickTip: _('Expand hierarchy'),
			animCollapse: false,
			headerCfg: { cls: 'zarafa-main-header x-panel-header' }
		});

		// parent constructor
		Zarafa.core.ui.NavigationPanel.superclass.constructor.call(this, config);

		// If previous value is there in state to "show all the folders" then simply assign it to respective config
		// TODO: This is achieved by overriding the applyState in Zarafa.hierarchy.ui.HierarchyTreePanel class.
		// But, the instance of Zarafa.core.ui.NavigationPanel is not created when applyState gets executed.
		var checkBoxState = container.getSettingsModel().get('zarafa/v1/state/sidebars/hierarchytree/showallcheckbox');
		this.showFolderList = checkBoxState || false;
	},

	/**
	 * @return {Ext.Container} Container which contains {@link Zarafa.hierarchy.ui.FolderSelectTree HierarchyTree}
	 * component and {@link Zarafa.hierarchy.ui.OpenSharedStorePanel OpenSharedStorePanel} component.
	 */
	getAllFoldersPanel: function()
	{
		return {
			xtype: 'zarafa.contextnavigation',
			ownerTitle: _('Folders List'),
			cls: 'zarafa-navigationpanel-centerpanel-allfolders',
			ref: 'allFoldersPanel',
			items: [{
				layout: 'fit',
				cls: 'zarafa-context-navigation-block',
				items: this.getFolderListPanel()
			}]
		};
	},

	/**
	 * @return {Zarafa.hierarchy.ui.Tree} Tree component showing folder list
	 */
	getFolderListPanel: function()
	{
		return {
			xtype: 'zarafa.hierarchytreepanel',
			enableDD: true,
			ref: '../allFoldersHierarchyTree',
			enableItemDrop: true,
			showAllFoldersDefaultValue: true,
			deferredLoading: true
		};
	},

	/**
	 * Fires on context switch from container. Updates local folder tree
	 * @param {Object} parameters contains folder details
	 * @param {Context} oldContext previously selected context
	 * @param {Context} newContext selected context
	 *
	 * @private
	 */
	onContextSwitch: function(parameters, oldContext, newContext)
	{
		this.activeContext = newContext;

		this.toggleVisibilityNavigationComponents(true);

		var title = newContext.getDisplayName() || _('Folders List');
		this.setTitle(title);
	},

	/**
	 * Set all components that are related to the active Context to visible and hide the other
	 * ones. The {@link #centerPanel centerPanel} is always visible and contains a card layout. It
	 * will switch to the tab that is related to the active Context as well. If no tab is related
	 * than it will show the {@link #AllFoldersPanel AllFoldersPanel} by default.
	 * @param {Boolean} isContextSwitched true if the context is switched else false.
	 * 
	 * @private
	 */
	toggleVisibilityNavigationComponents: function(isContextSwitched)
	{
		if (!Ext.isDefined(isContextSwitched)) {
			isContextSwitched = false;
		}

		// Loop through all items in the navigation panel and determine whether to hide or show them
		for (var i = 0, len = this.items.length; i < len; i++){
			var item = this.items.itemAt(i);
			// Do not change the visibility of the centerPanel
			if(item !== this.centerPanel){
				// If no navigationContext property is set always show it, otherwise only for the related Context
				if(!item.navigationContext || item.navigationContext == this.activeContext){
					item.setVisible(true);
				} else {
					item.setVisible(false);
				}
			}
		}

		var center = this.centerPanel;
		var layout = center.getLayout();

		var previousActiveSearchFilter = layout.activeItem && layout.activeItem.filterSearchTextBox;
		// Use this variable to see if we have to switch to the default one if none is found
		var contextNavPanelFound = false;
		// Loop through all items to find the one for the active Context
		for (var i = 0, len = center.items.length; i < len; i++){
			var item = center.items.itemAt(i);
			if(!this.showFolderList || item.restrictToShowAllFolderList === true) {
				if (item.getContext() === this.activeContext && item.plugin.getName() === item.getContext().getName()) {
					// Switch to the panel belonging to the active Context
					if (!Ext.isFunction(layout.setActiveItem)) {
						center.activeItem = i;
					} else {
						layout.setActiveItem(i);
					}
					contextNavPanelFound = true;
					break;
				}
			}
		}

		// If there is no center panel for the active Context found, switch to the "Show all Folders" by default
		if (!contextNavPanelFound) {
			if (!Ext.isFunction(layout.setActiveItem)) {
				center.activeItem = 0;
			} else {
				layout.setActiveItem(0);
			}
		}
		
		var currentActiveSearchFilter = layout.activeItem && layout.activeItem.filterSearchTextBox;
		this.setHierarchySearchFilterValue(isContextSwitched, previousActiveSearchFilter, currentActiveSearchFilter);
		this.doLayout();
	},

	/**
	 * Function will conditionally set the value of {@link Ext.form.textfield filterSearchTextBox} resides 
	 * in {@link Zarafa.hierarchy.ui.HierarchyTreePanel HierarchyTreePanel} of currently active panel in {@link #centerPanel centerPanel}.
	 * 
	 * @param {Boolean} isContextSwitched true if the context is switched else false.
	 * @param {Ext.form.textfield} previousActiveSearchFilter search filter text field of previously active panel in {@link #centerPanel centerPanel}
	 * @param {Ext.form.textfield} currentActiveSearchFilter search filter text field of currently active panel in {@link #centerPanel centerPanel}
	 * @private
	 */
	setHierarchySearchFilterValue: function(isContextSwitched, previousActiveSearchFilter, currentActiveSearchFilter)
	{
		if (Ext.isDefined(currentActiveSearchFilter)) {
			// If context is switched and "Show All" is not checked then clear currently active hierarchy search text field.
			if (isContextSwitched && !this.showFolderList) {
				currentActiveSearchFilter.reset();
			} else if (!isContextSwitched && Ext.isDefined(previousActiveSearchFilter)) {
				// If "Show All" toggle occurred then pass the previous filterSearchTextBox's value to the current filterSearchTextBox.
				var currSearchValue = currentActiveSearchFilter.getValue();
				var prevSearchValue = previousActiveSearchFilter.getValue();
				
				// Only change the value of active hierarchy filterSearchTextBox 
				// when previously active filterSearchTextBox's value is different.
				if (currSearchValue !== prevSearchValue) {
					currentActiveSearchFilter.setValue(previousActiveSearchFilter.getValue());
				}
			}	
		}
	},

	/**
	 * Sets the {@link #showFolderList showFolderList} property. This will determine whether the
	 * "Show all Folders" panel will be shown or if the Context-related
	 * {@link Zarafa.core.ui.ContextNavigationPanel ContextNavigationPanel} will be shown. If the
	 * state of the {@link #showFolderList} is not changed it will not do anything.
	 * @param {Boolean} showFolderList True to force the full folder list to be shown
	 */
	setShowFolderList: function(showFolderList)
	{
		if (this.showFolderList === showFolderList){
			return;
		}

		this.showFolderList = showFolderList;
		this.toggleVisibilityNavigationComponents();

		this.fireEvent('toggleshowallfolders', this.showFolderList);
	},

	/**
	 * Called during rendering of the panel, this will initialize all events.
	 * This will also update the {@link #activeContext active context} and
	 * call {@link #onContextSwitch} to ensure that the context is properly
	 * initialized.
	 * @private
	 */
	initEvents: function()
	{
		this.activeContext = container.getCurrentContext();
		this.mon(container, 'contextswitch', this.onContextSwitch, this);
		this.onContextSwitch(null, this.activeContext, this.activeContext);

		this.on('resize', this.onResizePanel, this);

		Zarafa.core.ui.NavigationPanel.superclass.initEvents.apply(this, arguments);
	},

	/**
	 * Event handler which is fired when the panel is resized. It will force the items to layout again.
	 *
	 * @param {Number} adjWidth The box-adjusted width that was set
	 * @param {Number} adjHeight The box-adjusted height that was set
	 * @param {Number} rawWidth The width that was originally specified
	 * @param {Number} rawHeight The height that was originally specified
	 * @private
	 */
	onResizePanel: function(adjWidth, adjHeight, rawWidth, rawHeight)
	{
		this.doLayout();
	},

	/**
	 * Override saveState to check if a user moves to the settingsContext,
	 * where the hierarchybar is disabled, which should not be saved to state.
	 *
	 * Since otherwise logging out from Settings breaks.
	 *
	 * @private
	 */
	saveState: function()
	{
		if (container.getCurrentContext().getName() !== "settings") {
			Zarafa.core.ui.NavigationPanel.superclass.saveState.apply(this);
		}
	}
});
Ext.reg('zarafa.navigationpanel', Zarafa.core.ui.NavigationPanel);
Ext.namespace('Zarafa.core.ui');

/**
 * @class Zarafa.core.ui.RecordContentPanel
 * @extends Zarafa.core.ui.ContentPanel
 * @xtype zarafa.recordcontentpanel
 *
 * The {@link Zarafa.core.ui.RecordContentPanel RecordContentPanel} extends the normal {@link Zarafa.core.ui.ContentPanel ContentPanel},
 * and should be used for any content panels which contain an {@link Zarafa.core.data.MAPIRecord MAPIRecord}.
 *
 * FIXME: Provide default buttons which are common for all content panels containing a record
 */
Zarafa.core.ui.RecordContentPanel = Ext.extend(Zarafa.core.ui.ContentPanel, {
	/**
	 * @cfg {Boolean} autoSave Automatically save all changes on the
	 * {@link Zarafa.core.data.IPMRecord IPMRecord} to the
	 * {@link Zarafa.core.data.IPMStore IPMStore}.
	 */
	autoSave: true,

	/**
	 * @cfg {Object} Configuration object which will be used
	 * to instantiate the {@link Zarafa.core.plugins.RecordComponentPlugin RecordComponent}.
	 * See the plugin for the available configuration options.
	 */
	recordComponentPluginConfig: undefined,

	/**
	 * @cfg {Zarafa.core.data.MAPIRecord} record (See {@link Zarafa.core.plugins.RecordComponentPlugin#record}).
	 */
	record: undefined,

	/**
	 * When this panel is {@link Zarafa.core.ui.ContentPanel#isModal modal} then this property will contain the
	 * original {@link Zarafa.core.data.MAPIRecord record} which is displayed in this content panel. During
	 * {@link #setRecord} however a copy will be made which is used during editing. When the content panel is closed
	 * the changes made to {@link #record} will be merged into {@link #modalRecord}.
	 * @property
	 * @type Zarafa.core.data.MAPIRecord
	 */
	modalRecord: undefined,

	/**
	 * Reference to the {@link Zarafa.core.plugins.RecordComponentPlugin RecordComponent} plugin
	 * which is used to send update events to all child {@link Ext.Container containers}
	 * in this container. This field is initialized by the
	 * {@link Zarafa.core.plugins.RecordComponentPlugin RecordComponent} itself.
	 *
	 * @property
	 * @type Zarafa.core.plugins.RecordComponentPlugin
	 */
	recordComponentPlugin: undefined,

	/**
	 * @cfg {Boolean} showLoadMask true if load mask should be shown else false.
	 */
	showLoadMask: true,

	/**
	 * The LoadMask object which will be shown when the {@link #record} is being opened, and
	 * the panel is waiting for the server to respond with the desired data. This will only
	 * be set if {@link #showLoadMask} is true.
	 * @property
	 * @type Zarafa.common.ui.LoadMask
	 */
	loadMask: undefined,

	/**
	 * @cfg {Boolean} showMaskMask true if a message should be shown to indicating if the
	 * message is being saved.
	 */
	showInfoMask: true,

	/**
	 * @cfg {String/Object} savingText When {@link #showInfoMask} is true, then this text
	 * will be shown when the message is being saved. When an object is provided which contains
	 * the 'msg' and 'title' fields respectively.
	 */
	savingText: { msg: _('Saving') + '...' },

	/**
	 * @cfg {String/Object} savingDoneText When {@link #showInfoMask} is true, then this text
	 * will be shown when the message has been saved. When an object is provided which contains
	 * the 'msg' and 'title' fields respectively.
	 */
	savingDoneText:{ title: _('Saved'), msg: _('Saved successfully') },

	/**
	 * When {@link #showInfoMask} is true, then we should check if any save request
	 * is fired internally and we should not show {@link #savingText} and {@link #savingDoneText}. This is handled by
	 * maintaining an array of {@link Zarafa.core.data.MAPIRecord#actions message actions} that will be matched with message action
	 * actually set on record to show or not show info mask.
	 * @property
	 * @type Array
	 */
	internalActions: undefined,

	/**
	 * Indicates if the panel is currently busy saving data to the server.
	 * @property
	 * @type Boolean
	 */
	isSaving: false,

	/**
	 * The reference as returned by {@link Zarafa.core.ui.notifier.Notifier#notify} to reference the
	 * message in order to remove the message as soon as the save was completed.
	 * @property
	 * @type Ext.Element
	 * @private
	 */
	savingEl: undefined,

	/**
	 * @cfg {Boolean} closeOnSave Config option to close the panel when client receives confirmation of message is saved.
	 */
	closeOnSave: false,

	/**
	 * @cfg {Boolean} confirmClose Option to launch a confirmation dialog when closing this panel with an unsaved record.
	 * This option is only used when the {@link Zarafa.settings.SettingsModel setting} 'zarafa/v1/main/confirm_close_dialog'
	 * is enabled.
	 */
	confirmClose: false,

	/**
	 * @cfg {Boolean} removeRecordOnCancel config to remove modal record from
	 * {@link Zarafa.core.data.MAPIStore MAPIStore} when user has not changed anything and closed
	 * the {@link Zarafa.core.data.UIFactoryLayer UIFactoryLayer}.
	 */
	removeRecordOnCancel: false,

	/**
	 * If set to true when the component is laid out, the loading mask will be displayed
	 * @property
	 * @type Boolean
	 */
	showLoadMaskOnStart: false,

	/**
	 * @cfg {Boolean} showModalWithoutParent Config option set to true when the dialog is modal dialog and
	 * It's doesn't have the parent dialog.
	 * @property
	 * @type Boolean
	 */
	showModalWithoutParent: false,

	/**
	 * @cfg {String} unSaveWarningMessage When {@link #record} has any unsaved changes
	 * And user trying to close separate window or tab if that is the case, then confirm dialog will show with this text
	 */
	unSaveWarningMessage: _('You will lose all unsaved work. Are you sure you want to close this window?'),

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

		Ext.applyIf(config, {
			title: _('grommunio Content Panel')
		});

		// Modal dialogs that will edit the record must work inside the shadowStore.
		if (config.recordComponentPluginConfig && config.recordComponentPluginConfig.allowWrite && config.modal) {
			config.recordComponentPluginConfig.useShadowStore = true;
		}

		// The confirmClose option depends on the settings
		if (config.confirmClose && !container.getSettingsModel().get('zarafa/v1/main/confirm_close_dialog')) {
			delete config.confirmClose;
		}

		config.plugins = Ext.value(config.plugins, []);
		config.plugins.push(Ext.applyIf(config.recordComponentPluginConfig || {}, {
			ptype: 'zarafa.recordcomponentplugin'
		}));

		config.plugins.push({
			ptype: 'zarafa.recordcomponentupdaterplugin'
		});

		this.addEvents(
			/**
			 * @event beforesaverecord
			 * Fires when the record of this {@link Zarafa.core.ui.RecordContentPanel contentpanel} is about to
			 * be saved to the server. Listening to this event can be used to update the record
			 * with the latest data from the {@link Ext.Component components}. The callback function
			 * can return false to prevent the {@link Zarafa.core.data.MAPIRecord MAPIRecord} to be saved.
			 * @param {Zarafa.core.ui.RecordContentPanel} contentpanel The contentpanel to which the record belongs
			 * @param {Zarafa.core.data.MAPIRecord} record The record which is being saved
			 * @return {Boolean} false to cancel the save action
			 */
			'beforesaverecord',
			/**
			 * @event saverecord
			 * Fires when the record of this {@link Zarafa.core.ui.RecordContentPanel contentpanel} is being
			 * saved to the server. Listening to this event can be used for the final updates to
			 * the record before the actual saving takes place.
			 * @param {Zarafa.core.ui.RecordContentPanel} contentpanel The contentpanel to which the record belongs
			 * @param {Zarafa.core.data.MAPIRecord} record The record which is being saved
			 */
			'saverecord',
			/**
			 * @event aftersaverecord
			 * Fires after the record has been saved successfully.
			 * This follows the {@link #updaterecord} event when the server responded to the save action
			 * @param {Zarafa.core.ui.RecordContentPanel} contentpanel The contentpanel from where the record is saved
			 * @param {Zarafa.core.data.IPMRecord} record The record which has been saved
			 */
			'aftersaverecord'
		);

		this.internalActions = [];

		Zarafa.core.ui.RecordContentPanel.superclass.constructor.call(this, config);

		if (Ext.isString(this.savingText)) {
			this.savingText = { title: '', msg: this.savingText };
		}
		if (Ext.isString(this.savingDoneText)) {
			this.savingDoneText = { title: '', msg: this.savingDoneText };
		}

		this.initEvents();

		if (this.record) {
			this.setRecord(this.record);
		}
	},

	/**
	 * Initialize all {@link Zarafa.core.data.MAPIRecord record} related events
	 * for this {@link Zarafa.core.ui.RecordContentPanel contentpanel}.
	 * @private
	 */
	initEvents: function()
	{
		this.mon(this, {
			'setrecord': this.onSetRecord,
			'beforeloadrecord': this.onBeforeLoadRecord,
			'loadrecord': this.onLoadRecord,
			'updaterecord': this.onUpdateRecord,
			'writerecord': this.onWriteRecord,
			'exceptionrecord': this.onExceptionRecord,
			'afterlayout': this.onAfterLayout,
			'scope': this
		});
	},

	/**
	 * If {@link #showLoadMask} is enabled, this function will display
	 * the {@link #loadMask}.
	 * @param {Boolean} errorMask True to show an error mask instead of the loading mask.
	 * @protected
	 */
	displayLoadMask: function(errorMask)
	{
		if (this.showLoadMask === false) {
			return;
		}

		if (!this.loadMask) {
			this.loadMask = new Zarafa.common.ui.LoadMask(this.el);
		}

		if (errorMask) {
			this.loadMask.showError();
		} else {
			this.loadMask.show();
		}
	},

	/**
	 * If {@link #showLoadMask} is enabled, and {@link #displayLoadMask} has been
	 * called to display the {@link #loadMask} this function will disable the
	 * loadMask.
	 * @protected
	 */
	hideLoadMask: function()
	{
		if (this.showLoadMask === false) {
			return;
		}

		if (this.loadMask) {
			this.loadMask.hide();
		}
	},

	/**
	 * If {@link showInfoMask} is enabled, this will display the {@link #savingText} to the user.
	 * @protected
	 */
	displayInfoMask: function()
	{
		if (this.showInfoMask === false) {
			return;
		}

		// Don't display save notification while system is marking the record as unread
		// which 'belongs to ShadowStore' and 'only message_flags gets modified'.
		// This happens when unread mail gets popped out.
		var modifications = this.record.modified;
		var isModifiedOnlyFlags = (modifications && Object.keys(modifications).length === 1 && Ext.isDefined(modifications.message_flags));
		var isShadowStore = (this.record.getStore() === container.getShadowStore());
		if (isModifiedOnlyFlags && isShadowStore) {
			return;
		}

		this.savingEl = container.getNotifier().notify('info.saving', this.savingText.title, this.savingText.msg, {
			container: this.getEl()
		});
	},

	/**
	 * If {@link #showInfoMask} is enabled, and {@link #displayInfoMask} has been called, this
	 * will remove the notification again. When saving has been successful, a new notification
	 * will be shown to display the {@link #savingDoneText}.
	 * @param {Boolean} success false to disable the display of {@link #savingDoneText}.
	 * @protected
	 */
	hideInfoMask: function(success)
	{
		if (this.showInfoMask === false) {
			return;
		}

		if (this.savingEl) {
			container.getNotifier().notify('info.saving', null, null, {
				container: this.getEl(),
				destroy: true,
				reference: this.savingEl
			});
			delete this.savingEl;

			if (success !== false) {
				container.getNotifier().notify('info.saved', this.savingDoneText.title, this.savingDoneText.msg);
			}
		}
	},

	/**
	 * See {@link Zarafa.core.plugins.RecordComponentPlugin#setRecord}.
	 *
	 * @param {Zarafa.core.data.MAPIRecord} record The record to set
	 */
	setRecord: function(record)
	{
		var cheapCopy; // Default value

		if (this.recordComponentPlugin) {
			// When write support is enabled, we have to check what our exact action should be.
			// When this is a modal, we must copy the record and make sure we reset
			// the unique identifier. This is required because this modal dialog
			// will be the second dialog with the same record (even though they
			// are copies, the entryid will be the same for both), and when removing
			// them from the ShadowStore will cause random() behavior.
			// When the record is opened in a separate dialog, we only need to copy the
			// record when it is not a phantom (e.g. it already exists on the server).
			// This is because a phantom record is only opened within this Dialog,
			// and unlikely to be available anywhere else, while a real Record can
			// be displayed in many UI components.
			if (this.recordComponentPlugin.allowWrite === true) {

				// Check if the record is created but not saved on the server yet, but is attached to a store.
				// In that case we must wait until the record is saved before we can start working with it.
				// This can happen when creating a quick appointment in the calendar. (Select a timeslot,
				// start typing, press [ENTER], immediately double-click on the appointment)
				// (The store_entryid must be checked because the jasmine tests create filled in phantom records
				// that are not being saved. But these records don't have a store_entryid)
				if ( record.phantom === true && Ext.isDefined(record.store) && record.store.isSaving ){
					// Show a loading mask either immediately or when the panel has been laid out)
					if ( this.el ){
						this.displayLoadMask();
					} else {
						this.showLoadMaskOnStart = true;
					}
					record.store.on('save', Ext.createDelegate(this.onStoreSave, this, [record]), this, {single: true});

					return;
				}

				if (this.isModal() && !this.showModalWithoutParent) {

					this.modalRecord = record;

					var store = record.getStore();
					if (Ext.isDefined(store)) {
						this.mon(store, 'datachanged', this.modalRecord.onDataChange, this);
					}

					record = record.copy('modal-' + record.id);
					record.isModalDialogRecord = true;

					// We already copied the record, don't copy it again.
					cheapCopy = true;
				}
			}

			this.recordComponentPlugin.setRecord(record, cheapCopy);
		}
	},

	/**
	 * Event listener that is used when the panel is opened with a record that is being saved.
	 * This can happen when the user creates a quick appointment and opens it before the save
	 * request has returned. When the record has been saved it will call setRecord again,
	 * so it will create a copy with all the necessary entryids.

	 * @param {Zarafa.core.data.MAPIRecord} record The record that is being saved
	 */
	onStoreSave: function(record)
	{
		// Hide the loading mask (or make sure we don't show it)
		this.showLoadMaskOnStart = false;
		if ( this.el ){
			this.hideLoadMask();
		}
		this.setRecord(record);
	},

	/**
	 * Save all changes made to the {@link #record} to the server.
	 * @param {Boolean} storeSave (optional) False to only update the record,
	 * but not save the changes to the store.
	 * @return {Boolean} false if the record could not be saved
	 * @protected
	 */
	saveRecord: function(storeSave)
	{
		// Check if saving is allowed, and if by chance we aren't
		// saving already.
		if (this.recordComponentPlugin.allowWrite === false || this.isSaving === true) {
			return false;
		}

		if (this.fireEvent('beforesaverecord', this, this.record) === false) {
			return false;
		}

		// Check if the record is valid before saving.
		if (!this.record.isValid()) {
			return false;
		}

		this.fireEvent('saverecord', this, this.record);

		if (Ext.isDefined(this.modalRecord)) {
			this.modalRecord.applyData(this.record);
		}

		if (storeSave !== false) {
			var record = this.modalRecord || this.record;

			// Check if the record has actual modifications which
			// we can save to the server. record.save() will do
			// nothing if the store doesn't have the record in
			// the modifications array. So we must prevent going
			// any further here when there are not modifications.
			if (record.getStore().modified.indexOf(record) < 0) {
				if (this.closeOnSave === true) {
					this.close();
				}
				return;
			}

			// When the HTML body has been modified we must also send the isHTML property
			// with the save request because otherwise the backend will think this is an
			// plaintext record. (See Conversion::mapXML2MAPI())
			if ( record.isModified('html_body') ){
				record.set('isHTML', record.get('isHTML'), true);
			}

			record.save();
		}
	},

	/**
	 * This will delete the {@link #record} from its store. If {@link #modalRecord}
	 * is present, this record will be used for the delete action. When the record
	 * has been deleted, the dialog will be automatically closed.
	 * @protected
	 */
	deleteRecord: function()
	{
		if (this.recordComponentPlugin.allowWrite === false) {
			return;
		}

		if (this.recordComponentPlugin.isChangedByUser === true) {
			Ext.MessageBox.show({
				title: _('Delete item'),
				msg: _('This item has been changed. Are you sure you want to delete it?'),
				buttons: Ext.MessageBox.YESNO,
				fn: this.onConfirmDelete,
				scope: this
			});
		} else {
			Zarafa.common.Actions.deleteRecords(this.modalRecord || this.record);
		}
		return;
	},

	/**
	 * Event handler for the Delete confirmation dialog.
	 * If the user pressed 'yes' the record will be deleted by {@link Zarafa.common.Actions#deleteRecords}.
	 * @param {String} btn The text of the button on which the user clicked
	 * @private
	 */
	onConfirmDelete: function(btn)
	{
		if (btn === 'yes') {
			Zarafa.common.Actions.deleteRecords(this.modalRecord || this.record);
		}
	},

	/**
	 * Event handler that will make sure that the loadmask is shown when the panel is rendered
	 * and the {#link showLoadMaskOnStart} property has been set to true.
	 */
	onAfterLayout: function()
	{
		if ( this.showLoadMaskOnStart === true ) {
			this.displayLoadMask();
		}
	},

	/**
	 * Fires when a record has been added to the {@link #field}.
	 * No event handler may modify any properties inside the provided record (if this
	 * is needed for the Panel initialization, use the {@link #beforesetrecord} event).
	 * @param {Ext.Container} panel The panel to which the record was set
	 * @param {Zarafa.core.data.MAPIRecord} record The record which was set
	 * @param {Zarafa.core.data.MAPIRecord} oldrecord The oldrecord which was previously set
	 */
	onSetRecord: function(panel, record, oldrecord)
	{
		if (!record) {
			return;
		}

		// Update the record, we might already have this.record set, but the recordcomponent might
		// have created a copy of that record. Hence we need to update our references now.
		this.record = record;

		// Add a beforesave handler to the store, this catches any saves from modal dialogs
		// and ensures we can still show the infomask in those cases.
		this.mon(this.record.getStore(), 'beforesave', this.onBeforeSaveRecord, this);
	},

	/**
	 * Event which is fired when the {@link #record} is going to be loaded.
	 * This will {@link #displayLoadMask display the loadmask}. This will be removed
	 * again during {@link #onLoadRecord}.
	 *
	 * @param {Ext.Container} panel The panel to which the record was set
	 * @param {Zarafa.core.data.MAPIRecord} record The record which was updated
	 * @private
	 */
	onBeforeLoadRecord: function(panel, record)
	{
		this.displayLoadMask();
	},

	/**
	 * Event which is fired when the {@link #record} has been completely loaded.
	 * This will {@link #hideLoadMask hide the loadmask}.
	 *
	 * @param {Ext.Container} panel The panel to which the record was set
	 * @param {Zarafa.core.data.MAPIRecord} record The record which was updated
	 * @private
	 */
	onLoadRecord: function(panel, record)
	{
		this.hideLoadMask();
	},

	/**
	 * Event handler which is fired when the the {@link Ext.data.Store store} for the {@link #record}
	 * fires the {@link Ext.data.Store#beforesave} event. This will check if the event was really regarding
	 * {@link #record} and will update the {@link #isSaving} property and {@link #displayInfoMask display the infobox}.
	 * @param {Ext.data.Store} store The store which fired the event
	 * @param {Object} data The object data which is being saved to the server
	 * @private
	 */
	onBeforeSaveRecord: function(store, data)
	{
		var record = this.record.isModalDialogRecord ? this.modalRecord : this.record;
		if (data &&
			((data.update && data.update.indexOf(record) >= 0) ||
			 (data.create && data.create.indexOf(record) >= 0))) {
			this.isSaving = true;

			if(!this.hasInternalAction()) {
				this.displayInfoMask();
			}
		}
	},

	/**
	 * Fired when the {@link #updaterecord} event has been fired. If {@link #showInfoMask} is enabled,
	 * this will display the {@link #savingText} to indicate the saving is in progress.
	 *
	 * @param {Zarafa.core.ui.RecordContentPanel} contentpanel The record which fired the event
	 * @param {String} action write Action that occurred. Can be one of
	 * {@link Ext.data.Record.EDIT EDIT}, {@link Ext.data.Record.REJECT REJECT} or
	 * {@link Ext.data.Record.COMMIT COMMIT}
	 * @param {Zarafa.core.data.IPMRecord} record The record which was updated
	 * @private
	 */
	onUpdateRecord: function(contentpanel, action, record)
	{
		if (this.isSaving === true && action == Ext.data.Record.COMMIT) {

			if (!this.hasInternalAction()) {
				this.hideInfoMask(true);
			}

			this.isSaving = false;
			this.fireEvent('aftersaverecord', this, this.record);

			if (this.closeOnSave) {
				this.close();
				// We closed the panel, stop the event propagation as there is
				// no longer an UI that can be updated.
				return false;
			}
		}
	},

	/**
	 * Fired when the {@link #exceptionrecord} event has been fired. Will reset {@link #isSaving}.
	 *
	 * @param {String} type See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception}
	 * for description.
	 * @param {String} action See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception}
	 * for description.
	 * @param {Object} options See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception}
	 * for description.
	 * @param {Object} response See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception}
	 * for description.
	 * @param {Zarafa.core.data.MAPIRecord} record The record which was subject of the request
	 * that encountered an exception.
	 * @param {String} error (Optional) Passed when a thrown JS Exception or JS Error is
	 * @private
	 */
	onExceptionRecord: function(proxy, type, action, options, response, args)
	{
		if (!this.hasInternalAction()) {
			if (type === "open") {
				this.displayLoadMask(true);
			} else {
				this.hideInfoMask(false);
				this.isSaving = false;
			}
		}
	},

	/**
	 * Action handler when the user presses the "Ok" button.
	 * This will call {@link #saveRecord} with the {@link #autoSave} argument.
	 */
	onOk: function()
	{
		if (this.saveRecord(this.autoSave) !== false) {
			if (this.closeOnSave !== true) {
				this.close();
			}
		}
	},

	/**
	 * Action handler when the user presses the "Cancel" button.
	 * This will close the panel without saving.
	 */
	onCancel: function()
	{
		this.removePhantomRecord();

		this.close();
	},

	/**
	 * Function will be called when user clicks on close tool on the {@link Ext.Window}
	 * and should remove phantom record if needed.
	 */
	closeWrap: function()
	{
		this.removePhantomRecord();

		Zarafa.core.ui.RecordContentPanel.superclass.closeWrap.apply(this, arguments);
	},

	/**
	 * Function is used to remove {@link Zarafa.core.data.MAPIRecord MAPIRecord} from
	 * {@link Zarafa.core.data.MAPIStore MAPIStore} when {@link #removeRecordOnCancel}
	 * is true and user has closed the dialog without saving it.
	 * @protected
	 */
	removePhantomRecord: function()
	{
		// check we should remove record on cancel or not
		if(!this.removeRecordOnCancel) {
			return false;
		}

		// for modal dialogs we need to remove original record not clone of the record
		// which is added in shadowstore
		var record = this.modalRecord;

		if(record) {
			var store = record.getStore();

			if(store) {
				store.remove(record);
			}
		}
	},

	/**
	 * Event handler will be called when the {@link Zarafa.core.plugins.RecordComponentPlugin RecordComponentPlugin}
	 * has fired a writerecord event.
	 * RecordComponentPlugin fires it if write event is fired on record.
	 *
	 * @param {Ext.data.Store} store
	 * @param {String} action [Ext.data.Api.actions.create|update|destroy]
	 * @param {Object} result The 'data' picked-out out of the response for convenience.
	 * @param {Ext.Direct.Transaction} res
	 * @param {Zarafa.core.data.IPMRecord} record Store's record, on which write event is fired
	 * @protected
	 */
	onWriteRecord: function(store, action, result, res, record)
	{
		if(action == Ext.data.Api.actions.destroy) {
			this.close();
			return false;
		}
	},

	/**
	 * Override close to snapshot the user-change state before the
	 * {@link #beforeclose} event fires. The beforeclose handler calls
	 * updateRecord() on every tab, which pushes all form values into the
	 * record. Empty form fields return "" while the record may hold
	 * null/undefined, and the string comparison in
	 * {@link Zarafa.core.data.MAPIRecord#set} treats these as different
	 * values, creating false-positive modifications that incorrectly
	 * mark the record as user-modified.
	 * @override
	 */
	close: function()
	{
		if (this.recordComponentPlugin) {
			this._isChangedBeforeClose = this.recordComponentPlugin.isChangedByUser;
		}
		Zarafa.core.ui.RecordContentPanel.superclass.close.call(this);
	},

	/**
	 * Override the doClose function to add support for the {@link #confirmClose}
	 * configuration option. This will check if the {@link #record} has any unsaved
	 * changes. And if that is the case, will show a confirmation dialog warning
	 * the user that he will lose all changes.
	 * @private
	 * @override
	 */
	doClose: function()
	{
		var isChanged = Ext.isDefined(this._isChangedBeforeClose)
			? this._isChangedBeforeClose
			: (this.recordComponentPlugin && this.recordComponentPlugin.isChangedByUser);
		delete this._isChangedBeforeClose;

		if (this.confirmClose && isChanged === true) {
			return this.showCloseConfirmationDialog();
		}
		Zarafa.core.ui.RecordContentPanel.superclass.doClose.call(this);
	},

	/**
	 * Show an isolated confirmation window for discard-on-close.
	 * This avoids relying on the global Ext.MessageBox singleton state.
	 *
	 * @return {Zarafa.common.dialogs.CustomMessageBox}
	 * @private
	 */
	showCloseConfirmationDialog: function()
	{
		if (!(Zarafa.common && Zarafa.common.dialogs && Zarafa.common.dialogs.CustomMessageBox)) {
			return Ext.MessageBox.show({
				title: _('Unsaved changes'),
				cls: Ext.MessageBox.WARNING_CLS,
				msg: this.unSaveWarningMessage,
				buttons: Ext.MessageBox.YESNO,
				fn: this.onConfirmClose,
				scope: this
			});
		}

		if (this.closeConfirmDialog && Ext.isFunction(this.closeConfirmDialog.isVisible) && this.closeConfirmDialog.isVisible()) {
			return this.closeConfirmDialog;
		}

		var confirmMsg = this.unSaveWarningMessage || _('You will lose all unsaved work. Are you sure you want to close this window?');
		confirmMsg = '<div style="line-height:1.4;white-space:normal;">' + confirmMsg + '</div>';

		this.closeConfirmDialog = new Zarafa.common.dialogs.CustomMessageBox({
			title: _('Unsaved changes'),
			msg: confirmMsg,
			icon: '',
			contentPanel: this,
			fn: function(button) {
				var action = (button && (button.name || button.itemId)) || 'cancel';
				if (this.contentPanel) {
					this.contentPanel.onConfirmClose(action);
					this.contentPanel.closeConfirmDialog = undefined;
				}
			},
			customButtons: [{
				name: 'yes',
				text: _('Yes'),
				cls: 'zarafa-action'
			}, {
				name: 'no',
				text: _('No')
			}]
		});

		this.closeConfirmDialog.on('close', function(win) {
			if (win.contentPanel) {
				win.contentPanel.closeConfirmDialog = undefined;
			}
		});

		return this.closeConfirmDialog;
	},

	/**
	 * Event handler for the Close confirmation dialog as created during {@link #onBeforeClose}.
	 * If the user pressed 'yes' the panel will be {@link #close closed}.
	 * @param {String} btn The text of the button on which the user clicked
	 * @private
	 */
	onConfirmClose: function(btn)
	{
		if (btn === 'yes') {
			if (this.recordComponentPlugin && Ext.isFunction(this.recordComponentPlugin.resetUserChangeTracker)) {
				this.recordComponentPlugin.resetUserChangeTracker();
			}
			this.record.reject();
			Zarafa.core.ui.RecordContentPanel.superclass.doClose.call(this);
		}
	},

	/**
	 * Update the components with the given record.
	 * Empty function in order to prevent {@link Ext.Container#update} from being called
	 *
	 * @param {Zarafa.core.data.MAPIRecord} record The record to update in this component
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 */
	update: Ext.emptyFn,

	/**
	 * Function will add sub action type to list of {@link internalActions} that will be used when
	 * showing info mask.
	 * @param {String} name name of the sub action type.
	 */
	addInternalAction: function(name)
	{
		this.internalActions.push(name);
	},

	/**
	 * Function will remove sub action type to list of {@link internalActions} that will be used when
	 * showing info mask.
	 * @param {String} name name of the sub action type.
	 */
	deleteInternalAction: function(name)
	{
		this.internalActions.splice(this.internalActions.indexOf(name), 1);
	},

	/**
	 * Function will return list of sub action types that will be used when showing info mask.
	 * @return {Array} array of all internal actions.
	 */
	getInternalActions: function()
	{
		return this.internalActions;
	},

	/**
	 * When sending/saving the record we need to check if we want to show info mask for this action,
	 * so this function will return boolean value to indicate that show mask should be shown or not.
	 * @return {Boolean} boolean to indicate that we are performing save on internal action or not.
	 */
	hasInternalAction: function()
	{
		var isInternalAction = false;
		var messageActions = this.record.getMessageActions();
		var internalActions = this.getInternalActions();

		Ext.iterate(messageActions, function(key, value) {
			if(internalActions.indexOf(key) > -1) {
				isInternalAction = true;

				// break loop
				return false;
			}
		}, this);

		return isInternalAction;
	},

	/**
	 * Function will check if the {@link #record} has any unsaved changes
	 * And user trying to close separate window if that is the case,
	 * will show a confirmation dialog warning the user that he will lose all changes.
	 * @return {String} warning message which will show in the leave requester dialog.
	 */
	onBeforeUnload: function()
	{
		var isChanged = this.recordComponentPlugin && this.recordComponentPlugin.isChangedByUser === true;
		if (this.fireEvent('beforeclose', this) !== false) {
			if (isChanged) {
				return this.unSaveWarningMessage;
			}
		}
	}
});

Ext.reg('zarafa.recordcontentpanel', Zarafa.core.ui.RecordContentPanel);
/*
 * #dependsFile client/zarafa/core/Container.js
 */
Ext.namespace('Zarafa.core.ui.notifier');

/**
 * @class Zarafa.core.ui.notifier.ConsolePlugin
 * @extends Zarafa.core.ui.notifier.NotifyPlugin
 *
 * Special {@link Zarafa.core.ui.notifier.NotifyPlugin NotifyPlugin} which sends all
 * messages to the browser console. This plugin will be registered to the
 * {@link Zarafa.core.ui.notifier.Notifier notifier} using the name 'console'.
 */
Zarafa.core.ui.notifier.ConsolePlugin = Ext.extend(Zarafa.core.ui.notifier.NotifyPlugin, {

	/**
	 * Notify the user with a message.
	 *
	 * The category can be either "error", "warning", "info" or "debug", or a subtype thereof (e.g. "info.newmail").
	 *
	 * @param {String} category The category which applies to the notification.
	 * @param {String} title The title which must be shown in the message.
	 * @param {String} message The message which should be displayed.
	 * @param {Object} config Configuration object which can be applied to the notifier
	 * This object can contain keys like:
	 * - container: Which is the container to which the notifier should be restricted
	 * - persistent: True to make the message persistent and don't disappear automatically
	 * - destroy: Don't create new message, but destroy previous one
	 * - update: Don't create a new message, but update previous one
	 * - reference: The original message which must be updated by this action
	 * - listeners: Event handlers which must be registered on the element
	 * @return {Mixed} A reference to the message which was created, this can be used
	 * as value for 'reference' in the config argument.
	 */
	notify: function(category, title, message, config)
	{
		// Internet Explorer only defines 'console' when the browser's
		// debugger is opened, and the console on which is going to be
		// printed is visible. Hence we have to check if the console
		// exists before using it.
		if (!console) {
			return;
		}

		var str = title + ': ' + message;

		if (category.indexOf('info') === 0) {
			console.info(str);
		} else if (category.indexOf('warning') === 0) {
			console.warn(str);
		} else if (category.indexOf('error') === 0) {
			console.error(str);
		} else {
			console.log(str);
		}
	}
});

Zarafa.onReady(function() {
	container.getNotifier().registerPlugin('console', new Zarafa.core.ui.notifier.ConsolePlugin());
});
Ext.namespace('Zarafa.core.ui.notifier');

/**
 * @class Zarafa.core.ui.notifier.DesktopNotifier
 * @extends Zarafa.core.ui.notifier.NotifyPlugin
 *
 * A plugin for notification plugin to show desktop notifications instead of normal in browser
 * notifications for actions like new mail, reminder etc.
 */
Zarafa.core.ui.notifier.DesktopNotifier = Ext.extend(Zarafa.core.ui.notifier.NotifyPlugin, {

	/**
	 * Notify the user with a message.
	 *
	 * The category can be either "error", "warning", "info" or "debug", or a subtype thereof (e.g. "info.newmail").
	 *
	 * @param {String} category The category which applies to the notification.
	 * @param {String} title The title which must be shown in the message.
	 * @param {String} message The message which should be displayed.
	 * @param {Object} config Configuration object which can be applied to the notifier
	 * This object can contain keys like:
	 * - autoclose: Auto close notification after sometime
	 * @return {Mixed} A reference to the message which was created, this can be used
	 * as value for 'reference' in the config argument.
	 */
	notify: function(category, title, message, config)
	{
		Zarafa.common.Actions.notify(title, {
			tag: category,
			body: message,
			icon: 'client/resources/images/grommunio.ico'
		}, {
			click: function() {
				// focus window which generated this notification
				window.focus();
			}
		});
	}
});

Zarafa.onReady(function() {
	container.getNotifier().registerPlugin('desktopnotifier', new Zarafa.core.ui.notifier.DesktopNotifier());
});
/*
 * #dependsFile client/zarafa/core/Container.js
 */
Ext.namespace('Zarafa.core.ui.notifier');

/**
 * @class Zarafa.core.ui.notifier.MessageBoxPlugin
 * @extends Zarafa.core.ui.notifier.NotifyPlugin
 *
 * Special {@link Zarafa.core.ui.notifier.NotifyPlugin NotifyPlugin} which sends all
 * messages to the user using a {@link Ext.messageBox MessageBox}. This plugin will be registered to the
 * {@link Zarafa.core.ui.notifier.Notifier notifier} using the name 'messagebox'.
 */
Zarafa.core.ui.notifier.MessageBoxPlugin = Ext.extend(Zarafa.core.ui.notifier.NotifyPlugin, {

	/**
	 * Notify the user with a message.
	 *
	 * The category can be either "error", "warning", "info" or "debug", or a subtype thereof (e.g. "info.newmail").
	 *
	 * @param {String} category The category which applies to the notification.
	 * @param {String} title The title which must be shown in the message.
	 * @param {String} message The message which should be displayed.
	 * @param {Object} config Configuration object which can be applied to the notifier
	 * This object can contain keys like:
	 * - container: Which is the container to which the notifier should be restricted
	 * - persistent: True to make the message persistent and don't disappear automatically
	 * - destroy: Don't create new message, but destroy previous one
	 * - update: Don't create a new message, but update previous one
	 * - reference: The original message which must be updated by this action
	 * - listeners: Event handlers which must be registered on the element
	 * @return {Ext.MessageBox} The message box
	 */
	notify: function(category, title, message, config)
	{
		var dialogCls;
		if (category.indexOf('warning') === 0) {
			dialogCls = Ext.MessageBox.WARNING_CLS;
		} else if (category.indexOf('error') === 0) {
			dialogCls = Ext.MessageBox.ERROR_CLS;
		}

		if (Ext.isDefined(config) && !Ext.isEmpty(config.details_message)) {
			return new Zarafa.common.dialogs.CustomMessageBox({
				title: title,
				msg: message,
				buttonAlign: 'left',
				cls: 'k-show-more-details' + ' '+dialogCls,
				customItems: this.getCustomItems(config),
				customButtons: this.getCustomButtons(),
				fn: this.customButtonsHandler
			});
		} else {
			return Ext.MessageBox.show({
				title: title,
				msg: message,
				cls: dialogCls,
				buttons: Ext.MessageBox.OK
			});
		}
	},

	/**
	 * Helper function which will return the array of items for custom message box.
	 * @param {Object} config Configuration object which can be applied to the notifier.
	 * @returns {Array} Items the array of items which will show in custom message box.
	 */
	getCustomItems: function (config)
	{
		return [{
			xtype: 'textarea',
			grow: true,
			cls: 'k-show-more-details-textarea',
			growMax: 190,
			growMin: 120,
			name: 'textarea',
			ref: '../showmoretextarea',
			hidden: true,
			readOnly: true,
			value: config.details_message,
			listeners: {
				'render': function () {
					this.setWidth(this.ownerCt.getWidth());
				}
			}
		}];
	},

	/**
	 * Helper function use to return array of custom buttons,
	 * Which will add in custom message box.
	 * @returns {Array} Buttons array of button which will show in message box.
	 */
	getCustomButtons: function ()
	{
		return [{
			name: 'showdetails',
			cls: 'zarafa-normal k-window-text-button',
			text: _('Show Details'),
			keepOpenWindow: true
		}, '->', {
			name: 'copydetails',
			cls: 'zarafa-normal',
			iconCls: 'icon_copy',
			hidden: true,
			text: _('Copy Details'),
			keepOpenWindow: true
		}, {
			name: 'cancel',
			cls: 'zarafa-action',
			text: _('Close')
		}];
	},

	/**
	 * Helper function which will return handler function for custom message box.
	 * @param {String} buttonName name of button which was click.
	 * @param {Ext.Button} button Button which was click.
	 */
	customButtonsHandler: function (button)
	{
		var showMoreTextArea = this.dlgItemContainer.showmoretextarea;
		var buttonName = button.name;
		if (buttonName === 'showdetails') {
			var isShowMoreTextVisible = showMoreTextArea.isVisible();
			var showMoreText = isShowMoreTextVisible ? _("Show Details") : _("Hide Details");
			showMoreTextArea.setVisible(!isShowMoreTextVisible);
			button.setText(showMoreText);
			var copyDetailsBtn = this.buttons.find(function (btn) {
				return btn.name === "copydetails";
			});
			copyDetailsBtn.setVisible(!isShowMoreTextVisible);
			this.center();
		} else if (buttonName === 'copydetails') {
			Zarafa.core.Util.copyToClipboard(showMoreTextArea.getValue());
			this.close();
		}
	}
});

Zarafa.onReady(function() {
	container.getNotifier().registerPlugin('messagebox', new Zarafa.core.ui.notifier.MessageBoxPlugin());
});
/*
 * #dependsFile client/zarafa/core/Container.js
 */
Ext.namespace('Zarafa.core.ui.notifier');

/**
 * @class Zarafa.core.ui.notifier.NonePlugin
 * @extends Zarafa.core.ui.notifier.NotifyPlugin
 *
 * Special {@link Zarafa.core.ui.notifier.NotifyPlugin NotifyPlugin} which sends all
 * messages with highest priority to the other side of the universe. Due to the distance
 * and the maximum speed limit, the user will probably never hear from this message again.
 *
 * In case the message is received by Aliens before it has arrived at its destination,
 * we can only hope they will be friendly and bring some of their delicous food when they
 * come and visit.
 *
 * This plugin will be registered to the {@link Zarafa.core.ui.notifier.Notifier notifier}
 * using the name 'none'.
 */
Zarafa.core.ui.notifier.NonePlugin = Ext.extend(Zarafa.core.ui.notifier.NotifyPlugin, {
});

Zarafa.onReady(function() {
	container.getNotifier().registerPlugin('none', new Zarafa.core.ui.notifier.NonePlugin());
});
Ext.namespace('Zarafa.core.ui.notifier');

/**
 * @class Zarafa.core.ui.notifier.PagingSliderPlugin
 * @extends Zarafa.core.ui.notifier.NotifyPlugin
 *
 * Special {@link Zarafa.core.ui.notifier.NotifyPlugin NotifyPlugin} which works by sliding
 * the {@link Zarafa.common.ui.PagingToolbar PagingToolbar} or live scroll loading information
 * at a certain position into the screen.
 *
 * This class is intended as Base implementation by any notifier which want to implement
 * some sort of sliding of {@link Zarafa.common.ui.PagingToolbar PagingToolbar} or live scroll
 * loading information at a certain position. This class defines the various locations and
 * will ensure that different sliders will properly stack at those locations.
 */
Zarafa.core.ui.notifier.PagingSliderPlugin = Ext.extend(Zarafa.core.ui.notifier.NotifyPlugin, {

	/**
	 * The {@link Zarafa.common.ui.PagingToolbar PagingToolbar} which is used
	 * to show currently loaded pages in grid.
	 * @property
	 * @type Object
	 */
	pagingToolbar: undefined,

	/**
	 * The {@link Zarafa.core.ContextModel} which is obtained from
	 * the {@link Zarafa.core.Context}.
	 *
	 * @property
	 * @type Zarafa.core.ContextModel
	 */
	model: undefined,

	/**
	 * The parentEl to which the notifier should be restricted
	 * @property
	 * @type Object
	 */
	parentEl: undefined,

	/**
	 * Timer which is used to slide out slider after specified time.
	 * @property
	 * @type Number
	 */
	timer: undefined,

	/**
	 * @cfg {String} sliderContainerPosition The position of the container
	 * in which the notifier will be shown. This can be any of the following values:
	 * - 'tl': The top left corner
	 * - 't': The center of the top edge (default)
	 * - 'tr': The top right corner
	 * - 'l': The center of the left edge
	 * - 'c': In the center of the element
	 * - 'r': The center of the right edge
	 * - 'bl': The bottom left corner
	 * - 'b': The center of the bottom edge
	 * - 'br': The bottom right corner
	 */
	sliderContainerPosition: 'b',

	/**
	 * @cfg {Char} slideInDirection the animation slideIn direction for notification
	 */
	slideInDirection: 'b',

	/**
	 * @cfg {Char} slideOutDirection the animation slideOut direction for notification
	 */
	slideOutDirection: 'b',

	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = Ext.applyIf(config || {}, {
			sliderCls: 'k-slider',
			sliderContainerPosition: 'b',
			slideInDirection: 'b',
			slideOutDirection: 'b'
		});

		Zarafa.core.ui.notifier.PagingSliderPlugin.superclass.constructor.call(this, config);
	},

	/**
	 * Notify the user with a {@link Zarafa.common.ui.PagingToolbar PagingToolbar}.
	 *
	 * @param {String} category The category which applies to the notification.
	 * @param {String} title The title which must be shown in the message.
	 * @param {String} message The message which should be displayed.
	 * @param {Object} config Configuration object which can be applied to the notifier
	 * This object can contain keys like:
	 * - parentEl: Which is the container to which the notifier should be restricted
	 * - destroy: Don't create new message, but destroy previous one
	 * - model: {@link Zarafa.core.ContextModel model} of the current context.
	 * - update: true to realign the slider.
	 * - slider: The slider container.
	 * @return {Ext.Element} The slider element which was created
	 */
	notify: function(category, title, message, config)
	{
		Ext.apply(this, config);
		if (config.destroy) {
			this.setSliderTimeOut();
		} else if (config.update) {
			this.slider.alignTo(this.parentEl, this.sliderContainerPosition + '-' + this.sliderContainerPosition, [-8, 0]);
		} else {
			clearTimeout(this.timer);
			this.slider = this.getSlider();
			if (!this.pagingEnabled) {
				this.setSliderTimeOut();
			} else if (Ext.isDefined(this.model)){
				// This check we need to add because sometimes
				// grid does not have model info or we don't need to update the
				// pagination slide info on folder change.
				this.model.on('folderchange', this.onFolderChange, this);
			}
		}
		return this.slider;
	},

	/**
	 * Event handler clear the timeout of slider.
	 */
	clearSliderTimeOut: function()
	{
		clearTimeout(this.timer);
	},

	/**
	 * Event handler set the timeout for the slider.
	 */
	setSliderTimeOut: function()
	{
		this.timer = setTimeout(function(slider) {
			if (!Ext.isDefined(slider)) {
				return;
			}
			slider.ghost(this.slideOutDirection, {remove: true});
		}, this.sliderDuration, this.slider);
	},

	/**
	 * Event handler triggered when folder was changed in hierarchy. It will
	 * make {@link Zarafa.common.ui.PagingToolbar#cursor cursor} to 0 which reset
	 * the pagination tool bar page counter.
	 */
	onFolderChange: function()
	{
		if (Ext.isDefined(this.pagingToolbar)) {
			this.pagingToolbar.cursor = 0;
		}
	},

	/**
	 * Create {@link Zarafa.common.ui.PagingToolbar PagingToolbar} object in slider.
	 * @param {Ext.Element} slider The element in which {@link Zarafa.common.ui.PagingToolbar PagingToolbar}
	 * should be rendered.
	 */
	createPagingToolbar: function(slider)
	{
		var cursor = !Ext.isEmpty(this.pagingToolbar) ? this.pagingToolbar.cursor : 0;
		this.pagingToolbar = Ext.create({
			xtype: 'zarafa.paging',
			renderTo: slider.dom,
			pageSize: container.getSettingsModel().get('zarafa/v1/main/page_size'),
			store: this.getStore(),
			cursor: cursor
		});
	},

	/**
	 * Function which prepare the slide element.
	 *
	 * @return {Ext.Element} slider The element which show current loaded items in grid.
	 */
	getSlider: function()
	{
		var sliderCfg = {
			id: this.parentEl.id + '-' + this.sliderCls,
			cls: this.sliderCls
		};

		if (!this.pagingEnabled) {
			var store = this.getStore();
			sliderCfg.html = this.getUpdatedPaginationText(store);
		}

		var element = Ext.get(sliderCfg.id);
		if (!element) {
			element = Ext.DomHelper.insertFirst(this.parentEl, sliderCfg, true);

			if (this.pagingEnabled) {
				this.createPagingToolbar(element);
			} else {
				element.on({
					'mouseenter': this.clearSliderTimeOut,
					'mouseleave': this.setSliderTimeOut,
					scope: this
				});
				store.on('load', this.onStoreLoad, this);
			}
			element.alignTo(this.parentEl, this.sliderContainerPosition + '-' + this.sliderContainerPosition, [-8, 0]);
			element = element.slideIn(this.slideInDirection);
		} else if (!this.pagingEnabled) {
			element.dom.innerHTML = sliderCfg.html;
		}
		return element;
	},

	/**
	 * Event handler triggered when store is load. it also update the
	 * pagination text for the livescroll slider.
	 *
	 * @param {Zarafa.core.data.ListModuleStore} store The store which
	 * fire the load event.
	 */
	onStoreLoad: function(store)
	{
		this.slider.dom.innerHTML = this.getUpdatedPaginationText(store);
	},

	/**
	 * Helper function which gives the updated pagination text for the livescroll slider.
	 * @param {Zarafa.core.data.ListModuleStore} store The store which used to update
	 * pagination text.
	 * @returns {String} return pagination text which shows in livescroll slider.
	 */
	getUpdatedPaginationText: function (store)
	{
		var sliderText = String.format(_('Loaded {0} of {1}'), store.getStoreLength(), store.getTotalCount());
		return String.format('<div>{0}</div>', sliderText);
	},

	/**
	 * Function is used to get the store from either store config or
	 * from give model.
	 *
	 * @returns {Ext.data.store} store The data store attached with pagination plugin.
	 */
	getStore: function()
	{
		return this.store || this.model.getStore();
	}
});
Ext.namespace('Zarafa.core.ui.notifier');

/**
 * @class Zarafa.core.ui.notifier.SliderNotifierPlugin
 * @extends Zarafa.core.ui.notifier.NotifyPlugin
 *
 * Special {@link Zarafa.core.ui.notifier.NotifyPlugin NotifyPlugin} which works by sliding
 * the message at a certain position into the screen.
 *
 * This class is intended as Base implementation by any notifier which want to implement
 * some sort of sliding of messages at a certain position. This class defines the various
 * locations and will ensure that different sliders will properly stack at those locations.
 */
Zarafa.core.ui.notifier.SliderNotifierPlugin = Ext.extend(Zarafa.core.ui.notifier.NotifyPlugin, {
	/**
	 * @cfg {Ext.XTemplate/String} The template used to create the PopupBox.
	 * The {@link Ext.XTemplate XTemplate} arguments are:
	 * - 'categorycls': The CSS class which is used to indicate the category for which the notifier is created
	 * - 'basecls': The CSS class which is used as base on the different elements
	 * - 'title': The title which must be shown (See the title argument for {@link #notify}).
	 * - 'message': The message which must be shown (See the message argument for {@link #notify}).
	 */
	msgTemplate: new Ext.XTemplate(
		'<div class="{categorycls} {basecls}">' +
			'<div class="left">'+
				'<div class="icon" ></div>' +
			'</div>'+
			'<div class="right">' +
				'<h2 class="{basecls}-title">' +
					'{title}' +
				'</h2>' +
				'<div class="{basecls}-message">' +
					'{message}' +
				'</div>' +
			'</div>'+
		'</div>'
	),

	/**
	 * @cfg {String} itemCls The base CSS class to be applied to {@link #msgTemplate}
	 */
	itemCls: 'zarafa-slide-notifier',

	/**
	 * @cfg {String} categoryCls The base CSS class for the categorycls to be applied to {@link #msgTemplate}
	 */
	categoryCls: 'zarafa-slide-notifier',

	/**
	 * @cfg {Number} msgLifetime The amount of seconds of which non-persistent messages should be displayed.
	 */
	msgLifetime: 5,

	/**
	 * @cfg {String} sliderContainerPosition The position of the container
	 * in which the notifier will be shown. This can be any of the following values:
	 * - 'tl': The top left corner
	 * - 't': The center of the top edge (default)
	 * - 'tr': The top right corner
	 * - 'l': The center of the left edge
	 * - 'c': In the center of the element
	 * - 'r': The center of the right edge
	 * - 'bl': The bottom left corner
	 * - 'b': The center of the bottom edge
	 * - 'br': The bottom right corner
	 */
	sliderContainerPosition: 't',

	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		Zarafa.core.ui.notifier.SliderNotifierPlugin.superclass.constructor.call(this, config);

		if (Ext.isString(this.msgTemplate)) {
			this.msgTemplate = new Ext.XTemplate(this.msgTemplate, { compiled: true });
		}
	},

	/**
	 * Obtain a {@link Zarafa.core.ui.notifier.SliderContainer SliderContainer} from
	 * {@lin #slideContainers} or instantiate a new one. This container can be used
	 * to show new notifications.
	 * @param {Ext.Element} parentCt The parent element in which the container should
	 * be placed
	 * @param {String} The position of the container, see {@link #sliderContainerPosition}.
	 * @return {Zarafa.core.ui.notifier.SliderContainer} The slider container
	 * @private
	 */
	getSliderContainer: function(parentCt, position)
	{
		parentCt = parentCt || Ext.getBody();

		var containerid = parentCt.id + '-' + position;
		var containers = Zarafa.core.ui.notifier.SliderNotifierPlugin.sliderContainers;

		// Check if the container exists in the DOM, otherwise create a new one
		if (!containers[containerid] || !Ext.get(containers[containerid].container.id) ) {
			containers[containerid] = new Zarafa.core.ui.notifier.SliderContainer({
				parentContainer: parentCt,
				containerPosition: position,
				slideinDirection: position[0],
				slideoutDirection: position[0]
			});
		} else {
			// The parent component could be removed from the DOM and rendered again later.
			// If we set it again we are sure we have the DOM object
			containers[containerid].parentContainer = parentCt;
		}

		return containers[containerid];
	},

	/**
	 * Notify the user with a message.
	 *
	 * The category can be either "error", "warning", "info" or "debug", or a subtype thereof (e.g. "info.newmail").
	 *
	 * @param {String} category The category which applies to the notification.
	 * @param {String} title The title which must be shown in the message.
	 * @param {String} message The message which should be displayed.
	 * @param {Object} config Configuration object which can be applied to the notifier
	 * This object can contain keys like:
	 * - container: Which is the container to which the notifier should be restricted
	 * - persistent: True to make the message persistent and don't disappear automatically
	 * - destroy: Don't create new message, but destroy previous one
	 * - update: Don't create a new message, but update previous one
	 * - reference: The original message which must be updated by this action
	 * - listeners: Event handlers which must be registered on the element
	 * @return {Ext.Element} The slider element which was created
	 */
	notify: function(category, title, message, config)
	{
		// Enable default config if none was provided
		config = config || {};

		// Generate a CSS class which must be applied
		var basecls = this.itemCls;
		var categorycls = this.categoryCls + '-' + category.split('.')[0] + ' ' + this.categoryCls + '-' + category.replace(/\./g, '-');

		// Create the HTML string which we want to generate
		var html = this.msgTemplate.apply({ title: title, message: message, basecls: basecls, categorycls: categorycls });
		var slider = this.getSliderContainer(config.container, this.sliderContainerPosition);

		if (config.reference) {
			if (config.destroy) {
				slider.removeMessage(config.reference);
			} else if (config.update) {
				return slider.updateMessage(config.reference, html);
			}
		} else {
			var lifetime = config.persistent ? false : this.msgLifetime;
			var reference = slider.createMessage(html, lifetime);
			// Register the click event on notifier if notification
			// is not persistent and does not have already registered click event,
			// which discard notification when user click on it.
			if (lifetime !== false && !(Ext.isDefined(config.listeners) && Ext.isDefined(config.listeners.click))) {
				config.listeners = {
					click: this.onNotifierClick.createDelegate(this,[category], 1),
					scope: this
				};
			}
			if (config.listeners) {
				this.addMessageListeners(reference, config.listeners);
			}

			return reference;
		}
	},

	/**
	 * Event handler which triggers when user click on notification.
	 * Where function is used to discard the existing notification.
	 *
	 * @param {Zarafa.core.ui.notifier.SliderContainer} notification The slider container of notification.
	 * @param {String} category The category which applies to the notification.
	 * @param {Ext.EventObject} event The event object
	 */
	onNotifierClick: function (notification, category, event) {
		container.getNotifier().notify(category, null, null, {
			container: notification.slider.parentContainer,
			destroy: true,
			reference: notification
		});
	},

	/**
	 * Register event handlers to the {@link Ext.Element message}.
	 * @param {Ext.Element} reference The Message reference
	 * @param {Object} listeners The object of event handlers to register
	 * @private
	 */
	addMessageListeners: function(reference, listeners)
	{
		for (var key in listeners) {
			var fn = listeners[key];

			if (key !== 'scope' && Ext.isFunction(fn)) {
				reference.on(key, function(event, reference, fn) {
					fn.call(this, reference, event);
				}.createDelegate(listeners.scope || reference, [ reference, fn ], 1));
			}
		}
	}
});

/**
 * Cache of all {@link Zarafa.core.ui.notifier.SliderContainer} instances which were
 * created by the {@link Zarafa.core.ui.notifier.SliderNotifierPlugin SliderNotifierPlugin}.
 * @property
 * @type Object
 * @private
 * @static
 */
Zarafa.core.ui.notifier.SliderNotifierPlugin.sliderContainers = {};
Ext.namespace('Zarafa.core.ui.notifier');

/**
 * @class Zarafa.core.ui.notifier.ToastPlugin
 * @extends Zarafa.core.ui.notifier.NotifyPlugin
 *
 * Modern toast notification plugin that displays non-intrusive notifications
 * at the bottom-center of the screen. Replaces the legacy PopupBox, DropdownBox,
 * and MessageBox notification plugins with a unified, modern design.
 *
 * Features:
 * - Bottom-center positioning with upward stacking
 * - CSS-driven slide-up/fade animations
 * - Color-coded severity indicators (info, warning, error)
 * - Auto-dismiss with visual progress countdown
 * - Persistent mode for errors/warnings requiring user acknowledgment
 * - Expandable details section for error diagnostics
 */
Zarafa.core.ui.notifier.ToastPlugin = Ext.extend(Zarafa.core.ui.notifier.NotifyPlugin, {
	/**
	 * The container element that holds all toast notifications.
	 * @property
	 * @type HTMLElement
	 * @private
	 */
	toastContainer: undefined,

	/**
	 * @cfg {Number} infoLifetime Auto-dismiss time in ms for info toasts.
	 */
	infoLifetime: 5000,

	/**
	 * @cfg {Number} warningLifetime Auto-dismiss time in ms for warning toasts.
	 */
	warningLifetime: 8000,

	/**
	 * Active toast references keyed by their DOM id, for update/destroy lookups.
	 * @property
	 * @type Object
	 * @private
	 */
	activeToasts: undefined,

	/**
	 * Counter for generating unique toast IDs.
	 * @property
	 * @type Number
	 * @private
	 */
	idCounter: 0,

	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		Zarafa.core.ui.notifier.ToastPlugin.superclass.constructor.call(this, config);
		this.activeToasts = {};
	},

	/**
	 * Ensure the toast container element exists in the DOM.
	 * Creates it on first call, appended to document.body.
	 * @return {HTMLElement} The toast container element
	 * @private
	 */
	ensureContainer: function()
	{
		if (!this.toastContainer || !document.body.contains(this.toastContainer)) {
			this.toastContainer = document.createElement('div');
			this.toastContainer.id = 'grommunio-toast-container';
			this.toastContainer.className = 'grommunio-toast-container';
			this.toastContainer.setAttribute('aria-live', 'polite');
			this.toastContainer.setAttribute('aria-atomic', 'false');
			this.toastContainer.setAttribute('role', 'status');
			document.body.appendChild(this.toastContainer);
		}
		return this.toastContainer;
	},

	/**
	 * Determine the severity class from the notification category.
	 * @param {String} category The notification category
	 * @return {String} One of 'info', 'warning', 'error'
	 * @private
	 */
	getSeverity: function(category)
	{
		if (category.indexOf('error') === 0) {
			return 'error';
		} else if (category.indexOf('warning') === 0) {
			return 'warning';
		}
		return 'info';
	},

	/**
	 * Get the SVG icon markup for the given severity.
	 * @param {String} severity One of 'info', 'warning', 'error'
	 * @return {String} SVG markup
	 * @private
	 */
	getIconSvg: function(severity)
	{
		switch (severity) {
			case 'error':
				return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">' +
					'<circle cx="12" cy="12" r="10"/>' +
					'<line x1="15" y1="9" x2="9" y2="15"/>' +
					'<line x1="9" y1="9" x2="15" y2="15"/>' +
					'</svg>';
			case 'warning':
				return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">' +
					'<path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/>' +
					'<line x1="12" y1="9" x2="12" y2="13"/>' +
					'<line x1="12" y1="17" x2="12.01" y2="17"/>' +
					'</svg>';
			default:
				return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">' +
					'<path d="M22 11.08V12a10 10 0 11-5.93-9.14"/>' +
					'<polyline points="22 4 12 14.01 9 11.01"/>' +
					'</svg>';
		}
	},

	/**
	 * Build the toast DOM element.
	 * @param {String} severity The severity level
	 * @param {String} title The notification title
	 * @param {String} message The notification message
	 * @param {Object} config The notification config
	 * @return {HTMLElement} The toast element
	 * @private
	 */
	buildToastElement: function(severity, title, message, config)
	{
		var toast = document.createElement('div');
		var toastId = 'grommunio-toast-' + (++this.idCounter);
		toast.id = toastId;
		toast.className = 'grommunio-toast grommunio-toast-' + severity;
		toast.setAttribute('role', severity === 'error' ? 'alert' : 'status');
		toast.setAttribute('aria-live', severity === 'error' ? 'assertive' : 'polite');

		var isPersistent = config.persistent || severity === 'error';
		var lifetime = isPersistent ? 0 : (severity === 'warning' ? this.warningLifetime : this.infoLifetime);

		var html = '<div class="grommunio-toast-accent" aria-hidden="true"></div>' +
			'<div class="grommunio-toast-icon" aria-hidden="true">' + this.getIconSvg(severity) + '</div>' +
			'<div class="grommunio-toast-body">';

		if (title) {
			html += '<div class="grommunio-toast-title"></div>';
		}
		if (message) {
			html += '<div class="grommunio-toast-message"></div>';
		}

		if (config.details_message) {
			html += '<div class="grommunio-toast-details-toggle" role="button" tabindex="0" aria-expanded="false" aria-controls="' + toastId + '-details">' + _('Show Details') + '</div>' +
				'<div class="grommunio-toast-details" style="display:none;" id="' + toastId + '-details">' +
					'<pre class="grommunio-toast-details-text">' + Ext.util.Format.htmlEncode(config.details_message) + '</pre>' +
					'<div class="grommunio-toast-details-copy" role="button" tabindex="0" aria-label="' + _('Copy Details') + '">' + _('Copy Details') + '</div>' +
				'</div>';
		}

		html += '</div>' +
			'<div class="grommunio-toast-close" role="button" tabindex="0" aria-label="' + _('Close') + '">&times;</div>';

		if (lifetime > 0) {
			html += '<div class="grommunio-toast-progress" aria-hidden="true"><div class="grommunio-toast-progress-bar" style="animation-duration:' + lifetime + 'ms;"></div></div>';
		}

		toast.innerHTML = html;

		var titleEl = toast.querySelector('.grommunio-toast-title');
		if (titleEl && title) {
			titleEl.textContent = title;
		}
		var msgEl = toast.querySelector('.grommunio-toast-message');
		if (msgEl && message) {
			msgEl.innerHTML = message;
		}

		toast._toastId = toastId;
		toast._lifetime = lifetime;
		toast._severity = severity;

		return toast;
	},

	/**
	 * Attach event listeners to a toast element.
	 * @param {HTMLElement} toast The toast element
	 * @param {Object} config The notification config
	 * @private
	 */
	bindToastEvents: function(toast, config)
	{
		var self = this;
		var toastId = toast._toastId;

		// Close button
		var closeBtn = toast.querySelector('.grommunio-toast-close');
		if (closeBtn) {
			closeBtn.addEventListener('click', function(e) {
				e.stopPropagation();
				self.dismissToast(toastId);
			});
			closeBtn.addEventListener('keydown', function(e) {
				if (e.key === 'Enter' || e.key === ' ') {
					e.preventDefault();
					e.stopPropagation();
					self.dismissToast(toastId);
				}
			});
		}

		// Details toggle
		var detailsToggle = toast.querySelector('.grommunio-toast-details-toggle');
		if (detailsToggle) {
			var toggleDetails = function(e) {
				e.stopPropagation();
				var details = toast.querySelector('.grommunio-toast-details');
				if (details) {
					var isHidden = details.style.display === 'none';
					details.style.display = isHidden ? 'block' : 'none';
					detailsToggle.textContent = isHidden ? _('Hide Details') : _('Show Details');
					detailsToggle.setAttribute('aria-expanded', isHidden ? 'true' : 'false');
				}
			};
			detailsToggle.addEventListener('click', toggleDetails);
			detailsToggle.addEventListener('keydown', function(e) {
				if (e.key === 'Enter' || e.key === ' ') {
					e.preventDefault();
					toggleDetails(e);
				}
			});
		}

		// Copy details
		var copyBtn = toast.querySelector('.grommunio-toast-details-copy');
		if (copyBtn) {
			var copyDetails = function(e) {
				e.stopPropagation();
				var text = toast.querySelector('.grommunio-toast-details-text');
				if (text) {
					Zarafa.core.Util.copyToClipboard(text.textContent);
					copyBtn.textContent = _('Copied!');
					setTimeout(function() {
						copyBtn.textContent = _('Copy Details');
					}, 2000);
				}
			};
			copyBtn.addEventListener('click', copyDetails);
			copyBtn.addEventListener('keydown', function(e) {
				if (e.key === 'Enter' || e.key === ' ') {
					e.preventDefault();
					copyDetails(e);
				}
			});
		}

		// Click on toast body to dismiss non-persistent toasts
		if (toast._lifetime > 0) {
			toast.addEventListener('click', function() {
				self.dismissToast(toastId);
			});
		}

		// Pause auto-dismiss on hover (both JS timeout and CSS animation)
		if (toast._lifetime > 0) {
			toast.addEventListener('mouseenter', function() {
				if (toast._dismissTimer) {
					clearTimeout(toast._dismissTimer);
					toast._dismissTimer = null;
				}
				// Store remaining time based on progress bar state
				var bar = toast.querySelector('.grommunio-toast-progress-bar');
				if (bar) {
					var computed = window.getComputedStyle(bar);
					var transform = computed.getPropertyValue('transform');
					// Extract scaleX from the matrix, default to 1 if not parseable
					var scaleX = 1;
					if (transform && transform !== 'none') {
						var match = transform.match(/matrix\(([^,]+)/);
						if (match) {
							scaleX = parseFloat(match[1]);
						}
					}
					toast._remainingTime = Math.max(0, scaleX * toast._lifetime);
					bar.style.animationPlayState = 'paused';
				}
			});
			toast.addEventListener('mouseleave', function() {
				var bar = toast.querySelector('.grommunio-toast-progress-bar');
				if (bar) {
					bar.style.animationPlayState = 'running';
				}
				// Resume the JS timeout with remaining time
				var remaining = toast._remainingTime || toast._lifetime;
				if (remaining > 0) {
					toast._dismissTimer = setTimeout(function() {
						self.dismissToast(toastId);
					}, remaining);
				}
			});
		}

		// External listeners from config
		if (config.listeners) {
			for (var key in config.listeners) {
				if (key !== 'scope' && Ext.isFunction(config.listeners[key])) {
					(function(eventName, handler, scope) {
						toast.addEventListener(eventName, function(e) {
							handler.call(scope || toast, toast, e);
						});
					})(key, config.listeners[key], config.listeners.scope);
				}
			}
		}
	},

	/**
	 * Animate a toast into view.
	 * @param {HTMLElement} toast The toast element
	 * @private
	 */
	showToast: function(toast)
	{
		var ct = this.ensureContainer();
		ct.appendChild(toast);

		// Force reflow so the enter animation triggers
		toast.offsetHeight;
		toast.classList.add('grommunio-toast-visible');

		// Set up auto-dismiss
		if (toast._lifetime > 0) {
			var self = this;
			toast._dismissTimer = setTimeout(function() {
				self.dismissToast(toast._toastId);
			}, toast._lifetime);
		}
	},

	/**
	 * Dismiss and remove a toast by its ID.
	 * @param {String} toastId The toast ID
	 */
	dismissToast: function(toastId)
	{
		var record = this.activeToasts[toastId];
		if (!record) {
			return;
		}
		var toast = record.element;

		if (toast._dismissTimer) {
			clearTimeout(toast._dismissTimer);
			toast._dismissTimer = null;
		}

		toast.classList.remove('grommunio-toast-visible');
		toast.classList.add('grommunio-toast-exit');

		var self = this;
		var onEnd = function() {
			toast.removeEventListener('transitionend', onEnd);
			if (toast.parentNode) {
				toast.parentNode.removeChild(toast);
			}
			delete self.activeToasts[toastId];
		};

		toast.addEventListener('transitionend', onEnd);

		// Fallback in case transitionend doesn't fire
		setTimeout(onEnd, 400);
	},

	/**
	 * Immediately dismiss all active toasts.
	 * @private
	 */
	dismissAll: function()
	{
		var ids = [];
		for (var id in this.activeToasts) {
			if (this.activeToasts.hasOwnProperty(id)) {
				ids.push(id);
			}
		}
		for (var i = 0; i < ids.length; i++) {
			this.dismissToastImmediate(ids[i]);
		}
	},

	/**
	 * Remove a toast immediately without animation.
	 * @param {String} toastId The toast ID
	 * @private
	 */
	dismissToastImmediate: function(toastId)
	{
		var record = this.activeToasts[toastId];
		if (!record) {
			return;
		}
		var toast = record.element;
		if (toast._dismissTimer) {
			clearTimeout(toast._dismissTimer);
			toast._dismissTimer = null;
		}
		if (toast.parentNode) {
			toast.parentNode.removeChild(toast);
		}
		delete this.activeToasts[toastId];
	},

	/**
	 * @inheritdoc
	 */
	notify: function(category, title, message, config)
	{
		config = config || {};
		var severity = this.getSeverity(category);

		// Handle destroy requests
		if (config.reference && config.destroy) {
			var refId = config.reference._toastId || config.reference;
			this.dismissToast(refId);
			return;
		}

		// Handle update requests
		if (config.reference && config.update) {
			var refId = config.reference._toastId || config.reference;
			var existing = this.activeToasts[refId];
			if (existing) {
				var titleEl = existing.element.querySelector('.grommunio-toast-title');
				var msgEl = existing.element.querySelector('.grommunio-toast-message');
				if (titleEl && title) {
					titleEl.textContent = title;
				}
				if (msgEl && message) {
					msgEl.innerHTML = message;
				}
				return config.reference;
			}
		}

		// Dismiss any existing toasts so the new one replaces them
		this.dismissAll();

		var toast = this.buildToastElement(severity, title, message, config);
		this.bindToastEvents(toast, config);
		this.activeToasts[toast._toastId] = { element: toast, category: category };
		this.showToast(toast);

		return toast;
	}
});

Zarafa.onReady(function() {
	container.getNotifier().registerPlugin('toast', new Zarafa.core.ui.notifier.ToastPlugin());
});
Ext.namespace('Zarafa.core.ui.notifier');

/**
 * @class Zarafa.core.ui.notifier.ToolbarNotifierPlugin
 * @extends Zarafa.core.ui.notifier.NotifyPlugin
 *
 * Special {@link Zarafa.core.ui.notifier.NotifyPlugin NotifyPlugin} which shows
 * notification messages inside {@link Ext.Toolbar Toolbar}. This plugin
 * will be registered to the {@link Zarafa.core.ui.notifier.Notifier notifier} using
 * the name 'toolbarnotifierplugin'.
 */
Zarafa.core.ui.notifier.ToolbarNotifierPlugin = Ext.extend(Zarafa.core.ui.notifier.NotifyPlugin, {
	/**
	 * Send a notification to the user. This {@link #getPlugin requests} the plugin for the provided category.
	 * On the plugin the {@link Zarafa.core.ui.notifier.NotifyPlugin#notify notify} function is called to display
	 * the message to the user. If no plugin could be found for
	 * the given category, then no message will be shown to the user.
	 *
	 * @param {String} category The category which applies to the notification.
	 * @param {String} message The message which should be displayed.
	 * @param {Object} config Configuration object which can be applied to the notifier
	 * This object can contain keys like:
	 * - toolbar: {@link Ext.Toolbar Toolbar} in which notification message will be added,
	 * notification message will be added at right most of the toolbar.
	 */
	notify: function(category, message, config)
	{
		if(category == 'info.mailsaving' || category == 'info.mailsaved') {
			if (config.toolbar) {
				if(!config.toolbar.saveMessageText) {
					var addInfoText = [{
							xtype: 'tbfill'
						}, {
							xtype: 'tbtext',
							autoWidth: true,
							ref: 'saveMessageText',
							listeners: {
								afterrender: function(cmp) {
									if (cmp.el) {
										cmp.el.set({ 'role': 'status', 'aria-live': 'polite' });
									}
								}
							}
						}];
					config.toolbar.add(addInfoText);
					config.toolbar.doLayout();
				}
				config.toolbar.saveMessageText.setText(message);
			}
		}
		Zarafa.core.ui.notifier.ToolbarNotifierPlugin.superclass.notify.apply(this, arguments);
	}
});

Zarafa.onReady(function() {
	container.getNotifier().registerPlugin('toolbarnotifierplugin', new Zarafa.core.ui.notifier.ToolbarNotifierPlugin());
});
Ext.namespace('Zarafa.core.ui.widget');

/**
 * @class Zarafa.core.ui.widget.AbstractFolderWidget
 * @extends Zarafa.core.ui.widget.Widget
 *
 * Widget which can be used to show the contents of a
 * {@link Zarafa.hierarchy.data.MAPIFolderRecord folder}
 * using a particular restriction (during {@link #store}{@link Ext.data.Store#load})
 * or a filter (using {@link #store}{@link Ext.data.Store#applyFilter}).
 *
 * Reload time is configurable per instance of the
 * widget (keys: 'reloadinterval', default 5 minutes). These values are in
 * saved in milliseconds but displayed in seconds. The reload
 * interval is how often the folder is fully reloaded from the
 * server, to show records that were added to the folder
 * outside of grommunio Web.
 */
Zarafa.core.ui.widget.AbstractFolderWidget = Ext.extend(Zarafa.core.ui.widget.Widget, {
	/**
	 * The folder which is shown inside this widget. This is initialized
	 * by {@link #onHierarchyLoad}.
	 * @property
	 * @type Zarafa.hierarchy.data.MAPIFolderRecord
	 * @private
	 */
	folder: undefined,

	/**
	 * @cfg {Zarafa.core.data.MAPIStore} store The store which is being used for loading the records
	 */
	store: undefined,

	/**
	 * @cfg {String} folderType The folder type to obtain the desired folder
	 * from the {@link Zarafa.hierarchy.data.HierarchyStore hierarchy}.
	 */
	folderType: undefined,

	/**
	 * The task that was {@link Ext.TaskMgr.start started} to reload the folder grid.
	 * @property
	 * @type Object
	 * @private
	 */
	reloadTask: undefined,

	/**
	 * The configuration object for the configure window of the widget
	 * @property
	 * @type Object
	 * @private
	 */
	configurationConfig: undefined,

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

		// Set the guid so we can use the get() function
		this.guid = config.guid;

		Ext.applyIf(config, {
			hasConfig: true,
			height: this.get('widgetheight') || 300
		});

		Zarafa.core.ui.widget.AbstractFolderWidget.superclass.constructor.call(this, config);
	},

	/**
	 * Initialize the event handlers for the {@link #store} and {@link Zarafa.hierarchy.data.HierarchyStore Hierarchy}.
	 * @protected
	 */
	initEvents: function ()
	{
		Zarafa.core.ui.widget.AbstractFolderWidget.superclass.initEvents.apply(this, arguments);

		// Wait for the hierarchy store to be loaded.
		var hierarchyStore = container.getHierarchyStore();
		this.mon(hierarchyStore, 'load', this.onHierarchyLoad, this);
		// needed when adding the widget after hierarchy load
		this.onHierarchyLoad(hierarchyStore);

		// Wait for the store to be loaded, so we can activate
		// the refreshing and reloading times.
		this.mon(this.store, 'load', this.startReloadTask, this, {single: true});

		// Apply the filter when we have new records
		this.mon(this.store, 'load', this.updateFilter, this);

		// Listen for record updates, as that might have impact on the filtering
		// which should be applied.
		this.mon(this.store, 'update', this.onStoreUpdate, this);
	},

	/**
	 * Load the default calendar folder and retrieve the records.
	 * @param {Zarafa.hierarchy.data.HierarchyStore} hierarchyStore The store which fired the event
	 * @private
	 */
	onHierarchyLoad: function (hierarchyStore)
	{
		this.folder = hierarchyStore.getDefaultFolder(this.folderType);
		if (this.folder) {
			this.reloadStore();
		}
	},

	/**
	 * Starts a reload task for the store. If one already exists, it will be stopped first.
	 * @private
	 */
	startReloadTask: function ()
	{
		if ( this.reloadTask ) {
			Ext.TaskMgr.stop(this.reloadTask);
		}

		// Periodically reload data from the server to remove stale
		// data from the store. But only do this when the store has
		// finished loading for the first time.
		var interval = this.get('reloadinterval') || 30;
		interval *= 1000; // convert seconds in milliseconds

		this.reloadTask = Ext.TaskMgr.start({
			run: this.reloadStore,
			interval: interval,
			scope: this
		});
	},

	/**
	 * When the store record has been changed, {@link #updateFilter apply filter}
	 * to ensure that unwanted records are immediately removed.
	 * @private
	 */
	onStoreUpdate: function ()
	{
		this.updateFilter();
	},

	/**
	 * This will {@link Ext.data.Store#load load} the {@link #store}.
	 * @private
	 */
	reloadStore: function ()
	{
		if (this.folder) {
			this.store.load({folder: this.folder});
		}
	},

	/**
	 * Update the filter.
	 * @private
	 */
	updateFilter: Ext.emptyFn,

	/**
	 * Apply custom style and content for the row body. This will color
	 * a task in red when its due-date is reached. If categories are applied
	 * to a task these categories will be displayed.
	 *
	 * @param {Ext.data.Record} record The {@link Ext.data.Record Record} corresponding to the current row.
	 * @param {Number} rowIndex The row index
	 * @param {Object} rowParams A config object that is passed to the row template during
	 * rendering that allows customization of various aspects of a grid row.
	 * If enableRowBody is configured true, then the following properties may be set by this function,
	 * and will be used to render a full-width expansion row below each grid row.
	 * @return {String} a CSS class name to add to the row
	 * @private
	 */
	viewConfigGetRowClass: function (record, rowIndex, rowParams)
	{
		rowParams.body = '<div class="zarafa-grid-body-container">';

		// Render the categories
		var categories = Zarafa.common.categories.Util.getCategories(record);
		var categoriesHtml = Zarafa.common.categories.Util.getCategoriesHtml(categories);
		rowParams.body += '<div class="k-category-container">' + categoriesHtml + '</div>';

		// Render the subject
		var subject = Zarafa.common.ui.grid.Renderers.subject(record.get('subject'), {});
		rowParams.body += String.format('<div class="grid_compact grid_compact_left grid_compact_subject_cell">{0}</div>', subject);
		rowParams.body += '</div>';

		// Get the due date class from the normal TaskGridView
		var rowClass = Zarafa.task.ui.TaskGridView.prototype.viewConfigGetRowClass(record);

		return "x-grid3-row-expanded " + rowClass;
	},

	/**
	 * Create spinner field with the given minimum and maximum value.
	 * @param {Number} minHeight minimum value of spinner field.
	 * @param {Number} maxHeight maximum value of spinner field.
	 * @return {Object[]} An array with configuration objects of spinner field.
	 */
	createConfigHeight: function (minHeight, maxHeight)
	{
		return {
			xtype: 'zarafa.spinnerfield',
			fieldLabel: _('Widget height'),
			name: 'widgetheight',
			boxLabel: _('pixels'),
			width: 60,
			minValue: minHeight || 100, // 100 pixel
			maxValue: maxHeight || 500, // 500 pixel
			incrementValue: 5, // 5 pixel
			defaultValue: this.get('widgetheight') || 300,
			listeners: {
				'change': this.onFieldChange,
				scope: this
			},
			plugins: ['zarafa.numberspinner']
		};
	},

	/**
	 * Configure the widget. At this time, only the reload and
	 * refresh times can be configured.
	 * @todo Also allow the user to select the folder(s) to show here.
	 * @private
	 */
	config: function ()
	{
		var config = Ext.apply({}, this.configurationConfig || {}, {
			title: _('Configure widget'),
			layout: 'fit',
			width: 320,
			height: 135,
			modal: true,
			items: [{
				xtype: 'form',
				frame: true,
				cls: 'k-configure-widget',
				labelWidth: 120,
				items: [{
					xtype: 'zarafa.spinnerfield',
					fieldLabel: _('Reload interval'),
					name: 'reloadinterval',
					boxLabel: _('seconds'),
					width: 60,
					minValue: 30, // 30 seconds
					maxValue: 1800, // 30 minutes
					incrementValue: 30, // 30 seconds
					defaultValue: this.get('reloadinterval') || 30,
					listeners: {
						'change': this.onFieldChange,
						scope: this
					},
					plugins: ['zarafa.numberspinner']
				},
					this.createConfigHeight()
				].concat(this.getConfigurationItems()),
				buttons: [{
					text: _('Close'),
					scope: this,
					handler: function () {
						this.win.close();
					}
				}]
			}]
		});

		this.win = new Ext.Window(config);
		this.win.show(this);
	},

	/**
	 * Returns an array with the items that should be added to the configuration window
	 * of the widget. Should be overridden by child classes.
	 * @return {array} An array of configuration objects for {@link Ext.Component components}
	 */
	getConfigurationItems: function()
	{
		return [];
	},

	/**
	 * Event handler which is fired when one of the fields in the Configuration dialog
	 * has been changed. This will update the corresponding option in the settings.
	 * @param {Ext.form.Field} field The field which fired the event
	 * @param {Mixed} newValue The new value which was applied
	 * @param {Mixed} oldValue The old value which was applied
	 * @private
	 */
	onFieldChange: function (field, newValue, oldValue)
	{
		var fieldName = field.getName();
		this.set(fieldName, newValue);

		if (fieldName === 'widgetheight') {
			this.setHeight(newValue);
		}

		if (fieldName === 'reloadinterval') {
			this.startReloadTask();
		}
	}
});
Ext.namespace('Zarafa.core.ui.widget');

/**
 * @class Zarafa.core.ui.widget.AbstractQuickItemWidget
 * @extends Zarafa.core.ui.widget.Widget
 *
 * Widget which can be used to quickly create a MAPI item.
 * This can be used to quickly create a mail, task, appointment,
 * sticky note, while using a minimum set of input fields.
 */
Zarafa.core.ui.widget.AbstractQuickItemWidget = Ext.extend(Zarafa.core.ui.widget.Widget, {
	/**
	 * The record as created by {@link #createRecord} which is currently
	 * loaded in the widget.
	 * @property
	 * @type Ext.data.Record
	 * @protected
	 */
	record: undefined,

	/**
	 * @cfg {Object} Configuration object for the instantiation of the
	 * {@link Zarafa.core.ui.MessageContentPanel} in the {@link #wrap} property.
	 */
	wrapCfg: undefined,

	/**
	 * The embedded {@link Zarafa.core.ui.MessageContentPanel} which handles
	 * the sending/saving of the {@link #record}.
	 * @property
	 * @type Zarafa.core.ui.MessageContentPanel
	 * @protected
	 */
	wrap: undefined,

	/**
	 * @cfg {Boolean} resetOnSave {@link #reset} the widget when client receives confirmation of message is saved.
	 */
	resetOnSave: true,

	/**
	 * @cfg {Boolean} resetOnSend {@link #reset} the widget when client receives confirmation of message is sent.
	 */
	resetOnSend: true,

	/**
	 * @cfg {Boolean} hasDialog True if a {@link #tools} button should be added for opening the record
	 * inside a dialog (See {@link #dialog}).
	 */
	hasDialog: true,

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

		config.wrapCfg = Ext.applyIf(config.wrapCfg || {}, {
			stateful: false,
			height: 200,
			closeOnSave: false,
			closeOnSend: false
		});

		config.wrapCfg.recordComponentPluginConfig = Ext.applyIf(config.wrapCfg.recordComponentPluginConfig || {}, {
			useShadowStore: false // We put the record into the shadowstore ourselves.
		});

		this.wrap = new Zarafa.core.ui.MessageContentPanel(config.wrapCfg);
		// The wrap will have the recordcomponentupdater plugin installed,
		// we hack the 'update' and 'updateRecord' functions to this widget
		// to ensure the widget can update itself.
		this.wrap.update = this.update.createDelegate(this);
		this.wrap.updateRecord = this.updateRecord.createDelegate(this);

		Ext.applyIf(config, {
			name: 'quickitem',
			layout: 'fit',
			items: [this.wrap]
		});

		Zarafa.core.ui.widget.AbstractQuickItemWidget.superclass.constructor.call(this, config);
	},

	/**
	 * Initialize the widget, this will add the 'plus' button to the {@link #tools}
	 * which can open the record in a new content panel.
	 * @protected
	 */
	initWidget: function()
	{
		Zarafa.core.ui.widget.AbstractQuickItemWidget.superclass.initWidget.apply(this, arguments);

		if (this.hasDialog) {
			this.tools.unshift({
				id: 'plus',
				qtip: _('Open in new tab'),
				handler: this.dialog,
				scope: this
			});
		}
	},

	/**
	 * Initialize the events
	 * @protected
	 */
	initEvents: function()
	{
		Zarafa.core.ui.widget.AbstractQuickItemWidget.superclass.initEvents.apply(this, arguments);

		this.mon(this, 'afterlayout', this.onAfterFirstLayout, this, { single: true });

		if (this.resetOnSave) {
			// Defer to ensure the all 'aftersaverecord' event handlers have completed
			// before we reset the content of the widget.
			this.mon(this.wrap, 'aftersaverecord', this.reset, this, { buffer: 1 });
		}
		if (this.resetOnSend) {
			// Defer to ensure the all 'aftersendrecord' event handlers have completed
			// before we reset the content of the widget.
			this.mon(this.wrap, 'aftersendrecord', this.reset, this, { buffer: 1 });
		}
	},

	/**
	 * Abandon editing of the record inside the widget and continue editing by
	 * {@link Zarafa.core.data.UIFactory#openCreateRecord opening it in a new dialog}.
	 * Afterwards it will {@link #reset} this widget.
	 * @private
	 */
	dialog: function()
	{
		this.wrap.inputAutoFocusPlugin.beginFocusEl.focus();
		this.wrap.saveRecord(false);
		Zarafa.core.data.UIFactory.openCreateRecord(this.record);

		// The record is now owned by the dialog, so clear our reference.
		this.record = null;
		this.reset();
	},

	/**
	 * This will {@link #createRecord create a new} {@link #record} and will
	 * {@link Zarafa.core.ui.MessageContentPanel#setRecord set it} to the {@link #wrap}.
	 * protected
	 */
	reset: function()
	{
		var store = container.getShadowStore();

		// Remove the old record from the Shadow Store
		if (this.record) {
			store.remove(this.record, true);
		}

		// Create a new record, put it into the shadowStore
		this.record = this.createRecord();
		store.add(this.record);

		// Load the record
		this.wrap.setRecord(this.record);
	},

	/**
	 * Create a new record which must be edited by this widget.
	 * Subclasses must implement this function.
	 * @return {Ext.data.Record} record The record to load into the {@link #wrap}
	 * @protected
	 */
	createRecord: Ext.emptyFn,

	/**
	 * Updates the widget by loading data from the record into the {@link #wrap}.
	 * Subclasses must implement this function.
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record update the panel with.
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 * @protected
	 */
	update: Ext.emptyFn,

	/**
	 * Updates the widget by loading data from the record into the {@link #wrap}.
	 * Subclasses must implement this function.
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record to update
	 * @protected
	 */
	updateRecord: Ext.emptyFn,

	/**
	 * Event handler which is fired for the first {@link #afterlayout} event.
	 * This will check if the {@link Zarafa.core.Container#getHierarchyStore hierarchy}
	 * has been loaded already, or if we need to wait a bit more. When the hierarchy is
	 * loaded the widget will be {@link #reset}.
	 * @private
	 */
	onAfterFirstLayout: function()
	{
		var hierarchy = container.getHierarchyStore();
		if (!hierarchy.getDefaultStore()) {
			this.mon(hierarchy, 'load', this.reset, this, { single: true });
		} else {
			this.reset();
		}
	},

	/**
	 * Called when the widget is being destroyed. If a {@link #record} is still
	 * active, this will {@link Zarafa.core.data.ShadowStore#remove remove} the
	 * {@link #record} from the {@link Zarafa.core.data.ShadowStore ShadowStore}.
	 * @protected
	 */
	onDestroy: function()
	{
		Zarafa.core.ui.widget.AbstractQuickItemWidget.superclass.onDestroy.apply(this, arguments);

		if (this.record) {
			container.getShadowStore().remove(this.record, true);
		}
	},

	/**
	 * Event handler which is triggered when one of the Input fields
	 * has been changed by the user. It will validate the new value,
	 * and if correct, will apply it to the {@link Zarafa.core.data.IPMRecord record}.
	 * @param {Object} field The field updated field
	 * @param {Object} value The value of the field updated
	 * @private
	 */
	onChange: function(field, value)
	{
		this.wrap.record.set(field.name, value);
	},

	/**
	 * Event handler which is triggered when one of the Input fields
	 * has been changed by the user. It will validate the new value,
	 * and if correct, will apply it to the {@link Zarafa.core.data.IPMRecord record}.
	 * @param {Ext.form.Field} field The {@link Ext.form.Field field} which was changed.
	 * @param {Mixed} newValue The new value
	 * @param {Mixed} oldValue The old value
	 * @private
	 */
	onBodyChange: function(field, newValue, oldValue)
	{
		this.wrap.record.beginEdit();
		if (field.isXType && field.isXType('zarafa.htmleditor')) {
			this.wrap.record.set('isHTML', true);
		} else {
			this.wrap.record.set('isHTML', false);
		}
		this.wrap.record.set(field.name, newValue);
		this.wrap.record.endEdit();
	},

	/**
	 * Event handler which is fired when the user pressed the 'Save' button.
	 * This will call {@link Zarafa.core.ui.MessageContentPanel#saveRecord} to start
	 * sending the mail.
	 * @private
	 */
	onSave: function()
	{
		this.wrap.saveRecord();
	},

	/**
	 * Event handler which is fired when the user pressed the 'Disacrd' button.
	 * This will call {@link #reset} to clear the contents.
	 * @private
	 */
	onDiscard: function()
	{
		this.reset();
	}
});
Ext.namespace('Zarafa.core.ui.widget');

/**
 * @class Zarafa.core.ui.widget.WidgetContentPanel
 * @extends Zarafa.core.ui.ContentPanel
 *
 * ContentPanel used by the user to select which {@link Zarafa.core.ui.widget.Widget widget}
 * must be added to the {@link #widgetPanel panel}.
 */
Zarafa.core.ui.widget.WidgetContentPanel = Ext.extend(Zarafa.core.ui.ContentPanel, {
	/**
	 * @cfg {Zarafa.core.ui.widget.WidgetPanel} widgetPanel The widget panel to which
	 * the selected widget will be added.
	 */
	widgetPanel: undefined,

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

		var template = new Ext.XTemplate(
			'<tpl for=".">',
				'<div class="k-widget-item {iconCls}" id="{name}">',
					'<div class="thumb-wrap">',
						'<img src="{icon}" alt="">',
					'</div>',
					'<span>{display_name}</span>',
				'</div>',
			'</tpl>',
			'<div class="x-clear"></div>'
		);

		var data = [];
		var widgets = container.getWidgetsMetaData();
		Ext.each(widgets, function(widget) {
			data.push({ name: widget.getName(), display_name: widget.getDisplayName(), icon: widget.getIconPath(), iconCls: widget.getIconCls() });
		}, this);

		var store = {
			xtype: 'jsonstore',
			fields: [ 'name', 'display_name', 'icon', 'iconCls' ],
			data: data,
			sortInfo: {
				field: 'display_name',
				direction: 'ASC'
			}
		};

		Ext.applyIf(config, {
			layout: 'fit',
			border: false,
			width: 550,
			height: 280,
			modal: true,
			title: _('Add widgets'),
			items: [{
				xtype: 'dataview',
				store: store,
				tpl: template,
				autoScroll: true,
				singleSelect: true,
				multiSelect: false,
				selectedClass: 'k-widgets-selectedwidget',
				overClass: 'k-widgets-hoverwidget',
				itemSelector: 'div.k-widget-item',
				deferEmptyText: false,
				emptyText: _('No widgets installed.'),
				listeners: {
					'dblclick': this.onWidgetDblClick,
					scope: this
				}
			}]
		});

		Zarafa.core.ui.widget.WidgetContentPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Event handler which is fired when the user double clicks on a widget
	 * @param {Ext.DataView} dataview The view which fired the event
	 * @param {Number} index The selected index
	 * @param {HTMLElement} node The node which was double clicked
	 * @param {Ext.EventObject} event The event object
	 * @private
	 */
	onWidgetDblClick: function(dataview, index, node, event)
	{
		if (this.widgetPanel) {
			this.widgetPanel.createWidget(dataview.getRecord(node).get('name'));
		}
	}
});

Ext.reg('zarafa.widgetcontentpanel', Zarafa.core.ui.widget.WidgetContentPanel);
Ext.namespace('Zarafa.core.ui.widget');

/**
 * @class Zarafa.core.ui.widget.WidgetPanel
 * @extends Zarafa.core.ui.MainViewSidebar
 * @xtype zarafa.widgetpanel
 *
 * The main panel which contains {@link Zarafa.core.ui.widget.Widget widgets}.
 */
Zarafa.core.ui.widget.WidgetPanel = Ext.extend(Zarafa.core.ui.MainViewSidebar, {
	/**
	 * @cfg {String} settingsPath The settings base in which all settings
	 * for this panel will be saved.
	 */
	settingsPath: '',

	/**
	 * @cfg {Array} showDefaultWidgets list of widgets which should be shown at first.
	 */
	showDefaultWidgets: undefined,

	/**
	 * @cfg {Number} numColumns The number of columns which should be used
	 * to divide the widgets over the panel.
	 */
	numColumns: 3,

	/**
	 * The portal in which the widgets are located
	 * @property
	 * @type Ext.ux.Portal
	 */
	portal: undefined,

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

		var columns = [];
		var numColumns = config.numColumns || this.numColumns;
		for (var i = 0; i < numColumns; i++) {
			columns.push(new Ext.ux.PortalColumn({
				columnWidth: 1 / numColumns,
				style:'padding:10px 10px 10px 10px'
			}));
		}

		var tools = [{
			id: 'plus',
			qtip: _('Add new widget'),
			scope: this,
			handler: function() {
				Zarafa.common.Actions.openWidgetsContent({
					widgetPanel: this,
					manager: Ext.WindowMgr
				});
			}
		}];

		this.portal = new Ext.ux.Portal({
			items: columns,
			border: false,
			dropConfig: {
				ddGroup: 'dd.widget'
			}
		});

		Ext.applyIf(config, {
			cls: 'zarafa-widgetpanel',
			headerCfg: { cls: 'zarafa-main-header x-panel-header' },
			layout: 'fit',
			tools: tools,
			animCollapse: false,
			items: [ this.portal ],
			collapseQuickTip: _('Collapse widget panel'),
			expandQuickTip: _('Expand widget panel'),
			listeners: {
				afterrender: function() {
					this.getEl().set({ 'role': 'complementary', 'aria-label': _('Widgets') });
				}
			}
		});

		Zarafa.core.ui.widget.WidgetPanel.superclass.constructor.call(this, config);

		this.loadWidgets();

		this.mon(this.portal, {
			'drop': this.onDrop,
			scope:	this
		});
	},

	/**
	 * Event handler which is fired when a {@link Zarafa.core.ui.widget.Widget widget} is
	 * being dropped on this panel. This will {@link #removeGuid unregister} the widget
	 * from the previous {@link Zarafa.core.ui.widget.WidgetPanel owner} and
	 * {@link #addGuid} it to this panel.
	 * @param {Ext.EventObject} event The event for this event
	 * @private
	 */
	onDrop: function(event)
	{
		// Check if the parent has been changed
		var widget = event.panel;
		if (widget.widgetPanel != this) {
			widget.widgetPanel.removeGuid(widget.guid);
			this.addGuid(widget.guid);
			widget.widgetPanel = this;
		}

		// HACK!
		// There seems to be an issue that when dropping
		// the item that the Drag/DropZone is not hiding the
		// proxy which results in the widget.getVisibilityEl()
		// to remain hidden.
		event.source.proxy.hide();
		widget.doLayout(true);

		// Save column index if changed.
		if (widget.columnIndex != event.columnIndex) {
			widget.set('columnIndex', event.columnIndex);
		}
	},

	/**
	 * Load all widgets from the settings and them to the correct column.
	 * @private
	 */
	loadWidgets: function()
	{
		var settings = container.getSettingsModel();
		var guids = settings.get(this.settingsPath + '/guids');

		var verifiedGuids = [];

		if(!Ext.isDefined(guids)) {
			/*
			 * For the first time when webapp reload for the user
			 * settingsPath/guids is undefined, and from then on
			 * it will be string either empty or guids pointing widgets.
			 * Here we are setting two widgets for the first load of webapp
			 * so that it doesn't show empty screen for today view on first load.
			 */
			if(Array.isArray(this.showDefaultWidgets)) {
				for (var i = 0; i < this.showDefaultWidgets.length; i++) {
					this.createWidget(this.showDefaultWidgets[i], i % 3);
				}
			}
		} else if (!Ext.isEmpty(guids)) {
			// Go over all guids
			Ext.each(guids.split(/\W+/), function(guid) {
				// If the guid is empty, there is no point in continuing.
				if (Ext.isEmpty(guid)) {
					return;
				}

				var widgetName = settings.get(Zarafa.core.ui.widget.Widget.settingsPath(guid, '__name'));
				var widgetMetaData = container.getWidgetMetaDataByName(widgetName);

				if (widgetMetaData) {
					// Widget has been found, allocate it
					var widget = widgetMetaData.getInstance({
						widgetPanel: this,
						guid: guid
					});

					// Obtain the column index from settings
					var columnIndex = (widget.get('columnIndex') || 0) % this.numColumns;

					this.portal.get(columnIndex).add(widget);

					verifiedGuids.push(guid);
				} else {
					// Widget is invalid or not installed, remove its settings
					settings.remove(Zarafa.core.ui.widget.Widget.settingsPath(guid, ''));
				}
			}, this);

			settings.set(this.settingsPath + '/guids', verifiedGuids.join(' '));
		}
	},

	/**
	 * Register a guid so it will be reopened automatically during next reload
	 * @param {String} guid The unique ID of the widget which has to be registered
	 * @private
	 */
	addGuid: function(guid)
	{
		var settings = container.getSettingsModel();

		// Add the guid to the list of current widget instances.
		var instanceList = settings.get(this.settingsPath + '/guids') || '';

		instanceList += (instanceList === '' ? '' : ' ') + guid;

		settings.set(this.settingsPath + '/guids', instanceList);
	},

	/**
	 * Unregister a previously {@link #addGuid registered} guid so the widget is no longer reopened
	 * @param {String} guid The unique ID of the widget which has to be unregistered
	 * @private
	 */
	removeGuid: function(guid)
	{
		var settings = container.getSettingsModel();

		// Add the guid to the list of current widget instances.
		var instanceList = settings.get(this.settingsPath + '/guids');

		instanceList = instanceList.replace(guid,'');
		instanceList = instanceList.replace(/\W+/,' ');

		settings.set(this.settingsPath + '/guids', instanceList);
	},

	/**
	 * Create a new widget which can be added to this panel.
	 * @param {String} name The name of the widget to add
	 * @param {Number} The preferred column in which the widget should be placed
	 */
	createWidget: function(name, column)
	{
		var widgetMetaData = container.getWidgetMetaDataByName(name);
		if (!Ext.isDefined(column)) {
			column = 0;
		}

		if (widgetMetaData) {
			var guid = Zarafa.generateId(32);

			this.addGuid(guid);

			var widget = widgetMetaData.getInstance({
				widgetPanel: this,
				guid: guid
			});

			// Record the type of the newly created widget.
			widget.set('__name', name);

			this.portal.get(column).add(widget);
			this.doLayout();
		}
	},

	/**
	 * Destroy a widget and {@link #removeGuid unregister the guid}.
	 * @param {Zarafa.core.ui.widget.Widget} widget The widget to destroy
	 * @private
	 */
	destroyWidget: function(widget)
	{
		// Remove guid from the parent widget panel.
		this.removeGuid(widget.guid);

		// Remove settings.
		container.getSettingsModel().remove(Zarafa.core.ui.widget.Widget.settingsPath(widget.guid, ''));

		widget.ownerCt.remove(widget, true);
	}
});

Ext.reg('zarafa.widgetpanel', Zarafa.core.ui.widget.WidgetPanel);
Ext.namespace('Zarafa.addressbook.dialogs');

/**
 * @class Zarafa.addressbook.dialogs.ABMultiUserSelectionContentPanel
 * @extends Zarafa.core.ui.ContentPanel
 * @xtype zarafa.abmultiuserselectioncontentpanel
 */
Zarafa.addressbook.dialogs.ABMultiUserSelectionContentPanel = Ext.extend(Zarafa.core.ui.ContentPanel, {
	/**
	 * @cfg {Function} callback the callback function to return to after selecting user from AdressBook.
	 */
	callback: undefined,

	/**
	 * @cfg {Function} convert the convert function which converts an
	 * {@link Zarafa.addressbook.AddressBookRecord user} to the correct type
	 * which can be placed in the {@link #store}.
	 * This function receives the selected AddressBookRecord as first argument,
	 * and optionally passes the {@link Ext.Component} which was generated from the
	 * {@link #selectionCfg} which was used to select the recipient as second argument.
	 */
	convert: undefined,

	/**
	 * @cfg {Object} scope The scope in which the {@link #callback} will be called
	 */
	scope: undefined,

	/**
	 * @cfg {Ext.data.Store} store The store in which all records should be placed.
	 */
	store: undefined,

	/**
	 * @cfg (Boolean) Set to true to hide contacts folders in the address book
	 * hierarchy dropdown.
	 */
	hideContactsFolders: false,

	/**
	 * @cfg {Object} listRestriction The default restriction which
	 * must be send to the server side when obtaining a fresh list
	 * from the server. This can be used to restrict the visibility
	 * of users, groups, companies etc.
	 */
	listRestriction: undefined,

	/**
	 * @cfg {Array} selectionCfg Array of {@link Zarafa.common.ui.BoxField} configuration
	 * objects which are created below the User list. These will show which users
	 * the user has selected.
	 */
	selectionCfg: undefined,

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

		config = Ext.applyIf(config, {
			xtype: 'zarafa.abmultiuserselectioncontentpanel',
			layout: 'fit',
			title: _('Address Book'),
			stateful: Zarafa.core.BrowserWindowMgr.isMainWindowActive(),
			items: [{
				xtype: 'zarafa.abmultiuserselectionpanel',
				hideContactsFolders: config.hideContactsFolders,
				listRestriction: config.listRestriction,
				selectionCfg: config.selectionCfg,
				callback: config.callback,
				convert: config.convert,
				scope: config.scope,
				store: config.store
			}]
		});

		// Call parent constructor
		Zarafa.addressbook.dialogs.ABMultiUserSelectionContentPanel.superclass.constructor.call(this, config);
	}
});


Ext.reg('zarafa.abmultiuserselectioncontentpanel', Zarafa.addressbook.dialogs.ABMultiUserSelectionContentPanel);
Ext.namespace('Zarafa.addressbook.ui');

/**
 * @class Zarafa.addressbook.ui.ABUserSelectionPanel
 * @extends Zarafa.addressbook.ui.AddressBookMainPanel
 * @xtype zarafa.abuserselectionpanel
 */
Zarafa.addressbook.ui.ABUserSelectionPanel = Ext.extend(Zarafa.addressbook.ui.AddressBookMainPanel, {
	/**
	 * @cfg {Function} callback the callback function to return to after selecting user from AdressBook.
	 */
	callback: Ext.emptyFn,

	/**
	 * @cfg {Boolean} singleSelect false to allow multiple selections(defaults to true allowing selection of only one row at a time)
	 */
	singleSelect: true,

	/**
	 * @cfg {Function} scope the scope in which the {@link #callback} will be called
	 */
	scope: undefined,

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

		Ext.applyIf(config, {
			xtype: 'zarafa.abuserselectionpanel',
			cls: 'k-abuserselectionpanel',
			buttons: [{
				text: _('Ok'),
				handler: this.onSubmit,
				scope: this
			},{
				text: _('Cancel'),
				handler: this.onCancel,
				scope: this
			}]
		});

		Zarafa.addressbook.ui.ABUserSelectionPanel.superclass.constructor.call(this, config);

		this.on('afterlayout', this.onAfterLayout, this, { single: true });
	},

	/**
	 * Calls callback function
	 * @return {Boolean} true, if callback was successful
	 */
	doCallBack: function()
	{
		var grid = this.getGridPanel();

		if (grid.getSelectionModel().hasSelection()) {
			var records = grid.getSelectionModel().getSelections();

			if(this.singleSelect) {
				this.callback.call(this.scope || this, records[0]);
			} else {
				this.callback.call(this.scope || this, records);
			}
			return true;
		}
	},

	/**
	 * Event handler which is triggered when the layout
	 * for the {@link Ext.Panel panel} has been completed.
	 * @private
	 */
	onAfterLayout: function()
	{
		this.mon(this.getGridPanel(), 'rowdblclick', this.onAddressBookRowDblClick, this);
		this.mon(this.getGridPanel(), 'rowcontextmenu', this.onAddressBookRowContextMenu, this);
	},

	/**
	 * Event handler which is triggered when the user doubleclicks on a
	 * row within the {@link Ext.grid.GridPanel gridpanel}. This will add
	 * the selected user or group to the {@link Zarafa.core.ui.RecipientField recipientfield}
	 * @private
	 */
	onAddressBookRowDblClick: function()
	{
		if (this.doCallBack() === true) {
			this.dialog.close();
		}
	},

	/**
	 * Event handler which is triggered when the user rightclicks
	 * on a row in the {@link Ext.grid.GridPanel gridpanel}. This will
	 * open a {@link Zarafa.core.ui.menu.ConditionalMenu contextmenu}
	 * for the selected row.
	 * @param {Ext.grid.GridPanel} grid The grid on which the user clicked
	 * @param {Number} rowIndex the row on which was doubleclicked
	 * @param {Ext.EventObject} event The event information
	 * @private
	 */
	onAddressBookRowContextMenu: function(grid, rowIndex, event)
	{
		var sm = grid.getSelectionModel();

		if (sm.hasSelection()) {
			// Some records were selected...
			if (!sm.isSelected(rowIndex)) {
				// But none of them was the record on which the
				// context menu was invoked. Reset selection.
				sm.clearSelections();
				sm.selectRow(rowIndex);
			}
		} else {
			// No records were selected,
			// select row on which context menu was invoked
			sm.selectRow(rowIndex);
		}

		Zarafa.core.data.UIFactory.openDefaultContextMenu(sm.getSelections(), {
			position: event.getXY(),
			dialog: this.dialog
		});
	},

	/**
	 * Event handler which is called when the user presses the "Ok" button.
	 * Function will store changed data in record and close the dialog
	 * @private
	 */
	onSubmit: function()
	{
		if (this.doCallBack() === true) {
			this.dialog.close();
		} else {
			Ext.MessageBox.show({
				title: _('No user selected'),
				msg: _('You must select a user.'),
				buttons: Ext.MessageBox.OK
			});
		}
	},

	/**
	 * Closes {@link Zarafa.core.ui.CreateFolderContentPanel CreateFolder} content panel
	 * @private
	 */
	onCancel: function()
	{
		this.dialog.close();
	}
});

Ext.reg('zarafa.abuserselectionpanel', Zarafa.addressbook.ui.ABUserSelectionPanel);
Ext.namespace('Zarafa.addressbook.ui');

/**
 * @class Zarafa.addressbook.ui.AddressBookContextMenu
 * @extends Zarafa.core.ui.menu.ConditionalMenu
 * @xtype zarafa.addressbookcontextmenu
 *
 * The context menu for the {@link Zarafa.addressbook.ui.AddressBookPanel AddressBookPanel}.
 */
Zarafa.addressbook.ui.AddressBookContextMenu = Ext.extend(Zarafa.core.ui.menu.ConditionalMenu, {
	// Insertion points for this class
	/**
	 * @insert context.addressbook.contextmenu.actions
	 * Insertion point for adding extra context menu items in this context menu.
	 * @param {Zarafa.addressbook.ui.AddressBookContextMenu} contextmeny This contextmenu
	 */

	/**
	 * @cfg {Boolean} enableSelect Enable the "Select" menu item. This requires a special handler
	 * to be provided which is called when a particular recipient or recipients have been selected.
	 */
	enableSelect: false,

	/**
	 * @cfg {Function} selectHandler Only used when {@link #enableSelect} is true. This function
	 * is called when the "select" button has been pressed. This function will be called with
	 * the selected {@link Ext.data.Record records} as argument.
	 */
	selectHandler: undefined,

	/**
	 * @cfg {Object} selectScope The scope in which {@link #selectHandler} will be called.
	 */
	selectScope: undefined,

	/**
	 * @cfg {Zarafa.core.ui.ContentPanel} contentpanel The content panel from where the contextmenu is requested
	 */
	dialog: undefined,

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

		Ext.applyIf(config, {
			items: [{
				xtype: 'zarafa.conditionalitem',
				text: _('Select'),
				// iconCls: FIXME
				beforeShow: function(item, records) { item.setDisabled(!this.enableSelect); },
				handler: this.onSelect,
				scope: this
			},{
				xtype: 'zarafa.conditionalitem',
				text: _('Show Details'),
				iconCls: 'icon_contact',
				singleSelectOnly: true,
				handler: this.onOpenDetails,
				scope: this
			},{
				xtype: 'menuseparator'
			},
				container.populateInsertionPoint('context.addressbook.contextmenu.actions', this),
			{
				xtype: 'menuseparator'
			},{
				xtype: 'zarafa.conditionalitem',
				text: _('Copy contact'),
				iconCls: 'icon_copy',
				hidden: true,
				beforeShow: this.onBeforeShowItem,
				singleSelectOnly: true,
				handler: this.onCopyContact,
				scope: this
			}]
		});

		Zarafa.addressbook.ui.AddressBookContextMenu.superclass.constructor.call(this, config);
	},

	/**
	 * Event handler which is called when the "Select" item has been selected. This
	 * will obtain the current {@link #records} and pass them to {@link #selectHandler}.
	 * @private
	 */
	onSelect: function()
	{
		if (Ext.isFunction(this.selectHandler)) {
			this.selectHandler.call(this.selectScope || this, this.records);
		}
	},

	/**
	 * Event handler which is called when the "Details" item has been selected. This
	 * will obtain the current {@link #records} and use them to open the
	 * {@link Zarafa.addressbook.Actions#openDetailsContent details dialog}.
	 * @private
	 */
	onOpenDetails: function()
	{
		Zarafa.addressbook.Actions.openDetailsContent(this.records);
	},

	/**
	 * Event handler triggers before the item shows. Disable the item "Copy contact" if selected contacts are not GAB contact.
	 *
	 * @param {Zarafa.core.ui.menu.ConditionalItem} item The item to enable/disable
	 * @param {Zarafa.core.data.IPMRecord[]} records The records which must be checked
	 * to see if the item must be enabled or disabled.
	 * @private
	 */
	onBeforeShowItem: function (item, records) {
		var isGABContact = this.selectedFolderType === 'gab';
		if (isGABContact && records.length > 1) {
			item.setText(_("Copy contacts"));
			// When multiple items are selected from the addressbook
			// check at least one GAB contact is selected.
			var hasGABContact = records.some(function(record){
				return record.get('object_type') === Zarafa.core.mapi.ObjectType.MAPI_MAILUSER;
			});

			item.setVisible(isGABContact && hasGABContact);
			return;
		}

		var hasDistlist = records.some(function(record){
			return record.get('object_type') === Zarafa.core.mapi.ObjectType.MAPI_DISTLIST;
		});
		item.setVisible(isGABContact && !hasDistlist);
	},

	/**
	 * Handler triggered when user click on "Copy contact" context menu item.
	 * It will open the {@link Zarafa.hierarchy.dialogs.FolderSelectionContentPanel FolderSelectionContentPanel} to select
	 * the folder to copy GAB contact.
	 */
	onCopyContact: function ()
	{
		Zarafa.hierarchy.Actions.openFolderSelectionContent({
			folder: container.getHierarchyStore().getDefaultFolder('contact'),
			hideTodoList: true,
			hideSearchFolders: true,
			IPMFilter: 'IPF.Contact',
			permissionFilter: Zarafa.core.mapi.Rights.RIGHTS_CREATE,
			callback: this.copyContactCallback,
			scope: this,
			modal: true
		});
	},

	/**
	 * Callback function called when ok button pressed of {@link Zarafa.hierarchy.dialogs.FolderSelectionPanel FolderSelectionPanel}.
	 * It will create the contact record and set necessary properties from the GAB contact.
	 *
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} The selected folder
	 */
	copyContactCallback: function (folder)
	{
		var model = container.getContextByName("contact").getModel();
		var store = container.getShadowStore();
		for (var record of this.records) {
			if (record.get('object_type') === Zarafa.core.mapi.ObjectType.MAPI_DISTLIST) {
				continue;
			}

			var contactRecord = model.createRecord(folder);
			contactRecord.set("fileas", record.get("full_name"));
			contactRecord.set("email_address_1", record.get("smtp_address"));
			contactRecord.set("address_type", "SMTP");
			contactRecord.set("email_address_type_1", "SMTP");
			contactRecord.set("given_name", record.get("given_name"));
			contactRecord.set("surname", record.get("surname"));
			contactRecord.set("display_name", record.get("display_name"));
			contactRecord.set("subject", record.get("display_name"));
			contactRecord.set("title", record.get("title"));
			contactRecord.set("department_name", record.get("department_name"));
			contactRecord.set("office_location", record.get("office_location"));
			contactRecord.set("primary_fax_number", record.get("primary_fax_number"));
			contactRecord.set("home_telephone_number", record.get("home_telephone_number"));
			contactRecord.set("pager_telephone_number", record.get("pager_telephone_number"));

			// Required to set the message action to update some of the props
			// from server side because when unopened address book record has
			// limited information so copied contact does not have all the information.
			contactRecord.addMessageAction("action_type", "copyToContact");
			contactRecord.addMessageAction("source_entryid", record.get("entryid"));
			contactRecord.updateAddressbookProps();

			store.add(contactRecord);
		}
		store.save();
	}
});

Ext.reg('zarafa.addressbookcontextmenu', Zarafa.addressbook.ui.AddressBookContextMenu);
Ext.namespace('Zarafa.addressbook.ui');

/**
 * @class Zarafa.addressbook.ui.AddressBookGrid
 * @extends Zarafa.common.ui.grid.GridPanel
 * @xtype zarafa.addressbookgrid
 *
 * Panel that shows the contents of the users/contacts in addressBook.
 */
Zarafa.addressbook.ui.AddressBookGrid = Ext.extend(Zarafa.common.ui.grid.GridPanel, {
	/**
	 * @cfg {Boolean} singleSelect true to allow selection of only one row at a time (defaults to false allowing multiple selections)
	 */
	singleSelect: false,

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

		var viewConfig = config.viewConfig || {};
		Ext.applyIf(viewConfig, {
			// render rows as they come into viewable area.
			scrollDelay: false,
			rowHeight: 31,
			borderHeight: 1,
			// Overwriting the header template to add space for an inline info message
			headerTpl: new Ext.Template(
				'<table border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',
					'<thead>',
						'<tr class="x-grid3-hd-row">{cells}</tr>',
					'</thead>',
				'</table>',
				'<div class="k-grid-info"></div>'
			)
		});

		Ext.applyIf(config, {
			ariaLabel: _('Address book'),
			autoExpandColumn: 'full_name',
			autoExpandMin: 100,
			loadMask: true,
			stateful: true,
			statefulRelativeDimensions: false,
			sm: this.createSelectionModel(config),
			cm: new Zarafa.addressbook.ui.GABColumnModel(),
			view: new Ext.ux.grid.BufferView(viewConfig)
		});

		Zarafa.addressbook.ui.AddressBookGrid.superclass.constructor.call(this, config);

		this.mon(this.getStore(), 'beforeload', this.onStoreBeforeLoad, this);
		this.mon(this.getStore(), 'load', this.onStoreLoad, this);

		this.mon(this, 'beforerender', function(){
			// Register the store to the PresenceManager to have it fetch the presence
			// status for all entries.
			Zarafa.core.PresenceManager.registerStore(this.getStore());
		}, this);
		this.mon(this, 'beforedestroy', function(){
			Zarafa.core.PresenceManager.unregisterStore(this.getStore());
		}, this);
	},

	/**
	 * Event handler for the render event of the grid panel. Will calculate the correct border
	 * size and update the value in the view. This is necessary because Chrome sometimes
	 * renders with incorrect width, i.e. we style it with 1px and chrome renders 0.667px The
	 * bufferedView however needs the correct size to calculate which record to render.
	 */
	onRender: function()
	{
		Zarafa.addressbook.ui.AddressBookGrid.superclass.onRender.apply(this, arguments);

		var headerEl = this.view.el.down('div').down('div');
		var borderHeight = window.getComputedStyle(headerEl.dom)['border-bottom-width'];
		this.view.borderHeight = parseFloat(borderHeight);
	},

	/**
	 * Event handler which is called before the {@link #store} will request a load.
	 * Will hide the inline grid error.
	 * @param {Ext.data.Store} store The store which was loaded
	 * @param {Object} options The options which were used to load the data
	 * @private
	 */
	onStoreBeforeLoad: function(store, options)
	{
		if (!this.view.innerHd) {
			return;
		}
		var errorEl = this.view.innerHd.querySelector('.k-grid-info');
		if (errorEl) {
			Ext.fly(errorEl).removeClass('k-show');
		}
		this.view.refresh();
	},

	/**
	 * Event handler which is called when the {@link #store} has been loaded. This will
	 * check what kind of folder has been loaded, and either loads the {@link Zarafa.addressbook.ui.GABColumnModel}
	 * or {@link Zarafa.addressbook.ui.GABPersonalColumnModel} column model.
	 * Will also check if there is an error (currently only the listexceederror is implemented)
	 * while loading and will show an error 'inline' in the grid.
	 * @param {Ext.data.Store} store The store which was loaded
	 * @param {Ext.data.Record[]} records The records which were loaded from the store
	 * @param {Object} options The options which were used to load the data
	 * @private
	 */
	onStoreLoad: function(store, records, options)
	{
		var columnModel = new Zarafa.addressbook.ui.GABColumnModel();

		// reconfigure grid with new column model
		if(this.colModel.name !== columnModel.name) {
			this.reconfigure(store, columnModel);
		}

		if ( store.error && store.error.code && store.error.code === 'listexceederror' ) {
			var errorEl = this.view.innerHd.querySelector('.k-grid-info');
			errorEl.innerHTML = String.format(_('Showing only the first {0} names. Use search to find more specific results.'), store.error.max_gab_users);
			Ext.fly(errorEl).addClass('k-show');
			this.view.refresh();
		}
	},

	/**
	 * creates and returns a selection model object, used in {@link Ext.grid.GridPanel.selModel selModel} config
	 * @param {Object} config config options for {@link Ext.grid.GridPanel.selModel selModel}
	 * @return {Zarafa.common.ui.grid.RowSelectionModel} selection model object
	 * @private
	 */
	createSelectionModel: function(config)
	{
		return new Zarafa.common.ui.grid.RowSelectionModel({
			singleSelect: config.singleSelect
		});
	},

	/**
	 * Returns the currently selected {@link Ext.data.Record records}
	 * from the {@link Ext.grid.GridPanel GridPanel}
	 * @return {Ext.data.Record[]} The selected records
	 */
	getSelectedItems: function()
	{
		return this.getSelectionModel().getSelections();
	},

	/**
	 * Obtain the path in which the {@link #getState state} must be saved.
	 * This option is only used when the {@link Zarafa.core.data.SettingsStateProvider SettingsStateProvider} is
	 * used in the {@link Ext.state.Manager}. This returns {@link #statefulName} if provided, or else generates
	 * a custom name.
	 * We are managing two different state settings for addressbook, one for addressbook containers and another for
	 * contact containers.
	 * @return {String} The unique name for this component by which the {@link #getState state} must be saved.
	 */
	getStateName: function()
	{
		var options = this.store.lastOptions;
		var name = 'globaladdressbook';

		if (options && options.params && options.params.entryid) {
			var entryid = options.params.entryid;

			// check for contacts container, all other containers would be assumed as global addressbook
			if(Zarafa.core.EntryId.hasContactProviderGUID(entryid)) {
				name = 'contacts';
			}
		}

		return 'gab/' + name + '/list';
	},

	/**
	 * When {@link #stateful} the State object which should be saved into the
	 * {@link Ext.state.Manager}.
	 * @return {Object} The state object
	 * @protected
	 */
	getState: function()
	{
		var state = Zarafa.addressbook.ui.AddressBookGrid.superclass.getState.call(this);

		// Superclass wrapped it, but we need to unwrap it again
		// because we store the settings slightly differently.
		var unwrap = { sort: state.sort };
		delete state.sort;
		Ext.apply(unwrap, state[this.getColumnModel().name]);

		return unwrap;
	},

	/**
	 * Apply the given state to this object activating the properties which were previously
	 * saved in {@link Ext.state.Manager}.
	 * @param {Object} state The state object
	 * @protected
	 */
	applyState: function(state)
	{
		if (state) {
			var wrap = { sort: state.sort };
			delete state.sort;
			wrap[this.getColumnModel().name] = state;

			Zarafa.addressbook.ui.AddressBookGrid.superclass.applyState.call(this, wrap);
		}
 	}
});

Ext.reg('zarafa.addressbookgrid', Zarafa.addressbook.ui.AddressBookGrid);
Ext.namespace('Zarafa.addressbook.ui');

/**
 * @class Zarafa.addressbook.ui.AddressBookPanel
 * @extends Zarafa.addressbook.ui.AddressBookMainPanel
 * @xtype zarafa.addressbookpanel
 */
Zarafa.addressbook.ui.AddressBookPanel = Ext.extend(Zarafa.addressbook.ui.AddressBookMainPanel, {

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

		Ext.applyIf(config, {
			xtype: 'zarafa.addressbookpanel',
			cls: 'k-addressbookpanel'
		});

		// Call parent constructor
		Zarafa.addressbook.ui.AddressBookPanel.superclass.constructor.call(this, config);

		this.on('afterlayout', this.onAfterLayout, this, { single: true });
	},

	/**
	 * Event handler which is triggered when the layout
	 * for the {@link Ext.Panel panel} has been completed.
	 * @private
	 */
	onAfterLayout: function()
	{
		this.mon(this.getGridPanel(), 'rowdblclick', this.onAddressBookRowDblClick, this);
		this.mon(this.getGridPanel(), 'rowcontextmenu', this.onAddressBookRowContextMenu, this);
	},

	/**
	 * Event handler which is triggered when the user doubleclicks on a
	 * row within the {@link Ext.grid.GridPanel gridpanel}. This will open
	 * a {@link Zarafa.core.ui.ContentPanel contentpanel} containing the details of the user/group.
	 * @param {Ext.grid.GridPanel} grid The grid on which the user doubleclicked
	 * @param {Number} rowIndex the row on which was doubleclicked
	 * @param {Ext.EventObject} event The event information
	 * @private
	 */
	onAddressBookRowDblClick: function(grid, rowIndex, event)
	{
		var records = grid.getStore().getAt(rowIndex);
		Zarafa.addressbook.Actions.openDetailsContent(records);
	},

	/**
	 * Event handler which is triggered when the user rightclicks
	 * on a row in the {@link Ext.grid.GridPanel gridpanel}. This will
	 * open a {@link Zarafa.core.ui.menu.ConditionalMenu contextmenu}
	 * for the selected row.
	 * @param {Ext.grid.GridPanel} grid The grid on which the user clicked
	 * @param {Number} rowIndex the row on which was doubleclicked
	 * @param {Ext.EventObject} event The event information
	 * @private
	 */
	onAddressBookRowContextMenu: function(grid, rowIndex, event)
	{
		var sm = grid.getSelectionModel();

		if (sm.hasSelection()) {
			// Some records were selected...
			if (!sm.isSelected(rowIndex)) {
				// But none of them was the record on which the
				// context menu was invoked. Reset selection.
				sm.clearSelections();
				sm.selectRow(rowIndex);
			}
		} else {
			// No records were selected,
			// select row on which context menu was invoked
			sm.selectRow(rowIndex);
		}

		var selectedFolder = this.getSelectedFolderRecord();
		Zarafa.core.data.UIFactory.openDefaultContextMenu(sm.getSelections(), {
			position: event.getXY(),
			dialog: this.dialog,
			selectedFolderType: selectedFolder.get('type')
		});
	}
});

Ext.reg('zarafa.addressbookpanel', Zarafa.addressbook.ui.AddressBookPanel);
/*
 * #dependsFile client/zarafa/common/ui/grid/Renderers.js
 */
Ext.namespace('Zarafa.addressbook.ui');

/**
 * @class Zarafa.addressbook.ui.GABColumnModel
 * @extends Zarafa.common.ui.grid.ColumnModel
 *
 * Column model which should be used in the Global Addressbook for
 * normal containers.
 */
Zarafa.addressbook.ui.GABColumnModel = Ext.extend(Zarafa.common.ui.grid.ColumnModel, {

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

		Ext.applyIf(config, {
			name: 'globaladdressbook',
			columns: this.getColumns()
		});

		Zarafa.addressbook.ui.GABColumnModel.superclass.constructor.call(this, config);
	},

	/**
	 * Gets the columns for the GAB, including from the insertion point
	 * @returns {Array} An array of objects with column configurations
	 *
	 * @private
	 */
	getColumns: function()
	{
		var items = container.populateInsertionPoint('context.addressbook.gridpanel', this);
		var defaultItems = [{
			dataIndex: 'icon_index',
			headerCls: 'zarafa-icon-column',
			header: '',
			sortable: true,
			tooltip: _('Sort by: Icon'),
			width: 25,
			fixed: true,
			renderer: Zarafa.common.ui.grid.Renderers.icon
		},{
			dataIndex: 'full_name',
			// gridPanel.autoExpandColumn config will reference to this id
			id: 'full_name',
			header: _('Name'),
			sortable: true,
			tooltip: _('Sort by: Name'),
			renderer: Zarafa.common.ui.grid.Renderers.fullName
		},{
			dataIndex: 'display_name',
			header: _('Display Name'),
			sortable: true,
			tooltip: _('Sort by: Display Name'),
			renderer: Zarafa.common.ui.grid.Renderers.displayName,
			width: 250
		},{
			dataIndex: 'fileas',
			header: _('File as'),
			hidden: true,
			sortable: true,
			tooltip: _('Sort by: File As'),
			renderer: Ext.util.Format.htmlEncode,
			width: 250
		},{
			dataIndex: 'given_name',
			header: _('First Name'),
			sortable: true,
			hidden: true,
			tooltip: _('Sort by: First Name'),
			renderer: Ext.util.Format.htmlEncode,
			width: 150
		},{
			dataIndex: 'surname',
			header: _('Last Name'),
			sortable: true,
			hidden: true,
			tooltip: _('Sort by: Last Name'),
			renderer: Ext.util.Format.htmlEncode,
			width: 150
		},{
			dataIndex: 'smtp_address',
			header: _('Email Address'),
			sortable: true,
			tooltip: _('Sort by: Email Address'),
			renderer: Ext.util.Format.htmlEncode,
			width: 250
		},{
			dataIndex: 'title',
			header: _('Job Title'),
			sortable: true,
			hidden: true,
			tooltip: _('Sort by: Job Title'),
			renderer: Ext.util.Format.htmlEncode,
			width: 150
		},{
			dataIndex: 'company_name',
			header: _('Company Name'),
			sortable: true,
			tooltip: _('Sort by: Company Name'),
			hidden: true,
			renderer: Ext.util.Format.htmlEncode,
			width: 150
		},{
			dataIndex: 'department_name',
			header: _('Department'),
			sortable: true,
			hidden: true,
			tooltip: _('Sort by: Department'),
			renderer: Ext.util.Format.htmlEncode,
			width: 150
		},{
			dataIndex: 'office_telephone_number',
			header: _('Office Phone'),
			sortable: true,
			tooltip: _('Sort by: Office Phone'),
			renderer: Ext.util.Format.htmlEncode,
			width: 150
		},{
			dataIndex: 'mobile_telephone_number',
			header: _('Mobile'),
			sortable: true,
			hidden: true,
			tooltip: _('Sort by: Mobile'),
			renderer: Ext.util.Format.htmlEncode,
			width: 150
		},{
			dataIndex: 'home_telephone_number',
			header: _('Home Phone'),
			sortable: true,
			hidden: true,
			tooltip: _('Sort by: Home Phone'),
			renderer: Ext.util.Format.htmlEncode,
			width: 150
		},{
			dataIndex: 'pager_telephone_number',
			header: _('Pager'),
			sortable: true,
			hidden: true,
			tooltip: _('Sort by: Pager'),
			renderer: Ext.util.Format.htmlEncode,
			width: 150
		},{
			dataIndex: 'office_location',
			header: _('Location'),
			sortable: true,
			hidden: true,
			tooltip: _('Sort by: Location'),
			renderer: Ext.util.Format.htmlEncode,
			width: 150
		},{
			dataIndex: 'primary_fax_number',
			header: _('Fax'),
			sortable: true,
			hidden: true,
			tooltip: _('Sort by: Fax'),
			renderer: Ext.util.Format.htmlEncode,
			width: 150
		}];

		// FIXME: find an alternative way to add columns from insertion point
		if(Array.isArray(items))
		{
			var pos = 1;
			items.forEach(function(item) {
				if (Ext.isObject(items[0])) {
					defaultItems.splice(pos, 0, item);
					pos += 1;
				}
			});
		}

		return defaultItems;
	}
});
/*
 * #dependsFile client/zarafa/common/ui/grid/Renderers.js
 */
Ext.namespace('Zarafa.addressbook.ui');

/**
 * @class Zarafa.addressbook.ui.GABPersonalColumnModel
 * @extends Zarafa.common.ui.grid.ColumnModel
 *
 * Column model which should be used in the Global Addressbook for
 * personal folders.
 */
Zarafa.addressbook.ui.GABPersonalColumnModel = Ext.extend(Zarafa.common.ui.grid.ColumnModel, {

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

		Ext.applyIf(config, {
			name: 'personal_contacts',

			columns: [{
				dataIndex: 'icon_index',
				headerCls: 'zarafa-icon-column icon',
				header: '',
				sortable: true,
				tooltip: _('Sort by: Icon'),
				width: 25,
				fixed: true,
				renderer: Zarafa.common.ui.grid.Renderers.icon
			},{
				dataIndex: 'full_name',
				// gridPanel.autoExpandColumn config will reference to this id
				id: 'full_name',
				header: _('Name'),
				sortable: true,
				tooltip: _('Sort by: Name'),
				renderer: Zarafa.common.ui.grid.Renderers.fullName
			},{
				dataIndex: 'display_name',
				header: _('Display Name'),
				sortable: true,
				tooltip: _('Sort by: Display Name'),
				renderer: Ext.util.Format.htmlEncode,
				width: 250
			},{
				dataIndex: 'fileas',
				header: _('File as'),
				sortable: true,
				hidden: true,
				tooltip: _('Sort by: File As'),
				renderer: Ext.util.Format.htmlEncode,
				width: 250
			},{
				dataIndex: 'email_address',
				header: _('Email Address'),
				sortable: true,
				hidden: false,
				tooltip: _('Sort by: Email Address'),
				renderer: Ext.util.Format.htmlEncode,
				width: 400
			}]
		});

		Zarafa.addressbook.ui.GABPersonalColumnModel.superclass.constructor.call(this, config);
	}
});
Ext.namespace('Zarafa.advancesearch.data');

/**
 * @class Zarafa.advancesearch.data.SearchComboBoxFieldsFlags
 * @extends Zarafa.core.Enum
 *
 * Enum containing the different folder flag, whether it is current selected folder in
 * hierarchy, IPM_SUBTREE or folder added explicitly in search ComboBox.
 *
 * @singleton
 */
Zarafa.advancesearch.data.SearchComboBoxFieldsFlags = Zarafa.core.Enum.create({
	/**
	 * Indicate that folder is IPM_SUBTREE
	 *
	 * @property
	 * @type Number
	 */
	ALL_FOLDERS: 0,

	/**
	 * Indicate that folder is currently selected in hierarchy.
	 *
	 * @property
	 * @type Number
	 */
	CURRENT_SELECTED_FOLDER: 1,

	/**
	 * Indicate that folder is added in search combo box using 'Other…' option in search combo box.
	 *
	 * @property
	 * @type Number
	 */
	IMPORTED_FOLDER: 2
});
Ext.namespace('Zarafa.advancesearch.dialogs');

/**
 * @class Zarafa.advancesearch.dialogs.SearchCategoriesContentPanel
 * @extends Zarafa.core.ui.ContentPanel
 * @xtype zarafa.searchcategoriescontentpanel
 *
 * ContentPanel for users to add/remove the categories into category filter.
 */
Zarafa.advancesearch.dialogs.SearchCategoriesContentPanel = Ext.extend(Zarafa.core.ui.ContentPanel, {

	/**
	 * @cfg {Zarafa.advancesearch.data.SearchCategoriesStore} store The store which contain categories,
	 * That added in category filter.
	 */
	searchCategoryStore: undefined,

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

		// Create dummy ext data record with 'categories' field with category string.
		if (Ext.isEmpty(config.record) && Ext.isDefined(config.searchCategoryStore)) {
			var categories = config.searchCategoryStore.getCategories();
			config.record = new Ext.data.Record({
				'categories': categories.join("; ")
			});
		}

		config = Ext.applyIf(config, {
			xtype: 'zarafa.searchcategoriescontentpanel',
			layout: 'fit',
			title: _('Select Category'),
			width: 400,
			height: 400,
			items: [{
				xtype: 'zarafa.categoriespanel',
				record: config.record,
				ref: 'categoriesPanel',
				hideActionButtons: true,
				buttons: [{
					text: _('Apply'),
					handler: this.onApply,
					scope: this
				}, {
					text: _('Cancel'),
					handler: this.close,
					scope: this
				}]
			}]
		});

		Zarafa.advancesearch.dialogs.SearchCategoriesContentPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Event handler which is raised when the user clicks the "Apply" {@link Ext.Button button}
	 * This will add selected categories to {@link #searchCategoryStore store}
	 * and will close the panel.
	 * @private
	 */
	onApply: function ()
	{
		var categories = this.categoriesPanel.getSelectedCategories();
		var existingCategories = this.searchCategoryStore.getCategories();

		// Find removed categories (unchecked by user) and remove this from {@link #searchCategoryStore store}.
		var removedCategories = existingCategories.filter(function (category) {
			return categories.indexOf(category) === -1;
		});
		if (!Ext.isEmpty(removedCategories)) {
			this.searchCategoryStore.removeCategories(removedCategories);
		}

		this.searchCategoryStore.addCategories(categories);
		this.close();
	}
});

Ext.reg('zarafa.searchcategoriescontentpanel', Zarafa.advancesearch.dialogs.SearchCategoriesContentPanel);
Ext.namespace('Zarafa.advancesearch.dialogs');

/**
 * @class Zarafa.advancesearch.dialogs.SearchContentPanel
 * @extends Zarafa.core.ui.ContentPanel
 * @xtype zarafa.searchcontentpanel
 *
 * Panel that is used to compose a search result panel.
 */
Zarafa.advancesearch.dialogs.SearchContentPanel = Ext.extend(Zarafa.core.ui.ContentPanel, {

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

		var advanceSearchTabName = 'advancesearchtab-' + (++Ext.Component.AUTO_ID);

		Ext.applyIf(config, {
			xtype: 'zarafa.searchcontentpanel',
			layout: 'fit',
			name: advanceSearchTabName,
			title: config.searchText,
			iconCls: 'icon_magnifier',
			border: false,
			parentSearchField: config.parentSearchField,
			parentSearchFolderCombo: config.parentSearchFolderCombo,
			items: [{
				xtype: 'zarafa.searchpanel',
				ref: 'searchPanel',
				searchTabId: advanceSearchTabName,
				searchFolder: config.searchFolder,
				searchText: config.searchText,
				virtualTokens: config.virtualTokens,
				searchContentPanel: this
			}]
		});

		Zarafa.advancesearch.dialogs.SearchContentPanel.superclass.constructor.call(this, config);
		this.initEvents();
	},

	/**
	 * Register initial events for the {@link Zarafa.advancesearch.dialogs.SearchContentPanel search content panel}.
	 */
	initEvents: function()
	{
		var contentPanel = container.getTabPanel();
		this.mon(contentPanel, 'tabchange', this.onTabChange, this);
	},

	/**
	 * Event handler triggers when content tab panel is changed, it's used to
	 * toggle search view button & parent context view button when search panel
	 * open/close.
	 *
	 * @param {Ext.TabPanel} tabPanel the tab panel which contains tabs.
	 * @param {Ext.Panel} activeTab the activeTab from tab panel
	 */
	onTabChange: function(tabPanel, activeTab)
	{
		var mainToolbar = container.getMainToolbar();
		var isSearchPanel = activeTab.isXType('zarafa.searchcontentpanel');
		var contextName = container.getCurrentContext().getName();

		// Toggle the search view button.
		mainToolbar.get('zarafa-maintoolbar-view-advancesearch').setVisible(isSearchPanel);

		var currentContextPrintBtn = mainToolbar.get('zarafa-maintoolbar-print-'+ contextName);

		// Disable Print button of other context and enable Print button of advance search,
		// if current active tab is searchPanel.
		// This condition is required for contexts which does not have "Print" button (i.e. Today context).
		if (!Ext.isEmpty(currentContextPrintBtn)) {
			currentContextPrintBtn.setVisible(!isSearchPanel);
		}
		mainToolbar.get('zarafa-maintoolbar-print-advancesearch').setVisible(isSearchPanel);

		switch(contextName) {
			case 'calendar':
				// Toggle calendar context view buttons
				var viewButtons = ['Day','Workweek','Week', 'Month', 'List'];
				Ext.each(viewButtons, function(item, index, array){
					var viewButton = mainToolbar['calendar'+item];
					viewButton.setVisible(!isSearchPanel);
				}, this);
			break;
			default:
				// Toggle other context view button
				var viewButton = mainToolbar.get('zarafa-maintoolbar-view-'+contextName);
				if(Ext.isDefined(viewButton)){
					viewButton.setVisible(!isSearchPanel);
				}
		}

		var searchPanel = activeTab.searchPanel;
		if (Ext.isDefined(searchPanel)) {
			var model = searchPanel.model;
			var searchStore = model.stores[activeTab.name];
			if (Ext.isDefined(searchStore)) {
				var searchGrid = searchPanel.centerRegion.switchBorder.searchGrid;
				var selectionModel = searchGrid.getSelectionModel();
				var records = selectionModel.getSelections();
				model.setSelectedRecords(records, false);
				model.setActiveStore(searchStore);

				var searchFolder = searchStore.searchFolder[activeTab.name];
				if (Ext.isDefined(searchFolder)) {
					searchStore.setSearchEntryId(searchFolder.get('entryid'));
				}
			}
		}
	},

	/**
	 * Function which used to set the parent {@link Zarafa.common.searchfield.ui.SearchTextField SearchTextField}.
	 * @param {Zarafa.common.searchfield.ui.SearchTextField} searchField the parent search field.
	 */
	setParentSearchField: function(searchField)
	{
		this.parentSearchField = searchField;
	},

	/**
	 * Function is used to get the parent {@link Zarafa.common.searchfield.ui.SearchTextField SearchTextField}.
	 * @return {Zarafa.common.searchfield.ui.SearchTextField} parent search field.
	 */
	getParentSearchField: function()
	{
		return this.parentSearchField;
	},

	/**
	 * Function is used to get the parent {@link Zarafa.common.searchfield.ui.SearchFolderCombo SearchFolderCombo}.
	 * @return {Zarafa.common.searchfield.ui.SearchFolderCombo} parent search folder combo.
	 */
	getParentSearchFolderCombo: function()
	{
		return this.parentSearchFolderCombo;
	}
});

Ext.reg('zarafa.searchcontentpanel', Zarafa.advancesearch.dialogs.SearchContentPanel);
Ext.namespace('Zarafa.advancesearch.dialogs');

/**
 * @class Zarafa.advancesearch.dialogs.SelectFolderContentPanel
 * @extends Zarafa.core.ui.ContentPanel
 * @xtype zarafa.selectfoldercontentpanel
 *
 * This will display a {@link Zarafa.core.ui.ContentPanel contentpanel}
 * for select {@link Zarafa.core.data.IPFRecord folder} on which search gets performed.
 */
Zarafa.advancesearch.dialogs.SelectFolderContentPanel = Ext.extend(Zarafa.core.ui.ContentPanel, {

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

		Ext.applyIf(config, {
			xtype: 'zarafa.selectfoldercontentpanel',
			layout: 'fit',
			title: _('Select Folder'),
			width: 300,
			height: 350,
			items: [{
				xtype: 'zarafa.selectfolderpanel',
				model: config.model,
				searchFolderCombo: config.searchFolderCombo
			}]
		});

		Zarafa.advancesearch.dialogs.SelectFolderContentPanel.superclass.constructor.call(this, config);
	}
});

Ext.reg('zarafa.selectfoldercontentpanel', Zarafa.advancesearch.dialogs.SelectFolderContentPanel);
Ext.namespace('Zarafa.advancesearch.ui');

/**
 * @class Zarafa.advancesearch.ui.SearchFolderContextMenu
 * @extends Zarafa.core.ui.menu.ConditionalMenu
 * @xtype zarafa.searchfoldercontextmenu
 */
Zarafa.advancesearch.ui.SearchFolderContextMenu = Ext.extend(Zarafa.core.ui.menu.ConditionalMenu, {

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

		Ext.applyIf(config, {
			items: [{
				xtype: 'zarafa.conditionalitem',
				text: _('Delete search folder'),
				iconCls: 'icon_folder_delete',
				handler: this.onContextItemDeleteFolder,
				scope: this
			}]
		});

		Zarafa.advancesearch.ui.SearchFolderContextMenu.superclass.constructor.call(this, config);
	},

	/**
	 * Event handler triggers when "Delete search folder" button was clicked.
	 * it is used to delete the search folder which marked as favorites.
	 */
	onContextItemDeleteFolder: function ()
	{
		var record = this.records;
		var store = record.getStore();
		store.remove(record);
		record.removeFromFavorites();
		store.save(record);
	}
});

Ext.reg('zarafa.searchfoldercontextmenu', Zarafa.advancesearch.ui.SearchFolderContextMenu);
/*
 * #dependsFile client/zarafa/common/ui/grid/Renderers.js
 */
Ext.namespace('Zarafa.advancesearch.ui');

/**
 * @class Zarafa.advancesearch.ui.SearchGridColumnModel
 * @extends Zarafa.common.ui.grid.ColumnModel
 *
 */
Zarafa.advancesearch.ui.SearchGridColumnModel = Ext.extend(Zarafa.common.ui.grid.ColumnModel, {

	/**
	 * @cfg {Boolean} useCompactView If true the compact column model will be
	 * used by default. Otherwise the default column model will be used which
	 * contains all possible columns.
	 */
	useCompactView: false,

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

		this.defaultColumns = this.createDefaultColumns(config);
		this.compactColumns = this.createCompactColumns(config);

		Ext.applyIf(config, {
			columns: this.defaultColumns
		});

		// Switch to compact view if needed
		if (config.useCompactView === true) {
			config.columns = this.compactColumns;
		}

		Zarafa.advancesearch.ui.SearchGridColumnModel.superclass.constructor.call(this, config);
	},

	/**
	 * Create an array of {@link Ext.grid.Column columns} which must be visible within
	 * the default view of this {@link Ext.grid.ColumnModel ColumnModel}.
	 *
	 * @return {Ext.grid.Column[]} The array of columns
	 * @private
	 */
	createDefaultColumns: function (config)
	{
		var grid = config.grid;
		return [{
			id: 'column_icon',
			dataIndex: 'icon_index',
			fixed: true,
			width: 28,
			renderer: Zarafa.common.ui.grid.Renderers.icon,
			preventRowSelection: true
		}, {
			id: 'name',
			sortable: false,
			hideable: false,
			width: 125,
			fixed: true,
			renderer: Zarafa.advancesearch.ui.SearchGridRenderers.nameColumn
		}, {
			id: grid.getId() + '-col0',
			sortable: false,
			hideable: false,
			renderer: Zarafa.advancesearch.ui.SearchGridRenderers.subjectWithBodyColumn
		}, {
			dataIndex: 'hasattach',
			width: 24,
			renderer: Zarafa.common.ui.grid.Renderers.attachment,
			fixed: true
		}, {
			id: grid.getId() + '-col1',
			dataIndex: 'searchdate',
			header: _('Date'),
			// Wide enough for the localized date-with-time formats
			// (e.g. '17.06.2026 13:44').
			width: 140,
			fixed: true,
			resizable: false,
			sortable: true,
			tooltip: _('Sort by: Date'),
			hideable: false,
			renderer: Zarafa.advancesearch.ui.SearchGridRenderers.dateColumn,
			css: 'padding: 0; margin:0;'
		}];
	},

	/**
	 * Create an array of {@link Ext.grid.Column columns} which must be visible within
	 * the compact view of this {@link Ext.grid.ColumnModel ColumnModel}.
	 *
	 * @return {Ext.grid.Column[]} The array of columns
	 * @private
	 */
	createCompactColumns: function (config)
	{
		var grid = config.grid;
		return [{
			id: 'column_icon',
			dataIndex: 'icon_index',
			fixed: true,
			width: 28,
			renderer: Zarafa.common.ui.grid.Renderers.icon,
			preventRowSelection: true
		}, {
			id: 'name',
			sortable: false,
			hideable: false,
			renderer: Zarafa.advancesearch.ui.SearchGridRenderers.nameColumn
		}, {
			dataIndex: 'hasattach',
			width: 24,
			renderer: Zarafa.common.ui.grid.Renderers.attachment,
			fixed: true
		}, {
			id: grid.getId() + '-col1',
			dataIndex: 'searchdate',
			header: _('Date'),
			// Wide enough for the localized date-with-time formats
			// (e.g. '17.06.2026 13:44').
			width: 140,
			fixed: true,
			resizable: false,
			sortable: true,
			tooltip: _('Sort by: Date'),
			hideable: false,
			renderer: Zarafa.advancesearch.ui.SearchGridRenderers.dateColumn,
			css: 'padding: 0; margin:0;'
		}];
	},

	/**
	 * This will switch the {@link Zarafa.advancesearch.ui.SearchGridColumnModel columnmodel}
	 * configuration to either the compact or extended configuration.
	 *
	 * @param {Boolean} compact True to enable the compact view
	 */
	setCompactView: function(compact)
	{
		if (this.useCompactView !== compact) {
			this.useCompactView = compact;

			if (compact) {
				// Extjs will store the this.columns into this.config after it has constructed
				// all the columns. At that point this.columns consists of the configuration objects,
				// while this.columns consists of all the allocated columns.
				this.defaultColumns = this.config;
				this.columns = this.compactColumns;
			} else {
				this.compactColumns = this.config;
				this.columns = this.defaultColumns;
			}

			this.setConfig(this.columns, false);
		}
	}
});

Ext.namespace('Zarafa.advancesearch.ui');

/**
 * @class Zarafa.advancesearch.ui.SearchResultPreviewPanel
 * @extends Zarafa.core.ui.PreviewPanel
 * @xtype zarafa.searchresultpreviewpanel
 */
Zarafa.advancesearch.ui.SearchResultPreviewPanel = Ext.extend(Zarafa.core.ui.PreviewPanel, {
	/**
	 * @constructor
	 * @param config Configuration structure.
	 */
	constructor: function (config)
	{
		config = config || {};

		if (!Ext.isDefined(config.model) && Ext.isDefined(config.searchContext)) {
			config.model = config.searchContext.getModel();
		}

		Ext.applyIf(config, {
			xtype: 'zarafa.searchresultpreviewpanel',
			cls: 'zarafa-previewpanel zarafa-context-mainpanel',
			recordComponentPluginConfig: Ext.applyIf(config.recordComponentPluginConfig || {}, {
				// Defer loading mail immediately, because the user
				// might be quickly clicking through the list of mails.
				enableOpenLoadTask: true,
				autoOpenLoadTaskDefer: 0
			}),
			width: 600,
			height: 400,
			/*
			 * TODO: make this code common for the Zarafa.advancesearch.ui.SearchResultPreviewPanel,
			 * Zarafa.mail.ui.MailPreviewPanel and Zarafa.advancesearch.dialogs.SearchToolbarPanel
			 */
			tbar: {
				height: 33,
				items: [{
					xtype: 'button',
					tooltip: _('Reply') + Zarafa.core.KeyMapMgr.formatShortcutHint('Ctrl + R', false),
					overflowText: _('Reply'),
					iconCls: 'icon_reply',
					ref: 'replyBtn',
					responseMode: Zarafa.mail.data.ActionTypes.REPLY,
					handler: this.onResponse,
					scope: this
				},{
					xtype: 'button',
					tooltip: _('Reply All') + Zarafa.core.KeyMapMgr.formatShortcutHint('Ctrl + Alt + R', false),
					overflowText: _('Reply All'),
					iconCls: 'icon_reply_all',
					ref: 'replyAllBtn',
					responseMode: Zarafa.mail.data.ActionTypes.REPLYALL,
					handler: this.onResponse,
					scope: this
				},{
					xtype: 'button',
					tooltip: _('Forward') + Zarafa.core.KeyMapMgr.formatShortcutHint('Ctrl + F', false),
					overflowText: _('Forward'),
					iconCls: 'icon_forward',
					ref: 'forwardBtn',
					responseMode: Zarafa.mail.data.ActionTypes.FORWARD,
					handler: this.onResponse,
					scope: this
				},{
					xtype: 'button',
					tooltip: _('Edit as New') + Zarafa.core.KeyMapMgr.formatShortcutHint('Ctrl + E', false),
					overflowText: _('Edit as New'),
					iconCls: 'icon_edit_as_new_mail',
					ref: 'editAsNewBtn',
					responseMode: Zarafa.mail.data.ActionTypes.EDIT_AS_NEW,
					handler: this.onResponse,
					scope: this
				}]
			}
		});

		this.addEvents([
			/**
			 * @event afterupdatesearchpreviewpanel
			 * Fired when {@link Zarafa.advancesearch.ui.SearchResultPreviewPanel SearchResultPreviewPanel}
			 * gets update.
			 * @param {Zarafa.advancesearch.ui.SearchResultPreviewPanel} SearchResultPreviewPanel The SearchResultPreviewPanel which fired the event
			 * @param {Zarafa.core.data.MAPIRecord} record The record to update in this component
			 * @param {Boolean} contentReset force the component to perform a full update of the data.
			 */
			'afterupdatesearchpreviewpanel'
		]);

		Zarafa.advancesearch.ui.SearchResultPreviewPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Update the components with the given record.
	 *
	 * @param {Zarafa.core.data.MAPIRecord} record The record to update in this component
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 */
	update: function (record, contentReset)
	{
		Zarafa.advancesearch.ui.SearchResultPreviewPanel.superclass.update.apply(this, arguments);

		this.fireEvent('afterupdatesearchpreviewpanel', this, record, contentReset);

		if(record) {
			var isFaultyMessage = record.isFaultyMessage();
			var isMessageReplyable = Zarafa.core.MessageClass.isClass(record.get('message_class'), ['IPM.NOTE', 'REPORT.IPM', 'IPM.SCHEDULE', 'IPM.APPOINTMENT']);

			// Additional check when the message is IPM.Appointment and not a meeting request
			// but a simple appointment which can not be replied as there is no reply-to recipients available.
			if(isMessageReplyable && Zarafa.core.MessageClass.isClass(record.get('message_class'), ['IPM.APPOINTMENT'])) {
				if(!record.isMeeting()){
					isMessageReplyable = false;
				}
			}

			var toolbar = this.getTopToolbar();

			toolbar.replyBtn.setVisible(!isFaultyMessage && isMessageReplyable);
			toolbar.replyAllBtn.setVisible(!isFaultyMessage && isMessageReplyable);
			toolbar.forwardBtn.setVisible(!isFaultyMessage && isMessageReplyable);

			// Currently pop-out functionality is not available for
			// contact, sticky note, distribution list, appointment and task
			// So disable showing popout button in search results preview panel for those context item
			// TODO Remove when we support popout for all context
			if (Zarafa.supportsPopOut()) {
				var isSupportPopout = Zarafa.core.MessageClass.isClass(record.get('message_class'), ['IPM.NOTE', 'REPORT.IPM.Note', 'IPM.Schedule.Meeting'], true);
				toolbar.popoutBtn.setVisible(isSupportPopout);
			}

			// Only show the "Edit as New" button in the toolbar when the item is in the Sent folder
			var defaultFolder = this.model.getDefaultFolder();
			toolbar.editAsNewBtn.setVisible(defaultFolder.getDefaultFolderKey()==='sent' && !isFaultyMessage && isMessageReplyable);
		}
	},

	/**
	 * See {@link Zarafa.core.plugins.RecordComponentPlugin#setRecord}. also it will
	 * disable the toolbar if {@link Zarafa.common.data.ViewModes#RIGHT_PREVIEW} else it will show the toolbar
	 *
	 * @param {Zarafa.core.data.MAPIRecord} record The record to set
	 */
	setRecord: function(record)
	{
		for (var i = 0; i < this.toolbars.length; i++) {
			if(this.searchContext.getCurrentViewMode() === Zarafa.common.data.ViewModes.RIGHT_PREVIEW) {
				this.toolbars[i].setVisible(false);
			} else {
				this.toolbars[i].setVisible(!!record);
			}
		}

		if (this.recordComponentPlugin) {
			this.recordComponentPlugin.setRecord(record);
		}
	},

	/**
	 * Function for 'previewrecordchange' and 'show' events before setting record into component also it will check
	 * that record is going to preview in correct {@link Zarafa.advancesearch.ui.SearchResultPreviewPanel SearchResultPreviewPanel}
	 * instance.
	 * @param {Zarafa.core.data.MAPIRecord} record
	 * @private
	 */
	showRecordInPanel: function(record)
	{
		if(this.model.store.getSearchStoreUniqueId() !== this.dialog.name) {
			return;
		}

		// Show the entire conversation of a mail search result. The search
		// store has no conversation structure, so the complete membership of
		// the conversation is fetched from the server once per conversation
		// (see #fetchConversationItems); while it is underway, the matched
		// hits (if the result was folded, see
		// Zarafa.advancesearch.AdvanceSearchStore#dedupeConversations) or the
		// single message are shown.
		if (container.getSettingsModel().get('zarafa/v1/contexts/mail/enable_conversation_preview', true) !== false &&
			container.isEnabledConversation() && record &&
			Ext.isFunction(record.isMessageClass) && record.isMessageClass('IPM.Note', true) &&
			!Ext.isEmpty(record.get('conversation_id'))) {
			var conversationId = record.get('conversation_id');
			var cached = this.getConversationCache()[conversationId];

			if (cached === undefined) {
				this.fetchConversationItems(record);
				cached = 'pending';
			}

			var conversationRecords = Ext.isArray(cached) ? cached : record.searchConversationRecords;
			if (Ext.isArray(conversationRecords) && conversationRecords.length > 1) {
				this.showConversationInPanel(conversationRecords, record);
				return;
			}
			// Not (yet) known to be a conversation: fall through to the normal
			// single view; when the fetch reveals more members, the preview
			// switches (see #onConversationItemsResponse).
		}

		if (this.get(0) instanceof Zarafa.mail.ui.ConversationViewPanel) {
			this.removeAll();
			this.record = undefined;
		}

		Zarafa.advancesearch.ui.SearchResultPreviewPanel.superclass.showRecordInPanel.call(this, record);
	},

	/**
	 * Mounts the conversation panel (when needed) and shows the given records.
	 *
	 * @param {Zarafa.core.data.IPMRecord[]} conversationRecords The conversation, newest first.
	 * @param {Zarafa.core.data.IPMRecord} record The selected search result.
	 * @private
	 */
	showConversationInPanel: function(conversationRecords, record)
	{
		if (!(this.get(0) instanceof Zarafa.mail.ui.ConversationViewPanel)) {
			this.removeAll();
			this.add(new Zarafa.mail.ui.ConversationViewPanel({
				resolveConversation: (function(rec) {
					if (!rec) {
						return false;
					}
					var cached = this.getConversationCache()[rec.get('conversation_id')];

					return Ext.isArray(cached) ? cached : rec.searchConversationRecords;
				}).createDelegate(this)
			}));
			this.doLayout();
		}
		this.get(0).showConversation(conversationRecords, record);

		// Standard record handling: toolbar, record bookkeeping and opening of
		// the selected record. The response actions act on the selected
		// message of the conversation.
		this.setRecord(record);
		this.hideLoadMask();
	},

	/**
	 * @return {Object} Map of conversation id to the fetched conversation
	 * records ('pending' while a fetch is underway).
	 * @private
	 */
	getConversationCache: function()
	{
		if (!this.conversationCache) {
			this.conversationCache = {};
		}

		return this.conversationCache;
	},

	/**
	 * Fetches the complete membership (inbox and sent items) of the
	 * conversation the given record belongs to.
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The search result.
	 * @private
	 */
	fetchConversationItems: function(record)
	{
		var conversationId = record.get('conversation_id');
		this.getConversationCache()[conversationId] = 'pending';

		container.getRequest().singleRequest(
			'maillistmodule',
			'conversationitems',
			{
				store_entryid: record.get('store_entryid'),
				conversation_id: conversationId,
				include_inbox: true
			},
			new Zarafa.mail.data.ConversationItemsResponseHandler({
				callback: this.onConversationItemsResponse.createDelegate(this)
			})
		);
	},

	/**
	 * Handles the fetched conversation membership: caches it and, when the
	 * currently previewed record belongs to it and it has more than one
	 * message, switches the preview to the conversation.
	 *
	 * @param {Object} response The 'conversationitems' response data.
	 * @private
	 */
	onConversationItemsResponse: function(response)
	{
		var conversationId = response ? response.conversation_id : undefined;
		if (Ext.isEmpty(conversationId)) {
			return;
		}

		var items = response.item || [];
		if (!Array.isArray(items)) {
			items = [items];
		}

		var records = [];
		if (items.length > 0) {
			records = this.model.store.reader.readRecords({
				count: items.length,
				item: items
			}).records;
		}
		this.getConversationCache()[conversationId] = records;

		// Switch the preview when the fetched conversation belongs to the
		// record that is still being previewed.
		var current = this.model.getPreviewRecord();
		if (records.length > 1 && current && !this.isDestroyed &&
			current.get('conversation_id') === conversationId) {
			var selected = current;
			Ext.each(records, function(rec) {
				if (Zarafa.core.EntryId.compareEntryIds(rec.get('entryid'), current.get('entryid'))) {
					selected = rec;
					return false;
				}
			});
			this.showConversationInPanel(records, selected);
		}
	},

	/**
	 * Called when one of the "Reply"/"Reply All"/"Forward"/"Edit as New" menuitems are clicked.
	 * @param {Ext.Button} button The button which was clicked
	 * @private
	 */
	onResponse: function(button)
	{
		var mailContextModel = container.getContextByName('mail').getModel();
		Zarafa.mail.Actions.openCreateMailResponseContent(this.record, mailContextModel, button.responseMode);
	}
});

Ext.reg('zarafa.searchresultpreviewpanel', Zarafa.advancesearch.ui.SearchResultPreviewPanel);

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

/**
 * @class Zarafa.calendar.KeyMapping
 * @extends Object
 *
 * The map of keys used in the Calendar Context.
 * @singleton
 */
Zarafa.calendar.KeyMapping = Ext.extend(Object, {
	/**
	 * @constructor
	 */
	constructor: function()
	{
		var newItemKeys = [{
			key: Ext.EventObject.A,
			ctrl: true,
			alt: true,
			shift: false,
			stopEvent: true,
			handler: this.onNewAppointment,
			scope: this,
			settingsCfg: {
				description: _('New appointment'),
				category: _('Creating an item')
			}
		},{
			key: Ext.EventObject.V,
			ctrl: true,
			alt: true,
			shift: false,
			stopEvent: true,
			handler: this.onNewMeetingRequest,
			scope: this,
			settingsCfg: {
				description: _('New meeting request'),
				category: _('Creating an item')
			}
		},{
			key: [Ext.EventObject.LEFT, Ext.EventObject.RIGHT],
			ctrl: false,
			alt: true,
			shift: false,
			stopEvent: true,
			handler: this.onSwitchRange,
			scope: this,
			settingsCfg: {
				description: _('Switch calendar range back and forth'),
				category: _('Calendar')
			}
		}];

		var mapiMessageKeys = [{
			key: Ext.EventObject.C,
			ctrl:true,
			alt: false,
			shift: false,
			stopEvent: true,
			enableGlobally: true,
			settingsCfg: {
				description: _('Copy selected item'),
				category: _('Calendar')
			},
			handler:this.onCopyItem,
			scope: this,
			basic: true
		},{
			key: Ext.EventObject.V,
			ctrl:true,
			alt: false,
			shift: false,
			stopEvent: true,
			enableGlobally: true,
			settingsCfg: {
				description: _('Paste selected item'),
				category: _('Calendar')
			},
			handler:this.onPaste,
			scope: this,
			basic: true
		}];

		Zarafa.core.KeyMapMgr.register('global', newItemKeys);
		Zarafa.core.KeyMapMgr.register('view.mapimessage', mapiMessageKeys);
	},

	/**
	 * Event handler for the keydown event of the {@link Zarafa.core.KeyMap KeyMap} when the user wants to
	 * create a new appointment.
	 * @param {Number} key Key code
	 * @param {Ext.EventObject} event The event
	 * @param {Ext.Component} component The component on which key event is fired.
	 */
	onNewAppointment: function(key, event, component)
	{
		Zarafa.calendar.Actions.openCreateAppointmentContent(container.getContextByName('calendar').getModel());
	},

	/**
	 * Event handler for the keydown event of the {@link Zarafa.core.KeyMap KeyMap} when the user wants to
	 * create a new meeting request.
	 * @param {Number} key Key code
	 * @param {Ext.EventObject} event The event
	 * @param {Ext.Component} component The component on which key event is fired.
	 */
	onNewMeetingRequest: function(key, event, component)
	{
		Zarafa.calendar.Actions.openCreateMeetingRequestContent(container.getContextByName('calendar').getModel());
	},

	/**
	 * Event handler for the keydown event of the {@link Zarafa.core.KeyMap KeyMap} when the user wants
	 * switch between {@link Zarafa.core.ui.MainContentTabPanel MainContentTabs} calendar ranges.
	 * @param {Number} key Key code
	 * @param {Ext.EventObject} event The event
	 * @param {Ext.Component} component The component on which key event is fired.
	 */
	onSwitchRange: function(key, event, component)
	{
		if(key === Ext.EventObject.LEFT){
			container.getContextByName('calendar').getModel().previousDate();
		} else {
			container.getContextByName('calendar').getModel().nextDate();
		}
	},

	/**
	 * Event handler for the keydown event of the {@link Zarafa.core.KeyMap KeyMap} when the user wants to
	 * copy selected appointment/meeting in calendar.
	 *
	 * @param {Number} key Key code
	 * @param {Ext.EventObject} event The event
	 * @param {Ext.Component} component The component on which key event is fired.
	 */
	onCopyItem: function (key, event, component)
	{
		var records = Zarafa.common.KeyMapping.getSelectedRecords(component);
		if (!Ext.isEmpty(records)) {
			component.clipBoardData = records[0].copy();
			component.isClipBoardDataRecurring = !Ext.isEmpty(records[0].get('basedate'));
		}
	},

	/**
	 * Function which is used to open the selected calendar item.
	 *
	 * @param {Ext.Component} component The component on which key event is fired.
	 * @param {Zarafa.core.data.IPMRecord} record A selected calendar item in calendar view.
	 * @private
	 */
	openRecord: function (component, record)
	{
		var store = container.getShadowStore();
		store.add(record);

		const openHandler = function(store, records)
		{
			// Remove the record from shadowStore and deregister 'open' event.
			store.remove(records, true);
			store.un('open', openHandler, this);
			component.doPaste(records);
		};
		store.on('open', openHandler, this);
		record.open();
	},

	/**
	 * Event handler for the keydown event of the {@link Zarafa.core.KeyMap KeyMap} when the user wants to
	 * paste the copied appointment/meeting in calendar.
	 *
	 * @param {Number} key Key code
	 * @param {Ext.EventObject} event The event
	 * @param {Ext.Component} component The component on which key event is fired.
	 * @private
	 */
	onPaste: function (key, event, component)
	{
		if(Ext.isEmpty(component.clipBoardData)) {
			return;
		}
		var record = component.clipBoardData.copy();
		if (component.isClipBoardDataRecurring) {
			var config = {
				component: component,
				scope: this
			};
			Zarafa.calendar.Actions.copyRecurringItemContent(record, config);
		} else if(!record.isOpened()) {
			this.openRecord(component, record);
		} else {
			component.doPaste(record);
		}
	}
});

Zarafa.calendar.KeyMapping = new Zarafa.calendar.KeyMapping();
// -*- coding: utf-8; indent-tabs-mode: nil -*-
Ext.namespace('Zarafa.calendar.printer');

/**
 * @class Zarafa.calendar.printer.ListViewRenderer
 * @extends Zarafa.common.printer.renderers.BaseRenderer
 *
 * Prints a list of all calendar items in a folder
 */
Zarafa.calendar.printer.ListViewRenderer = Ext.extend(Zarafa.common.printer.renderers.BaseRenderer, {
	/**
	 * @property customStylesheetPath
	 * @type String
	 * The path at which the print stylesheets can be found for this renderer
	 */
	customStylesheetPath: 'client/resources/css/external/print.list.css',

	/**
	 * Generate the XTemplate HTML text for printing a task list.
	 * This prints every task given in the context view.
	 * @param {Zarafa.task.TaskContext} context The task view in the webapp
	 * @return {String} The HTML for the XTemplate to print
	 */
	generateBodyTemplate: function(context) {

		/*
		 * +----------------------------------------------------+
		 * | priority | % complete | due date | subject | owner |
		 * ....
		 * +----------------------------------------------------+
		 * [name]                                    [print date]
		 */

		var html = '<div id="k-listview">'

			// Div with calendar name
			+ '<div id="k-name">'
			+	'<tr><td>' + _('An overview of') + ': ' + '{foldernames} </td><tr>'
			+ '</div>'
			+ '<table id="k-printlist" cellpadding=0 cellspacing=0>'
			+ 	'<tr>'
			+		'<th>' + _('Subject') + '</th>'
			+		'<th>' + _('Start') + '</th>'
			+		'<th>' + _('End') + '</th>'
			+		'<th>' + _('Duration') + '</th>'
			+		'<th>' + _('Location') + '</th>'
			+		'<th>' + _('Categories') + '</th>'
			+	'</tr>'
			// date format l jS F = Monday 1st January
			+ 	'<tpl for="appointments">'
			+		'<tr>'
			+			'<td>{values.data.subject:htmlEncode}</td>'
						// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
			+			'<td>{values.data.startdate:formatDefaultTimeString("' + _("l jS F Y {0}") + '")}</td>'
						// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
			+			'<td>{values.data.duedate:formatDefaultTimeString("' + _("l jS F Y {0}") + '")}</td>'
			+			'<td>{[Ext.util.Format.duration(values.data.duration, 1)]}</td>'
			+			'<td>{values.data.location:htmlEncode}</td>'
			+			'<td>{values.data.categories:htmlEncode}</td>'
			+		'</tr>'
			+	'</tpl>'
			+'</table>'

			// Bottom table with username and date
			+ '<table id="k-printlistbottom">'
			+	'<tr>'
					// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
     	        	+		'<td class="nowrap" align="left">'+_('Printed by') + ' ' + '{fullname}' + ' '+_('at') + ' ' + '{currenttime:formatDefaultTimeString("' + _("l jS F Y {0}") + '")}</td>'
			+	'</tr>'
			+ '</table>'
		+ '</div>';

		return html;
	},

	/**
	 * Returns the data for the XTemplate used in generateBodyTemplate()
	 * @param {Zarafa.calendar.CalendarContext} context The calendar list view in the webapp
	 * @return {Object} XTemplate data
	 */
	prepareData: function(context)
	{
		var data = {};
		var model = context.getModel();
		data['fullname'] = container.getUser().getDisplayName();
		data['currenttime'] = new Date();
		data['appointments'] = model.getStore().getRange();

		// Obtain the calendar name
        var folders = model.getFolders();
        var foldernames = [];
        for (var i = 0; i < folders.length; i++) {
            if (folders[i].getMAPIStore().get('display_name') === container.getUser().getDisplayName()) {
                foldernames.push(folders[i].get('display_name'));
            } else {
                foldernames.push(folders[i].get('display_name') + ' ' + _('of') + ' ' + folders[i].getMAPIStore().get('display_name'));
            }
        }

        data['foldernames'] = foldernames.join(', ');

		return data;
	}
});
/*
 * #dependsFile client/zarafa/common/ui/grid/Renderers.js
 */
Ext.namespace('Zarafa.calendar.attachitem');

/**
 * @class Zarafa.calendar.attachitem.AttachCalendarColumnModel
 * @extends Zarafa.common.ui.grid.ColumnModel
 *
 * The {@link Zarafa.calendar.attachitem.AttachCalendarColumnModel AttachCalendarColumnModel} is the column model containing
 * sets of {@link Ext.grid.Column columns} for calendar folders.
 * This column model will be used with {@link Zarafa.common.attachment.dialog.AttachItemGrid AttachItemGrid}.
 */
Zarafa.calendar.attachitem.AttachCalendarColumnModel = Ext.extend(Zarafa.common.ui.grid.ColumnModel, {
	/**
	 * @constructor
	 * @param {Object} config Configuration structure
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			columns: this.getColumns(),
			defaults: {
				sortable: true
			}
		});

		Zarafa.calendar.attachitem.AttachCalendarColumnModel.superclass.constructor.call(this, config);
	},

	/**
	 * Create an array of {@link Ext.grid.Column columns} which will be visible when loading data from
	 * calendar folder into {@link Zarafa.common.attachment.dialog.AttachItemGrid AttachItemGrid}.
	 *
	 * @return {Ext.grid.Column[]} The array of columns
	 * @private
	 */
	getColumns: function()
	{
		return [{
			header: '<p class=\'icon_index\'>&nbsp;</p>',
			headerCls: 'zarafa-icon-column',
			dataIndex: 'icon_index',
			tooltip: _('Sort by: Icon'),
			width: 24,
			fixed: true,
			renderer: Zarafa.common.ui.grid.Renderers.icon
		}, {
			header: '<p class=\'icon_paperclip\'>&nbsp;</p>',
			headerCls: 'zarafa-icon-column',
			dataIndex: 'hasattach',
			width: 24,
			fixed: true,
			renderer: Zarafa.common.ui.grid.Renderers.attachment,
			tooltip: _('Sort by: Attachment')
		}, {
			header: '<p class=\'icon_recurrence\'>&nbsp;</p>',
			headerCls: 'zarafa-icon-column',
			dataIndex: 'recurring',
			tooltip: _('Sort by: Recurring'),
			width: 24,
			renderer: Zarafa.common.ui.grid.Renderers.recurrence,
			fixed: true
		}, {
			header: _('Subject'),
			dataIndex: 'subject',
			tooltip: _('Sort by: Subject'),
			renderer: Zarafa.common.ui.grid.Renderers.subject
		}, {
			header: _('Start Date'),
			dataIndex: 'startdate',
			tooltip: _('Sort by: Start Date'),
			width: 180,
			renderer: Zarafa.common.ui.grid.Renderers.datetime
		}, {
			header: _('End Date'),
			dataIndex: 'duedate',
			tooltip: _('Sort by: End Date'),
			width: 180,
			renderer: Zarafa.common.ui.grid.Renderers.datetime
		}];
	}
});
Ext.namespace('Zarafa.calendar.attachitem');

/**
 * @class Zarafa.calendar.attachitem.AttachCalendarRenderer
 * @extends Zarafa.common.attachment.dialogs.AttachItemBaseRenderer
 *
 * Renderer that can be used to get text data from {@link Zarafa.calendar.AppointmentRecord AppointmentRecord}.
 */
Zarafa.calendar.attachitem.AttachCalendarRenderer = Ext.extend(Zarafa.common.attachment.dialogs.AttachItemBaseRenderer, {
	/**
	 * Constructor will initialize default properties
	 * @param {Object} config configuration object
	 */
	constructor: function(config)
	{
		Zarafa.calendar.attachitem.AttachCalendarRenderer.superclass.constructor.call(this, config);

		Ext.apply(this.labels, {
			'from': _('Organizer'),
			'location': _('Where'),
			'display_to': _('Required Attendee'),
			'display_cc': _('Optional Attendee'),
			'display_bcc': _('Resource'),
			'busystatus': _('Show Time As'),
			'startdate': _('Start Date'),
			'duedate': _('End Date'),
			'recurring_pattern': _('Recurrence pattern'),
			'alldayevent': _('All Day Event')
		});
	},

	/**
	 * Function should be used to generate template which can be used by {@link #generateText} to add data into template and return the string that is generated.
	 * This will generate template for the html format only.
	 * @return {String} The HTML for the XTemplate to use
	 */
	generateHTMLTemplate: function()
	{
		var html = '';

		html += this.addHTMLRowGroup({
			'subject': '{subject:htmlEncode}',
			'location': '{location:htmlEncode}'
		});

		html += this.addHTMLRowGroup({
			// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
			'startdate': '{startdate:formatDefaultTimeString("' + _("l jS F Y {0}") + '")}',
			// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
			'duedate': '{duedate:formatDefaultTimeString("' + _("l jS F Y {0}") + '")}',
			'busystatus': '{busystatus:busyStatusString}',
			'alldayevent': '{alldayevent}'
		});

		html += this.addHTMLRowGroup({
			'from': '{from:htmlEncode}'
		});

		html += this.addHTMLRowGroup({
			'meeting': {
				'display_to': '{display_to:htmlEncode}',
				'display_cc': '{display_cc:htmlEncode}',
				'display_bcc': '{display_bcc:htmlEncode}'
			}
		});

		html += this.addHTMLRowGroup({
			'recurring': {
				'recurring_pattern': '{recurring_pattern:htmlEncode}'
			}
		});

		html += this.addHTMLRowGroup({
			'categories': '{categories:htmlEncode}',
			'sensitivity': '{sensitivity:sensitivityString}',
			'importance': '{importance:importanceString}'
		});

		html += this.addHTMLRow('attachment_names', '{attachment_names:htmlEncode}');
		html += '{body}';

		return html;
	},

	/**
	 * Function should be used to generate template which can be used by {@link #generateText} to add data into template and return the string that is generated.
	 * This will generate template for the plain text format only.
	 * @return {String} The HTML for the XTemplate to use
	 */
	generatePlainTemplate: function()
	{
		var html = '';

		html += this.addPlainRowGroup({
			'subject': '{subject}',
			'location': '{location}'
		});

		html += this.addPlainRowGroup({
			// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
			'startdate': '{startdate:formatDefaultTimeString("' + _("l jS F Y {0}") + '")}',
			// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
			'duedate': '{duedate:formatDefaultTimeString("' + _("l jS F Y {0}") + '")}',
			'busystatus': '{busystatus:busyStatusString}',
			'alldayevent': '{alldayevent}'
		});

		html += this.addPlainRowGroup({
			'from': '{from}'
		});

		html += this.addPlainRowGroup({
			'meeting': {
				'display_to': '{display_to}',
				'display_cc': '{display_cc}',
				'display_bcc': '{display_bcc}'
			}
		});

		html += this.addPlainRowGroup({
			'recurring': {
				'recurring_pattern': '{recurring_pattern}'
			}
		});

		html += this.addPlainRowGroup({
			'categories': '{categories}',
			'sensitivity': '{sensitivity:sensitivityString}',
			'importance': '{importance:importanceString}'
		});

		html += this.addPlainRow('attachment_names', '{attachment_names}');
		html += '{body}';

		return html;
	},

	/**
	 * Prepares data suitable for use in an XTemplate from the record.
	 * @param {Zarafa.core.data.IPMRecord} record The record to acquire data from.
	 * @return {Array} An array of data which is customized for our purpose.
	 */
	prepareData: function(record)
	{
		var data = Zarafa.calendar.attachitem.AttachCalendarRenderer.superclass.prepareData.apply(this, arguments);

		if(!record.isMeeting()) {
			delete data['meeting'];
		}

		if(!record.get('recurring')) {
			delete data['recurring'];
		}

		if(!record.get('alldayevent')) {
			delete data['alldayevent'];
		}

		if(record.get('busystatus') === Zarafa.core.mapi.BusyStatus.BUSY) {
			delete data['busystatus'];
		}

		// for meeting request record there are differences in property names
		if(record.isMessageClass('IPM.Schedule.Meeting', true)) {
			var startDate = record.get('appointment_startdate');
			if(Ext.isDate(startDate)) {
				data['startdate'] = startDate;
			}

			var dueDate = record.get('appointment_duedate');
			if(Ext.isDate(dueDate)) {
				data['duedate'] = dueDate;
			}

			var recurring = record.get('appointment_recurring');
			if(!recurring) {
				data['recurring'] = recurring;
				data['recurring_pattern'] = record.get('appointment_recurring_pattern');
			}
		}

		return data;
	}
});Ext.namespace('Zarafa.calendar.data');

/**
 * @class Zarafa.calendar.data.DataModes
 * @extends Zarafa.core.Enum
 *
 * Enum containing the different data modes of the calendar context.
 *
 * @singleton
 */
Zarafa.calendar.data.DataModes = Zarafa.core.Enum.create({
	/**
	 * Load all appointments for a given day from the selected folder(s).
	 *
	 * @property
	 * @type Number
	 */
	DAY: 0,
	/**
	 * Load all appointments for a given workweek from the selected folder(s).
	 *
	 * @property
	 * @type Number
	 */
	WORKWEEK: 1,
	/**
	 * Load all appointments for a given week from the selected folder(s).
	 *
	 * @property
	 * @type Number
	 */
	WEEK: 2,
	/**
	 * Load all appointments for a given month from the selected folder(s).
	 *
	 * @property
	 * @type Number
	 */
	MONTH: 3,
	/**
	 * Load all appointments from the selected folder(s).
	 *
	 * @property
	 * @type Number
	 */
	ALL: 4,
	/**
	 * Search for appointments in the selected folder(s)
	 *
	 * @property
	 * @type Number
	 */
	SEARCH: 5
});
Ext.namespace('Zarafa.calendar.data');

/**
 * @class Zarafa.calendar.data.DragStates
 * @extends Zarafa.core.Enum
 *
 * Enum containing the different Drag & Drop states for the
 * {@link Zarafa.calendar.ui.CalendarViewDragZone Drag}&{@link Zarafa.calendar.ui.CalendarViewDropZone Drop}
 * support in the {@link Zarafa.calendar.ui.AbstractCalendarView calendar}.
 *
 * @singleton
 */
Zarafa.calendar.data.DragStates = Zarafa.core.Enum.create({
	/**
	 * No active D&D state
	 * @property
	 * @type Number
	 */
	NONE: 0,

	/**
	 * D&D proxy is currently dragging an item
	 * @property
	 * @type Number
	 */
	DRAGGING: 1,

	/**
	 * D&D proxy is currently selecting a date range
	 * @property
	 * @type Number
	 */
	SELECTING: 2,

	/**
	 * D&D proxy is resizing an item at the start of
	 * the appointment
	 * @property
	 * @type Number
	 */
	RESIZING_START: 3,

	/**
	 * D&D proxy is resizing an item at the end of
	 * the appointment
	 * @property
	 * @type Number
	 */
	RESIZING_DUE: 4
});
Ext.namespace('Zarafa.calendar.data');

/**
 * @class Zarafa.calendar.data.MeetingRequestButtonNames
 * @extends Zarafa.core.Enum
 *
 * Enum containing all the meetingrequest button names.
 * Which will be helpful in distinguishing meeting request buttons
 * in {@link Zarafa.calendar.ui.MeetingRequestButton MeetingRequestButton} base class.
 *
 * @singleton
 */
Zarafa.calendar.data.MeetingRequestButtonNames = Zarafa.core.Enum.create({
    /**
	 * Preserve name for RemoveFromCalendarButton.
	 *
	 * @property
	 * @type String
	 */
    REMOVEFROMCALENDAR: 'removeFromCalendarButton',

    /**
	 * Preserve name for NoResponseRequiredButton.
	 *
	 * @property
	 * @type String
	 */
    NORESPONSE: 'noResponseButton',

    /**
	 * Preserve name for AcceptButton.
	 *
	 * @property
	 * @type String
	 */
    ACCEPT: 'acceptButton',

    /**
	 * Preserve name for TentativeButton.
	 *
	 * @property
	 * @type String
	 */
    TENTATIVE: 'tentativeButton',

    /**
	 * Preserve name for DeclineButton.
	 *
	 * @property
	 * @type String
	 */
    DECLINE: 'declineButton',

    /**
	 * Preserve name for ProposeNewTimeButton.
	 *
	 * @property
	 * @type String
	 */
    PROPOSENEWTIME: 'proposeNewTimeButton',

    /**
	 * Preserve name for ViewAllProposalsButton.
	 *
	 * @property
	 * @type String
	 */
    VIEWPROPOSALS: 'viewProposalsButton',

    /**
	 * Preserve name for AcceptProposalButton.
	 *
	 * @property
	 * @type String
	 */
    ACCEPTPROPOSAL: 'acceptProposalButton',

    /**
	 * Preserve name for calendarButton.
	 *
	 * @property
	 * @type String
	 */
    CALENDAR: 'calendarButton',

    /**
	 * Preserve name for ForwardButton.
	 *
	 * @property
	 * @type String
	 */
    FORWARD: 'forwardButton'
});
Ext.namespace('Zarafa.calendar.data');

/**
 * @class Zarafa.calendar.data.SnapModes
 * @extends Zarafa.core.Enum
 *
 * Enum containing the different snap modes of the calendar context.
 *
 * @singleton
 */
Zarafa.calendar.data.SnapModes = Zarafa.core.Enum.create({
	/**
	 * Preserve current time.
	 *
	 * @property
	 * @type String
	 */
	NONE: 1,

	/**
	 * All selections should snap to the entire day.
	 *
	 * @property
	 * @type String
	 */
	DAY: 2,

	/**
	 * All selections should snap to the smallest time which is
	 * supported by the current zoomlevel.
	 *
	 * @property
	 * @type String
	 */
	ZOOMLEVEL: 3
});
Ext.namespace('Zarafa.calendar.data');

/**
 * @class Zarafa.calendar.data.ViewModes
 * @extends Zarafa.core.Enum
 *
 * Enum containing the different viewing modes of the calendar context.
 *
 * @singleton
 */
Zarafa.calendar.data.ViewModes = Zarafa.core.Enum.create({
	/**
	 * View all appointments for a given day(s) from the selected folder(s)
	 * inside the Days view (every day has its own column).
	 *
	 * @property
	 * @type Number
	 */
	DAYS: 0,
	/**
	 * View all appointments for a given day(s) from the selected folder(s)
	 * inside the Box view (every day is a Box within a table).
	 *
	 * @property
	 * @type Number
	 */
	BOX: 1,
	/**
	 * View all appointments for a given period in a simple list.
	 * @property
	 * @type Number
	 */
	LIST: 2,
	/**
	 * View the search results for the appointments in a simple list.
	 * @property
	 * @type Number
	 */
	SEARCH: 3
});
Ext.namespace('Zarafa.calendar.data');

/**
 * @class Zarafa.calendar.data.Views
 * @extends Zarafa.core.Enum
 *
 * Enum containing the different views of the calendar context.
 *
 * @singleton
 */
Zarafa.calendar.data.Views = Zarafa.core.Enum.create({
	/**
	 * View all appointments for a given day(s) from the selected folder(s)
	 * inside blocks view.
	 *
	 * @property
	 * @type Number
	 */
	BLOCKS: 0,
	/**
	 * View all appointments for a given day(s) from the selected folder(s)
	 * inside the grid view.
	 *
	 * @property
	 * @type Number
	 */
	LIST: 1,
	/**
	 * View all found appointments in the selected folder(s)
	 * inside the grid view.
	 *
	 * @property
	 * @type Number
	 */
	SEARCH: 2
});
Ext.namespace('Zarafa.calendar.data.busytime');

/**
 * @class Zarafa.calendar.data.busytime.BusyTimeProxy
 * @extends Zarafa.core.data.MAPIProxy
 */
Zarafa.calendar.data.busytime.BusyTimeProxy = Ext.extend(Zarafa.core.data.MAPIProxy,
{
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			listModuleName: Zarafa.core.ModuleNames.getListName('busytime')
		});

		Zarafa.calendar.data.busytime.BusyTimeProxy.superclass.constructor.call(this, config);
	},

	/**
	 * This will create a {@link Zarafa.core.data.ProxyResponseHandler ProxyResponseHandler} object
	 * which will be used by the {@link Zarafa.core.data.ResponseRouter ResponseRouter} when the
	 * response for the given request has returned.
	 *
	 * @param {String} modulename The modulename which is being accessed with this request
	 * @param {Zarafa.core.Actions} serverAction The action to perform on the server.
	 * @param {Ext.data.Api.action} action name of the action to perform.
	 * @param {Ext.data.Record[]} records list of records to operate on.
	 * @param {Object} parameters object containing user parameters such as range (pagination) information, sorting information, etc.
	 * @param {Ext.data.DataReader} reader data reader. Converts raw JavaScript objects (in our case) to instances of {@link Ext.data.Record}
	 * @param {Function} callback call back function to call when the request has finished successfully.
	 * @param {Object} scope scope for the call back function.
	 * @param {Object} args arguments object. This will be passed to the call back function on successful read.
	 * @return {Object} An instance of the {@link Zarafa.core.data.ProxyResponseHandler ProxyResponseHandler}
	 * which should be used for this request.
	 * @private
	 */
	getResponseHandlerForRequest: function(modulename, serverAction, action, records, parameters, reader, callback, scope, args)
	{
		return new Zarafa.calendar.data.busytime.BusyTimeResponseHandler({
			proxy: this,
			action: action,
			reader: reader,
			options: args,
			callback: callback,
			scope: scope
		});
	}
});
Ext.namespace('Zarafa.calendar.data.busytime');

/**
 * @class Zarafa.calendar.data.busytime.BusyTimeStore
 * @extends Zarafa.core.data.MAPIStore
 * @xtype zarafa.busytimestore
 */
Zarafa.calendar.data.busytime.BusyTimeStore = Ext.extend(Zarafa.core.data.MAPIStore,
{
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			batch: true,
			autoSave: false,
			remoteSort: false,
			proxy: new Zarafa.calendar.data.busytime.BusyTimeProxy(),
			reader: new Ext.data.JsonReader({
				root: 'item'
			}, Zarafa.calendar.data.busytime.BusyTimeRecord)
		});
		Zarafa.calendar.data.busytime.BusyTimeStore.superclass.constructor.call(this, config);
	},

	/**
	 * Clear all data in the store
	 * @private
	 */
	clearData: function()
	{
		// Skip the superclass implementation.
		Zarafa.core.data.MAPIStore.superclass.clearData.apply(this, arguments);
	}
});

Ext.reg('zarafa.busytimestore', Zarafa.calendar.data.busytime.BusyTimeStore);
Ext.namespace('Zarafa.calendar.dialogs');

/**
 * @class Zarafa.calendar.dialogs.ProposeNewTimeContentPanel
 * @extends Zarafa.core.ui.ContentPanel
 * @xtype zarafa.proposenewtimecontentpanel
 */
Zarafa.calendar.dialogs.ProposeNewTimeContentPanel = Ext.extend(Zarafa.core.ui.ContentPanel, {
	/**
	 * @cfg {Zarafa.core.ui.IPMRecord} record The record for which the
	 * propose new time panel is opened.
	 */
	record: undefined,

	/**
	 * @cfg {Boolean} autoSave Automatically save all changes on the
	 * {@link Zarafa.core.data.IPMRecord IPMRecord} to the
	 * {@link Zarafa.core.data.IPMStore IPMStore}.
	 */
	constructor: function(config)
	{
		config = config || {};

		config = Ext.applyIf(config, {
			// Override from Ext.Component
			xtype: 'zarafa.proposenewtimecontentpanel',
			layout: 'fit',
			title: _('Propose New Time'),
			width: 375,
			height: 200,
			recordComponentPluginConfig: Ext.applyIf(config.recordComponentPluginConfig || {}, {
				allowWrite: true
			}),
			items: [{

				xtype: 'zarafa.proposenewtimepanel',
				record: config.record,
				ref: 'proposeNewTimePanel',
				buttons: [{
					text: _('Ok'),
					handler: this.onOk,
					scope: this
				},{
					text: _('Cancel'),
					handler: this.onCancel,
					scope: this
				}]
			}]
		});

		Zarafa.calendar.dialogs.ProposeNewTimeContentPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Event handler which is raised when the user clicks the "Ok" {@link Ext.Button button}
	 * @private
	 */
	onOk: function()
	{
		this.proposeNewTimePanel.updateRecord(this.record);
		this.close();
	},

	/**
	 * Event handler which is raised when the user clicks the "Cancel" {@link Ext.Button button}
	 *
	 * This will close the panel.
	 * @private
	 */
	onCancel: function()
	{
		this.close();
	}
});

Ext.reg('zarafa.proposenewtimecontentpanel', Zarafa.calendar.dialogs.ProposeNewTimeContentPanel);
Ext.namespace('Zarafa.calendar.dialogs');

/**
 * @class Zarafa.calendar.dialogs.SendMeetingRequestCancellationContentPanel
 * @extends Zarafa.core.ui.ContentPanel
 * @xtype zarafa.sendmeetingrequestcancellationcontentpanel
 */
Zarafa.calendar.dialogs.SendMeetingRequestCancellationContentPanel = Ext.extend(Zarafa.core.ui.ContentPanel, {
	/**
	 * @cfg {Zarafa.core.ui.IPMRecord} record The record for which the
	 * cancellation content panel is opened.
	 */
	record: undefined,

	/**
	 * @cfg {Boolean} autoSave Automatically save all changes on the
	 * {@link Zarafa.core.data.IPMRecord IPMRecord} to the
	 * {@link Zarafa.core.data.IPMStore IPMStore}.
	 */
	constructor: function(config)
	{
		config = config || {};

		config = Ext.applyIf(config, {
			// Override from Ext.Component
			xtype: 'zarafa.sendmeetingrequestcancellationcontentpanel',
			layout: 'fit',
			title: _('Send Meeting Request Cancellation'),
			modal: true,
			width: 350,
			height: 250,
			items: [{
				xtype: 'zarafa.sendmeetingrequestcancellationpanel',
				record: config.record,
				ref: 'sendMRCancellationPanel',
				buttons: [{
					text: _('Ok'),
					handler: this.onOk,
					scope: this
				},{
					text: _('Cancel'),
					handler: this.onCancel,
					scope: this
				}]
			}]
		});

		Zarafa.calendar.dialogs.SendMeetingRequestCancellationContentPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Event handler which is raised when the user clicks the "Ok" {@link Ext.Button button}
	 * @private
	 */
	onOk: function()
	{
		this.sendMRCancellationPanel.updateRecord(this.record);
		this.close();
	},

	/**
	 * Event handler which is raised when the user clicks the "Cancel" {@link Ext.Button button}
	 *
	 * This will close the panel.
	 * @private
	 */
	onCancel: function()
	{
		this.close();
	}
});

Ext.reg('zarafa.sendmeetingrequestcancellationcontentpanel', Zarafa.calendar.dialogs.SendMeetingRequestCancellationContentPanel);
Ext.namespace('Zarafa.calendar.dialogs');

/**
 * @class Zarafa.calendar.dialogs.SendMeetingRequestConfirmationContentPanel
 * @extends Zarafa.core.ui.ContentPanel
 * @xtype zarafa.sendmeetingrequestconfirmcontentpanel
 */
Zarafa.calendar.dialogs.SendMeetingRequestConfirmationContentPanel = Ext.extend(Zarafa.core.ui.ContentPanel, {
	/**
	 * @cfg {Zarafa.core.ui.IPMRecord} record The record for which the
	 * propose new time content panel is opened.
	 */
	record: undefined,

	/**
	 * @cfg {Zarafa.core.mapi.ResponseStatus} responseType The response type value selected by user.
	 */
	responseType: undefined,

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

		config = Ext.applyIf(config, {
			// Override from Ext.Component
			xtype: 'zarafa.sendmeetingrequestconfirmcontentpanel',
			layout: 'fit',
			title: _('Send Meeting Request Confirmation'),
			modal: true,
			width: 350,
			height: 250,
			items: [{
				xtype: 'zarafa.sendmeetingrequestconfirmpanel',
				record: config.record,
				responseType: config.responseType,
				ref: 'sendMRConfirmationPanel',
				buttons: [{
					text: _('Ok'),
					handler: this.onOk,
					scope: this
				},{
					text: _('Cancel'),
					handler: this.onCancel,
					scope: this
				}]
			}]
		});

		Zarafa.calendar.dialogs.SendMeetingRequestConfirmationContentPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Event handler which is raised when the user clicks the "Ok" {@link Ext.Button button}
	 * @private
	 */
	onOk: function()
	{
		/*
		 * If user wants to perform an action (accept/decline/propose new time/ tentatively accept) on whole
		 * meeting request then we have to remove base date from record because if record contains basedate then it
		 * will be treated as exception on server side and requested operation carried out on single occurrence.
		 */
		if(this.record.isRecurringOccurrence() && Ext.isDefined(this.buttonName) && this.buttonName === 'recurring') {
			this.record.removeIdProp('basedate');
			this.record.set('basedate', '');
		}
		this.sendMRConfirmationPanel.updateRecord(this.record);
		this.close();
	},

	/**
	 * Event handler which is raised when the user clicks the "Cancel" {@link Ext.Button button}
	 *
	 * This will close the panel.
	 * @private
	 */
	onCancel: function()
	{
		this.close();
	}
});

Ext.reg('zarafa.sendmeetingrequestconfirmcontentpanel', Zarafa.calendar.dialogs.SendMeetingRequestConfirmationContentPanel);
// -*- coding: utf-8; indent-tabs-mode: nil -*-
Ext.namespace('Zarafa.calendar.printer');

/**
 * @class Zarafa.calendar.printer.AbstractViewRenderer
 * @extends Zarafa.common.printer.renderers.BaseRenderer
 *
 * An abstract class which is used by the {@link Zarafa.calendar.printer.DaysViewRenderer DaysViewRenderer} and
 * {@link Zarafa.calendar.printer.MonthViewRenderer MonthViewRenderer} to render the layout for the print appointment.
 */
Zarafa.calendar.printer.AbstractViewRenderer = Ext.extend(Zarafa.common.printer.renderers.BaseRenderer, {
	/**
	 * @property customStylesheetPath
	 * @type Array of Strings
	 * The paths at which the print stylesheets can be found for a specific renderer
	 */
	customStylesheetPath: 'client/resources/css/external/print.calendar.css',

	/**
	 * @cfg {String} timeStyle The style attribute which must be applied to  the
	 * &lt;td&gt; element containing the timestamp for the appointment
	 */
	timeStyle: '',

	/**
	 * Add additional rendering into the newly created dom tree containing the processed template
	 *
	 * @param {Document} printDOM DOM containing processed print template
	 * @param {Zarafa.calendar.CalendarContextModel} context calendar context to render for printing
	 */
	postRender: function(printDOM, context)
	{
		var model = context.getModel();
		if(model.getCurrentDataMode() === Zarafa.calendar.data.DataModes.MONTH) {
			var daterange = model.getActiveDateRange();
			var left = daterange.getStartDate().clone();
			var right = daterange.getDueDate().clone();
		} else {
			var daterange = model.dateRange;
			var left = daterange.getStartDate().clone();
			var right = daterange.getDueDate().clone();

			right.setMonth(right.getMonth()+1);
		}

		/*
		 * Create the left and right date picker
		 */
		new Zarafa.calendar.ui.DatePicker({
			renderTo: printDOM.getElementById('datepicker_left'),
			value: left,
			showToday: false
		});

		new Zarafa.calendar.ui.DatePicker({
			renderTo: printDOM.getElementById('datepicker_right'),
			value: right,
			showToday: false
		});
	}
});
Ext.namespace('Zarafa.calendar.settings');

/**
 * @class Zarafa.calendar.settings.SettingsCalendarCategory
 * @extends Zarafa.settings.ui.SettingsCategory
 * @xtype zarafa.settingscalendarcategory
 *
 * The calendar category for users which will
 * allow the user to configure Calendar related settings
 */
Zarafa.calendar.settings.SettingsCalendarCategory = Ext.extend(Zarafa.settings.ui.SettingsCategory, {
	// Insertion points for this class
	/**
	 * @insert context.settings.category.calendar
	 * Insertion point to register new {@link Zarafa.settings.ui.SettingsWidget widgets}
	 * for the {@link Zarafa.calendar.settings.SettingsCalendarCategory Calendar Category}.
	 * @param {Zarafa.calendar.settings.SettingsCalendarCategory} category The mail
	 * category to which the widgets will be added.
	 */

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

		Ext.applyIf(config, {
			title: _('Calendar'),
			categoryIndex: 4,
			iconCls: 'zarafa-settings-category-calendar',
			items: [{
				xtype: 'zarafa.settingscalendarwidget'
			},{
				xtype: 'zarafa.settingsoverlaywidget'
			},{
				xtype: 'zarafa.settingsreminderwidget'
			},
			container.populateInsertionPoint('context.settings.category.calendar', this)
			]
		});

		Zarafa.calendar.settings.SettingsCalendarCategory.superclass.constructor.call(this, config);
	}
});

Ext.reg('zarafa.settingscalendarcategory', Zarafa.calendar.settings.SettingsCalendarCategory);
Ext.namespace('Zarafa.calendar.settings');

/**
 * @class Zarafa.calendar.settings.SettingsCalendarWidget
 * @extends Zarafa.settings.ui.SettingsWidget
 * @xtype zarafa.settingscalendarwidget
 *
 * The {@link Zarafa.settings.ui.SettingsWidget widget} for configuring
 * the general calendar options in the {@link Zarafa.calendar.settings.SettingsCalendarCategory calendar category}.
 */
Zarafa.calendar.settings.SettingsCalendarWidget = Ext.extend(Zarafa.settings.ui.SettingsWidget, {

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

		var dayData = [];
		for (var i = 0, len = Date.dayNames.length; i < len; i++) {
			dayData.push({
				name: Date.dayNames[i],
				value: i
			});
		}

		var dayStore = {
			xtype: 'jsonstore',
			fields: [ 'name', 'value' ],
			data: dayData
		};

		var zoomStore = {
			xtype: 'jsonstore',
			fields: [ 'name', 'value' ],
			data: [{
				value: 5,
				name: Ext.util.Format.duration(5)
			},{
				value: 6,
				name: Ext.util.Format.duration(6)
			},{
				value: 10,
				name: Ext.util.Format.duration(10)
			},{
				value: 15,
				name: Ext.util.Format.duration(15)
			},{
				value: 30,
				name: Ext.util.Format.duration(30)
			},{
				value: 60,
				name: Ext.util.Format.duration(60)
			}]
		};

		Ext.applyIf(config, {
			title: _('General calendar settings'),
			layout: 'form',
			labelWidth: 250,
			items: [{
				xtype: 'combo',
				ref: 'weekStartCombo',
				fieldLabel: _('First day of the week'),
				name: 'zarafa/v1/main/week_start',
				store: dayStore,
				mode: 'local',
				triggerAction: 'all',
				displayField: 'name',
				valueField: 'value',
				lazyInit: false,
				autoSelect: true,
				forceSelection: true,
				editable: false,
				listeners: {
					select: this.onFieldSelect,
					scope: this
				}
			},{
				xtype: 'zarafa.timeperiodfield',
				ref: 'workingHourPeriod',
				labelWidth: 250,
				defaultValue: new Zarafa.core.DateRange(),
				defaultPeriod: 1,
				defaultPeriodType: Date.HOUR,
				startFieldConfig: {
					fieldLabel: _('Start of workday'),
					name: 'zarafa/v1/main/start_working_hour',
					incrementValue: 30,
					alternateIncrementValue: 1,
					minValue: new Date().clearTime(),
					maxValue: new Date().clearTime().add(Date.HOUR, 23)
				},
				endFieldConfig: {
					fieldLabel: _('End of workday'),
					name: 'zarafa/v1/main/end_working_hour',
					incrementValue: 30,
					alternateIncrementValue: 1,
					minValue: new Date().clearTime().add(Date.HOUR, 1),
					maxValue: new Date().clearTime().add(Date.HOUR, 24)
				},
				listeners: {
					change :this.onWorkTimeChange,
					scope: this
				}
			},{
				xtype: 'combo',
				ref: 'zoomLevelCombo',
				fieldLabel: _('Calendar resolution'),
				name: 'zarafa/v1/contexts/calendar/default_zoom_level',
				store: zoomStore,
				mode: 'local',
				triggerAction: 'all',
				displayField: 'name',
				valueField: 'value',
				lazyInit: false,
				autoSelect: true,
				forceSelection: true,
				editable: false,
				listeners: {
					select: this.onFieldSelect,
					scope: this
				}
			},{
				xtype: 'zarafa.compositefield',
				plugins: [{
					ptype: 'zarafa.splitfieldlabeler',
					firstLabelCfg: {
						style: 'text-align: left; padding: 3px 3px 3px 0px',
						width: 255
					}
				}],
				combineErrors: false,
				fieldLabel: _('Default appointment duration {A} minutes'),
				items: [{
					xtype: 'zarafa.spinnerfield',
					plugins: [ 'zarafa.numberspinner' ],
					ref: '../durationSpinner',
					name: 'zarafa/v1/contexts/calendar/default_appointment_period',
					labelSplitter: '{A}',
					allowBlank: false,
					allowDecimals: false,
					allowNegative: false,
					defaultValue: 30,
					minValue: 1,
					maxValue: 120,
					width: 60,
					validator: function(value) {
						if(value >= 1 && value <= 120) {
							return true;
						}
						return _('Duration must be between 1 and 120 minutes.');
					},
					listeners: {
						change: this.onDurationChange,
						scope: this
					}
				}]
			},{
				xtype: 'combo',
				ref: 'busyStatusCombo',
				fieldLabel: _('Default status for all day appointment'),
				name: 'zarafa/v1/contexts/calendar/default_allday_busy_status',
				store: {
					xtype: 'jsonstore',
					fields: [ 'name', 'value' ],
					data: Zarafa.calendar.data.BusyStatus
				},
				mode: 'local',
				triggerAction: 'all',
				displayField: 'name',
				valueField: 'value',
				lazyInit: false,
				autoSelect: true,
				forceSelection: true,
				editable: false,
				listeners: {
					select: this.onFieldSelect,
					scope: this
				}
			},{
				xtype: 'checkboxgroup',
				ref: 'workingDays',
				fieldLabel: _('Working days'),
				name: 'zarafa/v1/main/working_days',
				width: 225,
				columns: 4,
				items: [
				{
					boxLabel: _('Mon'),
					name: 'day-col-1',
					inputValue: 1
				},{
					boxLabel: _('Tue'),
					name: 'day-col-2',
					inputValue: 2
				},{
					boxLabel: _('Wed'),
					name: 'day-col-3',
					inputValue: 3
				},{
					boxLabel: _('Thu'),
					name: 'day-col-4',
					inputValue: 4
				},{
					boxLabel: _('Fri'),
					name: 'day-col-5',
					inputValue: 5
				},{
					boxLabel: _('Sat'),
					name: 'day-col-6',
					inputValue: 6
				},{
					boxLabel: _('Sun'),
					name: 'day-col-0',
					inputValue: 0
				}]
			},{
				xtype: 'checkbox',
				ref: 'boldCheck',
				boxLabel: _('Show busy dates as bold in the date picker'),
				hideLabel: true,
				name: 'zarafa/v1/contexts/calendar/datepicker_show_busy',
				listeners: {
					check: this.onBoldCheck,
					scope: this
				}
			}]
		});

		Zarafa.calendar.settings.SettingsCalendarWidget.superclass.constructor.call(this, config);
	},

	/**
	 * Called by the {@link Zarafa.settings.ui.SettingsCategory Category} when
	 * it has been called with {@link zarafa.settings.ui.SettingsCategory#update}.
	 * This is used to load the latest version of the settings from the
	 * {@link Zarafa.settings.SettingsModel} into the UI of this category.
	 * @param {Zarafa.settings.SettingsModel} settingsModel The settings to load
	 */
	update: function(settingsModel)
	{
		this.model = settingsModel;

		// Convert the start of workday from minutes to an actual time
		var startTime = new Date().clearTime();
		startTime = startTime.add(Date.MINUTE, settingsModel.get(this.workingHourPeriod.startField.name));
		// Convert the end of workday from minutes to an actual time
		var endTime = new Date().clearTime();
		endTime = endTime.add(Date.MINUTE, settingsModel.get(this.workingHourPeriod.endField.name));
		this.workingHourPeriod.getValue().set(startTime, endTime);

		this.weekStartCombo.setValue(settingsModel.get(this.weekStartCombo.name));
		this.zoomLevelCombo.setValue(settingsModel.get(this.zoomLevelCombo.name));
		this.busyStatusCombo.setValue(settingsModel.get(this.busyStatusCombo.name));
		this.boldCheck.setValue(settingsModel.get(this.boldCheck.name));

		this.durationSpinner.setValue(settingsModel.get(this.durationSpinner.name));

		// checkboxgroup.setValue() takes an object with name property as key and boolean values to set the checkboxes.
		// The workingDaysList contains a list of days which are enabled by the user.
		// Sunday 0, Monday 1, Tuesday 2, Wednesday 3, Thursday 4, Friday 5, Saturday 6
		var workingDaysList = this.model.get(this.workingDays.name);
		var dayList = {};
		for (var i = 0; i <= 6; i++) {
			if(workingDaysList.indexOf(i) > -1 ) {
				dayList['day-col-' + i] = true;
			} else {
				dayList['day-col-' + i] = false;
			}
		}
		this.workingDays.setValue(dayList);

		// Hack! @FIXME
		// Change event needs to be registered after the values are configured into the corresponding check box.
		// Because change event will be fired for every checkbox providing cumulative values, so we failed to figure out
		// if there is any actual change performed by user.
		this.mon(this.workingDays, 'change', this.onWorkingDaysChange, this, { single: true });
	},

	/**
	 * Called by the {@link Zarafa.settings.ui.SettingsCategory Category} when
	 * it has been called with {@link zarafa.settings.ui.SettingsCategory#updateSettings}.
	 * This is used to update the settings from the UI into the {@link Zarafa.settings.SettingsModel settings model}.
	 * @param {Zarafa.settings.SettingsModel} settingsModel The settings to update
	 */
	updateSettings: function(settingsModel)
	{
		var duration = this.durationSpinner.getValue();
		if (!this.durationSpinner.isValid()) {
			if(Ext.isEmpty(duration) || duration === 0) {
				duration = settingsModel.get(this.durationSpinner.name, false, true);
			} else {
				duration = this.durationSpinner.maxValue;
			}
		}

		settingsModel.beginEdit();
		this.onWorkTimeChange(this.workingHourPeriod, this.workingHourPeriod.getValue());
		settingsModel.set(this.weekStartCombo.name, this.weekStartCombo.getValue());
		settingsModel.set(this.zoomLevelCombo.name, this.zoomLevelCombo.getValue());
		settingsModel.set(this.busyStatusCombo.name, this.busyStatusCombo.getValue());
		settingsModel.set(this.boldCheck.name, this.boldCheck.getValue());
		settingsModel.set(this.durationSpinner.name, duration);
		settingsModel.set(this.workingDays.name, Ext.pluck(this.workingDays.getValue(), 'inputValue'));
		settingsModel.endEdit();
	},

	/**
	 * Event handler which is called when a duration was changed
	 * from {@link Zarafa.common.ui.SpinnerField duration} spinner field.
	 *
	 * @param {Zarafa.common.ui.SpinnerField} field The spinner field which holds
	 * default duration for the appointment
	 * @param {Number} newValue The new value for default duration for the appointments
	 */
	onDurationChange: function(field, newValue)
	{
		if (this.model && this.model.get(field.name) !== newValue) {
			this.model.set(field.name, newValue);
		}

	},

	/**
	 * Event handler which is called when a selection has been made in the
	 * {@link Ext.form.ComboBox combobox}.
	 * @param {Ext.form.ComboBox} field The field which fired the event
	 * @param {Ext.data.Record} record The selected record
	 * @private
	 */
	onFieldSelect: function(field, record)
	{
		if (this.model) {
			var set = record.get(field.valueField);

			// FIXME: The settings model should be able to detect if
			// a change was applied
			if (this.model.get(field.name) !== set) {
				this.model.set(field.name, set);
			}
		}
	},

	/**
	 * Event handler which is fired when the {@link Zarafa.common.ui.TimePeriodField}
	 * fires the {@link Zarafa.common.ui.TimePeriodField#change change} event.
	 * @param {Zarafa.common.ui.TimePeriodField} field The field which fired the event
	 * @param {Zarafa.core.DateRange} newValue The new value which is set on the field
	 * @param {Zarafa.core.DateRange} oldValue the old value which was previously set
	 * @private
	 */
	onWorkTimeChange: function(field, newValue, oldValue)
	{
		if (this.model) {
			// Convert the start of workday from date to number
			var startTime = newValue.getStartDate();
			startTime = (startTime.getHours() * 60) + startTime.getMinutes();
			var endTime = newValue.getDueDate();
			endTime = (endTime.getHours() * 60) + endTime.getMinutes();

			// Compensation in case the user managed to get a end time before the start time.
			if (endTime < startTime) {
				endTime = startTime + (this.workingHourPeriod.defaultPeriod * 60);
			}

			this.model.set(this.workingHourPeriod.startField.name, startTime);
			this.model.set(this.workingHourPeriod.endField.name, endTime);
		}
	},

	/**
	 * event handler which is fired when the "Bold days in datepicker" checkbox has been clicked.
	 * @param {Ext.form.ComboBox} field The field which fired the event
	 * @param {Ext.data.Record} record The selected record
	 * @private
	 */
	onBoldCheck: function(field, check)
	{
		if (this.model) {
			// FIXME: The settings model should be able to detect if
			// a change was applied
			if (this.model.get(field.name) !== check) {
				this.model.set(field.name, check);
			}
		}
	},

	/**
	 * Event handler which is fired when a checkbox in the "working days" checkboxgroup has been clicked
	 * @param {Ext.form.CheckboxGroup} field the field is {@link Ext.form.CheckboxGroup checkbox}
	 * @param {Array} checkedArray the checkedArray containing the checked boxes
	 * @private
	 */
	onWorkingDaysChange: function(field, checkedArray)
	{
		if (this.model) {
			this.model.set(this.workingDays.name, Ext.pluck(this.workingDays.getValue(), 'inputValue'));
		}
	}
});

Ext.reg('zarafa.settingscalendarwidget', Zarafa.calendar.settings.SettingsCalendarWidget);
Ext.namespace('Zarafa.calendar.settings');

/**
 * @class Zarafa.calendar.settings.SettingsOverlayWidget
 * @extends Zarafa.settings.ui.SettingsWidget
 * @xtype zarafa.settingsoverlaywidget
 *
 * The {@link Zarafa.settings.ui.SettingsWidget widget} for configuring
 * the calendar overlay behavior in the {@link Zarafa.calendar.settings.SettingsCalendarCategory calendar category}.
 */
Zarafa.calendar.settings.SettingsOverlayWidget = Ext.extend(Zarafa.settings.ui.SettingsWidget, {

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

		var overlayStore = {
			xtype: 'jsonstore',
			fields: [ 'name', 'value' ],
			data: [{
				name: _('Side-by-side mode'),
				value: 'separate'
			},{
				name: _('Overlay mode'),
				value: 'merge'
			}]
		};

		Ext.applyIf(config, {
			title: _('Calendar view settings'),
			layout: 'form',
			items: [{
				xtype: 'combo',
				ref: 'overlayCombo',
				fieldLabel: _('View multiple calendars in'),
				name: 'zarafa/v1/contexts/calendar/default_merge_state',
				store: overlayStore,
				mode: 'local',
				triggerAction: 'all',
				displayField: 'name',
				valueField: 'value',
				lazyInit: false,
				autoSelect: true,
				forceSelection: true,
				editable: false,
				listeners: {
					select: this.onOpenMethodSelect,
					scope: this
				}
			}]
		});

		Zarafa.calendar.settings.SettingsCalendarWidget.superclass.constructor.call(this, config);
	},

	/**
	 * Called by the {@link Zarafa.settings.ui.SettingsCategory Category} when
	 * it has been called with {@link zarafa.settings.ui.SettingsCategory#update}.
	 * This is used to load the latest version of the settings from the
	 * {@link Zarafa.settings.SettingsModel} into the UI of this category.
	 * @param {Zarafa.settings.SettingsModel} settingsModel The settings to load
	 */
	update: function(settingsModel)
	{
		this.model = settingsModel;

		var overlay = settingsModel.get(this.overlayCombo.name);
		this.overlayCombo.setValue(overlay ? 'merge' : 'separate');
	},

	/**
	 * Called by the {@link Zarafa.settings.ui.SettingsCategory Category} when
	 * it has been called with {@link zarafa.settings.ui.SettingsCategory#updateSettings}.
	 * This is used to update the settings from the UI into the {@link Zarafa.settings.SettingsModel settings model}.
	 * @param {Zarafa.settings.SettingsModel} settingsModel The settings to update
	 */
	updateSettings: function(settingsModel)
	{
		settingsModel.set(this.overlayCombo.name, this.overlayCombo.getValue() === 'merge');
	},

	/**
	 * Event handler which is called when a selection has been made in the
	 * {@link Ext.form.ComboBox combobox}.
	 * @param {Ext.form.ComboBox} field The field which fired the event
	 * @param {Ext.data.Record} record The selected record
	 * @private
	 */
	onOpenMethodSelect: function(field, record)
	{
		if (this.model) {
			var set = record.get(field.valueField);

			// FIXME: The settings model should be able to detect if
			// a change was applied
			if (this.model.get(field.name) !== set) {
				this.model.set(field.name, set === 'merge');
			}
		}
	}
});

Ext.reg('zarafa.settingsoverlaywidget', Zarafa.calendar.settings.SettingsOverlayWidget);
Ext.namespace('Zarafa.calendar.settings');

/**
 * @class Zarafa.calendar.settings.SettingsReminderWidget
 * @extends Zarafa.settings.ui.SettingsWidget
 * @xtype zarafa.settingsreminderwidget
 *
 * The {@link Zarafa.settings.ui.SettingsWidget widget} for configuring
 * the reminder options for new {@link Zarafa.calendar.AppointmentRecord appointments}.
 */
Zarafa.calendar.settings.SettingsReminderWidget = Ext.extend(Zarafa.settings.ui.SettingsWidget, {

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

		Ext.applyIf(config, {
			title: _('Reminder settings'),
			layout: 'form',
			items: [{
				xtype: 'checkbox',
				ref: 'reminderCheck',
				boxLabel: _('Enable reminder for new appointments'),
				hideLabel: true,
				name: 'zarafa/v1/contexts/calendar/default_reminder',
				listeners: {
					check: this.onReminderCheck,
					scope: this
				}
			},{
				xtype: 'combo',
				ref: 'timeCombo',
				labelWidth: 275,
				fieldLabel: _('Default reminder time'),
				name: 'zarafa/v1/contexts/calendar/default_reminder_time',
				store: {
					xtype: 'jsonstore',
					fields: ['name', 'value'],
					data: Zarafa.calendar.data.ReminderPeriods
				},
				mode: 'local',
				triggerAction: 'all',
				displayField: 'name',
				valueField: 'value',
				lazyInit: false,
				autoSelect: true,
				forceSelection: true,
				editable: false,
				listeners: {
					select: this.onFieldSelect,
					scope: this
				}
			},{
				xtype: 'combo',
				ref: 'alldayTimeCombo',
				labelWidth: 275,
				fieldLabel: _('Default all-day appointment reminder time'),
				name: 'zarafa/v1/contexts/calendar/default_allday_reminder_time',
				store: {
					xtype: 'jsonstore',
					fields: ['name', 'value'],
					data: Zarafa.calendar.data.ReminderPeriods
				},
				mode: 'local',
				triggerAction: 'all',
				displayField: 'name',
				valueField: 'value',
				lazyInit: false,
				autoSelect: true,
				forceSelection: true,
				editable: false,
				listeners: {
					select: this.onFieldSelect,
					scope: this
				}
			}]
		});

		Zarafa.calendar.settings.SettingsReminderWidget.superclass.constructor.call(this, config);
	},

	/**
	 * Called by the {@link Zarafa.settings.ui.SettingsCategory Category} when
	 * it has been called with {@link zarafa.settings.ui.SettingsCategory#update}.
	 * This is used to load the latest version of the settings from the
	 * {@link Zarafa.settings.SettingsModel} into the UI of this category.
	 * @param {Zarafa.settings.SettingsModel} settingsModel The settings to load
	 */
	update: function(settingsModel)
	{
		this.model = settingsModel;

		var enabled = settingsModel.get(this.reminderCheck.name);
		this.reminderCheck.setValue(enabled);
		this.timeCombo.setValue(settingsModel.get(this.timeCombo.name));
		this.alldayTimeCombo.setValue(settingsModel.get(this.alldayTimeCombo.name));

		this.timeCombo.setDisabled(!enabled);
		this.timeCombo.disableLabel(!enabled);
		this.alldayTimeCombo.setDisabled(!enabled);
		this.alldayTimeCombo.disableLabel(!enabled);
	},

	/**
	 * Called by the {@link Zarafa.settings.ui.SettingsCategory Category} when
	 * it has been called with {@link zarafa.settings.ui.SettingsCategory#updateSettings}.
	 * This is used to update the settings from the UI into the {@link Zarafa.settings.SettingsModel settings model}.
	 * @param {Zarafa.settings.SettingsModel} settingsModel The settings to update
	 */
	updateSettings: function(settingsModel)
	{
		settingsModel.beginEdit();
		settingsModel.set(this.reminderCheck.name, this.reminderCheck.getValue());
		settingsModel.set(this.timeCombo.name, this.timeCombo.getValue());
		settingsModel.set(this.alldayTimeCombo.name, this.alldayTimeCombo.getValue());
		settingsModel.endEdit();
	},

	/**
	 * Event handler which is fired when the "Enable reminder" checkbox has been clicked.
	 * @param {Ext.form.Checkbox} field The field which fired the event
	 * @param {Boolean} check True if the checkbox is currently checked
	 * @private
	 */
	onReminderCheck: function(field, check)
	{
		if (this.model) {
			// FIXME: The settings model should be able to detect if
			// a change was applied
			if (this.model.get(field.name) !== check) {
				this.model.set(field.name, check);
			}
		}

		this.timeCombo.setDisabled(!check);
		this.timeCombo.disableLabel(!check);
		this.alldayTimeCombo.setDisabled(!check);
		this.alldayTimeCombo.disableLabel(!check);
	},

	/**
	 * Event handler which is called when a selection has been made in the
	 * {@link Ext.form.ComboBox combobox}.
	 * @param {Ext.form.ComboBox} field The field which fired the event
	 * @param {Ext.data.Record} record The selected record
	 * @private
	 */
	onFieldSelect: function(field, record)
	{
		if (this.model) {
			var set = record.get(field.valueField);

			// FIXME: The settings model should be able to detect if
			// a change was applied
			if (this.model.get(field.name) !== set) {
				this.model.set(field.name, set);
			}
		}
	}
});

Ext.reg('zarafa.settingsreminderwidget', Zarafa.calendar.settings.SettingsReminderWidget);
Ext.namespace('Zarafa.calendar.ui');

/**
 * @class Zarafa.calendar.ui.AbstractCalendarView
 * @extends Zarafa.core.ui.View
 *
 * Base class for calendar views. It supports containing multiple {@link Zarafa.hierarchy.data.MAPIFolderRecord folders},
 * and can display the different folders side-by-side using {@link Zarafa.calendar.ui.CalendarTabView tabs}.
 *
 * TODO refactor the use of separate startDate, dueDate parameters in the events to use the DateRange object.
 */
Zarafa.calendar.ui.AbstractCalendarView = Ext.extend(Zarafa.core.ui.View, {
	/**
	 * @cfg {Zarafa.calendar.ui.DateRangeSelectionModel} rangeSelectionModel The selection model
	 * used for selecting a particular {@link Zarafa.core.DateRange daterange}.
	 */
	rangeSelectionModel: undefined,

	/**
	 * @cfg {Zarafa.calendar.ui.AppointmentSelectionModel} selectionModel The selection model
	 * used for selecting {@link Zarafa.core.data.IPMRecord records}.
	 */
	selectionModel: undefined,

	/**
	 * @cfg {Zarafa.calendar.CalendarContextModel} contextModel A reference to the context model of the parent view
	 */
	contextModel: undefined,

	/**
	 * The array of {@link Zarafa.hierarchy.data.MAPIFolderRecord folders} which are opened
	 * by this {@link Zarafa.core.ui.View view}.
	 * @property
	 * @type Array
	 */
	folders: undefined,

	/**
	 * The currently selected {@link Zarafa.hierarchy.data.MAPIFolderRecord folder} within
	 * this {@link Zarafa.core.ui.View view}. If set, this must be an element
	 * from the {@link #folders} array.
	 * @property
	 * @type Zarafa.hierarchy.data.MAPIFolderRecord
	 */
	selectedFolder: undefined,

	/**
	 * The array of {@link Zarafa.calendar.ui.CalendarTabView tabs} which
	 * are visible within this {@link Zarafa.core.ui.View view}. Each
	 * {@link Zarafa.hierarchy.data.MAPIFolderRecord folder} from the {@link #folders} array
	 * will have one {@link Zarafa.calendar.ui.CalendarTabView tab} assigned
	 * to it.
	 * @property
	 * @type Zarafa.calendar.ui.CalendarTabView
	 */
	tabs: undefined,

	/**
	 * The {@link Zarafa.calendar.ui.SelectionRangeView view} element which displays
	 * the currently selected period in this calendar {@link Zarafa.core.ui.View view}.
	 * @property
	 * @type Zarafa.calendar.ui.SelectionRangeView
	 */
	selectionView: undefined,

	/**
	 * The {@link Zarafa.calendar.ui.TextEditView view} which is enabled when the
	 * user starts typing after selecting a period. Using this view we add support
	 * for the quick-appointments which are placed into the calendar.
	 * @property
	 * @type Zarafa.calendar.ui.TextEditView
	 */
	textEditView: undefined,

	/**
	 * The array of {@link Zarafa.calendar.ui.AppointmentView appointments} which
	 * are visible within this calendar view.
	 * @property
	 * @type Array
	 */
	appointments: undefined,

	/**
	 * The offset all elements must have from the left side of the {@link #container}. This
	 * offset is configured before {@link #layout} by {@link #setLeftMargin}.
	 * @property
	 * @type Number
	 */
	leftOffset: 0,

	/**
	 * The total with for the tab to use. This must at least be the result of {@link #getMinimumWidth}.
	 * This is configured before {@link #layout} by {@link #setWidth}.
	 * @property
	 * @type Number
	 */
	width: 0,

	/**
	 * @cfg {Number} appointmentBodyLeftMargin The left margin which must be applied to
	 * the {@link Zarafa.calendar.ui.AppointmentView appointments} which are rendered within this
	 * calendar when the 'useMargin' argument to {@link #dateRangeToBodyBounds} is true.
	 */
	appointmentBodyLeftMargin: 0,

	/**
	 * @cfg {Number} appointmentBodyRightMargin The right margin which must be applied to
	 * the {@link Zarafa.calendar.ui.AppointmentView appointments} which are rendered within this
	 * calendar when the 'useMargin' argument to {@link #dateRangeToBodyBounds} is true.
	 */
	appointmentBodyRightMargin: 0,

	/**
	 * @cfg {Number} appointmentHeaderLeftMargins The left margin which must be applied to
	 * the {@link Zarafa.calendar.ui.AppointmentView appointments} which are rendered within
	 * the header of this calendar when the 'useMargin' argument to {@link #dateRangeToHeaderBounds} is true.
	 */
	appointmentHeaderLeftMargin: 6,

	/**
	 * @cfg {Number} appointmentHeaderRightMargins The right margin which must be applied to
	 * the {@link Zarafa.calendar.ui.AppointmentView appointments} which are rendered within
	 * the header of this calendar when the 'useMargin' argument to {@link #dateRangeToHeaderBounds} is true.
	 */
	appointmentHeaderRightMargin: 6,

	/**
	 * The body part of the calendar. This contains the view in which all {@link #appointments}
	 * are displayed. This field is created using {@link #createDiv} during {@link #render}.
	 * @property
	 * @type Ext.Element
	 */
	body: undefined,

	/**
	 * The header part of the calendar. This contains extra information (like the day, or weekday).
	 * This field is created using {@link #createDiv} during {@link #render}.
	 * @property
	 * @type Ext.Element
	 */
	header: undefined,

	/**
	 * The left border of the {@link #header}.
	 * This field is created using {@link #createDiv} during {@link #render}.
	 * @property
	 * @type Ext.Element
	 */
	headerBorderLeft: undefined,

	/**
	 * The right border of the {@link #header}.
	 * This field is created using {@link #createDiv} during {@link #render}.
	 * @property
	 * @type Ext.Element
	 */
	headerBorderRight: undefined,

	/**
	 * The left border of the {@link #body}.
	 * This field is created using {@link #createDiv} during {@link #render}.
	 * @property
	 * @type Ext.Element
	 */
	borderLeft: undefined,

	/**
	 * The right border of the {@link #body}.
	 * This field is created using {@link #createDiv} during {@link #render}.
	 * @property
	 * @type Ext.Element
	 */
	borderRight: undefined,

	/**
	 * The bottom border of the {@link #body}.
	 * This field is created using {@link #createDiv} during {@link #render}.
	 * @property
	 * @type Ext.Element
	 */
	borderBottom: undefined,

	/**
	 * The tab area in which the {@link #tabs} will be rendered.
	 * This field is created using {@link #createDiv} during {@link #render}.
	 * @property
	 * @type Ext.Element
	 */
	tabArea: undefined,

	/**
	 * @cfg {Boolean} enableDD true to enable drag & drop in both body and header
	 */
	enableDD: false,

	/**
	 * @cfg {Boolean} enableBodyDD true to enable drag and drop in the body
	 */
	enableBodyDD: false,

	/**
	 * @cfg {Boolean} enableHeaderDD true to enable drag and drop in the header
	 */
	enableHeaderDD: false,

	/**
	 * @cfg {Boolean} enableBodyDrag true to enable just drag in the body
	 */
	enableBodyDrag: false,

	/**
	 * @cfg {Boolean} enableHeaderDrag true to enable just drag in the header
	 */
	enableHeaderDrag: false,

	/**
	 * @cfg {Boolean} enableBodyDrop true to enable just drop in the body
	 */
	enableBodyDrop: false,

	/**
	 * @cfg {Boolean} enableHeaderDrop true to enable just drop in the body
	 */
	enableHeaderDrop: false,

	/**
	 * @cfg {String} ddGroup The DD group this TreePanel belongs to
	 */
	ddGroup: 'AppointmentDD',

	/**
	 * The DragZone which will be installed on the {@link #body}. This is initialized
	 * when either {@link #enableBodyDD} or {@link #enableBodyDrag} is enabled. It can optionally
	 * be configured using {@link #bodyDragConfig}.
	 * @property
	 * @type Zarafa.calendar.ui.CalendarViewDragZone
	 * @private
	 */
	bodyDragZone: undefined,

	/**
	 * @cfg {Object} bodyDragConfig Configuration object which will be used to initialize
	 * the {@link #bodyDragZone} when either {@link #enableBodyDD} or {@link #enableBodyDrag} is enabled.
	 */
	bodyDragConfig: undefined,

	/**
	 * The DropZone which will be installed on the {@link #body}. This is initialized
	 * when either {@link #enableBodyDD} or {@link #enableBodyDrop} is enabled. It can optionally
	 * be configured using {@link #bodyDropConfig}.
	 * @property
	 * @type Zarafa.calendar.ui.CalendarViewDropZone
	 * @private
	 */
	bodyDropZone: undefined,

	/**
	 * @cfg {Object} bodyDropConfig Configuration object which will be used to initialize
	 * the {@link #bodyDropZone} when either {@link #enableBodyDD} or {@link #enableBodyDrop} is enabled.
	 */
	bodyDropConfig: undefined,

	/**
	 * The DragZone which will be installed on the {@link #header}. This is initialized
	 * when either {@link #enableHeaderDD} or {@link #enableHeaderDrag} is enabled. It can optionally
	 * be configured using {@link #headerDragConfig}.
	 * @property
	 * @type Zarafa.calendar.ui.CalendarViewDragZone
	 * @private
	 */
	headerDragZone: undefined,

	/**
	 * @cfg {Object} headerDragConfig Configuration object which will be used to initialize
	 * the {@link #headerDragZone} when either {@link #enableHeaderDD} or {@link #enableHeaderDrag} is enabled.
	 */
	headerDragConfig: undefined,

	/**
	 * The DropZone which will be installed on the {@link #header}. This is initialized
	 * when either {@link #enableHeaderDD} or {@link #enableHeaderDrop} is enabled. It can optionally
	 * be configured using {@link #headerDropConfig}.
	 * @property
	 * @type Zarafa.calendar.ui.CalendarViewDropZone
	 * @private
	 */
	headerDropZone: undefined,

	/**
	 * @cfg {Object} headerDropConfig Configuration object which will be used to initialize
	 * the {@link #headerDropZone} when either {@link #enableHeaderDD} or {@link #enableHeaderDrop} is enabled.
	 */
	headerDropConfig: undefined,

	/**
	 * @cfg {String} groupId The unique identifier for the {@link Zarafa.core.MultiFolderContextModel#getGroupings group}
	 * which refers to this calendar and the group of {@link #folders}.
	 */
	groupId: undefined,

	/**
	 * @cfg {Boolean} active Flag whether this view is the currently active one inside the {@link Zarafa.calenaar.CalendarMultiView}
	 * There is one active group, but each group has a selected calendar in it
	 */
	active: false,

	/**
	 * The active tab stroke which will show stroke on top of the active calendar tabs
	 * This field is created using {@link #createDiv} during {@link #render}.
	 * @property
	 * @type Ext.Element
	 */
	activeTabStroke: undefined,

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

		Ext.applyIf(config, {
			baseCls: 'zarafa-calendar'
		});

		// define drag/drop events
		this.addEvents(
			/**
			 * @event appointmentcalendardrop
			 * Fires when an appointment is dragged from this calendar component onto another.
			 * @param {Zarafa.calendar.ui.AbstractCalendarDaysView} calendar The calendar which fired the event
			 * @param {Zarafa.calendar.ui.AbstractCalendarDaysView} source The calendar from where the appointment was dragged
			 * @param {Zarafa.calendar.AppointmentRecord} appointment the appointment record that was dropped
			 * @param {Zarafa.core.DateRange} dateRange The new daterange for the appointment
			 * @param {Ext.EventObject} event The original event object
			 */
			'appointmentcalendardrop',
			/**
			 * @event appointmentmouseover
			 * Fires when the mouse is being mover over an appointment.
			 * @param {Zarafa.calendar.ui.AbstractCalendarDaysView} calendar The calendar which fired the event
			 * @param {Zarafa.calendar.AppointmentRecord} record The appointment over which the mouse is moving
			 * @param {Ext.EventObject} event The original event object
			 */
			'appointmentmouseover',
			/**
			 * @event appointmentmouseover
			 * Fires when the mouse is being mover away from an appointment.
			 * @param {Zarafa.calendar.ui.AbstractCalendarDaysView} calendar The calendar which fired the event
			 * @param {Zarafa.calendar.AppointmentRecord} record The appointment over which the mouse has moved out
			 * @param {Ext.EventObject} event The original event object
			 */
			'appointmentmouseout',
			/**
			 * @event appointmentmove
			 * Fires when an appointment has been moved.
			 * @param {Zarafa.calendar.ui.AbstractCalendarDaysView} calendar The calendar which fired the event
			 * @param {Zarafa.calendar.AppointmentRecord} appointment the appointment record that was moved
			 * @param {Zarafa.core.DateRange} dateRange The new daterange for the appointment
			 * @param {Ext.EventObject} event The original event object
			 */
			'appointmentmove',
			/**
			 * @event appointmentresize
			 * Fires when an appointment has been resized. Resizing in this context means that either the start
			 * date or the due date has been changed.
			 * @param {Zarafa.calendar.ui.AbstractCalendarDaysView} calendar The calendar which fired the event
			 * @param {Zarafa.calendar.AppointmentRecord} appointment the appointment record that was resized
			 * @param {Zarafa.core.DateRange} dateRange The new daterange for the appointment
			 * @param {Ext.EventObject} event The original event object
			 */
			'appointmentresize',
			/**
			 * @event appointmentcreate
			 * Fires when an appointment has been created.
			 * @param {Zarafa.calendar.ui.AbstractCalendarDaysView} calendar The calendar which fired the event
			 * @param {Zarafa.core.DateRange} appointment date range.
			 * @param {String} text appointment description text
			 */
			'appointmentcreate',

			/**
			 * @event appointmentinitdrag
			 * Fired when the user has started dragging an appointment
			 * This means that the user held the mouse down over an appointment
			 * either for a long enough period, or started dragging with the mouse down
			 * This event is passed up to the {@link Zarafa.calendar.ui.CalendarPanel}
			 *
			 * @param {Zarafa.calendar.ui.CalendarMultiView} multiview The Calendar MultiView which fired the event
			 * @param {Ext.EventObject} event The mouse event
			 * @param {Zarafa.calendar.ui.AppointmentView} The appointment on which the event occurred
			 */
			'appointmentinitdrag',

			/**
			 * @event appointmentenddrag
			 * Fired when the user drops a previously dragged appointment
			 * i.e. released the mouse.
			 *
			 * @param {Zarafa.calendar.ui.CalendarMultiView} multiview The Calendar MultiView which fired the event
			 * @param {Ext.EventObject} event The mouse event
			 * @param {Zarafa.calendar.ui.AppointmentView} The appointment on which the event occurred
			 */
			'appointmentenddrag',

			/**
			 * @event contextmenu
			 * Fires when the user right-clicks on an appointment
			 * @param {Zarafa.calendar.ui.AbstractCalendarDaysView} calendar Calendar view
			 * @param {Ext.EventObject} event right-click event.
			 * @param {Ext.Record} appointment record
			 * @param {Zarafa.core.DateRange} range The Datarange on which the contextmenu
			 * was invoked. This is only provided when the event didn't occur on a record.
			 */
			'contextmenu',
			/**
			 * @event dblclick
			 * Fires when the user double-clicks on on an appointment.
			 * @param {Zarafa.calendar.ui.AbstractCalendarDaysView} calendar Calendar view
			 * @param {Ext.EventObject} event right-click event.
			 * @param {Ext.data.Record} record appointment record.
			 */
			'dblclick',
			/**
			 * @event dayclick
			 * Fires when a user clicks on the header of a day, or on the expand button in the box view.
			 * @param {Zarafa.core.ui.View} source event source
			 * @param {Date} date date of the day.
			 */
			'dayclick',
			/**
			 * @event activate
			 * Fires when the user clicks the tab body, indicating that the corresponding tab must be activated.
			 * @param {Zarafa.core.ui.View} source event source
			 * @param {MAPIFolder} folder The activated folder.
			 */
			'activate',
			/**
			 * @event merge
			 * Fires when the user clicks the left arrow in a tab, indicating that the corresponding folder should be merged left.
			 * @param {Zarafa.core.ui.View} source event source
			 * @param {MAPIFolder} folder The folder which is being merged
			 */
			'merge',
			/**
			 * @event merge
			 * Fires when the user clicks the right arrow in a tab, indicating that the corresponding folder should be separated and placed into a new view.
			 * @param {Zarafa.core.ui.View} source event source
			 * @param {MAPIFolder} folder The folder which is being separated
			 */
			'separate',
			/**
			 * @event close
			 * Fires when the user clicks the close icon in a tab.
			 * @param {Zarafa.core.ui.View} source event source
			 * @param {MAPIFolder} folder The folder which is being closed
			 */
			'close'
		);

		Zarafa.calendar.ui.AbstractCalendarView.superclass.constructor.call(this, config);
	},

	/**
	 * Initialises the view.
	 * @protected
	 */
	init: function()
	{
		// super.init
		Zarafa.calendar.ui.AbstractCalendarView.superclass.init.call(this);

		this.folders = [];
		this.tabs = {};
		this.appointments = [];

		// create a selection view
		this.selectionView = this.createAppointmentProxy();
		this.selectionView.setVisible(false);
		this.addChildView(this.selectionView);

		// create a text edit view
		this.textEditView = new Zarafa.calendar.ui.TextEditView();
		this.mon(this.textEditView, 'textentered', this.onTextEntered, this);
		this.addChildView(this.textEditView);

		// hook selection model
		this.mon(this.selectionModel, 'appointmentselect', this.onAppointmentSelect, this);
		this.mon(this.selectionModel, 'appointmentdeselect', this.onAppointmentDeselect, this);
		this.mon(this.selectionModel, 'selectionclear', this.onAppointmentSelectionClear, this);
		this.mon(this.rangeSelectionModel, 'selectionchange', this.onRangeSelectionChange, this);

	},

	/**
	 * Adds a MAPI folder to this calendar view. A new tab view is automatically created and added to
	 * the tab strip at the top of the view.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder MAPI folder to add to this view.
	 */
	addFolder: function(folder)
	{
		// Add the folder to the list.
		this.folders.push(folder);

		// Create a new Tab view to visually represent the new folder in the tab strip at the
		// top of the calendar view.
		var tab = new Zarafa.calendar.ui.CalendarTabView({
			parentView: this,
			folder: folder
		});

		// Hook event handlers to the newly created tab view.
		this.mon(tab, 'merge', this.onTabMerge, this);
		this.mon(tab, 'separate', this.onTabSeparate, this);
		this.mon(tab, 'close', this.onTabClose, this);
		this.mon(tab, 'click', this.onTabClick, this);

		// If this view has been rendered, render the tab. If the view has not been rendered yet,
		// the tab will be rendered automatically in the render() method through Zarafa.core.ui.View::renderChildren().
		if (this.rendered) {
			tab.render(this.container);
		}

		// Finally add the tab view to the local tab view hash.
		this.tabs[folder.get('entryid')] = tab;

		this.setSelectedFolder(folder);
	},

	/**
	 * Removes a MAPI folder from this calendar view. Automatically removes and destroys the associated tab view.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder MAPI folder to remove from this view.
	 */
	removeFolder: function(folder)
	{
		return this.removeFolderById(folder.get('entryid'));
	},

	/**
	 * Removes a MAPI folder from this calendar view. Automatically removes and destroys the associated tab view.
	 * @param {string} id MAPI ID of the folder to remove from this view.
	 */
	removeFolderById: function(id)
	{
		// Remove the folder from the list.
		this.folders.remove(this.getFolderById(id));

		// Get the tab view and unhook the event handlers.
		var tab = this.tabs[id];
		this.mun(tab, 'merge', this.onTabMerge, this);
		this.mun(tab, 'separate', this.onTabSeparate, this);
		this.mun(tab, 'close', this.onTabClose, this);
		this.mun(tab, 'click', this.onTabClick, this);

		// Remove the tab view as a child view and destroy.
		this.removeChildView(tab, true);

		// Remove the tab view from the tab view hash.
		delete this.tabs[id];
	},

	/**
	 * Gets a list of MAPI folder IDs currently in this view.
	 * @return {String}[] MAPI folders IDs currently in this view.
	 */
	getFolderIds: function()
	{
		var ret = [];
		for (var i = 0, folder; folder = this.folders[i]; i++) {
			ret[i] = folder.get('entryid');
		}
		return ret;
	},

	/**
	 * Gets a MAPI folder by its MAPI ID.
	 * @param {string} id folder MAPI ID.
	 * @return {Zarafa.hierarchy.data.MAPIFolderRecord} folder object if the folder was found, or undefined otherwise.
	 */
	getFolderById: function(id)
	{
		// Find folder object to go with the folder ID.
		for (var i=0, folder; folder = this.folders[i]; i++) {
			if (Zarafa.core.EntryId.compareEntryIds(folder.get('entryid'), id)) {
				return folder;
			}
		}
	},

	/**
	 * Gets a list of MAPI folders currently in this view. Is filled by the onLoad event.
	 * @return {Zarafa.hierarchy.data.MAPIFolderRecord[]} MAPI folders currently in this view.
	 */
	getFolders: function()
	{
		return this.folders;
	},

	/**
	 * Updates the currently {@link #selectedFolder selected folder}. If {@link #rendered} it will
	 * also {@link #layout relayout} the view.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder The folder which is currently selected
	 */
	setSelectedFolder: function(folder)
	{
		this.selectedFolder = folder;
		if (this.rendered) {
			this.layout();
		}
	},

	/**
	 * Gets the currently selected folder.
	 * @return {Zarafa.hierarchy.data.MAPIFolderRecord} currently selected MAPI folder.
	 */
	getSelectedFolder: function()
	{
		// Check if selected folder is available in the folders array. This could be false when
		// settings haven't been updated properly.
		if ( this.folders.indexOf(this.selectedFolder) < 0 ){
			// Set the first folder in the list as the selected one
			this.setSelectedFolder(this.folders[0]);
		}

		return this.selectedFolder;
	},

	/**
	 * Checks if this view is currently showing the given MAPI folder.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder MAPI folders currently in this view.
	 * @return true iff this view contains the given folder.
	 */
	containsFolder: function(folder)
	{
		return this.containsFolderId(folder.get('entryid'));
	},

	/**
	 * Checks if this view is currently showing the given MAPI folder.
	 * @param {string} id folder MAPI ID.
	 * @return true iff this view contains the given folder.
	 */
	containsFolderId: function(id)
	{
		return Ext.isDefined(this.getFolderById(id));
	},

	/**
	 * Sorts the calendar folders based on the order of the input folder list. It is assumed that the folders argument
	 * is a superset of the folder list currently active in this view.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord[]} folders Folder list.
	 */
	sortFolders: function(folders)
	{
		var sortedFolders = [];
		for (var i = 0, folder; folder = folders[i]; i++) {
			if (this.containsFolderId(folder.get('entryid'))) {
				sortedFolders.push(folder);
			}
		}

		this.folders = sortedFolders;
	},

	/**
	 * Returns the current date range for this calendar view. This value is retrieved
	 * from the parent view.
	 * @return {Zarafa.core.DateRange} The daterange
	 */
	getDateRange: function()
	{
		return this.parentView.getDateRange();
	},

	/**
	 * Returns the begin date for this calendar value. This value is retrieved
	 * from the parent view.
	 * @return {Date} calendar start date.
	 */
	getStartDate: function()
	{
		return this.getDateRange().getStartDate();
	},

	/**
	 * Returns the due date for this calendar value. This value is retrieved
	 * from the parent view.
	 * @return {Date} calendar due date.
	 */
	getDueDate: function()
	{
		return this.getDateRange().getDueDate();
	},

	/**
	 * The {@link Zarafa.calendar.ui.CalendarMultiView CalendarMultiView} view has a header area that automatically
	 * resizes when its child views require more space. In the days view for instance, appointments that span
	 * more than 24 hours are laid out in the header.
	 * @return {Number} height in pixels the calendar view needs to properly lay out its header.
	 */
	getDesiredHeaderHeight: function()
	{
		return 0;
	},

	/**
	 * The {@link Zarafa.calendar.ui.CalendarMultiView CalendarMultiView} view has a tab area that is visible when there
	 * are multiple calendars open.
	 * @return {Number} height in pixels of the tab area of the calendar view.
	 */
	getTabHeight: function()
	{
		return this.parentView.getTabHeight();
	},

	/**
	 * Finds an appointment view by record.
	 * @param {Ext.data.Record} record to look for
	 * @return {Zarafa.calendar.ui.AppointmentView} an appointment view iff found, undefined otherwise
	 */
	findAppointment: function(record)
	{
		for (var i=0, appointment; appointment=this.appointments[i]; i++) {
			if (appointment.getRecord().equals(record)) {
				return appointment;
			}
		}

		// not found
		return undefined;
	},

	/**
	 * Tests if an appointment view exists on this calendar view that represents the given record.
	 * @param {Ext.data.Record} record to look for
	 * @return {Boolean} true iff an appointment view exists that represents the given record.
	 */
	containsAppointment: function(record)
	{
		return Ext.isDefined(this.findAppointment(record));
	},

	/**
	 * Create a new {@link Zarafa.calendar.ui.AppointmentView Appointment} object
	 * for the given {@link Zarafa.core.data.IPMRecord record}.
	 * Subclasses must override this function to make the appointments visible
	 * in this view.
	 * @param {Zarafa.core.data.IPMRecord} record The record for which the AppointmentView
	 * must be created.
	 * @return {Zarafa.calendar.ui.AppointmentView} The new appointmentview
	 * @protected
	 */
	createAppointment: Ext.emptyFn,

	/**
	 * Create an Appointment Proxy object which can represent the selected text
	 * Must be implemented by the subclasses.
	 * @return Zarafa.calendar.ui.AbstractDateRangeView
	 * @protected
	 */
	createAppointmentProxy: Ext.emptyFn,

	/**
	 * Adds a new appointment to the view.
	 * @param {Ext.data.Record} record a record with the appointment data
	 * @param {Boolean} layout (optional) if true layout() will be called after the appointment was added. Defaults to true.
	 * @return {Boolean} True if an appointment was added, false otherwise.
	 */
	addAppointment: function(record, layout)
	{
		// Don't add anything that is not an appointment
		if ( Zarafa.core.MessageClass.getDefaultFolderTypeFromMessageClass(record.get('message_class')) !== 'calendar' ){
			return false;
		}

		var appointment = this.createAppointment(record);

		appointment.render(this.container);
		this.appointments.push(appointment);

		if (layout !== false) {
			this.layout();
		}

		return true;
	},

	/**
	 * Removes an appointment from the view.
	 * @param {Ext.data.Record} record appointment record
	 * @param {Boolean} layout (optional) if true layout() will be called after the appointment was added. Defaults to true.
	 * @return {Boolean} true if an appointment was removed, false otherwise (the appointment was not found in this view)
	 */
	removeAppointment: function(record, layout)
	{
		var appointment;
		if (appointment = this.findAppointment(record)) {
			this.appointments.remove(appointment);
			this.removeChildView(appointment, true);

			if (layout !== false) {
				this.layout();
			}
			return true;
		}

		return false;
	},

	/**
	 * Removes all appointments from the view and destroys them.
	 * @param {Boolean} destroy if true, destroy the appointments. Defaults to true.
	 * @param {Boolean} layout (optional) if true layout() will be called after the appointment was added. Defaults to true.
	 * @return {Zarafa.calendar.ui.AppointmentView[]} a list of appointments that were removed.
	 */
	clearAppointments: function(destroy, layout)
	{
		for (var i=0, appointment; appointment=this.appointments[i]; i++) {
			this.removeChildView(appointment, destroy);
		}

		// Save the appointments list so we can return it from the function.
		var ret = this.appointments;

		// Clear the appointments list.
		this.appointments = [];

		// Optionally layout the view.
		if (layout !== false) {
			this.layout();
		}

		return ret;
	},

	/**
	 * Gets a list of appointment records currently on the view.
	 * @return {Zarafa.core.data.IPMRecord[]} records.
	 */
	getAppointmentRecords: function()
	{
		var ret = [];

		for (var i=0, appointment; appointment=this.appointments[i]; i++) {
			ret.push(appointment.getRecord());
		}

		return ret;
	},

	/**
	 * Converts a date range ([startDate, dueDate>) to zero or more (left, right, top, bottom) bounds objects.
	 * This method is used to lay out appointments (and proxies) on the calendar body.
	 * @param {Zarafa.core.DateRange} dateRange date range
	 * @param {Number} column (optional) when several appointments are overlapping a column may be assigned
	 * @param {Number} columnCount (optional) the number of overlapping appointments in an overlap dependency graph
	 * @param {Boolean} useMargin (optional) True to apply margins to the appointments to prevent them from
	 * filling up the entire width of a daybox (This applies {@link #appointmentBodyLeftMargin} and
	 * {@link #appointmentBodyRightMargin}).
	 */
	dateRangeToBodyBounds: function(dateRange, column, columnCount, useMargin)
	{
		return [];
	},

	/**
	 * Converts a date range ([startDate, dueDate>) a (left, right, top, bottom) box.
	 * This method is used to lay out appointments (and proxies) on the calendar header.
	 * @param {Zarafa.core.DateRange} dateRange date range
	 * @param {Number} column (optional) when several appointments are overlapping a column may be assigned
	 * @param {Number} columnCount (optional) the number of overlapping appointments in an overlap dependency graph
	 * @param {Boolean} useMargin (optional) True to apply margins to the appointments to prevent them
	 * from filling up the entire width of the header (This applies {@link #appointmentHeaderLeftMargin} and
	 * {@link #appointmentHeaderRightMargin}).
	 */
	dateRangeToHeaderBounds: function(dateRange, row, rowCount, useMargin)
	{
		return {
			left: 0,
			right: 0,
			top: 0,
			bottom: 0
		};
	},

	/**
	 * Tests if the date range should be laid out in the header. Should be overridden by child classes. For example,
	 * the days view lays out appointments in the header if they span > 24 hours, but the box view never lays out
	 * appointments on the header.
	 * @return {Boolean} true iff the date range should be laid out on the calendar header.
	 */
	isHeaderRange: function(dateRange)
	{
		return false;
	},

	/**
	 * Sets the tab minimum offset from the left. Called by the parent
	 * {@link Zarafa.calendar.ui.CalendarMultiView CalendarMultiView} before layout.
	 * @param {Number} left The offset from the left
	 */
	setLeftMargin: function(left)
	{
		this.leftOffset = left;
	},

	/**
	 * Sets the tab width. Called by the parent
	 * {@link Zarafa.calendar.ui.CalendarMultiView CalendarMultiView} before layout.
	 * @param {Number} width tab width.
	 */
	setWidth: function(width)
	{
		this.width = width;
	},

	/**
	 * Sets whether this view can merge left. If the view is the leftmost on screen, this will be set to true by the
	 * parent multiview. This information is used to hide/show the merge icon on the tabs in the view.
	 * @param {Boolean} canMerge
	 */
	setCanMerge: function(canMerge)
	{
		this.canMerge = canMerge;
	},

	/**
	 * Sets whether this view can be closed. If the view is the only one on screen, this will be set to true by the
	 * parent multiview. This information is used to hide/show the close icon on the tabs in the view.
	 * @param {Boolean} canClose
	 */
	setCanClose: function(canClose)
	{
		this.canClose = canClose;
	},

	/**
	 * Converts a location in page coordinates to a corresponding date.
	 * @param {Number} x horizontal component of the location
	 * @param {Number} y vertical component of the location
	 * @return {Date} a Date object that corresponds to the given location
	 */
	screenLocationToDate: Ext.emptyFn,

	/**
	 * Converts a location in page coordinates to a corresponding daterange.
	 * @param {Number} x horizontal component of the location
	 * @param {Number} y vertical component of the location
	 * @return {Zarafa.core.DateRange} A DateRange object that corresponds to the given location
	 */
	screenLocationToDateRange: Ext.emptyFn,

	/**
	 * The border width in pixels. Retrieved from the parent view.
	 * If this calendar component is the only calendar on the view it will return 0.
	 * @return {Number} border width in pixels
	 * @private
	 */
	getBorderWidth: function()
	{
		return this.parentView.getBorderWidth();
	},

	/**
	 * Zoom level. Retrieved from the parent view.
	 * @return {Number} zoom level in minutes.
	 * @private
	 */
	getZoomLevel: function()
	{
		return this.parentView.zoomLevel;
	},

	/**
	 * @return {Ext.Element} the calendar body element
	 */
	getCalendarBody: function()
	{
		return this.body;
	},

	/**
	 * @return {Ext.Element} the calendar header element
	 */
	getCalendarHeader: function()
	{
		return this.header;
	},

	/**
	 * Compares two appointments. Used for sorting appointments by start date. If the start dates of both
	 * appointments are equal, further distinction is made by due date and entryId. Note that the tie
	 * breaker here is entryId because the quick sort used by JavaScript is not stable, so the user might
	 * experience appointments with equal start and due dates swapping places on updates without it.
	 *
	 * @param {Zarafa.calendar.ui.AppointmentView} a appointment A
	 * @param {Zarafa.calendar.ui.AppointmentView} b appointment B
	 * @return {Number} 1 if appointment A starts before appointment B, 0 if they are equal, -1 if appointment A
	 * starts after appointment B.
	 */
	appointmentCompare: function(a, b)
	{
		var dateComp = a.getDateRange().compare(b.getDateRange());
		if (dateComp === 0) {
			return a.getRecord().get('entryid')>b.getRecord().get('entryid')?1:-1;
		} else {
			return dateComp;
		}
	},

	/**
	 * Performs greedy coloring for a set of overlapping appointments. Each appointment is assigned a slot
	 * and the total slot count (number of required slots to lay out the set) is assigned to the appointment
	 * in the form of the 'slotCount' property.
	 * <p>
	 * The algorithm can lay out appointments by time (as in the body) or by whole days (as in the header).
	 * In the latter case if two appointments start or end on the same day they will always overlap, regardless
	 * of the time within that day.
	 * @param {Boolean} wholeDays if true the overlap test is for whole days rather than time
	 */
	doGreedyColoring: function(appointments, wholeDays)
	{
		// sort appointments by start date
		appointments.sort(this.appointmentCompare);

		// create the to do list. We'll remove fitted appointments from this list and continue until it is empty
		var todoList = [];

		// fill the to do list with start/due values (numbers). In the case of fitting by day we'll fill it
		// with day numbers. In the normal case it will be filled with milliseconds since epoch
		if (wholeDays) {
			Ext.each(appointments, function(appointment) {
				var calendarStartTime = this.getDateRange().getStartTime();
				todoList.push({
					appointment: appointment,
					start: Math.floor((appointment.getDateRange().getStartTime() - calendarStartTime) / Date.dayInMillis),
					due: Math.ceil((appointment.getDateRange().getDueTime() - calendarStartTime) / Date.dayInMillis)
				});
			}, this);
		} else {
			Ext.each(appointments, function(appointment) {
				todoList.push({
					appointment: appointment,
					start: appointment.getAdjustedDateRange().getStartTime(),
					due: appointment.getAdjustedDateRange().getDueTime()
				});
			});
		}

		// simple greedy lay out
		var slot = 0;
		while (todoList.length > 0) {
			// first element can always go into the empty slot
			var first = todoList.shift();
			first.appointment.slot = slot;

			// the slot now spans start-due
			var due = first.due;

			// look for appointments that are outside the start-due range, and can therefore
			// go into the current slot
			for (var i = 0; i < todoList.length; i++) {
				if (todoList[i].start >= due) {
					// add the selected item to the current slot
					todoList[i].appointment.slot = slot;

					// update the span to include the new appointment
					due = todoList[i].due;

					// remove the selected item from the to do list
					todoList.splice(i, 1);
					i--;
				}
			}

			// next slot
			slot++;
		}

		Ext.each(appointments, function(appointment) { appointment.slotCount = slot; } );
	},

	/**
	 * Renders the view. This class provides a this.header and this.body div that can be used
	 * to attach elements to the header and body areas respectively.
	 * <p>
	 * This class also provides a border with a tab (shown when there are more than one calendar
	 * in the parent view).
	 * @param {Ext.Element} container The Ext.Element into which the view must be rendered.
	 */
	render: function(container)
	{
		// FIXME: This should _never_ be true...
		if (this.rendered) {
			return;
		}

		// Update the themeCls, this will be used when assigning
		// the CSS class names to all objects.
		var selectedFolder = this.getSelectedFolder();
		var entryid = selectedFolder ? selectedFolder.get('entryid') : '';
		this.calendarColorScheme = this.contextModel.getColorScheme(entryid);

		// create tab, header and body divs
		this.createDiv(this.parentView.scrollable, 'body');
		this.createDiv(this.parentView.header, 'header', 'zarafa-calendar-header');

		// border divs
		this.createDiv(this.parentView.header, 'headerBorderLeft');
		this.createDiv(this.parentView.header, 'headerBorderRight');
		this.createDiv(this.parentView.scrollable, 'borderLeft');
		this.createDiv(this.parentView.scrollable, 'borderRight');
		this.createDiv(this.parentView.bottom, 'borderBottom');

		this.createDiv(this.parentView.tab, 'tabArea');
		this.createDiv(this.tabArea, 'activeTabStroke');

		Zarafa.calendar.ui.AbstractCalendarView.superclass.render.call(this, container);
		this.renderChildren();

		this.mon(this.selectionView, 'keypress', this.onKeyPress, this);
		this.mon(this.selectionView, 'keydown', this.onKeyPress, this);

		if (this.enableDD || this.enableBodyDD || this.enableBodyDrag) {
			if (!this.bodyDragZone && this.body) {
				this.bodyDragZone = new Zarafa.calendar.ui.CalendarViewDragZone(this, this.bodyDragConfig || {
					ddGroup: this.ddGroup || 'AppointmentDD',
					headerMode: false
				});
			}
		}

		if (this.enableDD || this.enableHeaderDD || this.enableHeaderDrag) {
			if (!this.headerDragZone && this.header) {
				this.headerDragZone = new Zarafa.calendar.ui.CalendarViewDragZone(this, this.headerDragConfig || {
					ddGroup: this.ddGroup || 'AppointmentDD',
					headerMode: true
				});
			}
		}

		if (this.enableDD || this.enableBodyDD || this.enableBodyDrop) {
			if (!this.bodyDropZone && this.body) {
				this.bodyDropZone = new Zarafa.calendar.ui.CalendarViewDropZone(this, this.bodyDropConfig || {
					ddGroup: this.ddGroup || 'AppointmentDD',
					headerMode: false,
					selectingSnapMode: Zarafa.calendar.data.SnapModes.ZOOMLEVEL,
					draggingSnapMode: Zarafa.calendar.data.SnapModes.ZOOMLEVEL
				});
				this.bodyDropZone.proxy = this.selectionView;
			}
		}

		if (this.enableDD || this.enableHeaderDD || this.enableHeaderDrop) {
			if (!this.headerDropZone && this.header) {
				this.headerDropZone = new Zarafa.calendar.ui.CalendarViewDropZone(this, this.headerDropConfig || {
					ddGroup: this.ddGroup || 'AppointmentDD',
					headerMode: true,
					selectingSnapMode: Zarafa.calendar.data.SnapModes.DAY,
					draggingSnapMode: Zarafa.calendar.data.SnapModes.DAY
				});
				this.headerDropZone.proxy = this.selectionView;
			}
		}
	},

	/**
	 * Event handler which is triggered when the user pressed a key after selection a
	 * time range with the {@link #rangeSelectionModel}. If the first character is
	 * {@link Ext.form.VTypes.alphanum alphanumeric} then the {@link #textEditView} will be enabled for creating
	 * a quick-appointment.
	 * @param {Object} event The event object for this event
	 * @private
	 */
	onKeyPress: function(event)
	{
		// If the key is not alphanumeric (a-z, A-Z, 0-9) ignore the event
		if (!Ext.form.VTypes.alphanum(String.fromCharCode(event.getKey()))) {
			return;
		}

		if (this.rangeSelectionModel.isActive() && this.rangeSelectionModel.getCalendarView() == this && !this.textEditView.isVisible())
		{
			var range = this.rangeSelectionModel.getDateRange();

			// Clear the range selection. Note that this causes the 'rangeSelectionChange' to fire, which in
			// turn causes the textEditView to be made invisible. Therefore we do this before making the
			// textEditView visible through the select(); method.
			this.rangeSelectionModel.clearSelections();

			// Show the text edit view.
			this.textEditView.setDateRange(range);
			this.textEditView.select();
		}

	},

	/**
	 * Event handler which is raised when the user has used the {@link #textEditView} to write
	 * the subject for a quick-appointment. If this subject is not empty, the {@link #appointmentcreate}
	 * event will be fired for creating a new appointment.
	 * @param {Zarafa.calendar.ui.TextEditView} view The view which raised the event.
	 * @param {String} text The text which was entered into the view.
	 * @private
	 */
	onTextEntered: function(view, text)
	{
		// Don't fire event on empty text
		if (Ext.isEmpty(text.trim())) {
			return;
		}

		this.fireEvent('appointmentcreate', this, view.getDateRange(), text);
	},

	/**
	 * Handles layout of the individual divs that make up the border around the calendar view.
	 * @param {Object} colorScheme the used colorScheme
	 * @private
	 */
	layoutBorder: function(colorScheme)
	{
		if (this.parentView.showBorder) {
			var borderWidth = this.getBorderWidth();

			// border divs in the header area
			var headerHeight = this.parentView.getHeaderAreaHeight();
			const styles = {
				'background-color': colorScheme.header,
				'border-color': colorScheme.header
			};


			this.headerBorderLeft.dom.className = this.getClassName('border', 'left');
			this.headerBorderLeft.setLeftTop(this.leftOffset, 0);
			this.headerBorderLeft.setSize(borderWidth, headerHeight);
			this.headerBorderLeft.applyStyles(styles);
			this.headerBorderLeft.show();

			this.headerBorderRight.dom.className = this.getClassName('border', 'right');
			this.headerBorderRight.setLeftTop(this.leftOffset + this.width - borderWidth, 0);
			this.headerBorderRight.setSize(borderWidth, headerHeight);
			this.headerBorderRight.applyStyles(styles);
			this.headerBorderRight.show();

			// border divs in the body area
			var bodyHeight = this.parentView.getHourHeight() * this.parentView.numHours;

			this.borderLeft.dom.className = this.getClassName('border', 'left');
			this.borderLeft.setLeftTop(this.leftOffset, 0);
			this.borderLeft.setSize(borderWidth, bodyHeight);
			this.borderLeft.applyStyles(styles);
			this.borderLeft.show();

			this.borderRight.dom.className = this.getClassName('border', 'right');
			this.borderRight.setLeftTop(this.leftOffset + this.width - borderWidth, 0);
			this.borderRight.setSize(borderWidth, bodyHeight);
			this.borderRight.applyStyles(styles);
			this.borderRight.show();

			// border div in the bottom area
			this.borderBottom.dom.className = this.getClassName('border', 'bottom');
			this.borderBottom.setLeftTop(this.leftOffset, 0);
			this.borderBottom.setSize(this.width, borderWidth);
			this.borderBottom.applyStyles(styles);
			this.borderBottom.show();
		} else {
			this.headerBorderLeft.hide();
			this.headerBorderRight.hide();
			this.borderLeft.hide();
			this.borderRight.hide();
			this.borderBottom.hide();
		}
	},

	/**
	 * Handles layout of the individual {@link #tabs}. This will also obtain the desired width
	 * for each individual tab, as to calculate how much width each tab could be assigned with.
	 * @private
	 */
	layoutTabs: function()
	{
		var tabs = [];
		var tabsThatNeedResizing = [];
		var tabsThatNeedNoResizing = [];

		// Update the settings of the tabs and calculate the total desired width.
		// Layout the tabs.
		var totalDesiredWidth = 0;

		for (var i=0, folder; folder=this.folders[i]; i++) {
			var tab = this.tabs[folder.get('entryid')];
			if (!tab) {
				continue;
			}
			tab.setSelected(this.selectedFolder == folder, this.active);
			tab.setShowMergeIcon(this.canMerge);
			tab.setShowSeparateIcon(this.folders.length > 1);
			tab.setShowCloseIcon(this.canClose);

			tabs.push(tab);
			tab.desiredWidth = tab.getDesiredWidth();
			tab.lrmargins = tab.tabContents.getMargins('lr');

			totalDesiredWidth += tab.desiredWidth + tab.lrmargins;
		}

		// If the width of all the tabs together is more than the width of this control,
		// the tabs will have to be shrunk.
		if ( totalDesiredWidth > this.width ){
			for ( i=0; i<tabs.length; i++ ){
				if ( (tabs[i].getDesiredWidth() + tabs[i].tabContents.getMargins('lr')) < this.width/tabs.length ){
					tabsThatNeedNoResizing.push(tabs[i]);
				} else {
					tabsThatNeedResizing.push(tabs[i]);
				}
			}

			var totalAvailableWidthForTabsThatNeedResizing = this.width;
			var totalWidthOfTabsThatNeedResizing = 0;
			for ( i=0; i<tabsThatNeedNoResizing.length; i++ ){
				totalAvailableWidthForTabsThatNeedResizing -= tabsThatNeedNoResizing[i].desiredWidth + tabsThatNeedNoResizing[i].lrmargins;
			}
			for ( i=0; i<tabsThatNeedResizing.length; i++ ){
				totalWidthOfTabsThatNeedResizing += tabsThatNeedResizing[i].desiredWidth + tabsThatNeedResizing[i].lrmargins;
			}

			if ( tabsThatNeedResizing.length === 1 ){
				tabsThatNeedResizing[0].desiredWidth = totalAvailableWidthForTabsThatNeedResizing - tabsThatNeedResizing[0].lrmargins;
			} else {
				while ( totalWidthOfTabsThatNeedResizing > totalAvailableWidthForTabsThatNeedResizing ){
					// find the largest two tabs
					var tab0 = tabsThatNeedResizing[0].desiredWidth > tabsThatNeedResizing[1].desiredWidth ? tabsThatNeedResizing[0] : tabsThatNeedResizing[1];
					var tab1 = tabsThatNeedResizing[0].desiredWidth > tabsThatNeedResizing[1].desiredWidth ? tabsThatNeedResizing[1] : tabsThatNeedResizing[0];
					for ( i=2; i<tabsThatNeedResizing.length; i++ ){
						if ( tabs[i].desiredWidth > tab0.desiredWidth ){
							tab0 = tabs[i];
						} else if ( tabs[i].desiredWidth > tab1.desiredWidth ){
							tab1 = tabs[i];
						}
					}

					if ( tab0.desiredWidth - tab1.desiredWidth > totalWidthOfTabsThatNeedResizing - totalAvailableWidthForTabsThatNeedResizing ){
						// Easy one: just shrink the biggest tab and we're good to go.
						tab0.desiredWidth -= totalWidthOfTabsThatNeedResizing - totalAvailableWidthForTabsThatNeedResizing + tab0.lrmargins;
						totalWidthOfTabsThatNeedResizing = totalAvailableWidthForTabsThatNeedResizing;
					} else {
						// Just make the largest tab smaller than the second largest tab and start all over (iterating until we are small enough)
						totalWidthOfTabsThatNeedResizing -= (tab0.desiredWidth-tab1.desiredWidth) + 1;
						tab0.desiredWidth = tab1.desiredWidth - 1;
					}
				}
			}
		}

		// Layout the tabs.
		for (var i=0, folder; folder=this.folders[i]; i++) {
			var tab = this.tabs[folder.get('entryid')];

			var width = tab.desiredWidth;

			// Check if we don't get conflicts with a possible min-width set in the css-files
			tab.tabContents.dom.style.removeProperty('min-width');
			var cssMinWidth = parseInt(tab.tabContents.getStyle('min-width')) + tab.tabContents.getPadding('lr');

			if ( cssMinWidth > width ){
				tab.tabContents.setStyle('min-width', 0);
				if ( totalDesiredWidth <= this.width ){
					width = cssMinWidth;
				}
			}

			tab.setWidth(width);
		}
	},

	/**
	 * Lays out the view.
	 * @protected
	 */
	onLayout: function()
	{
		var selectedFolder = this.getSelectedFolder();

		if ( !selectedFolder ) {
			return Zarafa.calendar.ui.AbstractCalendarView.superclass.onLayout.call(this);
		}

		// Update the themeCls, this will be used when assigning
		// the CSS class names to all objects.
		var colorScheme = this.contextModel.getColorScheme(selectedFolder.get('entryid'));
		if(colorScheme) {
			this.calendarColorScheme = colorScheme;
		}

		// layout border and tab
		this.layoutBorder(colorScheme);

		// layout body and header containers
		var dayStripHeight = this.parentView.getHourHeight() * this.parentView.numHours;

		this.body.dom.className = this.getClassName('container', 'body');
		this.body.setLeftTop(this.leftOffset + this.getBorderWidth(), 0);
		this.body.setSize(this.width - this.getBorderWidth() * 2, dayStripHeight);

		this.header.dom.className = this.getClassName('container', 'header');
		this.header.setLeftTop(this.leftOffset + this.getBorderWidth(), 0);
		this.header.setSize(this.width - this.getBorderWidth() * 2, this.parentView.getHeaderAreaHeight());

		this.tabArea.dom.className = this.getClassName('container', 'tabarea');
		this.tabArea.setLeftTop(this.leftOffset, 0);
		this.tabArea.setSize(this.width, this.parentView.getTabAreaHeight());

		this.activeTabStroke.dom.className = this.getClassName('tabarea', 'stroke');
		this.activeTabStroke.setSize(this.width, this.parentView.tabStrokeHeight);

		if (this.parentView.showBorder) {
			this.tabArea.show();
		} else {
			this.tabArea.hide();
		}

		// Determine the color scheme of the appointments
		for (var i=0, appointment; appointment=this.appointments[i]; i++) {
			var folderId = appointment.getRecord().get('parent_entryid');
			appointment.calendarColorScheme = this.contextModel.getColorScheme(folderId);

			var folder = this.getFolderById(folderId);
			appointment.setActive(folder == this.selectedFolder);
		}

		Zarafa.calendar.ui.AbstractCalendarView.superclass.onLayout.call(this);

		this.layoutTabs();
	},

	/**
	 * This will call {@link Zarafa.core.ui.View#layout layout} on all the
	 * {@link #children}.
	 * @protected
	 */
	onAfterLayout: function()
	{
		this.layoutChildren();
	},

	/**
	 * Called by the {@link #parentView} when the {@link Zarafa.core.data.IPMStore#beforeload beforeload} event
	 * has been fired from the appointment {@link Zarafa.calendar.ui.CalendarMultiView#store store}.
	 * @param {Zarafa.core.data.IPMStore} store store that fired the event.
	 * @param {Object} options the options (parameters) with which the load was invoked.
	 */
	beforeAppointmentsLoad: function(store, options)
	{
		// Keep existing appointments visible until fresh data arrives so the user
		// sees the previous snapshot instead of a blank calendar during loading.
		// Appointments are cleared just before new records are rendered in
		// onAppointmentsLoad().
	},

	/**
	 * Called by the {@link #parentView} when the {@link Zarafa.core.data.IPMStore#load load} event
	 * has been fired from the appointment {@link Zarafa.calendar.ui.CalendarMultiView#store store}.
	 * @param {Zarafa.core.data.IPMStore} store store that fired the event.
	 * @param {Zarafa.core.data.IPMRecord[]} records loaded record set
	 * @param {Object} options the options (parameters) with which the load was invoked.
	 */
	onAppointmentsLoad: function(store, records, options)
	{
		// Destroy all appointments.
		this.clearAppointments(true, false);

		// Create an appointment for each record.
		Ext.each(records, function(record) {
			if (this.containsFolderId(record.get('parent_entryid')) && record.isValid()) {
				this.addAppointment(record, false);
			}
		}, this);

		// Keeping the selection can be achieved properly by using 'recordselectionchange'
		// event of respective context instance in case of any GridView.
		// But we need to keep selection manually for this particular calendar view.
		var selectedRecords = this.contextModel.selectedRecords;
		if (!Ext.isEmpty(selectedRecords)) {
			this.selectionModel.selectRecord(selectedRecords[0], false);
		}
	},

	/**
	 * Called by the {@link #parentView} when the {@link Zarafa.core.data.IPMStore#add add} event
	 * has been fired from the appointment {@link Zarafa.calendar.ui.CalendarMultiView#store store}.
	 * Checks if the record belongs to this view (by checking the parent id)
	 * and adds a new AppointmentView to the calendar if required.
	 * @param {Ext.data.Store} store data store
	 * @param {Ext.data.Record} record that was added
	 * @param {String} operation mutation operation key. Equals 'add'
	 */
	onAppointmentAdd: function(store, record, operation)
	{
		if (this.containsFolderId(record.get('parent_entryid')) && !this.containsAppointment(record) && record.isValid()) {
			this.addAppointment(record);
		}
	},

	/**
	 * Called by the {@link #parentView} when the {@link Zarafa.core.data.IPMStore#remove remove} event
	 * has been fired from the appointment {@link Zarafa.calendar.ui.CalendarMultiView#store store}.
	 * Checks if an AppointmentView displaying the record exists on this calendar and removes it.
	 * @param {Ext.data.Store} store data store
	 * @param {Ext.data.Record} record that was added
	 * @param {String} operation mutation operation key. Equals 'remove'
	 */
	onAppointmentRemove: function(store, record, operation)
	{
		if (this.containsAppointment(record)) {
			this.removeAppointment(record);
		}
	},

	/**
	 * Called by the {@link #parentView} when the {@link Zarafa.core.data.IPMStore#remove remove} event
	 * has been fired from the appointment {@link Zarafa.calendar.ui.CalendarMultiView#store store}.
	 * This event is usually fired twice, once to signal that the data of a record has been changed (operation=='update'),
	 * and once to signal that the data has been successfully committed to the store (operation=='commit').
	 * The 'commit' operation means that the changed record has been written to the database back-end.
	 * @param {Ext.data.Store} store data store
	 * @param {Ext.data.Record} record that was added
	 * @param {String} operation mutation operation key. Equals 'update'
	 */
	onAppointmentUpdate: function(store, record, operation)
	{
		var appointment = this.findAppointment(record);

		if (!appointment) {
			// Appointment doesn't exist. We don't care about the update.
			return;
		}

		var dateRange = appointment.getDateRange();

		if (dateRange.getStartDate() !== record.get('startdate') || dateRange.getDueDate() !== record.get('duedate')) {
			// If the appointment start and/or duedate have been changed, then we must do a layout
			// of all appointments (appointments can now overlap which could cause the appointments
			// to be resized).
			appointment.updateDateRange(record);
			this.layout();
		} else {
			// The appointment presence nor size in this calendar has changed,
			// we can simply update the appointment with the new data.
			appointment.layout();
		}
	},

	/**
	 * Event handler which is fired when the {@link #selectionModel} fires the
	 * {@link Zarafa.calendar.ui.AppointmentSelectionModel#appointmentselect appointmentselect} event.
	 * This will lookup which {@link Zarafa.calendar.ui.AppointmentView appointment} belongs to the
	 * given {@link Zarafa.core.data.IPMRecord record} and will use
	 * {@link Zarafa.calendar.ui.AppointmentView#setSelected setSelected} to mark the appointment
	 * as selected.
	 * @param {Zarafa.calendar.ui.AppointmentSelectionModel} selectionModel The selection model which
	 * fired the event.
	 * @param {Zarafa.core.data.IPMRecord} record The record which was selected.
	 * @private
	 */
	onAppointmentSelect: function(selectionModel, record)
	{
		var appointment = this.findAppointment(record);

		// If the text edit view is still visible, hide it.
		if (this.textEditView.isVisible()) {
			this.textEditView.hide();
		}

		if (appointment) {
			appointment.setSelected(true);
		}
	},

	/**
	 * Event handler which is fired when the {@link #selectionModel} fires the
	 * {@link Zarafa.calendar.ui.AppointmentSelectionModel#appointmentdeselect appointmentdeselect} event.
	 * This will lookup which {@link Zarafa.calendar.ui.AppointmentView appointment} belongs to the
	 * given {@link Zarafa.core.data.IPMRecord record} and will use
	 * {@link Zarafa.calendar.ui.AppointmentView#setSelected setSelected} to mark the appointment
	 * as unselected.
	 * @param {Zarafa.calendar.ui.AppointmentSelectionModel} selectionModel The selection model which
	 * fired the event.
	 * @param {Zarafa.core.data.IPMRecord} record The record which was deselected.
	 * @private
	 */
	onAppointmentDeselect: function(selectionModel, record)
	{
		var appointment = this.findAppointment(record);

		// If the text edit view is still visible, hide it.
		if (this.textEditView.isVisible()) {
			this.textEditView.hide();
		}

		if (appointment) {
			appointment.setSelected(false);
		}
	},

	/**
	 * Event handler which is fired when the {@link #selectionModel} fires the
	 * {@link Zarafa.calendar.ui.AppointmentSelectionModel#selectionclear selectionclear} event.
	 * This will call {@link Zarafa.calendar.ui.AppointmentView#setSelected setSelected} for
	 * each {@link #appointments appointment} to mark them as unselected.
	 * @param {Zarafa.calendar.ui.AppointmentSelectionModel} selectionModel The selection model which
	 * fired the event.
	 * @private
	 */
	onAppointmentSelectionClear: function(selectionModel)
	{
		// If the text edit view is still visible, hide it.
		if (this.textEditView.isVisible()) {
			this.textEditView.hide();
		}

		for (var i = 0, len = this.appointments.length; i < len; i++) {
			this.appointments[i].setSelected(false);
		}
	},

	/**
	 * Fires when the selection range has changed. This means the selection range my have been cleared or
	 * set.
	 * @param {Zarafa.calendar.ui.DateRangeSelectionModel} selectionModel range selection model firing the event.
	 * @param {Zarafa.core.DateRange} dateRange selected date range, or undefined if not active.
	 * @param {Zarafa.calendar.ui.AbstractCalendarView} calendarView the calendar view the selection was made on.
	 * @param {Boolean} active true iff the selection is active (set).
	 * @private
	 */
	onRangeSelectionChange: function(selectionModel, dateRange, calendarView, active)
	{
		// Make sure the selection view has been created.
		if (!this.selectionView) {
			return;
		}

		// If the text edit view is still visible, hide it.
		if (this.textEditView.isVisible()) {
			this.textEditView.hide();
			this.textEditView.inputText = '';
		}

		// Check if the selected range is active and belongs to this calendar view, and
		// show the selection if so.
		if (active && calendarView == this) {
			this.selectionView.setDateRange(dateRange);
			this.selectionView.setVisible(true);
		} else if (this.selectionView.isVisible()) {
			// Otherwise hide the selection view
			this.selectionView.setVisible(false);
		}
	},

	/**
	 * Handles the 'merge' event of a tab, which is fired when the user clicks the left arrow (merge) button.
	 * Fires the 'merge' event.
	 * @param {MAPIFolder} folder MAPI folder.
	 * @private
	 */
	onTabMerge: function(folder)
	{
		this.fireEvent('merge', this, folder);
	},

	/**
	 * Handles the 'merge' event of a tab, which is fired when the user clicks the right arrow (separate) button.
	 * Fires the 'separate' event.
	 * @param {MAPIFolder} folder MAPI folder.
	 * @private
	 */
	onTabSeparate: function(folder)
	{
		this.fireEvent('separate', this, folder);
	},

	/**
	 * Handles the 'close' event of a tab, which is fired when the user clicks the tab itself.
	 * Fires the 'close' event.
	 * @param {MAPIFolder} folder MAPI folder.
	 * @private
	 */
	onTabClose: function(folder)
	{
		this.fireEvent('close', this, folder);
	},

	/**
	 * Handles the 'click' event of a tab, which is fired when the user clicks the tab itself.
	 * @param {MAPIFolder} folder MAPI folder.
	 * @private
	 */
	onTabClick: function(folder)
	{
		this.setSelectedFolder(folder);
		this.fireEvent('activate', this, folder);
	},

	/**
	 * Called by the {@link Zarafa.calendar.ui.CalendarViewDragZone} to determine the text
	 * of the {@link Zarafa.calendar.ui.AppointmentView appointment} which is being dragged.
	 * @return {String} The string which must be shown while dragging the appointment
	 */
	getDragDropText: function()
	{
		var count = this.selectionModel.getCount();
		return String.format(ngettext('{0} selected item', '{0} selected items', count), count);
	},

	/**
	 * Event handler for the D&D proxy, which is called when the appointment was dropped.
	 * This will fire the {@link #appointmentcalendardrop} event.
	 * @param {Ext.EventObject} event The event object
	 * @param {Zarafa.calendar.ui.AbstractCalendarView} calendar The calendar from where the appointment was dragged
	 * @param {Zarafa.calendar.ui.AppointmentView} appointment The appointment which was dropped
	 * @param {Zarafa.core.DateRange} dateRange The daterange for the appointment
	 * @private
	 */
	onDrop: function(event, calendar, appointment, dateRange)
	{
		//Make sure this view gets the focus by calling the onTabClick event handler
		this.onTabClick(this.getSelectedFolder());

		this.fireEvent('appointmentcalendardrop', this, calendar, appointment.getRecord(), dateRange, event);
		this.selectionModel.clearSelections();
	},

	/**
	 * Event handler for the D&D proxy, which is called when the appointment was moved.
	 * This will fire the {@link #appointmentmove} event.
	 * @param {Ext.EventObject} event The event object
	 * @param {Zarafa.calendar.ui.AppointmentView} appointment The moved appointment
	 * @param {Zarafa.core.DateRange} dateRange The daterange for the appointment
	 * @private
	 */
	onMove: function(event, appointment, dateRange)
	{
		this.fireEvent('appointmentmove', this, appointment.getRecord(), dateRange, event);
		this.selectionModel.clearSelections();
	},

	/**
	 * Event handler for the D&D proxy, which is called when the appointment was resized.
	 * This will fire the {@link #appointmentresize} event.
	 * @param {Ext.EventObject} event The event object
	 * @param {Zarafa.calendar.ui.AppointmentView} appointment The resized appointment
	 * @param {Zarafa.core.DateRange} dateRange The daterange for the appointment
	 * @private
	 */
	onResize: function(event, appointment, dateRange)
	{
		this.fireEvent('appointmentresize', this, appointment.getRecord(), dateRange, event);
		this.selectionModel.clearSelections();
	},

	/**
	 * Generic implementation for handling mousemove events on the calendar, this needs to be
	 * implemented by subclasses to correctly delegate the event to the proper location in
	 * the calendar.
	 * @param {Ext.EventObject} event The event object
	 */
	onMouseMove: Ext.emptyFn,

	/**
	 * Event handler for the D&D proxy, which is called when the mousedown event has been fired.
	 * Depending if the Ctrl key was pressed the previously selected record will remain selected
	 * or will be deselected.
	 * @param {Object} event The event object
	 * @param {Zarafa.calendar.ui.AppointmentView} appointment The appointment object
	 * @private
	 */
	onMouseDown: function(event, appointment)
	{
		// Make sure this view gets the focus by calling the onTabClick event handler.
		// Only call onTabClick when we actually change the selected folder group, to
		// avoid firing off an unrequired 'activate' event.
		if (this.groupId != this.contextModel.active_group) {
			this.onTabClick(this.getSelectedFolder());
		}

		if (appointment) {
			var record = appointment.getRecord();

			if (event.ctrlKey) {
				if (this.selectionModel.isSelected(record)) {
					this.selectionModel.deselectRecord(record);
				} else {
					this.selectionModel.selectRecord(record, true);
				}
			} else {
				this.selectionModel.selectRecord(record, false);
			}

			this.rangeSelectionModel.clearSelections();
		} else {
			this.selectionModel.clearSelections();
			var xy = event.getXY();
			var range = this.screenLocationToDateRange(xy[0], xy[1]);

			// Manage selection into selection model if range is defined
			if (range) {
				this.rangeSelectionModel.set(range, this);
			}
		}
	},

	/**
	 * Event handler for the D&D proxy, which is called when the mouseup event has been fired.
	 * If an {@link Zarafa.calendar.ui.AppointmentView appointment} was selected and moved this will mark
	 * the given appointment as selected.
	 * @param {Object} event The event object
	 * @param {Zarafa.calendar.ui.AppointmentView} appointment The selected appointment
	 * @private
	 */
	onMouseUp: function(event, appointment)
	{
		if (appointment && (appointment.isAllDay() ? appointment.eventOverHeader(event) : appointment.eventOverBody(event))) {
			this.selectionModel.deselectRecord(appointment.getRecord());
		}
	},

	/**
	 * Method called from {@link Zarafa.calendar.ui.CalendarViewDragZone#onInitDrag} when the user starts dragging
	 *
	 * @param {Ext.EventObject} event The mouse event
	 * @param {Zarafa.calendar.ui.AppointmentView} appointment The appointment on which the event occurred
	 * @private
	 */
	onInitDrag: function(event, appointment)
	{
		this.fireEvent('appointmentinitdrag', this, event, appointment);
	},

	/**
	 * Method called from {@link Zarafa.calendar.ui.CalendarViewDragZone#onInitDrag} when the user releases an appointment they had been dragging.
	 *
	 * @param {Ext.EventObject} event The mouse event
	 * @param {Zarafa.calendar.ui.AppointmentView} The appointment on which the event occurred
	 * @private
	 */
	onEndDrag: function(event, appointment)
	{
		this.fireEvent('appointmentenddrag', this, event, appointment);
	},

	/**
	 * Event handler for the D&D proxy, which is called when an given {@link Zarafa.core.DateRange dateRange}
	 * has been selected. This will update the {@link #rangeSelectionModel}
	 * @param {Object} event The event object
	 * @param {Zarafa.core.DateRange} dateRange The dateRange which was selected.
	 * @private
	 */
	onSelect: function(event, dateRange)
	{
		this.rangeSelectionModel.set(dateRange, this);
	},

	/**
	 * Destroy all elements which were created by this calendar view. Note that all
	 * cleaned up by the superclass.
	 * @override
	 */
	destroy: function()
	{
		Ext.destroy(this.bodyDragZone, this.bodyDropZone);
		Ext.destroy(this.headerDragZone, this.headerDropZone);

		Zarafa.calendar.ui.AbstractCalendarView.superclass.destroy.call(this);
	},

	/**
	 * Set this view to the currently active one in the {@link Zarafa.calendar.CalendarMultiView}
	 * @param {Boolean} active
	 */
	setActive: function(active)
	{
		this.active = active;

		// Update the header's visual active state immediately
		if ( this.headerBackgroundLayer ) {
			if ( active ) {
				this.headerBackgroundLayer.removeClass('k-inactive-calendar');
			} else {
				this.headerBackgroundLayer.addClass('k-inactive-calendar');
			}
		}
	}
});
Ext.namespace('Zarafa.calendar.ui');

/**
 * @class Zarafa.calendar.ui.AbstractDateRangeView
 * @extends Zarafa.core.ui.View
 *
 * An abstract class that implements common functionality of views that represent [start, due> date ranges on calendars.
 * These views are child views on a calendar view, and rely on parent view's <code>dateRangeToBodyBounds</code> and
 * <code>dateRangeToHeaderBounds</code> methods for layout.  When the range spans 24 hours or more the view is automatically
 * laid out on the header of the parent view.
 * <p>
 * The view uses zero or more so called 'boxes' defined by (left, right, top, bottom) tuples to define the range on the
 * calendar body. As the range changes, boxes may be added or removed dynamically. Each box is represented by one or more
 * HTML elements on the calendar body. The default implementation uses a single <code>DIV</code> element for each box, but
 * it is possible to override the <code>createBodyElement</code> and <code>destroyBodyElement</code> functions to implement
 * your own representations.
 * <p>
 * The view carries 'slot' information. Multiple ranges (most notably, appointments) may overlap in time. When laid out on the
 * header these appear in separate rows. When laid out in the body, they appear in separate columns. These rows and columns
 * are referred to here as slots, and which slot a range should be laid out in is determined by the parent calendar view
 * (using greedy graph coloring).
 */
Zarafa.calendar.ui.AbstractDateRangeView = Ext.extend(Zarafa.core.ui.View, {
	/**
	 * Multiple ranges (most notably, appointments) may overlap in time. When laid out on the
	 * header these appear in separate rows. When laid out in the body, they appear in separate columns.
	 * These rows and columns are referred to using a slot number.
	 * @property
	 * @type Number
	 */
	slot: undefined,

	/**
	 * Multiple ranges (most notably, appointments) may overlap in time. When laid out on the
	 * header these appear in separate rows. When laid out in the body, they appear in separate columns.
	 * The total sum of these rows and columns are referred to as the slot Count.
	 * @property
	 * @type Number
	 */
	slotCount: undefined,

	/**
	 * Marks the view as visible. This can be set using {@link #setVisible}.
	 * @property
	 * @type Boolean
	 */
	visible: true,

	/**
	 * Date range.
	 * @property
	 * @type Zarafa.core.DateRange
	 */
	dateRange: undefined,

	/**
	 * Sets date range. This method does not auto-update.
	 * @param {Zarafa.core.DateRange} dateRange
	 */
	setDateRange: function(dateRange)
	{
		this.dateRange = dateRange;
	},

	/**
	 * Returns the current date range.
	 * @return {Zarafa.core.DateRange} dateRange
	 */
	getDateRange: function()
	{
		return this.dateRange;
	},

	/**
	 * Tests whether a mouse event is over the appointment body.
	 * NOTE: Must be implemented by subclass.
	 * @param {Ext.EventObject} event event object.
	 * @return {Boolean} true iff the event is over the body.
	 */
	eventOverBody: function(event)
	{
		return false;
	},

	/**
	 * Tests whether a mouse event is over the body start resize handle.
	 * NOTE: Must be implemented by subclass.
	 * @param {Ext.EventObject} event event object.
	 * @return {Boolean} true iff the event is over the resize handle.
	 */
	eventOverBodyStartHandle: function(event)
	{
		return false;
	},

	/**
	 * Tests whether a mouse event is over the body Due resize handle.
	 * NOTE: Must be implemented by subclass.
	 * @param {Ext.EventObject} event event object.
	 * @return {Boolean} true iff the event is over the resize handle.
	 */
	eventOverBodyDueHandle: function(event)
	{
		return false;
	},

	/**
	 * Tests whether a mouse event is over the appointment header.
	 * NOTE: Must be implemented by subclass.
	 * @param {Ext.EventObject} event event object.
	 * @return {Boolean} true iff the event is over the header.
	 */
	eventOverHeader: function(event)
	{
		return false;
	},

	/**
	 * Tests whether a mouse event is over the header start resize handle.
	 * NOTE: Must be implemented by subclass.
	 * @param {Ext.EventObject} event event object.
	 * @return {Boolean} true iff the event is over the resize handle.
	 */
	eventOverHeaderStartHandle: function(event)
	{
		return false;
	},

	/**
	 * Tests whether a mouse event is over the header Due resize handle.
	 * NOTE: Must be implemented by subclass.
	 * @param {Ext.EventObject} event event object.
	 * @return {Boolean} true iff the event is over the resize handle.
	 */
	eventOverHeaderDueHandle: function(event)
	{
		return false;
	},

	/**
	 * Sets the visibility of the range
	 * @param {Boolean} visible if true the view will be shown, if false it will be hidden
	 */
	setVisible: function(visible)
	{
		this.visible = visible;
		for (var i = 0, len = this.elements.length; i < len; i++) {
			this.elements[i].setVisible(visible);
		}
	},

	/**
	 * @return {Boolean} true iff the range is visible.
	 */
	isVisible: function()
	{
		return this.visible;
	},

	/**
	 * Lays out the header elements of the view.
	 * @private
	 */
	layoutInHeader: Ext.emptyFn,

	/**
	 * Lays out the body of the view.
	 * @private
	 */
	layoutInBody: Ext.emptyFn,

	/**
	 * Lays out the view. If the range represented by this view spans over 24 hours,
	 * the body is made invisible and the header element is shown instead.
	 * If the range is shorter the body is visible and the header invisible.
	 * @protected
	 */
	onLayout: function()
	{
		if (this.visible) {
			if (this.isHeaderRange()) {
				this.layoutInHeader();
			} else {
				this.layoutInBody();
			}
		}

		Zarafa.calendar.ui.AbstractDateRangeView.superclass.onLayout.call(this);
	},

	/**
	 * Tests if the date range should be laid out in the header, which is when a range spans 24 hours or more.
	 * @return {Boolean} true if the date range represents 24 hours or more, false otherwise.
	 */
	isHeaderRange: function()
	{
		return this.parentView.isHeaderRange(this.getDateRange());
	}
});
Ext.namespace('Zarafa.calendar.ui');

/**
 * @class Zarafa.calendar.ui.CalendarContextMenu
 * @extends Zarafa.core.ui.menu.ConditionalMenu
 * @xtype zarafa.calendarcontextmenu
 *
 * Extend {@link Zarafa.core.ui.menu.ConditionalMenu ConditionalMenu} to add the
 * {@link Zarafa.core.ui.menu.ConditionalItems ConditionalItems} for the
 * CalendarContext.
 */
Zarafa.calendar.ui.CalendarContextMenu = Ext.extend(Zarafa.core.ui.menu.ConditionalMenu, {
	// Insertion points for this class
	/**
	 * @insert context.calendar.contextmenu.actions
	 * Insertion point for adding actions menu items into the context menu
	 * @param {Zarafa.calendar.ui.CalendarContextMenu} contextmenu This contextmenu
	 */
	/**
	 * @insert context.calendar.contextmenu.options
	 * Insertion point for adding options menu items into the context menu
	 * @param {Zarafa.calendar.ui.CalendarContextMenu} contextmenu This contextmenu
	 */

	/**
	 * @cfg {Zarafa.calendar.ui.CalendarPanel} The calendar panel for which this context
	 * menu is shown
	 */
	calendarPanel: null,

	/**
	 * @cfg {Zarafa.core.data.IPMRecord[]} The records on which this context menu acts
	 */
	records: undefined,

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

		if (Ext.isDefined(config.records) && !Array.isArray(config.records)) {
			config.records = [ config.records ];
		}

		config = Ext.applyIf(config, {
			items: [
				this.createContextActionItems(),
				{ xtype: 'menuseparator' },
				container.populateInsertionPoint('context.calendar.contextmenu.actions', this),
				{ xtype: 'menuseparator' },
				this.createContextOptionsItems(config.records),
				{ xtype: 'menuseparator' },
				container.populateInsertionPoint('context.calendar.contextmenu.options', this),
				{ xtype: 'menuseparator' },
				this.createOptionsMenuItem()
			],
			listeners: {
				scope: this,
				mouseover: this.onMouseover
			}
		});

		Zarafa.calendar.ui.CalendarContextMenu.superclass.constructor.call(this, config);
	},

	/**
	 * Create the Action context menu items
	 * @return {Zarafa.core.ui.menu.ConditionalItem[]} The list of Action context menu items
	 * @private
	 */
	createContextActionItems: function()
	{
		return [{
			xtype: 'zarafa.conditionalitem',
			iconCls: 'icon_new_appointment',
			text: _('New Appointment'),
			beforeShow: this.beforeShowPhantom,
			meetingRequest: false,
			handler: this.onCreate,
			scope: this
		},{
			xtype: 'zarafa.conditionalitem',
			iconCls: 'icon_new_meeting_request',
			text: _('New Meeting Request'),
			beforeShow: this.beforeShowPhantom,
			meetingRequest: true,
			handler: this.onCreate,
			scope: this
		},{
			xtype: 'zarafa.conditionalitem',
			iconCls: 'icon_open',
			text: _('Open'),
			beforeShow: this.beforeShowNonPhantom,
			handler: this.onOpen,
			scope: this
		},{
			xtype: 'zarafa.conditionalitem',
			text: _('Copy/Move'),
			iconCls: 'icon_copy',
			beforeShow: this.beforeShowNonPhantom,
			hideOnDisabled: false,
			handler: this.onCopyMove,
			scope: this
		},{
			xtype: 'zarafa.conditionalitem',
			iconCls: 'icon_delete',
			text: _('Delete'),
			beforeShow: this.beforeShowNonPhantom,
			handler: this.onDelete,
			scope: this
		},{
			xtype: 'zarafa.conditionalitem',
			text: _('Reply'),
			iconCls: 'icon_reply',
			singleSelectOnly: true,
			beforeShow: this.beforeShowOnMeetingForward,
			responseMode: Zarafa.mail.data.ActionTypes.REPLY,
			handler: this.onReplyMeeting,
			scope: this
		},{
			xtype: 'zarafa.conditionalitem',
			text: _('Reply All'),
			iconCls: 'icon_reply_all',
			singleSelectOnly: true,
			beforeShow: this.beforeShowOnMeetingForward,
			responseMode: Zarafa.mail.data.ActionTypes.REPLYALL,
			handler: this.onReplyMeeting,
			scope: this
		},{
			xtype: 'zarafa.conditionalitem',
			text: _('Forward'),
			iconCls: 'icon_forward',
			singleSelectOnly: true,
			beforeShow: this.beforeShowOnMeetingForward,
			handler: this.onForwardMeeting,
			scope: this
		},{
			xtype: 'menuseparator'
		},{
			xtype: 'zarafa.conditionalitem',
			ref: 'acceptButton',
			text: _('Accept'),
			hidden: true,
			iconCls: 'icon_calendar_appt_accept',
			beforeShow: this.beforeShowOnMeeting,
			responseStatus: Zarafa.core.mapi.ResponseStatus.RESPONSE_ACCEPTED,
			scope: this
		},{
			xtype: 'zarafa.conditionalitem',
			ref: 'tentativeButton',
			text: _('Tentative'),
			hidden: true,
			iconCls: 'icon_calendar_appt_tentative',
			beforeShow: this.beforeShowOnMeeting,
			responseStatus: Zarafa.core.mapi.ResponseStatus.RESPONSE_TENTATIVE,
			scope: this
		},{
			xtype: 'zarafa.conditionalitem',
			ref: 'declineButton',
			text: _('Decline'),
			iconCls: 'icon_calendar_appt_cancelled',
			hidden: true,
			beforeShow: this.beforeShowOnMeeting,
			responseStatus: Zarafa.core.mapi.ResponseStatus.RESPONSE_DECLINED,
			scope: this
		},{
			xtype: 'zarafa.conditionalitem',
			ref: 'proposeNewTimeButton',
			text: _('Propose New Time'),
			proposeNewTime: true,
			hidden: true,
			iconCls: 'icon_calendar_appt_newtime',
			beforeShow: this.beforeShowOnMeeting,
			scope: this
		}];
	},

	/**
	 * Create the Option context menu items
	 * @param {Zarafa.core.data.IPMRecord[]} records The records on which this menu acts
	 * @return {Zarafa.core.ui.menu.ConditionalItem[]} The list of Option context menu items
	 * @private
	 */
	createContextOptionsItems: function(records)
	{
		return [{
			xtype: 'zarafa.conditionalitem',
			cls: 'k-unclickable',
			iconCls: 'icon_categories',
			text: _('Categories'),
			hideOnClick: false,
			beforeShow: this.beforeShowNonPhantom,
			menu: this.createSubCategories(records)
		},{
			xtype: 'zarafa.conditionalitem',
			iconCls: 'icon_busystatus',
			text: _('Show as'),
			beforeShow: this.beforeShowNonPhantom,
			menu: {
				xtype: 'zarafa.conditionalmenu',
				items: this.createBusyStatusItems()
			}
		},{
			xtype: 'zarafa.conditionalitem',
			text: _('Send to…'),
			iconCls: 'icon_embedded_attachment',
			singleSelectOnly: true,
			beforeShow: this.beforeShowItem,
			responseMode: Zarafa.mail.data.ActionTypes.FORWARD_ATTACH,
			handler: this.onContextItemResponse,
			scope: this
		},{
			xtype: 'zarafa.conditionalitem',
			text: _('Export as'),
			cls: 'k-unclickable',
			iconCls: 'icon_export',
			hideOnClick: false,
			beforeShow: this.beforeShowItem,
			scope: this,
			menu: {
				xtype: 'zarafa.exportitemcontextmenu',
				records: records
			}
		}];
	},

	/**
	 * Create the sub context menu items for categories only if records are supplied.
	 * @param {Zarafa.core.data.IPMRecord[]} records The records on which this menu acts
	 * @return {Object} The object of Options containing {@link Zarafa.common.categories.ui.CategoriesContextMenu}
	 * @private
	 */
	createSubCategories: function(records)
	{
		if (records) {
			return {
				xtype: 'zarafa.categoriescontextmenu',
				records: records
			};
		}
	},

	/**
	 * Create the Busy status context submenu items
	 * @return {Zarafa.core.ui.menu.ConditionalItem[]} The list of Busy status context submenu items
	 * @private
	 */
	createBusyStatusItems: function()
	{
		return [{
			xtype: 'zarafa.conditionalitem',
			iconCls: 'icon_busystatus_free',
			text: Zarafa.core.mapi.BusyStatus.getDisplayName(Zarafa.core.mapi.BusyStatus.FREE),
			busyStatus: Zarafa.core.mapi.BusyStatus.FREE,
			handler: this.onSetBusyStatus,
			scope: this
		},{
			xtype: 'zarafa.conditionalitem',
			iconCls: 'icon_busystatus_tentative',
			text: Zarafa.core.mapi.BusyStatus.getDisplayName(Zarafa.core.mapi.BusyStatus.TENTATIVE),
			busyStatus: Zarafa.core.mapi.BusyStatus.TENTATIVE,
			handler: this.onSetBusyStatus,
			scope: this
		},{
			xtype: 'zarafa.conditionalitem',
			iconCls: 'icon_busystatus_busy',
			text: Zarafa.core.mapi.BusyStatus.getDisplayName(Zarafa.core.mapi.BusyStatus.BUSY),
			busyStatus: Zarafa.core.mapi.BusyStatus.BUSY,
			handler: this.onSetBusyStatus,
			scope: this
		},{
			xtype: 'zarafa.conditionalitem',
			iconCls: 'icon_busystatus_outofoffice',
			text: Zarafa.core.mapi.BusyStatus.getDisplayName(Zarafa.core.mapi.BusyStatus.OUTOFOFFICE),
			busyStatus: Zarafa.core.mapi.BusyStatus.OUTOFOFFICE,
			handler: this.onSetBusyStatus,
			scope: this
		},{
			xtype: 'zarafa.conditionalitem',
			iconCls: 'icon_busystatus_workingelsewhere',
			text: Zarafa.core.mapi.BusyStatus.getDisplayName(Zarafa.core.mapi.BusyStatus.WORKINGELSEWHERE),
			busyStatus: Zarafa.core.mapi.BusyStatus.WORKINGELSEWHERE,
			handler: this.onSetBusyStatus,
			scope: this
		}];
	},

	/**
	 * Open the {@link Zarafa.common.dialogs.CopyMoveContentPanel CopyMoveContentPanel} for copying
	 * or moving the currently selected appointment/meeting requests.
	 * @private
	 */
	onCopyMove: function()
	{
		Zarafa.common.Actions.openCopyMoveContent(this.records);
	},

	/**
	 * Makes the given menuitem invisible when any of the records is not a phantom record.
	 * @param {Zarafa.core.ui.menu.MenuItem} item The item which is being tested
	 * @param {Zarafa.core.data.MAPIRecord[]} records The records on which this context
	 * menu is operating.
	 * @private
	 */
	beforeShowPhantom: function(item, records)
	{
		var hasNonPhantoms = false;
		if (records) {
			for (var i = 0, len = records.length; i < len; i++) {
				if (records[i].phantom === false) {
					hasNonPhantoms = true;
				}
			}
		}
		item.setVisible(!hasNonPhantoms);
	},

	/**
	 * Makes the given menuitem invisible when any of the records is a phantom record.
	 * @param {Zarafa.core.ui.menu.MenuItem} item The item which is being tested
	 * @param {Zarafa.core.data.MAPIRecord[]} records The records on which this context
	 * menu is operating.
	 * @private
	 */
	beforeShowNonPhantom: function(item, records)
	{
		if (!Ext.isEmpty(records)) {
			item.setVisible(!records.some(function (record) {
				return record.phantom === true;
			}));
		} else {
			item.setVisible(false);
		}
	},

	/**
	 * Makes the given menuitem invisible when any of the records has read
	 * only access or is a phantom record.
	 *
	 * @param {Zarafa.core.ui.menu.MenuItem} item The item which is being tested
	 * @param {Zarafa.core.data.MAPIRecord[]} records The records on which this context
	 * menu is operating.
	 * @private
	 */
	beforeShowItem: function(item, records)
	{
		if(!Ext.isEmpty(records)) {
			item.setVisible(!records.some(function (record) {
				return record.get('access') === Zarafa.core.mapi.Access.ACCESS_READ || record.phantom === true;
			}));
		} else {
			item.setVisible(false);
		}
	},

	/**
	 * Makes the given menuitem invisible when record is appointment or login user is organizer of meeting.
	 * @param {Zarafa.core.ui.menu.MenuItem} item The item which is being tested
	 * @param {Zarafa.core.data.MAPIRecord[]} records The records on which this context
	 * menu is operating.
	 * @private
	 */
	beforeShowOnMeeting: function(item, records)
	{
		// If user has select more then one record then we should not have to show the menu items.
		if(!records || records.length > 1){
			return;
		}

		var record = records[0];
		var isProposeButton = Ext.isDefined(item.proposeNewTime) && item.proposeNewTime;

		// if record is received meeting then show all buttons (accept/decline/ tentatively accept) in context menu
		if(record.isMeetingReceived()) {
			item.setVisible(true);
		}

		// if selected record is received simple meeting request then set the handler on each button.
		if(record.isMeetingReceived() && !(record.isRecurringOccurrence() || record.get('recurring'))) {
			if(isProposeButton) {
				item.setHandler(this.openProposeNewTimeContent, this);
			} else {
				item.setHandler(this.openSendConfirmationContent, this);
			}
		}

		if(record.isMeetingReceived() && (record.isRecurringOccurrence() || record.get('recurring'))) {
			// Add sub menu item while selected received meeting request is recurring.
			// it will show the two sub menu items which provide facility to user to accept, tentatively accept,
			// decline recurring series or occurrence and allow user to propose new time for single occurrence
			// of recurring meeting request.
			item.menu = new Ext.menu.Menu({
				items: [{
					xtype: 'zarafa.conditionalitem',
					text: item.text +' '+ _('Occurrence'),
					responseStatus: item.responseStatus,
					beforeShow: function(item, records) {
						if(isProposeButton) {
							item.setHandler(this.openProposeNewTimeContent, this);
						} else {
							item.setHandler(this.openSendConfirmationContent, this);
						}
					},
					scope: this
				},{
					xtype: 'zarafa.conditionalitem',
					text: item.text +' '+ _('Series'),
					name: 'recurring',
					beforeShow: function(item, records) {
						if(isProposeButton) {
							item.setVisible(false);
						} else {
							item.setHandler(this.openSendConfirmationContent, this);
						}
					},
					responseStatus: item.responseStatus,
					isProposeButton: isProposeButton,
					scope: this
				}],
				scope: this
			});
		}
	},

	/**
	 * Event handler for the mouseover event of this menu. Will make sure that any
	 * open tooltip is closed.
	 */
	onMouseover: function()
	{
		// In the list view there is no calendarPanel (and no tooltip)
		if ( this.calendarPanel ){
			this.calendarPanel.getView().getTooltipInstance().hide(0);
		}
	},

	/**
	 * Opens the Propose New Time Content Panel
	 * @param {Ext.Button} button The clicked button
	 * @param {EventObject} eventObject The click event object
	 * @private
	 */
	openProposeNewTimeContent: function(button, eventObject)
	{
		var record;
		if(Array.isArray(this.records)) {
			record = this.records[0];
		}

		if (record.get('appointment_not_found')) {
			Ext.MessageBox.show({
				title: _('Appointment not found'),
				msg:_('This appointment has been moved or deleted, do you want to continue?'),
				cls: Ext.MessageBox.WARNING_CLS,
				record: record,
				fn: this.onProposeNewTimeAppointmentNotFoundConfirmation.createDelegate(this, [record], 1),
				scope: this,
				buttons: Ext.MessageBox.YESNO
			});
		} else {
			Zarafa.calendar.Actions.openProposeNewTimeContent(record);
		}
	},

	/**
	 * Callback function for {@link #openProposeNewTimeContent}, which opens a {@link Ext.MessageBox} if
	 * the appointment is not found in the calendar, but we still wants to propose a new time.
	 * @param {String} button The button which was clicked by the user
	 * @param {Zarafa.core.data.MAPIRecord} record The record on which this context
	 * @private
	 */
	onProposeNewTimeAppointmentNotFoundConfirmation: function(button, record)
	{
		if (button === 'yes') {
			Zarafa.calendar.Actions.openProposeNewTimeContent(record);
		}
	},

	/**
	 * Opens a {@link Zarafa.calendar.dialogs.SendMeetingRequestConfirmationContentPanel SendMeetingRequestConfirmationContentPanel}
	 * if meeting was recurring occurrence then remove the basedate.
	 * @param {Ext.Button} button button object.
	 * @param {EventObject} eventObject The click event object.
	 * @private
	 */
	openSendConfirmationContent: function(button, eventObject)
	{
		var record;
		if(Array.isArray(this.records)) {
			record = this.records[0];
		} else {
			return;
		}

		if (record.get('appointment_not_found')) {
			Ext.MessageBox.show({
				title: _('Appointment not found'),
				msg:_('This appointment has been moved or deleted, do you want to continue?'),
				cls: Ext.MessageBox.WARNING_CLS,
				record: record,
				fn: this.onRespondAppointmentNotFoundConfirmation.createDelegate(this, [ button.responseStatus, record ], 1),
				scope: this,
				buttons: Ext.MessageBox.YESNO
			});
		} else {
			Zarafa.calendar.Actions.openSendConfirmationContent(record, {
					responseType: button.responseStatus,
					buttonName: button.name
			});
		}
	},

	/**
	 * Callback function for {@link #openSendConfirmationContent}, which opens a {@link Ext.MessageBox} if
	 * the appointment is not found in the calendar, but we still want to accept it.
	 * @param {String} button The button which was clicked by the user
	 * @param {Zarafa.core.mapi.ResponseStatus} responseType The response type which was selected by the user
	 * @param {Zarafa.core.data.MAPIRecord} record The record on which this context
	 * @private
	 */
	onRespondAppointmentNotFoundConfirmation: function(button, responseType, record)
	{
		if (button === 'yes') {
			Zarafa.calendar.Actions.openSendConfirmationContent(record, { responseType: responseType });
		}
	},

	/**
	 * Open the categories dialog for all selected records
	 * @param {Zarafa.core.ui.menu.ConditionalItem} button The selected menuitem
	 */
	onCategories: function(button)
	{
		Zarafa.common.Actions.openCategoriesContent(this.records);
	},

	/**
	 * Set the busy state for all selected records
	 * @param {Zarafa.core.ui.menu.ConditionalItem} button The selected menuitem
	 */
	onSetBusyStatus: function(button)
	{
		var store;
		var records = this.records;

		Ext.each(records, function(record) {
			store = record.getStore();
			record.set('busystatus', button.busyStatus);
		}, this);

		store.save(records);
	},

	/**
	 * Open the selected record
	 * @param {Zarafa.core.ui.menu.ConditionalItem} button The selected menuitem
	 */
	onOpen: function(open)
	{
		Zarafa.calendar.Actions.openAppointmentContent(this.records);
	},

	/**
	 * Delete all selected records
	 * @param {Zarafa.core.ui.menu.ConditionalItem} button The selected menuitem
	 */
	onDelete: function(button)
	{
		Zarafa.common.Actions.deleteRecords(this.records);
	},

	/**
	 * Create a new appointment / meeting request
	 * @param {Zarafa.core.ui.menu.ConditionalItem} button The selected menuitem
	 */
	onCreate: function(button)
	{
		if (!this.records) {
			var model = this.calendarPanel.model;
			var calendarView = this.calendarPanel.getView();
			var activeCalendar = calendarView.rangeSelectionModel.calendarView;
			var rangeModel = this.calendarPanel.getRangeSelectionModel();

			model.createRecord(function(record){
				this.openContent(button, [record]);
			}.createDelegate(this, [button], true), activeCalendar.getSelectedFolder(), rangeModel.dateRange);
		} else {
			this.openContent(button, this.records);
		}
	},

	/**
	 * Helper function to open dialog for new appointment / meeting request.
	 * @param {Zarafa.core.ui.menu.ConditionalItem} button The selected menuitem
	 * @param {Zarafa.core.data.MAPIRecord[]} records The records on which this contextmenu gets opened
	 */
	openContent: function(button, records)
	{
		if (button.meetingRequest) {
			for (var i = 0, len = records.length; i < len; i++) {
				var record = records[i];
				// Change meeting status only if it is not a meeting, otherwise leave as it is (in order not to overwrite old meeting status)
				if (record.get('meeting') === Zarafa.core.mapi.MeetingStatus.NONMEETING) {
					record.convertToMeeting();
				}
			}
			Zarafa.calendar.Actions.openMeetingRequestContent(records);
		} else {
			Zarafa.calendar.Actions.openAppointmentContent(records);
		}
	},

	/**
	 * Makes the Forward menu item visible only when a single meeting
	 * record (organized or received) is selected.
	 * @param {Zarafa.core.ui.menu.MenuItem} item The item which is being tested
	 * @param {Zarafa.core.data.MAPIRecord[]} records The records on which this context
	 * menu is operating.
	 * @private
	 */
	beforeShowOnMeetingForward: function(item, records)
	{
		if (!records || records.length !== 1) {
			item.setVisible(false);
			return;
		}

		var record = records[0];
		item.setVisible(record.isMeeting() && !record.isMeetingCanceled() && !record.phantom);
	},

	/**
	 * Called when the "Forward" menu item is clicked. Opens the
	 * forward meeting request dialog for the selected appointment.
	 * @private
	 */
	onForwardMeeting: function()
	{
		if (Array.isArray(this.records) && this.records.length > 0) {
			Zarafa.calendar.Actions.openForwardMeetingRequestContent(this.records[0]);
		}
	},

	/**
	 * Called when "Reply" or "Reply All" is clicked on a calendar
	 * meeting. AppointmentRecords lack the 'reply-to' sub-store
	 * that the mail model's createResponseRecord expects, so we
	 * build the reply manually from the appointment's properties.
	 * @param {Ext.Button} button The button which was clicked
	 * @private
	 */
	onReplyMeeting: function(button)
	{
		if (!Array.isArray(this.records) || this.records.length === 0) {
			return;
		}

		var record = this.records[0];
		var isReplyAll = (button.responseMode === Zarafa.mail.data.ActionTypes.REPLYALL);

		var buildReply = function(rec) {
			var iAmOrganizer = rec.isMeetingOrganized && rec.isMeetingOrganized();

			// Reply (not All) as organizer is replying to yourself
			if (!isReplyAll && iAmOrganizer) {
				container.getNotifier().notify('info.meeting',
					pgettext('calendar.contextmenu', 'Use Reply All to reply to all attendees of your meeting.'));
				return;
			}

			var mailModel = container.getContextByName('mail').getModel();
			var responseRecord = mailModel.createRecord();

			// Use the RFC 5322 prefix setting
			var prefix = container.getSettingsModel().get('zarafa/v1/contexts/mail/use_english_abbreviations') ?
				'Re' : _('Re');
			var subject = rec.get('normalized_subject') || rec.get('subject') || '';
			responseRecord.set('subject', prefix + ': ' + subject);

			var recipientStore = responseRecord.getRecipientStore();
			var loggedInEntryId = container.getUser().getEntryId();

			var addRecipient = function(name, email, addrType, entryId, type) {
				if (!email) {
					return;
				}
				if (loggedInEntryId && entryId &&
					Zarafa.core.EntryId.compareABEntryIds(entryId, loggedInEntryId)) {
					return;
				}
				recipientStore.add(Zarafa.core.data.RecordFactory.createRecordObjectByCustomType(
					Zarafa.core.data.RecordCustomObjectType.ZARAFA_RECIPIENT, {
						display_name: name || '',
						smtp_address: email,
						address_type: addrType || 'SMTP',
						entryid: entryId || '',
						recipient_type: type
					}));
			};

			if (iAmOrganizer) {
				// Reply All as organizer: all attendees → To
				var apptRecipientStore = rec.getRecipientStore();
				if (apptRecipientStore) {
					apptRecipientStore.each(function(r) {
						if (r.isMeetingOrganizer()) {
							return;
						}
						addRecipient(
							r.get('display_name'),
							r.get('smtp_address') || r.get('email_address'),
							r.get('address_type'),
							r.get('entryid'),
							Zarafa.core.mapi.RecipientType.MAPI_TO
						);
					});
				}
			} else {
				// Attendee: organizer → To
				addRecipient(
					rec.get('sent_representing_name') || rec.get('sender_name'),
					rec.get('sent_representing_email_address') || rec.get('sender_email_address'),
					rec.get('sent_representing_address_type') || rec.get('sender_address_type'),
					rec.get('sent_representing_entryid') || rec.get('sender_entryid'),
					Zarafa.core.mapi.RecipientType.MAPI_TO
				);

				// Reply All as attendee: other attendees → CC
				if (isReplyAll) {
					var apptRecipientStore = rec.getRecipientStore();
					if (apptRecipientStore) {
						apptRecipientStore.each(function(r) {
							if (r.isMeetingOrganizer()) {
								return;
							}
							addRecipient(
								r.get('display_name'),
								r.get('smtp_address') || r.get('email_address'),
								r.get('address_type'),
								r.get('entryid'),
								Zarafa.core.mapi.RecipientType.MAPI_CC
							);
						});
					}
				}
			}

			Zarafa.core.data.UIFactory.openCreateRecord(responseRecord);
		};

		// We need the recipient store for Reply All, and also for
		// Reply as organizer (to show the notification after checking).
		var needRecipients = isReplyAll;
		if (needRecipients && !(record.getRecipientStore && record.getRecipientStore())) {
			var store = record.getStore();
			var openHandler = function(s, r) {
				if (record !== r) {
					return;
				}
				store.un('open', openHandler, record);
				buildReply(r);
			};
			store.on('open', openHandler, record);
			record.open();
		} else {
			buildReply(record);
		}
	},

	/**
	 * Called when one of the "Send to" menuitems are clicked.
	 * @param {Ext.Button} button The button which was clicked
	 * @private
	 */
	onContextItemResponse: function(button)
	{
		var mailModel = container.getContextByName('mail').getModel();
		Zarafa.mail.Actions.openCreateMailResponseContent(this.records, mailModel, button.responseMode, {"attachAsIcs":true});
	},

	/**
	 * Create the Options menu entry.
	 * @return {Object} Menu item configuration
	 * @private
	 */
	createOptionsMenuItem: function()
	{
		return {
			xtype: 'zarafa.conditionalitem',
			text: _('Options'),
			iconCls: 'icon_cogwheel',
			beforeShow: this.beforeShowNonPhantom,
			singleSelectOnly: true,
			handler: this.onContextItemOptions,
			scope: this
		};
	},

	/**
	 * Open the options dialog for the selected record.
	 * @private
	 */
	onContextItemOptions: function()
	{
		Zarafa.calendar.Actions.openOptionsContent(this.records, {
			autoSave: true
		});
	}
});

Ext.reg('zarafa.calendarcontextmenu', Zarafa.calendar.ui.CalendarContextMenu);
Ext.namespace('Zarafa.calendar.ui');

/**
 * @class Zarafa.calendar.ui.CalendarGridColumnModel
 * @extends Zarafa.common.ui.grid.ColumnModel
 *
 * The {@link Zarafa.calendar.ui.CalendarGridColumnModel CalendarGridColumnModel
 * is the default {@link Ext.grid.ColumnModel ColumnModel} for the Calendar.
 */
Zarafa.calendar.ui.CalendarGridColumnModel = Ext.extend(Zarafa.common.ui.grid.ColumnModel, {
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			columns: this.createColumns(),
			defaults: {
				sortable: true
			}
		});

		Zarafa.calendar.ui.CalendarGridColumnModel.superclass.constructor.call(this, config);
	},

	/**
	 * Create an array of {@link Ext.grid.Column columns} which must be visible within
	 * the default view of this {@link Ext.grid.ColumnModel ColumnModel}.
	 *
	 * @return {Ext.grid.Column[]} The array of columns
	 * @private
	 */
	createColumns: function()
	{
		return [{
			header: '<p class="icon_index">&nbsp;<span class="title">' + _('Icon') + '</span></p>',
			headerCls: 'zarafa-icon-column icon',
			dataIndex: 'icon_index',
			tooltip: _('Sort by: Icon'),
			width: 24,
			renderer: Zarafa.common.ui.grid.Renderers.icon,
			fixed: true
		},{
			xtype: 'checkcolumn',
			headerCls: 'zarafa-icon-column',
			header: '<p class="icon_all_day">&nbsp;<span class="title">' + _('All Day') + '</span></p>',
			dataIndex: 'alldayevent',
			tooltip: _('Sort by: All Day'),
			width: 24,
			fixed: true,
			// disable checkbox selection
			processEvent: Ext.emptyFn
		},{
			header: '<p class="icon_recurrence">&nbsp;<span class="title">' + _('Recurring') + '</span></p>',
			headerCls: 'zarafa-icon-column',
			dataIndex: 'recurring',
			tooltip: _('Sort by: Recurring'),
			width: 24,
			renderer: Zarafa.common.ui.grid.Renderers.recurrence,
			fixed: true
		},{
			header: _('Subject'),
			dataIndex: 'subject',
			tooltip: _('Sort by: Subject'),
			width: 200,
			renderer: Zarafa.common.ui.grid.Renderers.subject
		},{
			header: _('Start Date'),
			dataIndex: 'startdate',
			tooltip: _('Sort by: Start Date'),
			width: 160,
			renderer: Zarafa.common.ui.grid.Renderers.datetime
		},{
			header: _('End Date'),
			dataIndex: 'duedate',
			tooltip: _('Sort by: End Date'),
			width: 160,
			renderer: Zarafa.common.ui.grid.Renderers.datetime
		},{
			header: _('Location'),
			dataIndex: 'location',
			tooltip: _('Sort by: Location'),
			width: 200,
			renderer: Zarafa.common.ui.grid.Renderers.subject
		},{
			header: _('Duration'),
			dataIndex: 'duration',
			tooltip: _('Sort by: Duration'),
			width: 160,
			renderer: Zarafa.common.ui.grid.Renderers.duration
		},{
			header: _('Meeting Status'),
			dataIndex: 'meeting',
			tooltip: _('Sort by: Meeting Status'),
			width: 160,
			renderer: Zarafa.common.ui.grid.Renderers.meetingstatus
		},{
			header: _('Show as'),
			dataIndex: 'busystatus',
			tooltip: _('Sort by: Show as'),
			width: 160,
			renderer: Zarafa.common.ui.grid.Renderers.busystatus,
			hidden: true
		},{
			header: _('Categories'),
			dataIndex: 'categories',
			tooltip: _('Sort by: Categories'),
			width: 160,
			renderer: Zarafa.common.ui.grid.Renderers.categories
		},{
			header: _('Created On'),
			dataIndex: 'creation_time',
			tooltip: _('Sort by: Created On'),
			width: 160,
			renderer: Zarafa.common.ui.grid.Renderers.datetime,
			hidden: true
		},{
			header: _('Due By'),
			dataIndex: 'duedate',
			tooltip: _('Sort by: Due By'),
			width: 160,
			renderer: Zarafa.common.ui.grid.Renderers.dueBy,
			hidden: true
		},{
			header: _('Label'),
			dataIndex: 'label',
			tooltip: _('Sort by: Label'),
			width: 160,
			renderer: Zarafa.common.ui.grid.Renderers.label,
			hidden: true
		},{
			header: _('Organizer'),
			dataIndex: 'sent_representing_name',
			tooltip: _('Sort by: Organizer'),
			hidden: true,
			renderer: Zarafa.common.ui.grid.Renderers.organizer
		},{
			header: _('Required Attendee'),
			dataIndex: 'display_to',
			tooltip: _('Sort by: Required Attendee'),
			hidden: true,
			renderer: Ext.util.Format.htmlEncode
		},{
			header: _('Optional Attendee'),
			dataIndex: 'display_cc',
			tooltip: _('Sort by: Optional Attendee'),
			hidden: true,
			renderer: Ext.util.Format.htmlEncode
		},{
			header: _('Resources'),
			dataIndex: 'display_bcc',
			tooltip: _('Sort by: Resources'),
			hidden: true,
			renderer: Ext.util.Format.htmlEncode
		},{
			header: _('Recurring Start Date'),
			dataIndex: 'startdate_recurring',
			tooltip: _('Sort by: Recurring Start Date'),
			width: 160,
			renderer: Zarafa.common.ui.grid.Renderers.date,
			hidden: true
		},{
			header: _('Recurring End Date'),
			dataIndex: 'enddate_recurring',
			tooltip: _('Sort by: Recurring End Date'),
			width: 160,
			renderer: Zarafa.common.ui.grid.Renderers.date,
			hidden: true
		},{
			header: _('Recurring Pattern'),
			dataIndex: 'recurring_pattern',
			tooltip: _('Sort by: Recurring Pattern'),
			width: 400,
			renderer: Zarafa.common.ui.grid.Renderers.subject,
			hidden: true
		},{
			header: '<p class="icon_reminder">&nbsp;<span class="title">' + _('Reminder') + '</span></p>',
			headerCls: 'zarafa-icon-column',
			dataIndex: 'reminder',
			tooltip: _('Sort by: Reminder'),
			width: 24,
			renderer: Zarafa.common.ui.grid.Renderers.reminder,
			fixed: true,
			hidden: true
		},{
			header: _('Sensitivity'),
			dataIndex: 'sensitivity',
			tooltip: _('Sort by: Sensitivity'),
			width: 160,
			renderer: Zarafa.common.ui.grid.Renderers.sensitivity,
			hidden: true
		},{
			header: _('Importance'),
			dataIndex: 'importance',
			tooltip: _('Sort by: Importance'),
			width: 160,
			renderer: Zarafa.common.ui.grid.Renderers.importance,
			hidden: true}];
	}
});
Ext.namespace('Zarafa.calendar.ui');

/**
 * @class Zarafa.calendar.ui.CalendarMainPanel
 * @extends Zarafa.common.ui.ContextMainPanel
 * @xtype zarafa.calendarmainpanel
 *
 * This class will be containing all the views that will be created for calendar folder
 */
Zarafa.calendar.ui.CalendarMainPanel = Ext.extend(Zarafa.common.ui.ContextMainPanel, {
	// Insertion points for this class
	/**
	 * @insert context.calendar.toolbar.item
	 *
	 * Insertion point for populating calendar context's toolbar.
	 * This item is only visible when this context is active.
	 * @param {Zarafa.calendar.CalendarMainPanel} panel This panel
	 */
	/**
	 * @insert context.calendar.toolbar.paging
	 *
	 * Insertion point for populating calendar context's toolbar with extra
	 * pagination buttons. This can be used to replace the default {@link Ext.PagingToolbar}
	 * with an alternative. Note that by default all paging toolbars will be visible, and
	 * hiding a particular toolbar is the responsibility of the new pagers.
	 * @param {Zarafa.calendar.CalendarMainPanel} panel This panel
	 */
	/**
	 * @insert context.calendar.views
	 * Insertion point for adding views within the main panel of calendar context.
	 * This insertion point should be used in combination with 'main.maintoolbar.view.calendar'
	 * insertion point, and also view should set its store in the config object, the reference of
	 * {@link Zarafa.calendar.CalendarContext CalendarContext} is passed as parameter of this
	 * insertion point.
	 * @param {Zarafa.calendar.ui.CalendarMainPanel} mainpanel This mainpanel
	 * @param {Zarafa.calendar.CalendarContext} context The context for this panel
	 */

	/**
	 * The main panel in which the various views are located.
	 * @property
	 * @type Zarafa.core.ui.SwitchViewContentContainer
	 */
	viewPanel: undefined,

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

		config = Ext.applyIf(config, {
			xtype: 'zarafa.calendarmainpanel',
			layout: 'fit',
			items: [{
				xtype: 'zarafa.switchviewcontentcontainer',
				ref: 'viewPanel',
				layout: 'card',
				lazyItems: this.initViews(config.context)
			}],
			tbar: {
				xtype: 'zarafa.calendarpaneltoolbar',
				defaultTitle: _('Calendar'),
				paging: container.populateInsertionPoint('context.calendar.toolbar.paging', this),
				items: container.populateInsertionPoint('context.calendar.toolbar.item', this),
				context: config.context,
				model: config.context.getModel()
			}
		});

		Zarafa.calendar.ui.CalendarMainPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Function will initialize all views associated with contact context
	 * it will also get views added through 3rd party plugins and add it here
	 * @param {Zarafa.calendar.CalendarContext} context The calendar context
	 * @return {Array} array of config objects of different views
	 * @private
	 */
	initViews: function(context)
	{
		// add the standard available views
		var allViews = [{
			xtype: 'zarafa.calendarblockpanel',
			id   : 'calendar_blocks',
			context: context
		},{
			xtype: 'zarafa.calendargrid',
			id   : 'calendar_grid',
			context: context
		}];

		var additionalViewItems = container.populateInsertionPoint('context.calendar.views', this, context);
		allViews = allViews.concat(additionalViewItems);

		return allViews;
	},

	/**
	 * Function called by Extjs when the panel has been {@link #render rendered}.
	 * At this time all events can be registered.
	 * @private
	 */
	initEvents: function()
	{
		if (Ext.isDefined(this.context)) {
			this.mon(this.context, 'viewchange', this.onViewChange, this);

			this.onViewChange(this.context, this.context.getCurrentView());
		}
	},

	/**
	 * Event handler which is fired when the currently active view inside the {@link #context}
	 * has been updated. This will update the call
	 * {@link #viewPanel}#{@link Zarafa.core.ui.SwitchViewContentContainer#switchView}
	 * to make the requested view active.
	 *
	 * @param {Zarafa.core.Context} context The context which fired the event
	 * @param {Zarafa.calendar.data.Views} viewId The ID of the selected view
	 * @private
	 */
	onViewChange: function(context, viewId)
	{
		switch (viewId) {
			case Zarafa.calendar.data.Views.BLOCKS:
			/* falls through */
			default:
				this.viewPanel.switchView('calendar_blocks');
				break;
			case Zarafa.calendar.data.Views.LIST:
			case Zarafa.calendar.data.Views.SEARCH:
				this.viewPanel.switchView('calendar_grid');
				break;
		}
	}
});

Ext.reg('zarafa.calendarmainpanel', Zarafa.calendar.ui.CalendarMainPanel);
Ext.namespace("Zarafa.calendar.ui");

/**
 * @class Zarafa.calendar.ui.CalendarMultiView
 * @extends Zarafa.core.ui.View
 *
 * The CalendarMultiView implements the visual presentation for the {@link Zarafa.calendar.ui.CalendarPanel CalendarPanel}
 * component.
 * <p>
 * A single instance of this view manages several {@link Zarafa.calendar.ui.AbstractCalendarView AbstractCalendarView} views, each showing one
 * or more appointment folders. As folders are added and removed in the store, new views are created and old ones destroyed to reflect these
 * changes. This view also manages the movement of folders from one view to another, merging, and separating.
 * <p>
 * The view provides several areas its child views can draw into, such as a scrollable body area, a tab strip area, and a header area.
 * The individual {@link Zarafa.calendar.ui.AbstractCalendarView AbstractCalendarView} instances are laid out on these areas.
 * <p>
 * The view also manages zero or more time strip views that represent time zones, and are visible when the view is in 'days view' mode.
 * <p>
 * Child views are expected to be subclasses of {@link Zarafa.calendar.ui.AbstractCalendarView AbstractCalendarView}.
 */
Zarafa.calendar.ui.CalendarMultiView = Ext.extend(Zarafa.core.ui.View, {
	/**
	 * @cfg {Zarafa.core.Context} context The context which this view is associated with
	 */
	context: undefined,
	/**
	 * Used to compare the calendar groupings to the previous value. Contains a JSON stringified version
	 * of the groupings at the previous load.
	 * @property
	 * @type String
	 */
	cachedGroupingsString: '',
	/**
	 * @cfg {Number} tabAreaHeight height in pixels of the tab strip
	 */
	tabAreaHeight: 39,
	/**
	 * @cfg {Number} height of the header text in pixels. This is the day number and friendly day name text (e.g. 'monday', 'tuesday', etc)
	 */
	headerTextHeight: 24,
	/**
	 * @cfg {Number} height of a header appointment in pixels.
	 */
	headerItemHeight: 24,
	/**
	 * @cfg {Number} headerLineHeight height in pixels of the dividing line between the scrollable
	 * body of the calendar view and the header above it.
	 */
	headerLineHeight: 1,
	/**
	 * @cfg {Number} calendarGap number of pixels (horizontally) between multiple calendars
	 */
	calendarGap: 6,
	/**
	 * this is probably always going to be 24. Made it into a property to avoid magic numbers
	 * in the code.
	 * @property
	 * @type Number
	 */
	numHours: 24,
	/**
	 * @cfg {Number} timeStripWidth width in pixels of the time strips on the left of the panel.
	 */
	timeStripWidth: 62,
	/**
	 * @cfg {Number} timeStripGap gap in pixels between timestrips (defaults to 0)
	 */
	timeStripGap: 0,
	/**
	 * The zoomLevel used in this view. This determines how many numbers are reflected by 2 horizontal
	 * lines in the calendar view.
	 *
	 * @property
	 * @type Number
	 */
	zoomLevel: 30,
	/**
	 * The {@link Zarafa.core.ContextModel contextmodel} obtained from the {@link #context}.
	 * @property
	 * @type Zarafa.core.ContextModel
	 */
	model: undefined,
	/**
	 * number of pixels the strips are shifted left by. This is used to obtain black vertical lines between
	 * multiple time strips. Each strip as 'border-left: 1px solid black', and all strips are shifted left
	 * one pixel so that the border of the first time strip is hidden (it's horizontal position is -1).
	 * @property
	 * @type Number
	 */
	timeStripShift: 1,
	/**
	 * @cfg {Number} firstWorkingHour The number of minutes counted from the start of the day which represents
	 * the start of the working hours.
	 */
	firstWorkingHour: 9 * 60,
	/**
	 * @cfg {Number} lastWorkingHour The number of minutes counted from the start of the day which represents
	 * the end of the working hours.
	 */
	lastWorkingHour: 17 * 60,
	/**
	 * @cfg {Array} workingDays The array of working days
	 */
	workingDays: undefined,

	/**
	 * If {@link #showTooltip tooltips are enabled} this will hold the reference to the tooltip
	 * instance to be used on the calendars.
	 *
	 * @property
	 * @type Zarafa.calendar.ui.ToolTip
	 * @private
	 */
	tooltip: undefined,

	/**
	 * @cfg {Number} tabStrokeHeight height in pixels of the active tab stroke.
	 */
	tabStrokeHeight: 9,

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

		if (Ext.isDefined(config.context)) {
			if (!Ext.isDefined(config.zoomLevel)) {
				config.zoomLevel = config.context.getZoomLevel();
			}
			if (!Ext.isDefined(config.model)) {
				config.model = config.context.getModel();
			}
		}

		// If not explicitly configured, the workingHours and days are determined
		// by the Settings option.
		if (!Ext.isDefined(config.firstWorkingHour)) {
			config.firstWorkingHour = container.getSettingsModel().get('zarafa/v1/main/start_working_hour');
		}
		if (!Ext.isDefined(config.lastWorkingHour)) {
			config.lastWorkingHour = container.getSettingsModel().get('zarafa/v1/main/end_working_hour');
		}
		if (!Ext.isDefined(config.workingDays)) {
			config.workingDays = container.getSettingsModel().get('zarafa/v1/main/working_days');
		}

		config = Ext.applyIf(config, {
			baseCls: 'zarafa-calendar',

			// zooming constants
			timeUnitHeight: 24,

			// configuration for the individual calendar views
			minHeaderDayTextWidth: 150,
			showBorder: false,
			borderWidth: 1,
			showTimeStrips: true
		});

		this.addEvents(
			/**
			 * @event appointmentcalendardrop
			 * Fires when an appointment is dragged from this calendar component onto another.
			 * @param {Zarafa.calendar.ui.CalendarMultiView} multiview The Calendar MultiView which fired the event
			 * @param {Zarafa.calendar.AppointmentRecord} appointment the appointment record that was dragged
			 * @param {Zarafa.calendar.core.MAPIFolder} sourceFolder source folder
			 * @param {Zarafa.calendar.core.MAPIFolder} targetFolder target folder
			 * @param {Zarafa.core.DateRange} dateRange The new daterange for the appointment
			 * @param {Ext.EventObject} event The original event object
			 */
			'appointmentcalendardrop',
			/**
			 * @event appointmentmouseover
			 * Fires when the mouse is being mover over an appointment.
			 * @param {Zarafa.calendar.ui.CalendarMultiView} multiview The Calendar MultiView which fired the event
			 * @param {Zarafa.calendar.AppointmentRecord} record The appointment over which the mouse is moving
			 * @param {Ext.EventObject} event The original event object
			 */
			'appointmentmouseover',
			/**
			 * @event appointmentmouseover
			 * Fires when the mouse is being mover away from an appointment.
			 * @param {Zarafa.calendar.ui.CalendarMultiView} multiview The Calendar MultiView which fired the event
			 * @param {Zarafa.calendar.AppointmentRecord} record The appointment over which the mouse has moved out
			 * @param {Ext.EventObject} event The original event object
			 */
			'appointmentmouseout',
			/**
			 * @event appointmentmove
			 * Fires when an appointment has been moved.
			 * @param {Zarafa.calendar.ui.CalendarMultiView} multiview The Calendar MultiView which fired the event
			 * @param {Zarafa.calendar.AppointmentRecord} appointment the appointment record that was moved
			 * @param {Zarafa.core.DateRange} dateRange The new daterange for the appointment
			 * @param {Ext.EventObject} event The original event object
			 */
			'appointmentmove',
			/**
			 * @event appointmentresize
			 * Fires when an appointment has been resized. Resizing in this context means that either the start
			 * date or the due date has been changed.
			 * @param {Zarafa.calendar.ui.CalendarMultiView} multiview The Calendar MultiView which fired the event
			 * @param {Zarafa.calendar.AppointmentRecord} appointment the appointment record that was resized
			 * @param {Zarafa.core.DateRange} dateRange The new daterange for the appointment
			 * @param {Ext.EventObject} event The original event object
			 */
			'appointmentresize',
			/**
			 * @event appointmentcreate
			 * Fires when an new appointment should be created.
			 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder MAPI folder the appointment should be created in.
			 * @param {Zarafa.core.DateRange} dateRange Appointment date range.
			 * @param {String} text Appointment text
			 */
			'appointmentcreate',
			/**
			 * @event appointmentinitdrag
			 * Fires when the user starts dragging an appointment in the calendar
			 * @param {Ext.EventObject} event The mouse event
			 * @param {Zarafa.calendar.ui.AppointmentView} appointment The appointment that is being dragged
			 */
			'appointmentinitdrag',
			/**
			 * @event appointmentenddrag
			 * Fires when the user releases a dragged appointment in the calendar
			 * @param {Ext.EventObject} event The mouse event
			 * @param {Zarafa.calendar.ui.AppointmentView} appointment The appointment that was dragged
			 */
			'appointmentenddrag',
			/**
			 * @event contextmenu
			 * Fires when the user right-clicks on the calendar body or on an appointment.
			 * @param {Ext.EventObject} event right-click event.
			 * @param {Ext.data.Record} record (optional) if the user right-clicked on an appointment
			 * this will contain the appointment record, undefined otherwise
			 */
			'contextmenu',
			/**
			 * @event dblclick
			 * Fires when the user double-clicks on on an appointment.
			 * @param {Ext.EventObject} event right-click event.
			 * @param {Ext.data.Record} record appointment record.
			 */
			'dblclick',
			/**
			 * @event dayclick
			 * Fires when a user clicks on the header of a day, or on the expand button in the box view.
			 * @param {Zarafa.core.ui.View} source event source
			 * @param {Date} date date of the day.
			 */
			'dayclick',
			/**
			 * @event calendarclose
			 * Fires when the user closes a calendar using the close icon in the calendar tabs.
			 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder MAPI folder.
			 */
			'calendarclose'
		);

		Zarafa.calendar.ui.CalendarMultiView.superclass.constructor.call(this, config);
	},

	/**
	 * Initialises the view.
	 * @protected
	 */
	init: function()
	{
		// parent init
		Zarafa.calendar.ui.CalendarMultiView.superclass.init.call(this);

		this.calendars = [];
		this.timeStrips = [];

		if (Ext.isDefined(this.context)) {
			this.mon(this.context, 'viewmodechange', this.onViewModeChanged, this);
			this.mon(this.context, 'zoomchange', this.onZoomLevelChanged, this);
		}
		if (Ext.isDefined(this.model)) {
			this.mon(this.model, 'foldermergestatechanged', this.onFolderChange, this);
			this.mon(this.model, 'folderchange', this.onFolderChange, this);
		}

		this.addTimeStrip();
	},

	/**
	 * @return {Zarafa.calendar.ui.AppointmentSelectionModel} selection model.
	 */
	getSelectionModel: function()
	{
		return this.selectionModel;
	},

	/**
	 * @return {Zarafa.calendar.ui.DateRangeSelectionModel} range selection model.
	 */
	getRangeSelectionModel: function()
	{
		return this.rangeSelectionModel;
	},

	/**
	 * Returns the current date range (start, due) of the view.
	 * @return {Zarafa.core.DateRange} Current date range.
	 */
	getDateRange: function()
	{
		return this.model.dateRange;
	},

	/**
	 * Returns the current height of the tab strip in pixels. When there are multiple folders in the view, this will return the default
	 * tab area height, otherwise it will return 0.
	 * @return {Number} height in pixels of the tab strip area.
	 */
	getTabHeight: function()
	{
		return this.showBorder ? this.tabAreaHeight : 0;
	},

	/**
	 * Adds a time strip to the view. Multiple time strips may be added for multiple time zones.
	 * @param {Number} time difference in hours. See {Zarafa.calendar.ui.TimeStripView TimeStripView}.
	 * @param {String} name of the time zone. Will be shown above the time strip.
	 */
	addTimeStrip: function(timeDifference, name)
	{
		// add a view
		var timeStrip = new Zarafa.calendar.ui.TimeStripView({
			timeDifference: timeDifference || 0,
			name: name || ''
		});
		this.addChildView(timeStrip);
		this.timeStrips.splice(0, 0, timeStrip);

		// render the view (adds the neccesairy UI components)
		if (this.rendered)  {
			timeStrip.render(this.container);
			this.layout();
		}
	},

	/**
	 * Removes a time strip from the view.
	 * @param {String} name Time strip name.
	 */
	removeTimeStrip: function(name)
	{
		for (var i=0, strip; strip=this.timeStrips[i]; i++) {
			if (strip.name==name) {
				this.timeStrips.splice(i, 1);
				this.removeChildView(strip, true);
				break;
			}
		}

		// do layout
		this.layout();
	},

	/**
	 * Obtain the singleton instance of the {@link Zarafa.calendar.ui.ToolTip Tooltip}.
	 * @return {Zarafa.calendar.ui.ToolTip} The tooltip
	 */
	getTooltipInstance: function()
	{
		if (!this.tooltip) {
			this.tooltip = new Zarafa.calendar.ui.ToolTip({
				target: this.el,
				view: this
			});
		}

		return this.tooltip;
	},

	/**
	 * Event handler which is fired when the {@link Zarafa.core.Context#viewmodechange viewmodechange}
	 * event has been fired by the {@link #context}. This will update the {@link #showTimeStrips}
	 * and {@link #calendarViewConstructor} which will ensure the calendar to be correctly shown.
	 *
	 * @param {Zarafa.core.Context} context The context which fired the event
	 * @param {Zarafa.calendar.data.ViewModes} viewMode The new viewmode
	 * @param {Zarafa.calendar.data.ViewModes} oldViewMode The previous active viewmode
	 * @private
	 */
	onViewModeChanged: function(context, viewMode, oldViewMode)
	{
		if (!this.rendered) {
			return;
		}

		switch (viewMode) {
			case Zarafa.calendar.data.ViewModes.DAYS:
				this.showTimeStrips = true;
				this.calendarViewConstructor = Zarafa.calendar.ui.html.CalendarDaysView;
				this.container.addClass('zarafa-calendar-daysview');
				this.container.removeClass('zarafa-calendar-boxview');
				break;
			case Zarafa.calendar.data.ViewModes.BOX:
				this.showTimeStrips = false;
				this.calendarViewConstructor = Zarafa.calendar.ui.html.CalendarBoxView;
				this.container.removeClass('zarafa-calendar-daysview');
				this.container.addClass('zarafa-calendar-boxview');
				break;
			default:
				this.showTimeStrips = false;
				this.container.removeClass('zarafa-calendar-daysview');
				this.container.removeClass('zarafa-calendar-boxview');
		}

		// We will be changing the calendars array while
		// converting all calendars...
		var calendars = this.calendars.clone();
		for (var i = 0, len = calendars.length; i < len; i++) {
			var calendar = calendars[i];
			var folders = calendar.getFolders();

			if (folders.length > 0) {
				this.createCalendarView(calendar.groupId, folders);
			}

			this.removeCalendarView(calendar);
		}
	},

	/**
	 * Event handler which is fired when the {@link #zoomLevel} on the
	 * {@link #context} has been changed. This will update the
	 * {@link #zoomLevel} and will layout the view.
	 * @param {Zarafa.core.Context} context The context which fired the event
	 * @param {Number} zoomLevel The new zoomLevel
	 */
	onZoomLevelChanged: function(context, zoomLevel)
	{
		this.zoomLevel = zoomLevel;
		this.layout();
	},

	/**
	 * Event handler which is triggered when the mergeState has
	 * changed for the folders or when the folders have changed.
	 */
	onFolderChange: function()
	{
		// if folder is not present than we cannot proceed pruning and creating views
		if (!Ext.isDefined(this.folders)) {
			return;
		}
		this.manageCalendarViews();
	},

	/**
	 * Forwards the 'appointmentcalendardrop' event to the parent calendar panel.
	 * @param {Zarafa.calendar.ui.AbstractCalendarDaysView} calendar The calendar which fired the event
	 * @param {Zarafa.calendar.ui.AbstractCalendarDaysView} source The calendar from where the appointment was dragged
	 * @param {Zarafa.calendar.AppointmentRecord} appointment the appointment record that was dropped
	 * @param {Zarafa.core.DateRange} dateRange The new daterange for the appointment
	 * @param {Ext.EventObject} event The original event object
	 * @private
	 */
	onAppointmentCalendarDrop: function(calendar, source, appointment, dateRange, event)
	{
		this.fireEvent('appointmentcalendardrop', this, appointment, source.getSelectedFolder(), calendar.getSelectedFolder(), dateRange, event);
	},

	/**
	 * Forwards the 'appointmentmouseover' event to the parent calendar panel
	 * @param {Zarafa.calendar.ui.AbstractCalendarDaysView} calendar The calendar which fired the event
	 * @param {Zarafa.calendar.AppointmentRecord} appointment The appointment over which the mouse is moving
	 * @param {Ext.EventObject} event The original event object
	 * @private
	 */
	onAppointmentMouseOver: function(calendar, appointment, event)
	{
		this.fireEvent('appointmentmouseover', this, appointment, event);
	},

	/**
	 * Forwards the 'appointmentmouseout' event to the parent calendar panel
	 * @param {Zarafa.calendar.ui.AbstractCalendarDaysView} calendar The calendar which fired the event
	 * @param {Zarafa.calendar.AppointmentRecord} appointment The appointment over which the mouse has moved out
	 * @param {Ext.EventObject} event The original event object
	 * @private
	 */
	onAppointmentMouseOut: function(calendar, appointment, event)
	{
		this.fireEvent('appointmentmouseout', this, appointment, event);
	},

	/**
	 * Forwards the 'appointmentmove' event to the parent calendar panel.
	 * @param {Zarafa.calendar.ui.AbstractCalendarDaysView} calendar The calendar which fired the event
	 * @param {Zarafa.calendar.AppointmentRecord} appointment the appointment record that was moved
	 * @param {Zarafa.core.DateRange} dateRange The new daterange for the appointment
	 * @param {Ext.EventObject} event The original event object
	 * @private
	 */
	onAppointmentMove: function(calendar, appointment, dateRange, event)
	{
		this.fireEvent('appointmentmove', this, appointment, dateRange, event);
	},

	/**
	 * Forwards the 'appointmentresize' event to the parent calendar panel.
	 * @param {Zarafa.calendar.ui.AbstractCalendarDaysView} calendar The calendar which fired the event
	 * @param {Zarafa.calendar.AppointmentRecord} appointment the appointment record that was resized
	 * @param {Zarafa.core.DateRange} dateRange The new daterange for the appointment
	 * @param {Ext.EventObject} event The original event object
	 * @private
	 */
	onAppointmentResize: function(calendar, appointment, dateRange, event)
	{
		this.fireEvent('appointmentresize', this, appointment, dateRange, event);
	},

	/**
	 * Forwards the 'appointmentcreate' event to the parent calendar panel.
	 * @param {Zarafa.calendar.ui.AbstractCalendarView} calendarView Calendar view the appointment belongs to.
	 * @param {Zarafa.core.DateRange} dateRange Date range of the appointment.
	 * @param {String} text Appointment text.
	 * @private
	 */
	onAppointmentCreate: function(calendarView, dateRange, text)
	{
		this.fireEvent('appointmentcreate', this, calendarView.getSelectedFolder(), dateRange, text);
	},

	/**
	 * Handler for the appointmentinitdrag event, forwarded by the {@link Zarafa.calendar.ui.AbstractCalendarDaysView},
	 * originally coming from {@link Zarafa.calendar.ui.CalendarViewDragZone}
	 * initiated from {@link Zarafa.calendar.ui.CalendarViewDragZone}
	 *
	 * @param {Zarafa.calendar.ui.AbstractCalendarView} calendarView Calendar view the appointment was dragged from
	 * @param {Ext.EventObject} event The mouse event
	 * @param {Zarafa.calendar.ui.AppointmentView} appointment The appointment on which the event occurred
	 * @private
	 */
	onAppointmentInitDrag: function(calendarView, event, appointment)
	{
		this.fireEvent('appointmentinitdrag', this, event, appointment);
	},

	/**
	 * Handler for the appointmentenddrag event, forwarded by the {@link Zarafa.calendar.ui.AbstractCalendarDaysView},
	 * initiated from {@link Zarafa.calendar.ui.CalendarViewDragZone}
	 *
	 * @param {Zarafa.calendar.ui.AbstractCalendarView} calendarView Calendar view the appointment was dragged to
	 * @param {Ext.EventObject} event The mouse event
	 * @param {Zarafa.calendar.ui.AppointmentView} appointment The appointment on which the event occurred
	 * @private
	 */
	onAppointmentEndDrag: function(calendarView, event, appointment)
	{
		this.fireEvent('appointmentenddrag', this, event, appointment);
	},

	/**
	 * Delegates the 'contextmenu' event, which fires when the user right-clicks on the panel.
	 * @param {Zarafa.calendar.ui.AbstractCalendarDaysView} calendar Calendar view
	 * @param {Ext.EventObject} event Mouse event object.
	 * @param {Zarafa.core.data.IPMRecord} record If the user right-clicked on an appointment,
	 * this will contain the IPMRecord of the appointment in question. Otherwise undefined.
	 * @param {Zarafa.core.DateRange} range The Datarange on which the contextmenu
	 * was invoked. This is only provided when the event didn't occur on a record.
	 * @private
	 */
	onContextMenu: function(calendar, event, record, range)
	{
		// If the user clicked on an appointment, make that appointment selected.
		// If the user did not click on an appointment, clear the appointment selection
		if (record) {
			// disable context menu on private appointments
			if(record.get('access') === 0) {
				return;
			}

			if (record.phantom !== true) {
				this.rangeSelectionModel.clearSelections();
				this.selectionModel.selectRecord(record, false);
			}

			this.fireEvent('contextmenu', event, record);
		} else {
			if (range) {
				this.rangeSelectionModel.set(range, calendar);
				this.selectionModel.clearSelections();
			}

			this.fireEvent('contextmenu', event);
		}
	},

	/**
	 * Delegates the 'dblclick' event, which fires when the user double-clicks on the panel.
	 * @param {Zarafa.calendar.ui.AbstractCalendarDaysView} calendar Calendar view
	 * @param {Ext.EventObject} event Mouse event object
	 * @param {Zarafa.core.data.IPMRecord} record If the user double-clicked on an appointment,
	 * this will contain the IPMRecord of the appointment in question. Otherwise undefined.
	 * @private
	 */
	onDoubleClick: function(calendar, event, record)
	{
		if (record) {
			// disable dblclick on private appointments
			if (record.get('access') === 0) {
				return;
			}

			// If the user clicked on an appointment, make that appointment selected.
			if (record.phantom !== true) {
				this.selectionModel.selectRecord(record, false);
				this.rangeSelectionModel.clearSelections();
			}
		} else {
			this.selectionModel.clearSelections();
		}

		this.fireEvent('dblclick', event, record);
	},

	/**
	 * Delegates the 'dayclick' event, which fires when the user clicks on a day header.
	 * @param {Zarafa.calendar.ui.AbstractCalendarView} source Source calendar view.
	 * @param {Date} date Date of the day that was selected.
	 */
	onDayClick: function(source, date)
	{
		this.fireEvent('dayclick', source, date);
	},

	/**
	 * @return {Number} the height in pixels of a single hour. Depends on the zoom level.
	 */
	getHourHeight: function()
	{
		return (60 / this.zoomLevel) * this.timeUnitHeight;
	},

	/**
	 * Determines the width of the border. If there is more than one folder visible in the view borders are shown
	 * around the views, and this function will return the default border with. Used by the layout mechanism.
	 * @return {Number} Width in pixels of the calendar view borders. If zero, borders should not be shown.
	 */
	getBorderWidth: function()
	{
		return this.showBorder ? this.borderWidth : 0;
	},

	/**
	 * Determines the height in pixels of the tab area. If there is more than one folder visible in the view tabs are shown,
	 * and this function will return the default tab area height. It will return zero otherwise. Used by the layout mechanism.
	 * @return {Number} Height in pixels of the tab area.
	 * @private
	 */
	getTabAreaHeight: function()
	{
		return this.showBorder ? this.tabAreaHeight : 0;
	},

	/**
	 * Determines the height in pixels of the header area by finding the maximum of the vertical space required by
	 * the child views to show all their appointments. Used by the layout mechanism.
	 * @return {Number} Height in pixels of the header area.
	 * @private
	 */
	getHeaderAreaHeight: function()
	{
		// Find the largest header height for all the child calendar views
		var headerHeight = 0;

		for (var i=0, calendar; calendar=this.calendars[i]; i++) {
			headerHeight = Math.max(headerHeight, calendar.getDesiredHeaderHeight());
		}

		return headerHeight;
	},

	/**
	 * Renders the view. The view will be rendered to the view container.
	 * @param {Ext.Element} container The Ext.Element into which the view must be rendered.
	 */
	render: function(container)
	{
		if (this.rendered) {
			return;
		}

		container.addClass(this.getClassName('body'));
		container.set({
			'role': 'region',
			'aria-label': _('Calendar')
		});

		// Create areas
		this.createDiv(container, 'tab', this.getClassName('tabarea'));
		this.createDiv(container, 'header', this.getClassName('header', undefined, 'scrollable'));
		this.createDiv(container, 'scrollable', this.getClassName('scrollable', undefined, 'zoom-' + this.zoomLevel + 'm'));
		this.createDiv(container, 'bottom', this.getClassName('bottomarea'));

		// Little gray line on top of the time strips
		this.createDiv(container, 'headerLine', this.getClassName('headerLine'));

		this.scrollable.ddScrollConfig = {
			vthresh: 50,
			hthresh: -1,
			frequency: 100,
			increment: 100
		};

		this.header.ddScrollConfig = {
			vthresh: 50,
			hthresh: -1,
			frequency: 100,
			increment: 100
		};

		Zarafa.calendar.ui.CalendarMultiView.superclass.render.call(this, container);

		this.renderChildren();

		// Fake the 'viewmodechange' event, to make sure we initialize the correct view.
		// If the store already has some data loaded, the onViewModeChange will also
		// load this for us into the view.
		this.onViewModeChanged(this.context, this.context.getCurrentViewMode());
	},

	/**
	 * Binds a store to the view, making the view represent the contents of that store and respond to changes in that store.
	 * It hooks event handlers to various events such as load, item CRUD, clear, etc. If the view is already connected to a store,
	 * this store is first unbound by unhooking said event listeners.
	 * <p>
	 * If the new store replaces a previous store, and if that store has its autoDestroy property set to true, that store will
	 * be destroyed.
	 * <p>
	 * @param {Zarafa.core.data.IMPStore} store Store to bind to this view.
	 */
	bindStore: function(store)
	{
		if (this.store == store) {
			return;
		}

		// bind events for the new store
		if (Ext.isDefined(store)) {
			this.mon(store, {
				'beforeload': this.onBeforeLoad,
				'load': this.onLoad,
				'add': this.onAdd,
				'remove': this.onRemove,
				'update': this.onUpdate,
				scope: this
			});
		}

		this.store = store;

		// If the store already contains loaded data, then we must
		// apply all data into the view now.
		if (store && store.lastOptions && !store.isExecuting('list')) {
			this.onLoad(store, store.getRange(), store.lastOptions);

			if ( this.rendered ) {
				this.manageCalendarViews(store.lastOptions.folder);
			}
		}
	},

	/**
	 * Release the store which was previously bound using {@link #bindStore}
	 *
	 * @param {Zarafa.core.data.IPMStore} store Store to release from this view.
	 */
	releaseStore: function(store)
	{
		if (Ext.isDefined(store)) {
			this.mun(store, {
				'beforeload': this.onBeforeLoad,
				'load': this.onLoad,
				'add': this.onAdd,
				'remove': this.onRemove,
				'update': this.onUpdate,
				scope: this
			});
		}

		// Destroy the store if autoDestroy is set to true.
		if (store && store.autoDestroy) {
			store.destroy();
		}

		this.store = undefined;
	},

	/**
	 * Positions the DOM elements that make up the four separate areas of the view (tab, header, scrollable (body), bottom).
	 * @param {Boolean} skipResizingScrollPosition True will skip resizing scrollbar position.
	 * @private
	 */
	resizeAreas: function(skipResizingScrollPosition)
	{
		var tabHeight = this.getTabHeight();

		var headerTop = tabHeight;
		var headerHeight = this.getHeaderAreaHeight();
		if(this.showTimeStrips) {
			// limit height of the header area to half of the total height
			// after that scroller will be shown
			headerHeight = Math.min(headerHeight, this.container.getHeight() / 2);
		}
		var scrollableTop = headerTop + headerHeight;
		var scrollableHeight = this.container.getHeight() - scrollableTop - this.getBorderWidth();
		if (this.showTimeStrips) {
			scrollableHeight = Math.min(scrollableHeight, this.getHourHeight() * this.numHours);
		}
		var bottomTop = scrollableHeight + scrollableTop;

		// Set bounds for the tab area
		this.tab.setLeftTop(0, 0);
		this.tab.setSize(this.container.getWidth(), tabHeight);

		// Set bounds for the header area
		this.header.setLeftTop(0, headerTop);
		this.header.setSize(this.container.getWidth(), headerHeight);

		// Set bounds for the scrollable area
		this.scrollable.setLeftTop(0, scrollableTop);
		this.scrollable.setSize(this.container.getWidth(), scrollableHeight);
		// fistWorkingHour and lastWorkingHour are expressed in minutes,
		// so divide by 60 to get the number of hours. We don't need to
		// round the value because that way we can support having time
		// which is not an entire hour (9:30 for example).
		if (this.showTimeStrips && !skipResizingScrollPosition) {
			var targetScrollHeight = (this.firstWorkingHour / 60) * this.getHourHeight();
			this.scrollable.scrollTo('top', targetScrollHeight);
		}

		// Set bounds for the bottom area
		this.bottom.setLeftTop(0, bottomTop);
		this.bottom.setSize(this.container.getWidth(), this.getBorderWidth());

		for (var i=0, timeStrip; timeStrip=this.timeStrips[i]; i++)
		{
			timeStrip.setLeftMargin((this.timeStripWidth + this.timeStripGap) * i - this.timeStripShift);
			timeStrip.setWidth(this.timeStripWidth);
		}

		// Set bounds for the header line area (the little gray line above the
		// time strips)
		var totalTimeStripsWidth = this.showTimeStrips?(this.timeStrips.length * this.timeStripWidth + (this.timeStrips.length-1) * this.timeStripGap - this.timeStripShift):0;
		this.headerLine.setLeftTop(0, scrollableTop - this.headerLineHeight);
		this.headerLine.setSize(totalTimeStripsWidth, this.headerLineHeight);
	},

	/**
	 * Lays out the view, setting the position and size of the individual DOM elements.
	 * It calculates the positions of the child calendar views and calls layout on them.
	 * @protected
	 */
	onLayout: function()
	{
		this.resizeAreas();

		this.scrollable.dom.style.overflowY = this.showTimeStrips ? 'scroll' : 'hidden';
		if(this.showTimeStrips && (this.getHeaderAreaHeight() > this.container.getHeight() / 2)) {
			this.header.dom.style.overflowY = 'scroll';
		} else {
			this.header.dom.style.overflowY = 'hidden';
		}

		if (!this.showTimeStrips) {
			this.scrollable.scrollTo('top', 0);
			this.header.scrollTo('top', 0);
		}

		Ext.dd.ScrollManager.unregister(this.scrollable);
		Ext.dd.ScrollManager.unregister(this.header);
		// Register the scrollable element or not based on the viewMode.
		if (this.context.getCurrentViewMode() === Zarafa.calendar.data.ViewModes.DAYS) {
			Ext.dd.ScrollManager.register(this.scrollable);
			Ext.dd.ScrollManager.register(this.header);
		}

		// Layout time strip UI elements
		for (var i=0, timeStrip; timeStrip=this.timeStrips[i]; i++) {
			timeStrip.setVisible(this.showTimeStrips);
		}

		// Layout calendar UI elements
		var totalTimeStripsWidth = this.showTimeStrips?(this.timeStrips.length * this.timeStripWidth + (this.timeStrips.length-1) * this.timeStripGap - this.timeStripShift):0;
		var totalCalendarWidth = this.scrollable.dom.clientWidth - totalTimeStripsWidth - (this.calendars.length - 1) * this.calendarGap;
		var calendarWidth = totalCalendarWidth/this.calendars.length + this.calendarGap;
		for (var i=0, calendar; calendar=this.calendars[i]; i++)
		{
			var left = Math.round(calendarWidth * i + totalTimeStripsWidth);
			var right = Math.round(calendarWidth * (i+1) + totalTimeStripsWidth - this.calendarGap);
			calendar.setActive(this.model.getActiveGroup() == calendar.groupId);
			calendar.setCanMerge(i > 0);
			calendar.setCanClose(this.folders.length>1);
			calendar.setLeftMargin(left);
			calendar.setWidth(right - left);
		}
	},

	/**
	 * Called just after this Calendar multiview has been {@link #layout laid out}.
	 * @protected
	 */
	onAfterLayout: function()
	{
		this.layoutChildren();
	},

	/**
	 * Finds the child calendar view that is currently showing the given folder.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder Folder to find.
	 * @return {Zarafa.calendar.ui.AbstractCalendarView} the calendar view that contains the given folder or undefined if not found.
	 * @private
	 */
	getCalendarViewByFolder: function(folder)
	{
		for (var i=0, calendar; calendar = this.calendars[i]; i++) {
			if (calendar.containsFolder(folder)) {
				return calendar;
			}
		}

		return undefined;
	},

	/**
	 * Creates a new calendar view showing a given folder. The type (days view, box view) depends on
	 * the current value of this.calendarViewConstructor.
	 * @param {String} groupId The group from {@link Zarafa.core.MultiFolderContextModel#getGroupings} for which this calendar is created.
	 * @param {Mixed} folders MAPI folder(s) to show in the newly created calendar view. Can either a single {@link Zarafa.hierarchy.data.MAPIFolderRecord folder} or an array.
	 * @return {Zarafa.calendar.ui.AbstractCalendarView} calendarView new created calendarView.
	 * @private
	 */
	createCalendarView: function(groupId, folders)
	{
		if (!Ext.isEmpty(folders) && !Array.isArray(folders)) {
			folders = [ folders ];
		}

		var calendarView = new this.calendarViewConstructor({
			selectionModel: this.selectionModel,
			rangeSelectionModel: this.rangeSelectionModel,
			enableDD: this.enableDD,
			groupId: groupId,
			contextModel: this.model
		});

		this.mon(calendarView, {
			'appointmentcalendardrop': this.onAppointmentCalendarDrop,
			'appointmentmouseover': this.onAppointmentMouseOver,
			'appointmentmouseout': this.onAppointmentMouseOut,
			'appointmentmove': this.onAppointmentMove,
			'appointmentresize': this.onAppointmentResize,
			'appointmentcreate': this.onAppointmentCreate,
			'appointmentinitdrag': this.onAppointmentInitDrag,
			'appointmentenddrag': this.onAppointmentEndDrag,
			'contextmenu': this.onContextMenu,
			'dblclick': this.onDoubleClick,
			'dayclick': this.onDayClick,
			'activate': this.onCalendarActivate,
			'merge': this.onCalendarMerge,
			'separate': this.onCalendarSeparate,
			'close': this.onCalendarClose,
			scope: this
		});

		if (folders) {
			Ext.each(folders, calendarView.addFolder, calendarView);
		}

		this.addChildView(calendarView);
		this.calendars.push(calendarView);

		// render the view (adds the neccesairy UI components)
		if (this.rendered) {
			calendarView.render(this.container);
		}

		return calendarView;
	},

	/**
	 * Removes a calendar view, unhooking event handlers and destroying it.
	 * @param {Zarafa.calendar.ui.AbstractCalendarView} calendar calendar to remove
	 * @private
	 */
	removeCalendarView: function(calendar)
	{
		this.calendars.remove(calendar);

		this.mun(calendar, {
			'appointmentcalendardrop': this.onAppointmentCalendarDrop,
			'appointmentmouseover': this.onAppointmentMouseOver,
			'appointmentmouseout': this.onAppointmentMouseOut,
			'appointmentmove': this.onAppointmentMove,
			'appointmentresize': this.onAppointmentResize,
			'appointmentcreate': this.onAppointmentCreate,
			'appointmentinitdrag': this.onAppointmentInitDrag,
			'appointmentenddrag': this.onAppointmentEndDrag,
			'contextmenu': this.onContextMenu,
			'dblclick': this.onDoubleClick,
			'dayclick': this.onDayClick,
			'activate': this.onCalendarActivate,
			'merge': this.onCalendarMerge,
			'separate': this.onCalendarSeparate,
			'close': this.onCalendarClose,
			scope: this
		});

		this.removeChildView(calendar, true);
	},

	/**
	 * Removes folders from calendars that are not in the input folder list. Calendars that become 'empty' (don't display any folders) are removed.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord[]} folders list of folders.
	 * @private
	 */
	pruneCalendarViews: function(folders)
	{
		// Go over all the calendar views and prune any IDs that are not in the list.
		var calendars = this.calendars.clone();
		for (var i=0, calendar; calendar = calendars[i]; i++) {
			var calendarFolders = calendar.getFolders().clone();
			for (var j=0, calendarFolder; calendarFolder=calendarFolders[j]; j++) {
				if (folders.indexOf(calendarFolder) == -1) {
					this.model.removeFolderFromGroup(calendarFolder, calendar.groupId);
					calendar.removeFolder(calendarFolder);
				}
			}

			// If the view is empty, remove it completely
			this.removeCalendarViewIfEmpty(calendar);
			if (!calendar.isDestroyed) {
				this.updateActiveFolder(calendar);
			}
		}
	},


	/**
	 * Creates new child calendar views to make sure that all the MAPI folders given in the input are represented on screen.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord[]} folders list of folders.
	 * @private
	 */
	createCalendarViews: function(folders)
	{
		var grouping = this.model.getGroupings();

		// If we find grouping as an empty object, then we need to generate grouping variable again
		// As without grouping variable it won't load the calendar views, and sometime groupings are
		// malformed in settings which will rise this situation
		if(!Ext.isDefined(grouping) || Object.keys(grouping).length === 0) {
			this.model.resetGroupings();
			this.model.applyGrouping();
			grouping = this.model.getGroupings();
		}

		Ext.iterate(grouping, function(key, group) {
			var groupFolders = [];

			if(Ext.isEmpty(group.folders)) {
				// continue looping for other groups
				return true;
			}

			for (var j = 0; j < folders.length; j++) {
				if (group.folders.indexOf(folders[j].get('entryid')) > -1) {
					groupFolders.push(folders[j]);
				}
			}

			if (!Ext.isEmpty(groupFolders)) {
				var folder = groupFolders[0];
				var targetCalendar = this.getCalendarViewByFolder(folder);
				if (targetCalendar) {
					// We have a target calendar, if our folder is the only occupant,
					// then we can reuse the calendar. Otherwise we create a new
					// calendar view and move the folder.
					if (targetCalendar.folders.length > 1) {
						var newCalendar = this.createCalendarView(key);

						this.moveFolder(targetCalendar, newCalendar, folder);
						targetCalendar = newCalendar;
					} else {
						// Check if the groupId is different, if that is the case,
						// deregister the old ID so we can apply the new one.
						if (targetCalendar.groupId !== key) {
							targetCalendar.groupId = key;
						}
					}
				} else {
					// This is a new folder for which no calendar yet exists,
					// create a new calendar view.
					targetCalendar = this.createCalendarView(key, folder);
				}

				// Now have the view into which all folders should be loaded,
				// go over the remaining items in the group.
				for (var j = 1; j < groupFolders.length; j++) {
					var addFolder = groupFolders[j];

					var sourceCalendar = this.getCalendarViewByFolder(addFolder);
					if (sourceCalendar && sourceCalendar !== targetCalendar) {
						// The folder already exists in another calendar view,
						// officially move the folder to the new target (this will
						// cleanup the old calendar if needed).
						this.moveFolder(sourceCalendar, targetCalendar, addFolder);
					} else if (!sourceCalendar) {
						// This is a new folder, so it can be added to the view
						// without problems.
						targetCalendar.addFolder(addFolder);
					}
				}

				targetCalendar.setSelectedFolder(targetCalendar.getFolderById(group.active));
			}
		}, this);
	},

	/**
	 * Sorts the child calendar views. Makes sure that the view are laid out from left to right in the same order
	 * as they appear in the load request (which is the order in which they appear in the folder tree).
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord[]} folders list of folders. The order of the calendar views will be according to the order of the folders in this list.
	 * @private
	 */
	sortCalendarViews: function(folders)
	{
		var calendars = [];

		// Run over the list of folders and find for each folder a calendar view that goes with it.
		// The views are added in sequence to the calendars list and in this way are ordered in the same
		// way as the folders in the input list. And yeah, I know this is O(n^2), but n is small, so meh:)
		for (var i=0, folder; folder = folders[i]; i++)
		{
			var calendarView = this.getCalendarViewByFolder(folder);
			if (calendarView && calendars.indexOf(calendarView) == -1) {
				calendars.push(calendarView);
			}
		}

		// Sort the tabs on the calendars.
		for (var i=0, calendar; calendar=calendars[i]; i++) {
			calendar.sortFolders(folders);
		}

		this.calendars = calendars;
	},

	/**
	 * Manages the child calendar views. Removes folders from views that are not currently loaded,
	 * and adds new views for folders that are not yet represented.
	 *
	 * @param {Zarafa.core.data.IPFRecord[]} folders Array of the folders that are shown in the view. Normally
	 * this function will use this.folders, but when we are calling this function from {#bindStore} we will pass
	 * the folders of the store when available. That way we will immediately have the folders when we switch the view.
	 * @private
	 */
	manageCalendarViews: function(folders)
	{
		// Use this.folders when folders is undefined
		folders = folders || this.folders;

		// Remove calendar views that are not in the folder list.
		this.pruneCalendarViews(folders);

		// Add new calendar views for folders that are currently not displayed.
		this.createCalendarViews(folders);

		// Sorts the calendars based on the order of their folders in the hierarchy model.
		this.sortCalendarViews(folders);
	},

	/**
	 * Handles the {@link Zarafa.core.data.IPMStore#beforeload beforeload} event from the appointment {@link #store store}.
	 * @param {Zarafa.core.data.IPMStore} store store that fired the event.
	 * @param {Object} options the options (parameters) with which the load was invoked.
	 * @private
	 */
	onBeforeLoad: function(store, options)
	{
		if (!this.rendered) {
			return;
		}

		// Call beforeLoad on all child calendars.
		for (var i = 0, len = this.calendars.length; i < len; i++) {
			this.calendars[i].beforeAppointmentsLoad(store, options);
		}

		// Force layout.
		this.layout();
	},

	/**
	 * Called in response to a {@link Zarafa.core.data.IPMStore#load load} event from the appointment {@link #store store}.
	 * @param {Zarafa.core.data.IPMStore} store store that fired the event.
	 * @param {Zarafa.core.data.IPMRecord[]} records loaded record set
	 * @param {Object} options the options (parameters) with which the load was invoked.
	 * @private
	 */
	onLoad: function(store, records, options)
	{
		if (!this.rendered) {
			return;
		}

		this.folders = options.folder || [];

		//always show tab
		this.showBorder = true;

		// Only create the views here when the groupings have changed
		// Compare the goupings by caching a stringified version
		const groupingsString = JSON.stringify(this.model.getGroupings());

		if ( groupingsString !== this.cachedGroupingsString ) {
			this.cachedGroupingsString = groupingsString;
			this.manageCalendarViews();
		}

		var hasRecords = Array.isArray(records) && records.length > 0;
		// Bucket appointments by folder so we don't iterate the full dataset for every calendar view.
		var recordsByFolder = {};
		// Preserve store ordering when multiple folders share the same calendar.
		var recordOrder = {};
		var getRecordKey = function(record) {
			if (store && Ext.isFunction(store.getRecordKey)) {
				return store.getRecordKey(record);
			}
			return Ext.isDefined(record.internalId) ? record.internalId : record.id;
		};

		if (hasRecords) {
			for (var index = 0; index < records.length; index++) {
				var record = records[index];
				var folderId = record.get('parent_entryid');
				if (folderId) {
					if (!recordsByFolder[folderId]) {
						recordsByFolder[folderId] = [];
					}
					recordsByFolder[folderId].push(record);
				}

				recordOrder[getRecordKey(record)] = index;
			}
		}

		// forward the event to the individual calendar views
		for (var i = 0, len = this.calendars.length; i < len; i++) {
			var calendar = this.calendars[i];
			var filteredRecords = [];

			if (hasRecords) {
				var folderIds = calendar.getFolderIds();
				for (var j = 0; j < folderIds.length; j++) {
					var folderRecords = recordsByFolder[folderIds[j]];
					if (folderRecords && folderRecords.length) {
						for (var r = 0; r < folderRecords.length; r++) {
							filteredRecords.push(folderRecords[r]);
						}
					}
				}

				if (filteredRecords.length > 1) {
					filteredRecords.sort(function(a, b) {
						var orderA = recordOrder[getRecordKey(a)];
						var orderB = recordOrder[getRecordKey(b)];
						orderA = Ext.isNumber(orderA) ? orderA : Number.MAX_VALUE;
						orderB = Ext.isNumber(orderB) ? orderB : Number.MAX_VALUE;
						return orderA - orderB;
					});
				}
			}

			calendar.onAppointmentsLoad(store, filteredRecords, options);
		}

		this.layout();
	},

	/**
	 * Called in response to an {@link Zarafa.core.data.IPMStore#add add} event from the appointment {@link #store store}.
	 * @param {Ext.data.Store} store data store
	 * @param {Ext.data.Record} record that was added
	 * @param {String} operation mutation operation key. Equals 'add'
	 * @private
	 */
	onAdd: function(store, record, operation)
	{
		if (Array.isArray(record)) {
			for (var i = 0, len = record.length; i < len; i++) {
				this.onAdd(store, record[i], operation);
			}
			return;
		}

		// forward the event to the individual calendar views
		for (var i = 0, len = this.calendars.length; i < len; i++) {
			this.calendars[i].onAppointmentAdd(store, record, operation);
		}
	},

	/**
	 * Called in response to a {@link Zarafa.core.data.IPMStore#remove remove} event from the appointment {@link #store store}.
	 * @param {Ext.data.Store} store data store
	 * @param {Ext.data.Record} record that was added
	 * @param {String} operation mutation operation key. Equals 'remove'
	 * @private
	 */
	onRemove: function(store, record, operation)
	{
		if (Array.isArray(record)) {
			for (var i = 0, len = record.length; i < len; i++) {
				this.onRemove(store, record[i], operation);
			}
			return;
		}

		// forward the event to the individual calendar views
		for (var i = 0, len = this.calendars.length; i < len; i++) {
			this.calendars[i].onAppointmentRemove(store, record, operation);
		}

		// Resize calendar view after removing record.
		if (record.get('alldayevent') === true && Date.diff(Date.DAY, record.get('duedate'), record.get('startdate')) >= 1) {
			this.resizeAreas(true);
		}
	},

	/**
	 * Called in response to an {@link Zarafa.core.data.IPMStore#update update} event from the appointment {@link #store store}.
	 * @param {Ext.data.Store} store data store
	 * @param {Ext.data.Record} record that was added
	 * @param {String} operation mutation operation key. Equals 'update'
	 * @private
	 */
	onUpdate: function(store, record, operation)
	{
		if (Array.isArray(record)) {
			for (var i = 0, len = record.length; i < len; i++) {
				this.onUpdate(store, record[i], operation);
			}
			return;
		}

		// Forward the event to the individual calendar views.
		for (var i = 0, len = this.calendars.length; i < len; i++) {
			var calendar = this.calendars[i];
			var containsAppointment = calendar.containsAppointment(record);
			var shouldContain = calendar.containsFolderId(record.get('parent_entryid'));

			if (containsAppointment && !shouldContain) {
				calendar.onAppointmentRemove(store, record, operation);
			} else if (!containsAppointment && shouldContain) {
				calendar.onAppointmentAdd(store, record, operation);
			} else {
				this.calendars[i].onAppointmentUpdate(store, record, operation);
			}
		}
	},

	/**
	* Checks if a calendar view is empty and removes it if that's the case. This may happen when a merge or close
	* event removes a folder from a view.
	* @param {Zarafa.calendar.ui.AbstractCalendarView} calendar The calendar to remove.
	* @private
	*/
	removeCalendarViewIfEmpty: function(calendar)
	{
		if (calendar.getFolders().length === 0) {
			this.removeCalendarView(calendar);
		}
	},

	/**
	 * Moves a folder from one calendar view to another. If the source calendar view ends up empty, it is destroyed.
	 * This method does not force a layout, nor register the change to the {@link #model}.
	 * @param {Zarafa.calendar.ui.AbstractCalendarView} sourceCalendar Source calendar view.
	 * @param {Zarafa.calendar.ui.AbstractCalendarView} targetCalendar Target calendar view.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder MAPI folder to move.
	 * @private
	 */
	moveFolder: function(sourceCalendar, targetCalendar, folder)
	{
		// Move the folder to the calendar view that goes before it in the list.
		targetCalendar.addFolder(folder);

		// Move all the appointments from the source calendar view to the target calendar view.
		var records = sourceCalendar.getAppointmentRecords().clone();
		for (var i=0, record; record=records[i]; i++) {
			if (Zarafa.core.EntryId.compareEntryIds(record.get('parent_entryid'), folder.get('entryid'))) {
				sourceCalendar.removeAppointment(record, false);
				targetCalendar.addAppointment(record, false);
			}
		}

		// Sort the tabs of the calendar the folder has moved to.
		targetCalendar.sortFolders(this.folders);
		targetCalendar.setActive(true);
		// Remove the folder from the calendar and remove it from the view if it is now empty.
		sourceCalendar.removeFolder(folder);
		this.removeCalendarViewIfEmpty(sourceCalendar);
		if (!sourceCalendar.isDestroyed) {
			this.updateActiveFolder(sourceCalendar);
			sourceCalendar.setActive(false);
		}
	},

	/**
	 * Merges a folder from one calendar view to another.
	 * If the source calendar view ends up empty, it is destroyed.
	 * This method does not force a layout, but does
	 * {@link Zarafa.calendar.CalendarContextModel#mergeFolderToGroup register}
	 * the change to the {@link #model}.
	 * @param {Zarafa.calendar.ui.AbstractCalendarView} sourceCalendar Source calendar view.
	 * @param {Zarafa.calendar.ui.AbstractCalendarView} targetCalendar Target calendar view.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder MAPI folder to move.
	 * @private
	 */
	mergeFolder: function(sourceCalendar, targetCalendar, folder)
	{
		this.model.mergeFolderToGroup(folder, targetCalendar.groupId, sourceCalendar.groupId);
		this.moveFolder(sourceCalendar, targetCalendar, folder);
	},

	/**
	 * Separates a folder from one calendar view to a new calendar.
	 * If the source calendar view ends up empty, it is destroyed.
	 * This method does not force a layout, but does
	 * {@link Zarafa.calendar.CalendarContextModel#separateFolderFromGroup register}
	 * the change to the {@link #model}.
	 * @param {Zarafa.calendar.ui.AbstractCalendarView} sourceCalendar Source calendar view.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder MAPI folder to move.
	 * @private
	 */
	separateFolder: function(sourceCalendar, folder)
	{
		// Create a new calendar view.
		var newGroupId = this.model.separateFolderFromGroup(folder, sourceCalendar.groupId);
		var newCalendar = this.createCalendarView(newGroupId);

		// Move the folder to the newly created calendar.
		this.moveFolder(sourceCalendar, newCalendar, folder);
	},

	/**
	 * Check the {@link #model} {@link Zarafa.core.MultiFolderContextModel#getGroupings groupings}
	 * to {@link Zarafa.calendar.ui.AbstractCalendarView#setSelectedFolder apply} the new active
	 * folder to the calendar.
	 * @param {Zarafa.calendar.ui.AbstractCalendarView} calendar The calendar to update
	 * @private
	 */
	updateActiveFolder: function(calendar)
	{
		var group = this.model.getGroupings()[calendar.groupId];
		if (group && group.active) {
			calendar.setSelectedFolder(calendar.getFolderById(group.active));
		}
	},

	/**
	 * Handles the {@link Zarafa.calendar.ui.AbstractCalendarView#activate} event
	 * from one of the {@link #calendars calendar views}.
	 * @param {Zarafa.calendar.ui.AbstractCalendarView} calendar event source.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder The folder which was activated
	 * @private
	 */
	onCalendarActivate: function(calendar, folder)
	{
		this.model.activateFolderInGroup(folder, calendar.groupId);
		this.model.setActiveFolder(folder);
		// Only redraw tabs
		for (var i = 0, len = this.calendars.length; i < len; i++) {
			this.calendars[i].setActive(this.model.getActiveGroup() == this.calendars[i].groupId);
			this.calendars[i].layoutTabs();
		}
	},

	/**
	 * Handles a 'merge' event from one of the calendar views.
	 * @param {Zarafa.calendar.ui.AbstractCalendarView} calendar event source.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder folder to merge.
	 * @private
	 */
	onCalendarMerge: function(calendar, folder)
	{
		// Find the index of the calendar view that fired the event in the local calendar view list.
		var calendarIndex = -1;
		for (var i=0, cal; cal=this.calendars[i]; i++) {
			if (cal == calendar) {
				calendarIndex = i;
			}
		}

		// If the calendar view is the first (left most) one, don't do anything and return.
		if (calendarIndex < 1) {
			return;
		}

		var targetCalendar = this.calendars[calendarIndex - 1];
		// Move the folder to the target calendar.
		this.mergeFolder(calendar, targetCalendar, folder);

		this.sortCalendarViews(this.folders);

		this.layout();
	},

	/**
	 * Handles a 'separate' event from one of the calendar views.
	 * @param {Zarafa.calendar.ui.AbstractCalendarView} calendar event source.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder folder to separate.
	 * @private
	 */
	onCalendarSeparate: function(calendar, folder)
	{
		// Check if the calendar view has more than one folder. If it has only one, there is no need to separate
		// as it would have no effect.
		if (calendar.getFolders().length == 1) {
			return;
		}

		// Move the folder to a new calendar.
		this.separateFolder(calendar, folder);

		this.sortCalendarViews(this.folders);

		this.layout();
	},

	/**
	 * Handles a 'close' event from one of the calendar views.
	 * @param {Zarafa.calendar.ui.AbstractCalendarView} calendar event source.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder folder to close.
	 * @private
	 */
	onCalendarClose: function(calendar, folder)
	{
		this.fireEvent('calendarclose', folder);
	},

	/**
	 * Called when the View is being destroyed.
	 * @protected
	 */
	onDestroy: function()
	{
		Zarafa.calendar.ui.CalendarMultiView.superclass.onDestroy.apply(this, arguments);

		if (this.scrollable) {
			Ext.dd.ScrollManager.unregister(this.scrollable);
		}

		if (this.header) {
			Ext.dd.ScrollManager.unregister(this.header);
		}

		// Remove the tooltip when the view is destroyed
		if ( this.tooltip ){
			this.tooltip.destroy();
		}
	}
});
/*
 * #dependsFile client/zarafa/common/ui/ContextMainPanelToolbar.js
 */
Ext.namespace('Zarafa.calendar.ui');

/**
 * @class Zarafa.calendar.ui.CalendarPanelToolbar
 * @extends Zarafa.common.ui.ContextMainPanelToolbar
 * @xtype zarafa.calendarpaneltoolbar
 *
 * A panel tool bar for the calendaring components. This consists of 2
 * separate sub-pagination toolbars. The {@link Zarafa.common.ui.PagingToolbar}
 * which is used in the ListView add by {@link Zarafa.common.ui.ContextMainPanelToolbar},
 * and a special pagination toolbar which indicates the daterange which is currently selected.
 * There are also folder title header and context related buttons like copy/move and delete
 * add by {@link Zarafa.common.ui.ContextMainPanelToolbar}.
 */
Zarafa.calendar.ui.CalendarPanelToolbar = Ext.extend(Zarafa.common.ui.ContextMainPanelToolbar, {
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

		// Add an extra pagination toolbar
		config.paging = config.paging || [];
		config.paging.push({
			xtype: 'toolbar',
			ref: 'dateToolbar',
			items: [{
				xtype: 'button',
				iconCls: 'x-tbar-page-prev',
				tooltip: _('Previous'),
				handler: this.onPrevious,
				scope: this
			},{
				xtype: 'button',
				iconCls: 'x-tbar-page-next',
				tooltip: _('Next'),
				handler: this.onNext,
				scope: this
			},{
				xtype: 'tbseparator'
			},{
				xtype: 'tbtext',
				ref: '../text',
				listeners: {
					afterrender: function(cmp) {
						if (cmp.el) {
							cmp.el.set({ 'role': 'status', 'aria-live': 'polite' });
						}
					}
				}
			}]
		});

		Zarafa.calendar.ui.CalendarPanelToolbar.superclass.constructor.call(this, config);
	},

	/**
	 * Called automatically by superclass. This will initialize the component.
	 * @private
	 */
	initComponent: function()
	{
		Zarafa.calendar.ui.CalendarPanelToolbar.superclass.initComponent.call(this);

		if (this.model) {
			this.mon(this.model, 'daterangechange', this.onDateRangeChange, this);
			this.mon(this.model, 'datamodechange', this.onDataModeChange, this);

			this.on('afterlayout', this.onAfterFirstLayout, this, { single: true });
		}
	},

	/**
	 * Event handler for the {@link #afterlayout} event. This will update the {@link #text} from
	 * the {@link #model} and will update the {@link #onDataModeChange active datamode}.
	 * @private
	 */
	onAfterFirstLayout: function()
	{
		if (this.model) {
			this.text.setText(this.model.getDateRangeText());

			var mode = this.model.getCurrentDataMode();
			this.onDataModeChange(this.model, mode, mode);
		}
	},

	/**
	 * Event handler which is fired when the {@link #model} fires the {@link Zarafa.core.ContextModel#datamodechange}
	 * event. This will toggle the visible pagination toolbar depending on the current datamode.
	 * @param {Zarafa.calendar.CalendarContextModel} model The model on which datamode was changed
	 * @param {Zarafa.calendar.data.DataModes} mode The new mode which was applied
	 * @param {Zarafa.calendar.data.DataModes} oldMode the old mode which was previously applied
	 * @private
	 */
	onDataModeChange: function(model, mode, oldMode)
	{
		var hasDataModesAll = mode === Zarafa.calendar.data.DataModes.ALL;
		this.dateToolbar.setVisible(!hasDataModesAll);
		if (!hasDataModesAll) {
			// we have to set the width of tbtext field based on the different views of calendar
			// because when we navigate the months or days in calendar, text length of month/days
			// gets resize the tbtext and due to that copy/delete button gets hide, to overcome this
			// problem we give the fixed width to tbtext as per the calendar view
			switch(mode) {
				case Zarafa.calendar.data.DataModes.WEEK:
				case Zarafa.calendar.data.DataModes.WORKWEEK:
					this.text.setWidth(250);
				break;
				case Zarafa.calendar.data.DataModes.DAY:
					this.text.setWidth(250);
				break;
				default:
					this.text.setWidth(120);
				break;
			}
		}

		this.doLayout();
	},

	/**
	 * Event handler which is fired when the {@link Zarafa.core.DateRange DateRange}
	 * has been changed. This will update the help text inside the toolbar.
	 * @param {Zarafa.calendar.CalendarContextModel} model The model on which the daterange was set
	 * @param {Zarafa.core.data.DateRange} dateRange The new dateRange object
	 * @private
	 */
	onDateRangeChange: function(model, dateRange)
	{
		this.text.setText(model.getDateRangeText());
	},

	/**
	 * Event handler which is fired when the "Next" {@link Ext.Button button} was pressed.
	 * This will move the model to the next date period.
	 * @private
	 */
	onNext: function()
	{
		this.model.nextDate();
	},

	/**
	 * Event handler which is fired when the "Previous" {@link Ext.Button button} was pressed.
	 * This will move the model to the previous date period.
	 * @private
	 */
	onPrevious: function()
	{
		this.model.previousDate();
	}
});

Ext.reg('zarafa.calendarpaneltoolbar', Zarafa.calendar.ui.CalendarPanelToolbar);
Ext.namespace('Zarafa.calendar.ui');

/**
 * @class Zarafa.calendar.ui.CalendarTabView
 * @extends Zarafa.core.ui.View
 *
 * A tab that sits on top of a calendar view. Calendar views can show multiple folders at once, and each folder has
 * a corresponding tab. The tabs can be used to move the corresponding folder to the view left of it (merge), move
 * the folder into its own view (separate), close the view, or make it the active folder.
 */
Zarafa.calendar.ui.CalendarTabView = Ext.extend(Zarafa.core.ui.View, {
	/**
	 * @cfg {String} title The title which must be displayed as tab title.
	 * If not provided, then the {@link #folder} {@link Zarafa.hierarchy.data.MAPIFolderRecord#getDisplayname name}
	 * will be used.
	 */
	title: undefined,

	/**
	 * @cfg {Zarafa.hierarchy.data.MAPIFolderRecord} folder The folder which is being loaded in the {@link Zarafa.calendar.ui.AbstractCalenderView Calendar}
	 * to which this {@link Zarafa.calendar.ui.CalendarTabView tab} is attached.
	 */
	folder: undefined,

	/**
	 * The &lt;div&gt; element which is used as tab. It is placed between the left ({@link #tabLeft}) and
	 * right ({@link #tabRight}) elements. These three elements together are the entire visible tab. This
	 * middle element is used to provide the background of the tab.
	 * @property
	 * @type Ext.Element
	 */
	tab: undefined,

	/**
	 * The &lt;div&gt; element which is used as the content container for the {@link #tabText},
	 * {@link #mergeIcon}, {@link #separateIcon} and {@link #closeIcon} elements. These combined
	 * elements represent the contents of the tab.
	 * @property
	 * @type Ext.Element
	 */
	tabContents: undefined,

	/**
	 * The &lt;div&gt; element which is used to contain the {@link #title} for this tab. It is placed
	 * on top of the tab. But depending on the availability of the {@link #mergeIcon} this could be
	 * placed a bit over the {@link #leftTab}.
	 * @property
	 * @type Ext.Element
	 */
	tabText: undefined,

	/**
	 * The &lt;div&gt; element which is used to display the merge icon. This icon has an event
	 * handler which can merge two {@link Zarafa.calendar.ui.AbstractCalendarView calendars}
	 * together. This icon is placed on top of the tab, but depending on the position settings,
	 * this could be placed a bit over the {@link #leftTab}. This element will only be visible
	 * if {@link #showMergeIcon} is true.
	 * @property
	 * @type Ext.Element
	 */
	mergeIcon: undefined,

	/**
	 * The &lt;div&gt; element which is used to display the separate icon. This icon has
	 * an event handler which can separate two {@link Zarafa.calendar.ui.AbstractCalendarView calendars}
	 * after they were merged together with {@link #mergeIcon}. This element will only be visible
	 * if {@link #showSeparateIcon} is true.
	 * @property
	 * @type Ext.Element
	 */
	separateIcon: undefined,

	/**
	 * The &lt;div&gt; element which is used to display the close icon. This icon has
	 * an event handler which can close the selected {@link Zarafa.calendar.ui.AbstractCalendarView calendar}.
	 * @property
	 * @type Ext.Element
	 */
	closeIcon: undefined,

	/**
	 * The total with for the tab to use. This must at least be the result of {@link #getMinimumWidth}.
	 * This is configured before {@link #layout} by {@link #setWidth}.
	 * @property
	 * @type Number
	 */
	width: 0,

	/**
	 * Indicates if the current tab is selected by the user. This adds the 'selected' postfix to the
	 * CSS className if it is set to true. This is configured using {@link #setSelected}.
	 * @property
	 * @type Boolean
	 */
	selected: true,

	/**
	 * Indicates if the current tab is in the active group of calendars
	 * @property
	 * @type Boolean
	 */
	active: false,

	/**
	 * Indicates if the {@link #mergeIcon} must be made visible. This is configured before
	 * {@link #layout} by {@link #setShowMergeIcon}.
	 * @property
	 * @type Boolean
	 */
	showMergeIcon: false,

	/**
	 * Indicates if the {@link #separateIcon} must be made visible. This is configured before
	 * {@link #layout} by {@link #setShowSeparateIcon}.
	 * @property
	 * @type Boolean
	 */
	showSeparateIcon: false,

	/**
	 * indicates if the {@link #closeIcon} must be made visible. This is configured before
	 * {@link #layout} by {@link #setShowCloseIcon}.
	 * @property
	 * @type Boolean
	 */
	showCloseIcon: false,

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

		//TODO: move this to a separate function or make a folder method for it?
		var displayName;
		if (config.folder) {
			displayName = config.folder.getFullyQualifiedDisplayName();
		}

		Ext.applyIf(config, {
			baseCls: 'zarafa-calendar',
			title: displayName ? Ext.util.Format.htmlEncode(displayName) : undefined
		});

		// define drag/drop events
		this.addEvents(
			/**
			 * @event merge
			 * Fires when the left arrow icon is clicked.
			 * @param {MAPIFolder} folder MAPI folder.
			 */
			'merge',
			/**
			 * @event separate
			 * Fires when the right arrow icon is clicked.
			 * @param {MAPIFolder} folder MAPI folder.
			 */
			'separate',
			/**
			 * @event close
			 * Fires when the close icon is clicked.
			 * @param {MAPIFolder} folder MAPI folder.
			 */
			'close',
			/**
			 * @event click
			 * Fires when the tab is clicked.
			 * @param {MAPIFolder} folder MAPI folder.
			 */
			'click'
		);

		Zarafa.calendar.ui.CalendarTabView.superclass.constructor.call(this, config);
	},

	/**
	 * Calculates the desired width of the tab, that is, the size required to show the text,
	 * close icon, and any other enabled icons (merge, separate).
	 * Used by {@link Zarafa.calendar.ui.AbstractCalendarView AbstractCalendarView} to lay out and size the tabs.
	 * @return {Number} desired tab width
	 */
	getDesiredWidth: function()
	{
		// textMetrics has not been set up if not rendered.
		if (!this.rendered) {
			return 0;
		}

		// Desired width is the minimum width (due to the icons), plus the width of the tab text.
		// We use the TextMetrics to calculate the width and height for the
		// contents of the tabText.
		var addClass = !this.tabContents.hasClass('zarafa-calendar-tab-selected');
		if ( addClass ){
			this.tabContents.addClass('zarafa-calendar-tab-selected');
		}
		var textMetrics = Ext.util.TextMetrics.createInstance(this.tabText);
		if ( addClass ){
			this.tabContents.removeClass('zarafa-calendar-tab-selected');
		}

		var desiredWidth = this.getMinimumWidth() + textMetrics.getWidth(this.title);

		// Check if we don't want a bigger width because of a min-width set in the css files
		this.tabContents.dom.style.removeProperty('min-width');
		var cssMinWidth = parseInt(this.tabContents.getStyle('min-width')) + this.tabContents.getPadding('lr');

		return Math.max(desiredWidth, cssMinWidth);
	},

	/**
	 * Calculates the minimum width of the tab, that is, the size required to show the close icon and
	 * any other enabled icons (merge, separate). This means the tabs should always be large enough to
	 * at least show all the icons.
	 * Used by {@link Zarafa.calendar.ui.AbstractCalendarView AbstractCalendarView} to lay out and size the tabs.
	 * @return {Number} minimum tab width
	 */
	getMinimumWidth: function()
	{
		if (!this.rendered) {
			return 0;
		}

		var width = this.tabContents.getPadding('lr');

		// Calculate the width of each icon
		if (this.showCloseIcon){
			width += this.closeIcon.getWidth();
		}
		if (this.showMergeIcon){
			width += this.mergeIcon.getWidth();
		}
		if (this.showSeparateIcon){
			width += this.separateIcon.getWidth();
		}

		return width;
	},

	/**
	 * Sets the tab width. Called by the parent
	 * {@link Zarafa.calendar.ui.AbstractCalendarView AbstractCalendarView} before layout.
	 * @param {Number} width tab width.
	 */
	setWidth: function(width)
	{
		this.width = width;
	},

	/**
	 * Sets whether the folder this tab corresponds to is the selected folder for that view.
	 * The effect of setting this to 'true' is that the text on the tab is shown in bold face.
	 * This.will also update the classNames assigned to {@link #tabBackground} and {@link #tabContents}.
	 * @param {Boolean} selected true iff the tab should be displayed as selected.
	 * @param {Boolean} active true iff the tab should be displayed as active.
	 * This is different from selected in that only one tab can be active at any one point,
	 * while there is one selected tab for each calendar (i.e. the one on top, whose color scheme is used)
	 * @param {Boolean} init True to force the classes to be applied
	 */
	setSelected: function(selected, active, init)
	{
		if (init === true || this.selected !== selected || this.active !== active) {
			var className = this.getBaseClassName() + '-tab-selected';

			if (selected === true) {
				if (active === true) {
					this.tabContents.addClass(className);
					this.parentView.activeTabStroke.addClass(className);
				} else {
					this.tabContents.removeClass(className);
					this.parentView.activeTabStroke.removeClass(className);
				}
			} else {
				this.tabContents.removeClass(className);
				if (active !== true) {
					this.parentView.activeTabStroke.removeClass(className);
				}
			}
		}

		this.active = active;
		this.selected = selected;
	},

	/**
	 * Sets whether the merge icon (left arrow) is visible. When a tab belongs to the leftmost calendar
	 * view it cannot be moved to the left, thus the icon will not be displayed.
	 * @param {Boolean} showMergeIcon true iff the merge icon should be displayed.
	 */
	setShowMergeIcon: function(showMergeIcon)
	{
		this.showMergeIcon = showMergeIcon;
	},

	/**
	 * Sets whether the separate icon (right arrow) is visible. When a calendar view is showing
	 * only a single folder, that folder cannot be separated out from the view, thus the icon will not be displayed.
	 * @param {Boolean} showSeparateIcon true iff the merge icon should be displayed.
	 */
	setShowSeparateIcon: function(showSeparateIcon)
	{
		this.showSeparateIcon = showSeparateIcon;
	},

	/**
	 * Sets whether the close icon is visible. When a calendar view is showing only a single folder,
	 * that folder cannot be closed, thus the icon will not be displayed.
	 * @param {Boolean} showCloseIcon true iff the close icon should be displayed.
	 */
	setShowCloseIcon: function(showCloseIcon)
	{
		this.showCloseIcon = showCloseIcon;
	},

	/**
	 * Assign each {@link Ext.Element element} generated during {@link #render} with
	 * a CSS className. This will use {@link #getClassName} to obtain the desired
	 * className for each element.
	 * @private
	 */
	applyCSSClassNames: function()
	{
		var contextModel = container.getCurrentContext().getModel();
		if (contextModel instanceof Zarafa.calendar.CalendarContextModel) {
			var colorScheme = contextModel.getColorScheme(this.folder.get('entryid'));

			// Contents
			this.tabContents.dom.className = this.getClassName('tab', 'contents');
			this.mergeIcon.dom.className = this.getClassName('tab-icon', 'merge');
			this.tabText.dom.className = this.getClassName('tab-title');
			this.closeIcon.dom.className = this.getClassName('tab-icon', 'close');
			this.separateIcon.dom.className = this.getClassName('tab-icon', 'separate');

			this.tabContents.applyStyles({
				'background-color': colorScheme.header
			});

			// Call setSelected to force the classed to be updated accordingly
			if (this.selected) {
				this.setSelected(this.selected, this.active, true);
			}
		}
	},

	/**
	 * Lays out the components of the tab.
	 * @protected
	 */
	onLayout: function()
	{
		// Apply CSS to all created elements
		// FIXME: We already did this during render, and we only repeat it here in
		// case things like the themeCls have been changed. We should however have
		// some mechanism to only set new class names when a given condition for a
		// particular class name has changed.
		this.applyCSSClassNames();

		var contextModel = container.getCurrentContext().getModel();
		if (contextModel instanceof Zarafa.calendar.CalendarContextModel) {
			var colorScheme = contextModel.getColorScheme(this.folder.get('entryid'));
			// Check if we have a light or dark color by converting the color code to HSL and checking the L-value
			var isDarkColor = Zarafa.core.ColorSchemes.isDark(colorScheme.base);
			if ( !isDarkColor ){
				this.tabContents.addClass('light-background');
			}
		}

		// Add or remove classes to the container, so we can style it properly
		if ( this.showMergeIcon ){
			this.tabContents.addClass('zarafa-with-merge-icon');
		} else {
			this.tabContents.removeClass('zarafa-with-merge-icon');
		}
		if ( this.showSeparateIcon ){
			this.tabContents.addClass('zarafa-with-separate-icon');
		} else {
			this.tabContents.removeClass('zarafa-with-separate-icon');
		}
		if ( this.showCloseIcon ){
			this.tabContents.addClass('zarafa-with-close-icon');
		} else {
			this.tabContents.removeClass('zarafa-with-close-icon');
		}

		this.tabContents.setWidth(this.width);
		this.tabText.setWidth(this.width - this.getMinimumWidth());

		// Left arrow (merge left) icon.
		this.mergeIcon.setVisible(this.showMergeIcon);

		// The tabText element is a div that's laid out on top of the actual tab images and contains, surprise surprise,
		// the tab text.
		if (this.tabText.dom.innerHTML != this.title) {
			this.tabText.dom.innerHTML = this.title;
		}

		// Close icon.
		this.closeIcon.setVisible(this.showCloseIcon);

		// Right icon.
		this.separateIcon.setVisible(this.showSeparateIcon);

		Zarafa.calendar.ui.CalendarTabView.superclass.onLayout.call(this);
	},

	/**
	 * Renders the tab. This will create the {@link #tabBackground},
	 * {@link #tabContents} contains with the underlying {@link #tabLeft}, {@link #tabRight}, {@link #tab},
	 * {@link #tabText}, {@link #mergeIcon}, {@link #separateIcon} and {@link #closeIcon} elements.
	 * @param {Ext.Element} container The Ext.Element into which the view must be rendered.
	 */
	render: function(container)
	{
		// Container divs
		this.createDiv(this.parentView.tabArea, 'tabContents');

		// content divs
		this.createDiv(this.tabContents, 'mergeIcon');
		this.createDiv(this.tabContents, 'tabText');
		this.createDiv(this.tabContents, 'closeIcon');
		this.createDiv(this.tabContents, 'separateIcon');

		this.mergeIcon.setVisibilityMode(Ext.Element.DISPLAY);
		this.closeIcon.setVisibilityMode(Ext.Element.DISPLAY);
		this.separateIcon.setVisibilityMode(Ext.Element.DISPLAY);

		this.mon(this.mergeIcon, 'click', this.onMerge, this);
		this.mon(this.separateIcon, 'click', this.onSeparate, this);
		this.mon(this.closeIcon, 'click', this.onClose, this);
		this.mon(this.tabText, 'click', this.onClick, this);

		// We use the TextMetrics to calculate the width and height for the
		// contents of the tabText.
		this.tabText.textMetrics = Ext.util.TextMetrics.createInstance(this.tabText);

		// Apply CSS to all created elements, this will guarantee that we can request
		// styles from the elements like getWidth() and getMargins().
		this.applyCSSClassNames();

		Zarafa.calendar.ui.CalendarTabView.superclass.render.call(this, container);
	},

	/**
	 * Handles the 'click' event of the merge icon, and fires the {@link #merge} event.
	 * @private
	 */
	onMerge: function()
	{
		this.fireEvent('merge', this.folder);
	},

	/**
	 * Handles the 'click' event of the separate icon, and fires the {@link #separate} event.
	 * @private
	 */
	onSeparate: function()
	{
		this.fireEvent('separate', this.folder);
	},

	/**
	 * Handles the 'click' event of the close icon, and fires the {@link #close} event.
	 * @private
	 */
	onClose: function()
	{
		this.fireEvent('close', this.folder);
	},

	/**
	 * Handles the 'click' event of the tab text, and fires the {@link #click} event.
	 * @private
	 */
	onClick: function()
	{
		this.fireEvent('click', this.folder);
	}
});
Ext.namespace('Zarafa.calendar.ui');

/**
 * @class Zarafa.calendar.ui.TextEditView
 * @extends Zarafa.core.ui.View
 *
 * The TextEditView shows a text area for a given date range. It is shown automatically when a user selects a
 * date range on the calendar and starts typing. It allows for convenient creation of appointments.
 * <p>
 * Two TextArea HTML components are used, one in the header and one on the body. Only one of them is visible depending
 * on the date range given (>=24 hour appointments are laid out in the header).
 *
 */
Zarafa.calendar.ui.TextEditView = Ext.extend(Zarafa.core.ui.View, {
	/**
	 * Date range.
	 * @property
	 * @type Zarafa.core.DateRange
	 */
	dateRange: undefined,

	/**
	 * @cfg {Number} minimumHeight The minimum height for this view.
	 */
	minimumHeight: 20,

	/**
	 * @cfg {String} inputText The text which was entered into the view.
	 */
	inputText: '',

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

		Ext.applyIf(config, {
			baseCls: 'zarafa-calendar',
			itemCls: 'textedit'
		});

		this.addEvents(
			/**
			 * Fires when the user is done entering text.
			 * @event textentered
			 * @param {Zarafa.calendar.ui.TextEditView} view source view.
			 * @param {String} text The text that was entered.
			 */
			'textentered',
			/**
			 * Fires when the user cancelled entering text by pressing escape or removing focus from the text area.
			 * @param {Zarafa.calendar.ui.TextEditView} view source view.
			 * @event cancelled
			 */
			'cancelled'
		);

		Zarafa.calendar.ui.TextEditView.superclass.constructor.call(this, config);
	},

	/**
	 * Sets date range. This method does not auto-update.
	 * @param {Zarafa.core.DateRange} dateRange
	 */
	setDateRange: function(dateRange)
	{
		this.dateRange = dateRange;
	},

	/**
	 * Returns the current date range.
	 * @return {Zarafa.core.DateRange} dateRange
	 */
	getDateRange: function()
	{
		return this.dateRange;
	},

	/**
	 * @return {Boolean} true iff the text edit area is visible.
	 */
	isVisible: function()
	{
		return this.visible;
	},

	/**
	 * Renders the view.
	 * @param {Ext.Element} container The Ext.Element into which the view must be rendered.
	 */
	render: function(container)
	{
		this.create('textarea', this.parentView.getCalendarBody(), 'body', this.getClassName('body'));
		this.create('textarea', this.parentView.getCalendarHeader(), 'header', this.getClassName('header'));
		this.body.set({ 'aria-label': _('New appointment subject') });
		this.header.set({ 'aria-label': _('New appointment subject') });
		this.header.setSize(2,2);
		this.mon(this.body, 'keypress', this.onKeyPress, this);
		this.mon(this.body, 'keydown', this.onKeyPress, this);
		this.mon(this.body, 'blur', this.onBlur, this);
		this.mon(this.body, 'keyup', this.onKeyUp, this);
		this.mon(this.header, 'keypress', this.onKeyPress, this);
		this.mon(this.header, 'keydown', this.onKeyPress, this);
		this.mon(this.header, 'blur', this.onBlur, this);
		this.mon(this.header, 'keyup', this.onKeyUp, this);

		Zarafa.calendar.ui.TextEditView.superclass.render.call(this, container);

		this.hide();
	},

	/**
	 * Sets the visibility of a given set elements. This function is used instead of ExtJS's Element.show()/hide() because
	 * for some reason Chrome seemed to crash on hide(). Freaky.
	 * @param {Ext.Element[]} elements elements to show or hide.
	 * @param {Boolean} visible whether to show or hide the elements.
	 * @private
	 */
	makeElementsVisible: function(elements, visible)
	{
		Ext.each(elements, function(element) {
			element.setVisible(visible);
		});
	},

	/**
	 * Hides the view.
	 */
	hide: function()
	{
		if (!this.rendered) {
			return;
		}

		this.makeElementsVisible([this.body, this.header], false);

		this.visible = false;
	},

	/**
	 * Makes the view visible. The proper order is to first set the date range, then call show() which will
	 * automatically lay out the appropriate TextArea component.
	 */
	show: function()
	{
		if (!this.rendered) {
			return;
		}

		this.visible = true;
		this.layout();

	},

	/**
	 * Handles the keypress event.
	 * @param {Ext.EventObject} event ExtJS event object.
	 * @private
	 */
	onKeyPress: function(event)
	{

		// Escape pressed. Blur.
		if (event.keyCode == event.ESC) {
			this.hide();
		}

		// Enter pressed. Fire 'textentered' event.
		if (event.keyCode == event.RETURN) {
			var text = event.browserEvent.target.value;
			event.browserEvent.target.value = '';
			this.inputText = '';
			this.hide();

			this.fireEvent('textentered', this, text);
		}

	},

	/**
	 * Handles the keyup event.
	 * Function will get and store input text.
	 * @param {Ext.EventObject} event ExtJS event object.
	 * @private
	 */
	onKeyUp: function (event)
	{
		if (event.keyCode !== event.RETURN) {
			this.inputText = event.browserEvent.target.value;
		}
	},

	/**
	 * Handles a blur event (lost focus) from a text area. The view is hidden and a 'cancel' event is fired.
	 * @param {Ext.EventObject} event ExtJS event object.
	 * @private
	 */
	onBlur: function(event)
	{
		var isHeaderRange = this.parentView.isHeaderRange(this.getDateRange());

		if ( (isHeaderRange && event.target==this.header.dom) || (!isHeaderRange && event.target == this.body.dom)) {
			this.hide();
			this.fireEvent('cancelled', this);
		}
	},

	/**
	 * Lays out the body of the view.
	 * @private
	 */
	layoutInBody: function()
	{
		this.makeElementsVisible(this.header, false);

		// get an array of bounds (left, right, top, bottom) objects to represent the range on the calendar body
		var bounds = this.parentView.dateRangeToBodyBounds(this.dateRange);

		if (bounds.length === 0) {
			this.makeElementsVisible(this.body, false);
		} else {
			this.makeElementsVisible(this.body, true);
			this.body.setLeftTop(bounds[0].left, bounds[0].top);
			this.body.setSize(bounds[0].right - bounds[0].left, bounds[0].bottom - bounds[0].top);
		}

	},

	/**
	 * Lays out the header of the view.
	 * @private
	 */
	layoutInHeader: function()
	{
		this.makeElementsVisible(this.body, false);

		// get an array of bounds (left, right, top, bottom) objects to represent the range on the calendar body
		var bounds = this.parentView.dateRangeToHeaderBounds(this.dateRange, this.slot);

		this.makeElementsVisible(this.header, true);
		this.header.setLeftTop(bounds.left, bounds.top);
		this.header.setSize(bounds.right - bounds.left, this.minimumHeight);
	},

	/**
	 * Lays out the view.
	 * @protected
	 */
	onLayout: function()
	{
		if (!this.rendered) {
			return;
		}

		if (this.visible) {
			if (this.parentView.isHeaderRange(this.getDateRange())) {
				this.layoutInHeader();
			} else {
				this.layoutInBody();
			}
		} else {
			this.makeElementsVisible([this.body, this.header], false);

			// If input text is not empty then create the appointment
			if (!Ext.isEmpty(this.inputText)) {
				var text = this.inputText;
				this.inputText = '';
				this.fireEvent('textentered', this, text);
			}
		}

		Zarafa.calendar.ui.TextEditView.superclass.onLayout.call(this);
	},

	/**
	 * Makes the view visible (set the date range first) and sets the appropriate text area control selected - focus is moved
	 * to the text area and the user can immediately start typing.
	 */
	select: function()
	{
		if (!this.rendered) {
			return;
		}

		this.show();

		if (this.parentView.isHeaderRange(this.getDateRange())) {
			this.header.dom.select();
		} else {
			this.body.dom.select();
		}
	}
});
Ext.namespace('Zarafa.calendar.ui');

/**
 * @class Zarafa.calendar.ui.TimeStripView
 * @extends Zarafa.core.ui.View
 *
 * Handles drawing of the time strips that appear on the left in the calendar. Multiple time strips can be
 * placed on a single calendar, each representing different time zones. Time strips may have names, which
 * are rendered into the header section of the parent calendar view.
 *
 * Also see {@link Zarafa.calendar.ui.CalendarMultiView}
 */
Zarafa.calendar.ui.TimeStripView = Ext.extend(Zarafa.core.ui.View, {
	/**
	 * @cfg {Number} timeDifference difference in hours between this strip's time zone and the local time zone.
	 * For example if the local time zone is GMT +2:00, and you want to have a strip that shows the time in Mumbai
	 * (GMT +5:30), the time difference is +3:30, or 3.5 hours.
	 */
	timeDifference: 0,

	/**
	 * @cfg {String} name the name of this time strip, for example 'Mumbai' or 'Zurich'. Will be shown above the
	 * strip in the header area.
	 */
	name: '',

	/**
	 * @cfg {String} textTemplate The template to be used for the Hour indications inside the strip.
	 */
	textTemplate: '<div>{0}</div>',

	/**
	 * The offset all elements must have from the left side of the {@link #container}. This
	 * offset is configured before {@link #layout} by {@link #setLeftMargin}.
	 * @property
	 * @type Number
	 */
	leftOffset: 0,

	/**
	 * The total with for the tab to use. This must at least be the result of {@link #getMinimumWidth}.
	 * This is configured before {@link #layout} by {@link #setWidth}.
	 * @property
	 * @type Number
	 */
	width: 0,

	/**
	 * The &lt;div&gt; element which is used as the header container for the
	 * timestrip. This element is created using {@link #createDiv} during {@link #render}.
	 * @property
	 * @type Ext.Element
	 */
	header: undefined,

	/**
	 * The &lt;div&gt; element which is used as the body container for the
	 * timestrip. This contains the &lt;div&gt; elements from {@link #bodyElements} and {@link #bodyLines}.
	 * This element is created using {@link #createDiv} during {@link #render}.
	 * @property
	 * @type Ext.Element
	 */
	body: undefined,

	/**
	 * The &lt;div&gt; elements which indicate the time for the given hour. The contents
	 * of this &lt;div&gt; is generated using the {@link #textTemplate}.
	 * These elements are created using {@link #createDiv} during {@link #render}.
	 * @property
	 * @type Array
	 */
	bodyElements: undefined,

	/**
	 * The &lt;div&gt; elements which are used to separate the different
	 * {@link #bodyElements} from each other.
	 * These elements are created using {@link #createDiv} during {@link #render}.
	 * @property
	 * @type Array
	 */
	bodyLines: undefined,

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

		Ext.applyIf(config, {
			baseCls: 'zarafa-calendar',
			itemCls: 'timestrip'
		});

		Zarafa.calendar.ui.TimeStripView.superclass.constructor.call(this, config);
	},

	/**
	 * Sets the tab minimum offset from the left. Called by the parent
	 * {@link Zarafa.calendar.ui.AbstractCalendarView AbstractCalendarView} before layout.
	 * @param {Number} left The offset from the left
	 */
	setLeftMargin: function(left)
	{
		this.leftOffset = left;
	},

	/**
	 * Sets the tab width. Called by the parent
	 * {@link Zarafa.calendar.ui.AbstractCalendarView AbstractCalendarView} before layout.
	 * @param {Number} width tab width.
	 */
	setWidth: function(width)
	{
		this.width = width;
	},

	/**
	 * Renders the view. This will create the {@link #body} and {@link #header} elements,
	 * as well as the subelements {@link #bodyElements} and {@link #bodyLines}.
	 * @param {Ext.Element} container The Ext.Element into which the view must be rendered.
	 */
	render: function(container)
	{
		this.createDiv(this.parentView.scrollable, 'body', this.getClassName('body'));

		this.bodyElements = [];
		this.bodyLines = [];

		// create timestrip hour divs for each hour of the day
		var numHours = this.parentView.numHours;
		for (var i = 0; i< numHours; i++) {
			this.createDiv(this.body, 'bodyElements', this.getClassName('element'));
			this.createDiv(this.body, 'bodyLines', this.getClassName('line'));
		}

		// create a small header div for the time strip name (if any)
		this.createDiv(this.parentView.header, 'header',  this.getClassName('header'));
		this.header.dom.innerHTML = this.name;

		Zarafa.calendar.ui.TimeStripView.superclass.render.call(this, container);

		// Everything has been rendered, now update the elements to contains the
		// hour text.
		this.generateText();
	},

	/**
	 * Mark the elements for this {@link Zarafa.core.ui.View View} visible.
	 * @param {Boolean} visible
	 */
	setVisible: function(visible)
	{
		if (this.rendered) {
			this.body.setVisible(visible);
			this.header.setVisible(visible);
		}
	},

	/**
	 * Generates the text for the header element that shows the time strip name.
	 * This will apply the {@link #textTemplate} to each of the {@link #bodyElements} elements.
	 * @private
	 */
	generateText: function()
	{
		if (!this.rendered) {
			return;
		}

		// resize each of the hour divs
		for (var i = 0, len = this.bodyElements.length; i < len; i++) {
			var hour = (i + Math.floor(this.timeDifference) + 24) % 24;
			var date = new Date(0, 0, 0, hour);
			// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
			this.bodyElements[i].dom.innerHTML = String.format(this.textTemplate, date.formatDefaultTime());
		}
	},

	/**
	 * Lays out the time strip, setting the position and size of the individual DOM elements.
	 * The desired {@link #leftOffset} and {@link #width} have already been configured by
	 * the {@link #parentView} using the {@link #setLeftMargin} and {@link #setWidth} respectively.
	 */
	onLayout: function()
	{
		if (!this.rendered) {
			return;
		}

		this.body.setLeftTop(this.leftOffset, 0);
		this.body.setSize(this.width, this.parentView.getHourHeight() * this.parentView.numHours);

		var shift = Math.round(this.timeDifference) - this.timeDifference;

		// resize each of the hour divs
		for (var i = 0, len = this.bodyElements.length; i < len; i++) {
			this.bodyElements[i].setSize(this.width, this.parentView.getHourHeight());
			this.bodyElements[i].setLeftTop(0, this.parentView.getHourHeight() * (i + shift));
		}

		// Layout the lines between each of the hour blocks
		for (var i = 0, len = this.bodyLines.length; i < len; i++) {
			this.bodyLines[i].setSize(this.width, 1);
			this.bodyLines[i].setLeftTop(0, this.parentView.getHourHeight() * (i + shift) -1);
		}

		// layout the header element
		this.header.setWidth(this.width);
		this.header.setLeft(this.leftOffset);
		this.header.setBottom(0);

		Zarafa.calendar.ui.TimeStripView.superclass.onLayout.call(this);
	},

	/**
	 * Sets the time difference in hours between this strip's time zone and the local time zone.
	 * For example if the local time zone is GMT +2:00, and you want to have a strip that shows the time in Mumbai
	 * (GMT +5:30), the time difference is +3:30, or 3.5 hours.
	 * @param {Number} timeDifference timeDifference difference in hours.
	 */
	setTimeDifference: function(timeDifference)
	{
		this.timeDifference = timeDifference;
		this.generateText();
		this.layout();
	},

	/**
	 * Sets the name of the time strip. Auto-updates the text on screen.
	 * @param {String} name name of the time strip
	 */
	setName: function(name)
	{
		this.name = name;
		this.generateText();
	}
});
/*
 * #dependsFile client/zarafa/core/KeyMapMgr.js
 */
Ext.namespace('Zarafa.common');

/**
 * @class Zarafa.common.KeyMapping
 * @extends Object
 *
 * The map of keys used for global functionalities.
 * @singleton
 */
Zarafa.common.KeyMapping = Ext.extend(Object, {

	/**
	 * @constructor
	 */
	constructor: function()
	{
		var numberKeys = [
			// number keys from main keyboard
			Ext.EventObject.ZERO, Ext.EventObject.ONE, Ext.EventObject.TWO,
			Ext.EventObject.THREE, Ext.EventObject.FOUR, Ext.EventObject.FIVE,
			Ext.EventObject.SIX, Ext.EventObject.SEVEN, Ext.EventObject.EIGHT,
			Ext.EventObject.NINE,
			// number keys from numpad
			Ext.EventObject.NUM_ZERO, Ext.EventObject.NUM_ONE, Ext.EventObject.NUM_TWO,
			Ext.EventObject.NUM_THREE, Ext.EventObject.NUM_FOUR, Ext.EventObject.NUM_FIVE,
			Ext.EventObject.NUM_SIX, Ext.EventObject.NUM_SEVEN, Ext.EventObject.NUM_EIGHT,
			Ext.EventObject.NUM_NINE
		];

		var mainTabBar = [{
			key: numberKeys,
			ctrl: true,
			alt: false,
			shift: false,
			// Overwrites the browser behavior to switch between tabs
			stopEvent: true,
			handler: this.onSwitchContexts,
			scope: this,
			settingsCfg: {
				description: _('Switch between different contexts e.g. inbox, calendar, etc.'),
				category: _('Basic navigation')
			}
		}];

		var mainToolbarKeys = [{
			key: numberKeys,
			ctrl: true,
			alt: true,
			shift: false,
			stopEvent: true,
			handler: this.onSwitchViews,
			scope: this,
			settingsCfg: {
				description: _('Switch between different views'),
				category: _('Basic navigation')
			}
		},{
			key: Ext.EventObject.N,
			ctrl: true,
			alt: true,
			shift: false,
			stopEvent: true,
			handler: this.onNewItem,
			scope: this,
			settingsCfg: {
				description: _('New item'),
				category: _('Creating an item')
			}
		},{
			key: Ext.EventObject.F5,
			ctrl: false,
			alt: false,
			shift: false,
			stopEvent: true,
			handler: this.onRefresh,
			scope: this,
			settingsCfg: {
				description: _('Refresh'),
				category: _('All views')
			},
			basic: true
		}];

		var contentPanelActionsKeys = [{
			key: Ext.EventObject.W,
			ctrl: true,
			alt: true,
			shift: false,
			stopEvent: true,
			handler: this.onCloseCurrentMainContentTab,
			scope: this,
			settingsCfg: {
				description: _('Close tab'),
				category: _('Basic navigation')
			}
		}];

		var defaultPrintMessageKey = [{
			key: Ext.EventObject.P,
			ctrl: true,
			alt: false,
			shift: false,
			// Overwrites the browser behavior of opening print dialog
			stopEvent: true,
			handler: this.onPrintItem,
			scope: this,
			settingsCfg: {
				description: _('Print item'),
				category: _('Items')
			},
			basic: true
		}];

		var defaultRecordActionsKeys = [{
			key: Ext.EventObject.S,
			ctrl: true,
			alt: false,
			shift: false,
			// Overwrites the browser behavior of opening save dialog
			stopEvent: true,
			handler: this.onSaveTabItem,
			scope: this,
			settingsCfg: {
				description: _('Save an item e.g. mail, contact, etc.'),
				category: _('Items')
			},
			basic: true
		}];

		var defaultMessageActionsKeys = [{
			key: Ext.EventObject.ENTER,
			ctrl: true,
			alt: false,
			shift: false,
			stopEvent: true,
			handler: this.onSendTabItem,
			scope: this,
			settingsCfg: {
				description: _('Send an item e.g. mail, meeting request, etc.'),
				category: _('Items')
			},
			basic: true
		}];

		var selectionKey = [{
			key: Ext.EventObject.A,
			ctrl: true,
			alt: false,
			shift: false,
			// Overwrites the browser behavior to select everything
			stopEvent: true,
			// enable this key combination globally because we will be having a different
			// key binding to disable ctrl + a in global scope
			enableGlobally: true,
			handler: this.onSelectAll,
			scope: this,
			settingsCfg: {
				description: _('Select all items'),
				category: _('All views')
			},
			basic: true
		},{
			key: Ext.EventObject.HOME,
			ctrl: false,
			alt: false,
			shift: true,
			handler: this.onSelectUpDown,
			scope: this,
			settingsCfg: {
				description: _('Extend the selection to the first item in the list'),
				category: _('All views')
			}
		},{
			key: Ext.EventObject.END,
			ctrl: false,
			alt: false,
			shift: true,
			handler: this.onSelectUpDown,
			scope: this,
			settingsCfg: {
				description: _('Extend the selection to the last item in the list'),
				category: _('All views')
			}
		}];

		var mapiMessageKeys = [{
			key: Ext.EventObject.ENTER,
			ctrl: false,
			alt: false,
			shift: false,
			stopEvent: true,
			enableGlobally: true,
			handler: this.onOpenItem,
			scope: this,
			settingsCfg: {
				description: _('Open selected item'),
				category: _('All views')
			},
			basic: true
		},{
			key: Ext.EventObject.G,
			ctrl: true,
			alt: false,
			shift: false,
			stopEvent: true,
			handler: this.onCategorize,
			scope: this,
			settingsCfg: {
				description: _('Open dialog to categorize selected item'),
				category: _('All views')
			}
		},{
			key: Ext.EventObject.P,
			ctrl: true,
			alt: true,
			shift: false,
			// Overwrites the browser behavior of opening print dialog
			stopEvent: true,
			handler: this.onPrintList,
			scope: this,
			settingsCfg: {
				description: _('Print list'),
				category: _('All views')
			}
		},{
			key: Ext.EventObject.P,
			ctrl: true,
			alt: false,
			shift: false,
			// Overwrites the browser behavior of opening print dialog
			stopEvent: true,
			handler: this.onPrintListItem,
			scope: this,
			settingsCfg: {
				description: _('Print selected item'),
				category: _('All views')
			},
			basic: true
		},{
			key: Ext.EventObject.M,
			ctrl: true,
			alt: false,
			shift: false,
			stopEvent: true,
			handler: this.onMoveItem,
			scope: this,
			settingsCfg: {
				description: _('Open copy/move dialog'),
				category: _('All views')
			}
		},{
			key: Ext.EventObject.DELETE,
			ctrl: false,
			alt: false,
			shift: false,
			stopEvent: true,
			enableGlobally: true,
			handler: this.onDeleteItem,
			scope: this,
			settingsCfg: {
				description: _('Delete selected item'),
				category: _('All views')
			},
			basic: true
		},{
			key: Ext.EventObject.DELETE,
			ctrl: false,
			alt: false,
			shift: true,
			stopEvent: true,
			enableGlobally: true,
			handler: this.onSoftDeleteItem,
			scope: this,
			settingsCfg: {
				description: _('Delete selected item without moving it to the Deleted Items folder. The deleted item can still be restored.'),
				category: _('All views')
			},
			basic: true
		}];

		var reminderKeys = [{
			key: Ext.EventObject.ENTER,
			ctrl: false,
			alt: false,
			shift: false,
			stopEvent: true,
			enableGlobally: true,
			handler: this.onOpenReminder,
			scope: this
			// not specifying settingsCfg as we already have an entry of opening item in all views
		}];

		Zarafa.core.KeyMapMgr.register('global', mainTabBar);
		Zarafa.core.KeyMapMgr.register('global', mainToolbarKeys);

		Zarafa.core.KeyMapMgr.register('grid', selectionKey);
		Zarafa.core.KeyMapMgr.register('view.mapimessage', selectionKey);

		Zarafa.core.KeyMapMgr.register('grid.mapimessage', mapiMessageKeys);
		Zarafa.core.KeyMapMgr.register('view.mapimessage', mapiMessageKeys);
		Zarafa.core.KeyMapMgr.register('grid.reminder', reminderKeys);

		Zarafa.core.KeyMapMgr.register('contentpanel', contentPanelActionsKeys);
		Zarafa.core.KeyMapMgr.register('contentpanel.record', defaultPrintMessageKey);
		Zarafa.core.KeyMapMgr.register('contentpanel.record', defaultRecordActionsKeys);
		Zarafa.core.KeyMapMgr.register('contentpanel.record.message', defaultMessageActionsKeys);

		Zarafa.core.KeyMapMgr.register('previewpanel.mail', defaultPrintMessageKey);
	},

	/**
	 * Event handler for the keydown event of the {@link Zarafa.core.KeyMap KeyMap}
	 * when the user wants to create a new item.
	 * @param {Number} key Key code
	 * @param {Ext.EventObject} event The event
	 * @param {Ext.Component} component The component on which key event is fired.
	 */
	onNewItem: function(key, event, component)
	{
		var newButton = container.getMainPanel().mainToolbar.newButton;
		newButton.handler.call(newButton.scope);
	},

	/**
	 * Event handler for the keydown event of the {@link Zarafa.core.KeyMap KeyMap}
	 * when the user wants to refresh the view.
	 * @param {Number} key Key code
	 * @param {Ext.EventObject} event The event
	 * @param {Ext.Component} component The component on which key event is fired.
	 */
	onRefresh: function(key, event, component)
	{
		var refreshButton = container.getMainPanel().mainToolbar.refreshButton;

		if(!refreshButton.isVisible()) {
			// don't call handler if button is hidden
			return;
		}

		refreshButton.handler.call(refreshButton.scope);
	},

	/**
	 * Event handler for the keydown event of the {@link Zarafa.core.KeyMap KeyMap}
	 * when the user wants to switch between {@link Zarafa.core.Context Contexts}.
	 * @param {Number} key Key code
	 * @param {Ext.EventObject} event The event
	 * @param {Ext.Component} component The component on which key event is fired.
	 */
	onSwitchContexts: function(key, event, component)
	{
		var itemNumber = parseInt(event.getKeyCharCode(), 10);
		var mainTabBar = container.getMainPanel().mainTabBar;
		var item, items = mainTabBar.find('type', 'button');
		item = items[itemNumber];

		if(item && item.handler && item.handler !== mainTabBar.onLogoutButton){
			item.handler.call(item.scope || item);
		}
	},

	/**
	 * Event handler for the keydown event of the {@link Zarafa.core.KeyMap KeyMap}
	 * when the user wants to switch between the available views.
	 * @param {Number} key Key code
	 * @param {Ext.EventObject} event The event
	 * @param {Ext.Component} component The component on which key event is fired.
	 */
	onSwitchViews: function(key, event, component)
	{
		// Get key number which was pressed, we need to decrease it by one
		// as in views array we have first view saved at '0' index.
		var itemNumber = parseInt(event.getKeyCharCode(), 10) - 1;
		var activeContext = container.getCurrentContext();

		// Get the view buttons directly from the Context
		var item, items = activeContext.getMainToolbarViewButtons();
		item = items[itemNumber];

		if(item && item.handler){
			item.handler.call(item.scope || item, item);
		}
	},

	/**
	 * Event handler for the keydown event of the {@link Zarafa.core.KeyMap KeyMap}
	 * when the user wants to close the current tab he has open in the
	 * {@link Zarafa.core.ui.MainContentTabPanel MainContentTabPanel}.
	 * @param {Number} key Key code
	 * @param {Ext.EventObject} event The event
	 * @param {Ext.Component} component The component on which key event is fired.
	 */
	onCloseCurrentMainContentTab: function(key, event, component)
	{
		if(component.closable) {
			component[component.closeAction]();
		}
	},

	/**
	 * Event handler for the keydown event of the {@link Zarafa.core.KeyMap KeyMap}
	 * when the user wants to send the item that is open in the
	 * {@link Zarafa.core.ui.MessageContentPanel MessageContentPanel}.
	 * @param {Number} key Key code
	 * @param {Ext.EventObject} event The event
	 * @param {Ext.Component} component The component on which key event is fired.
	 */
	onSendTabItem: function(key, event, component)
	{
		if(Ext.isFunction(component.sendRecord)) {
			component.sendRecord();
		}
	},

	/**
	 * Event handler for the keydown event of the {@link Zarafa.core.KeyMap KeyMap}
	 * when the user wants to save the item that is open in the
	 * {@link Zarafa.core.ui.RecordContentPanel RecordContentPanel}.
	 * @param {Number} key Key code
	 * @param {Ext.EventObject} event The event
	 * @param {Ext.Component} component The component on which key event is fired.
	 */
	onSaveTabItem: function(key, event, component)
	{
		if(Ext.isFunction(component.saveRecord) && component.dialog.autoSave) {
			component.saveRecord();
		}
	},

	/**
	 * Event handler for the keydown event of the {@link Zarafa.core.KeyMap KeyMap}
	 * when the user wants to print an item.
	 * @param {Number} key Key code
	 * @param {Ext.EventObject} event The event
	 * @param {String} context The name of the context the key is pressed in
	 * @param {Ext.Component} component The component on which key event is fired.
	 */
	onPrintItem: function(key, event, component)
	{
		if(component.record){
			Zarafa.common.Actions.openPrintDialog(component.record);
		}
	},

	/**
	 * Event handler for the keydown event of the {@link Ext.KeyMap KeyMap}
	 * when the user wants to print a list of items.
	 * @param {Number} key Key code
	 * @param {Ext.EventObject} event The event
	 * @param {String} context The name of the context the key is pressed in
	 * @param {Ext.Component} component The component on which key event is fired.
	 */
	onPrintList: function(key, event, component)
	{
		// Currently Print List functionality is only available with task,
		// In future, Print List will be implemented for all the Contexts.
		if (component.context) {
			Zarafa.common.Actions.openPrintDialog(component.context);
		}
	},

	/**
	 * Helper function to get selected records from component.
	 * @param {Ext.Component} component The component on which key event is fired.
	 * @return {Ext.data.Record[]} The records which are selected int the view.
	 * @private
	 */
	getSelectedRecords: function (component)
	{
		if(Ext.isFunction(component.getSelectedRecords)) {
			return component.getSelectedRecords();
		} else if(Ext.isFunction(component.getSelectionModel)) {
			return component.getSelectionModel().getSelections();
		}

		return [];
	},

	/**
	 * Event handler for the keydown event of the {@link Zarafa.core.KeyMap KeyMap}
	 * when the user wants to print selected item.
	 * @param {Number} key Key code
	 * @param {Ext.EventObject} event The event
	 * @param {Ext.Component} component The component on which key event is fired.
	 */
	onPrintListItem: function(key, event, component)
	{
		var records = this.getSelectedRecords(component);

		if(!Ext.isEmpty(records)) {
			Zarafa.common.Actions.openPrintDialog(records);
		}
	},

	/**
	 * Event handler for the keydown event of the {@link Zarafa.core.KeyMap KeyMap}
	 * when the user wants to move an item.
	 * @param {Number} key Key code
	 * @param {Ext.EventObject} event The event
	 * @param {Ext.Component} component The component on which key event is fired.
	 */
	onMoveItem: function(key, event, component)
	{
		var records = this.getSelectedRecords(component);

		if(!Ext.isEmpty(records)) {
			Zarafa.common.Actions.openCopyMoveContent(records);
		}
	},

	/**
	 * Event handler for the keydown event of the {@link Zarafa.core.KeyMap KeyMap}
	 * when the user wants to delete an item.
	 * @param {Number} key Key code
	 * @param {Ext.EventObject} event The event
	 * @param {Ext.Component} component The component on which key event is fired.
	 */
	onDeleteItem: function(key, event, component)
	{
		var records = this.getSelectedRecords(component);

		if(!Ext.isEmpty(records)) {
			Zarafa.common.Actions.deleteRecords(records);
		}
	},

	/**
	 * Event handler for the keydown event of the {@link Zarafa.core.KeyMap KeyMap}
	 * when the user wants to soft delete an item.
	 * @param {Number} key Key code
	 * @param {Ext.EventObject} event The event
	 * @param {Ext.Component} component The component on which key event is fired.
	 */
	onSoftDeleteItem: function(key, event, component)
	{
		var records = this.getSelectedRecords(component);

		if(!Ext.isEmpty(records)) {
			Zarafa.common.Actions.deleteRecords(records, false, true);
		}
	},

	/**
	 * Event handler for the keydown event of the {@link Ext.KeyMap KeyMap}
	 * when the user wants to categorize an item.
	 * @param {Number} key Key code
	 * @param {Ext.EventObject} event The event
	 * @param {Ext.Component} component The component on which key event is fired.
	 */
	onCategorize: function(key, event, component)
	{
		var records = this.getSelectedRecords(component);

		if(!Ext.isEmpty(records)) {
			Zarafa.common.Actions.openCategoriesContent(records);
		}
	},

	/**
	 * Event handler for the keydown event of the {@link Ext.KeyMap KeyMap}
	 * when the user wants to open an item.
	 * @param {Number} key Key code
	 * @param {Ext.EventObject} event The event
	 * @param {Ext.Component} component The component on which key event is fired.
	 */
	onOpenItem: function(key, event, component)
	{
		var records = this.getSelectedRecords(component);

		if(!Ext.isEmpty(records)) {
			for(var i = 0, len = records.length; i < len; i++){
				if(records[i].isUnsent() && !records[i].isFaultyMessage()) {
					Zarafa.core.data.UIFactory.openCreateRecord(records[i]);
				} else {
					Zarafa.core.data.UIFactory.openViewRecord(records[i]);
				}
			}
		}
	},

	/**
	 * Event handler for the keydown event of the {@link Ext.KeyMap KeyMap}
	 * when the user wants to open an item from reminder dialog.
	 * @param {Number} key Key code
	 * @param {Ext.EventObject} event The event
	 * @param {Ext.Component} component The component on which key event is fired.
	 */
	onOpenReminder: function(key, event, component)
	{
		var records = this.getSelectedRecords(component);
		if(!Ext.isEmpty(records)) {
			for(var i = 0, len = records.length; i < len; i++){
				Zarafa.common.Actions.openReminderRecord(records[i]);
			}
		}
	},

	/**
	 * Event handler for the keydown event of the {@link Ext.KeyMap KeyMap}
	 * when the user wants to select all items below or above the current selected item {@link Zarafa.common.ui.grid.GridPanel}.
	 * @param {Number} key Key code
	 * @param {Ext.EventObject} event The event
	 * @param {Ext.Component} component The component on which key event is fired.
	 */
	onSelectUpDown: function(key, event, component)
	{
		var store = component.getStore();
		var selectionRange = null;

		if(key === Ext.EventObject.HOME) {
			selectionRange = 0;
		} else if (key === Ext.EventObject.END) {
			selectionRange = store.getCount();
		}

		if(Ext.isFunction(component.selectRange)) {
			var selected = component.getSelectedRecords();
			if(!Ext.isEmpty(selected)) {
				component.selectRange(store.indexOf(selected[0]), selectionRange);
			}
		} else if(Ext.isFunction(component.getSelectionModel)) {
			var selectionModel = component.getSelectionModel();
			var selected = selectionModel.getSelected();
			if(selected) {
				if(!selectionModel.singleSelect) {
					// retrieve selected index
					var index = store.indexOf(selected);
					// select range from current item till last range
					selectionModel.selectRange(index,selectionRange);
				}
			}
		}
	},

	/**
	 * Event handler for the keydown event of the {@link Ext.KeyMap KeyMap}
	 * when the user wants to select all items in the {@link Zarafa.common.ui.grid.GridPanel}.
	 * @param {Number} key Key code
	 * @param {Ext.EventObject} event The event
	 * @param {Ext.Component} component The component on which key event is fired.
	 */
	onSelectAll: function(key, event, component)
	{
		if(Ext.isFunction(component.selectRange)) {
			// Select all elements for data-views.
			component.selectRange();
		} else if(Ext.isFunction(component.getSelectionModel)) {
			// Select all elements for components with selection model, e.g. grids.
			var selectionModel = component.getSelectionModel();
			if(!selectionModel.singleSelect) {
				selectionModel.selectAll();
			}
		}
	}
});

Zarafa.common.KeyMapping = new Zarafa.common.KeyMapping();
Ext.namespace('Zarafa.common.attachment.dialogs');

/**
 * @class Zarafa.common.attachment.dialogs.AttachItemContentPanel
 * @extends Zarafa.core.ui.ContentPanel
 * @xtype zarafa.attachitemcontentpanel
 *
 * This content panel will be used to show hierarchy and a grid to select messages, selected messages
 * can be added to message as an embedded attachment.
 */
Zarafa.common.attachment.dialogs.AttachItemContentPanel = Ext.extend(Zarafa.core.ui.ContentPanel, {
	/**
	 * The MAPIFolder which was selected the last time this panel was opened.
	 * This is used when {@link #stateful} is enabled.
	 * @property
	 * @type Zarafa.hierarchy.data.MAPIFolderRecord
	 */
	last_selected_folder: undefined,

	/**
	 * The Radio item which was selected the last time this panel was opened.
	 * This is used when {@link #stateful} is enabled.
	 * @property
	 * @type String
	 */
	last_selected_radio: undefined,

	/**
	 * @cfg {Zarafa.core.data.MAPIRecord} record record in which we will add embedded attachment if user is adding it as attachment.
	 */
	record: undefined,

	/**
	 * @cfg {Zarafa.common.ui.EditorField} editor editor in which we will be adding message as text if user has selected to add text in body.
	 */
	editor: undefined,

	/**
	 * The LoadMask object which will be shown when the {@link #record} is being opened, and
	 * the dialog is waiting for the server to respond with the desired data. This will only
	 * be set if {@link #loadMask} is undefined.
	 * @property
	 * @type Zarafa.common.ui.LoadMask
	 */
	loadMask: undefined,

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

		Ext.applyIf(config, {
			title: _('Insert item'),
			layout: 'fit',
			cls: 'k-insert-item-dialog',
			items: [{
				xtype: 'zarafa.attachitempanel',
				record: config.record,
				editor: config.editor
			}]
		});

		Zarafa.common.attachment.dialogs.AttachItemContentPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Obtain the {@link Zarafa.hierarchy.data.MAPIFolderRecord folder} which should be selected by
	 * default. This will use {@link #last_selected_folder} if provided, otherwise will default inbox folder from
	 * {@link Zarafa.core.Container#getHierarchyStore hierarchy}.
	 * @return {Zarafa.hierarchy.data.MAPIFolderRecord} The default selected folder
	 */
	getSelectedFolder: function()
	{
		if(!this.last_selected_folder) {
			var hierarchy = container.getHierarchyStore();
			this.last_selected_folder = hierarchy.getDefaultFolder('inbox');
		}

		return this.last_selected_folder;
	},

	/**
	 * Obtain the radio group item inputValue which should be selected by default.
	 * This will use {@link #last_selected_radio} if provided, otherwise will default to 'attachment'.
	 * @return {String} The default selected radio item
	 */
	getSelectedRadioItem: function()
	{
		if(!this.last_selected_radio) {
			this.last_selected_radio = 'attachment';
		}

		return this.last_selected_radio;
	},

	/**
	 * Mark the given folder as selected, this will update {@link #last_selected_folder}
	 * and will call {@link #saveState} if this panel is {@link #stateful}.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder The selected folder
	 */
	setFolderInState: function(folder)
	{
		this.last_selected_folder = folder;
		if(this.stateful !== false) {
			this.saveState();
		}
	},

	/**
	 * Mark the given radio item as selected, this will update {@link #last_selected_radio}
	 * and will call {@link #saveState} if this panel is {@link #stateful}.
	 * @param {Ext.form.Radio} radioItem The selected radio item
	 */
	setRadioItemInState: function(radioItem)
	{
		// don't save radio selection in state settings, we are unsure we should have state setting for this
		this.last_selected_radio = radioItem.inputValue;
		if(this.stateful !== false) {
			this.saveState();
		}
	},

	/**
	 * When {@link #stateful} the State object which should be saved into the
	 * {@link Ext.state.Manager}.
	 * @return {Object} The state object
	 * @protected
	 */
	getState: function()
	{
		var state = Zarafa.common.attachment.dialogs.AttachItemContentPanel.superclass.getState.call(this);

		if(this.last_selected_folder) {
			state.last_selected_folder = this.last_selected_folder.get('entryid');
		}

		if(this.last_selected_radio) {
			state.last_selected_radio = this.last_selected_radio;
		}

		return state;
	},

	/**
	 * Apply the given state to this object activating the properties which were previously
	 * saved in {@link Ext.state.Manager}.
	 * @param {Object} state The state object
	 * @protected
	 */
	applyState: function(state)
	{
		if(state && state.last_selected_folder) {
			this.last_selected_folder = container.getHierarchyStore().getFolder(state.last_selected_folder);
			delete state.last_selected_folder;
		}

		// we don't want to support selection radio from state at this time
		delete state.last_selected_radio;

		Zarafa.common.attachment.dialogs.AttachItemContentPanel.superclass.applyState.call(this, state);
	},

	/**
	 * If {@link #showLoadMask} is enabled, this function will display the {@link #loadMask}.
	 * @protected
	 */
	showLoadMask: function()
	{
		if (!this.loadMask) {
			this.loadMask = new Zarafa.common.ui.LoadMask(this.el);
		}

		this.loadMask.show();
	},

	/**
	 * If {@link #showLoadMask} is enabled, and {@link #showLoadMask} has been
	 * called to display the {@link #loadMask} this function will disable the loadMask.
	 * @protected
	 */
	hideLoadMask: function()
	{
		if (this.loadMask) {
			this.loadMask.hide();
		}
	},

	/**
	 * Function will be used to get instance of store which should be used to get data from a particular folder.
	 * Function will use bidding process of contexts to find context which will be loaded when folder is selected in hierarchy,
	 * after getting the context we can easily get an instance of the store, but instead we will need a new instance of the store
	 * so we will create a new instance of the store by using the constructor.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder folder that should be used for bidding process.
	 * @return {Zarafa.core.data.ListModuleStore} store that will be used to load data in {@link Zarafa.common.attachment.dialogs.AttachItemGrid AttachItemGrid}.
	 * @private
	 */
	getStoreByFolder: function(folder)
	{
		// find context which can successfully bid for this particular folder
		var context = container.getContextByFolder(folder);

		// get store instance from context
		var store = context.getModel().getStore();

		if(!store) {
			// if we are not able to get the store then fallback to MailStore
			// this will normally happen with IPM_SUBTREE of default store
			store = new Zarafa.mail.MailStore();
		}

		// we will get the instance but instead we need to create a new instance of the store
		store = new store.constructor({
			// make sure that store is not registered with IPMStoreMgr as we don't want to propagate updates from other stores
			// because this store is used in a modal dialog
			standalone: true,

			// pass the folder from which we need to load the data
			folder: folder,

			// destroy store when folder selection is changed
			// and store is not needed anymore
			autoDestroy: true
		});

		return store;
	},

	/**
	 * Function will be used to get instance of {@link Zarafa.common.ui.grid.ColumnModel ColumnModel}. Function will use
	 * bidding process to get instance of column model.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder folder that should be used for bidding process.
	 * @return {Zarafa.core.data.ListModuleStore} store that will be used to show columns in {@link Zarafa.common.attachment.dialogs.AttachItemGrid AttachItemGrid}.
	 * @private
	 */
	getColumnModelByFolder: function(folder)
	{
		// find the correct column model for showing in grid, this will use bidding process
		var componentType = Zarafa.core.data.SharedComponentType['common.attachment.dialog.attachitem.columnmodel'];
		var columnModel = container.getSharedComponent(componentType, folder);

		return new columnModel();
	},

	/**
	 * Function will be used to get instance of {@link Zarafa.common.attachment.dialogs.AttachItemBaseRenderer Renderer}. Function will use
	 * bidding process to get instance of renderer.
	 * @param {Zarafa.core.data.IPMRecord} record record that is selected and should use for bidding process.
	 * @private
	 */
	getRendererByMessage: function(record)
	{
		// find the correct renderer for getting text data from record, this will use bidding process
		var componentType = Zarafa.core.data.SharedComponentType['common.attachment.dialog.attachitem.textrenderer'];
		var renderer = container.getSharedComponent(componentType, record);

		return new renderer();
	}
});

Ext.reg('zarafa.attachitemcontentpanel', Zarafa.common.attachment.dialogs.AttachItemContentPanel);
Ext.namespace('Zarafa.common.attachment.dialogs');

/**
 * @class Zarafa.common.attachment.dialogs.AttachItemGrid
 * @extends Zarafa.common.ui.grid.GridPanel
 * @xtype zarafa.attachitemgrid
 *
 * Grid will be used to display contents of folder that is selected in hierarchy.
 * Messages from this grid can be selected to add as an embedded attachment to message.
 */
Zarafa.common.attachment.dialogs.AttachItemGrid = Ext.extend(Zarafa.common.ui.grid.GridPanel, {
	/**
	 * @constructor
	 * @param {Object} config Configuration structure
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			xtype: 'zarafa.attachitemgrid',
			border: true,
			colModel: this.initColumnModel(),
			selModel: this.initSelectionModel(),
			// provide a dummy store for initialization, this will be changed later on
			store: new Zarafa.core.data.ListModuleStore(),
			loadMask: true,
			tbar: [{
				xtype: 'zarafa.paging',
				pageSize: 50,
				// provide a dummy store for initialization, this will be changed later on
				store: new Zarafa.core.data.ListModuleStore()
			}],
			listeners: {
				// Register event that will change store associated with pagination
				'reconfigure': this.onGridReconfigure,
				// Register event that will add message as attachment to message
				'rowdblclick': this.onRowDblClick,
				scope: this
			}
		});

		Zarafa.common.attachment.dialogs.AttachItemGrid.superclass.constructor.call(this, config);
	},

	/**
	 * Creates and returns a column model object, used in {@link Ext.grid.GridPanel.colModel colModel} config.
	 * This will be just a dummy column model, actual column model will be loaded when folder is selected.
	 * @return {Ext.grid.ColumnModel} column model object
	 * @private
	 */
	initColumnModel: function()
	{
		return new Zarafa.common.ui.grid.ColumnModel();
	},

	/**
	 * Creates and returns a selection model object, used in {@link Ext.grid.GridPanel.selModel selModel} config
	 * @return {Ext.grid.RowSelectionModel} selection model object
	 * @private
	 */
	initSelectionModel: function()
	{
		return new Ext.grid.RowSelectionModel({
			singleSelect: true
		});
	},

	/**
	 * Event handler which is fired when the {@link #store store} is loaded. And we can select the first row.
	 * @private
	 */
	onStoreLoad: function()
	{
		this.getSelectionModel().selectFirstRow();
	},

	/**
	 * Function will change {@link Zarafa.core.data.ListModuleStore ListModuleStore} associated with {@link Zarafa.common.ui.PagingToolbar PagingToolbar}
	 * when {@link Zarafa.common.ui.grid.GridPanel GridPanel} is configured with new store.
	 * @param {Zarafa.common.ui.grid.GridPanel} grid grid panel which is reconfigured with new store and column model.
	 * @param {Zarafa.core.data.ListModuleStore} store new store which is now bound with grid panel.
	 * @param {Zarafa.common.ui.grid.ColumnModel} colModel column model which is now bound with grid panel.
	 * @private
	 */
	onGridReconfigure: function(grid, store, colModel)
	{
		// change store of paging toolbar
		var paging = this.getTopToolbar().findByType('zarafa.paging')[0];

		paging.bindStore(store);

		// register event that will select first item in the grid
		this.mon(store, 'load', this.onStoreLoad, this);
	},

	/**
	 * Event handler which is fired when a row in the grid has been double-clicked.
	 * This will call {@link Zarafa.common.attachment.dialogs.AttachItemGrid#attachItem} method.
	 * @param {Ext.grid.GridPanel} grid The grid which fired the event
	 * @param {Number} rowIndex The index of the row which was double clicked
	 * @param {Ext.EventObject} event The event
	 * @private
	 */
	onRowDblClick: function(grid, rowIndex, event)
	{
		this.ownerCt.attachItem();
	}
});

Ext.reg('zarafa.attachitemgrid', Zarafa.common.attachment.dialogs.AttachItemGrid);
Ext.namespace('Zarafa.common.attachment.dialogs');

/**
 * @class Zarafa.common.attachment.dialogs.ImportToFolderPanel
 * @extends Zarafa.common.dialogs.CopyMovePanel
 * @xtype zarafa.importtofolderpanel
 *
 * Panel for users to import the given {@link Zarafa.core.data.IPMAttachmentRecord records}
 * to {@link Zarafa.hierarchy.data.MAPIFolderRecord folder}.
 */
Zarafa.common.attachment.dialogs.ImportToFolderPanel = Ext.extend(Zarafa.common.dialogs.CopyMovePanel, {

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

		Ext.applyIf(config, {
			// Override from Ext.Component
			xtype: 'zarafa.importtofolderpanel',
			permissionFilter: Zarafa.core.mapi.Rights.RIGHTS_CREATE,
			buttons: [{
				text: _('Import'),
				handler: this.onImport,
				scope: this,
				ref: '../importButton',
				disabled: true
			},{
				text: _('New folder'),
				handler: this.onCreateFolder,
				scope: this,
				ref: '../createFolderButton',
				disabled: true
			},
			'->',
			{
				text: _('Cancel'),
				handler: this.onCancel,
				cls: 'zarafa-normal',
				scope: this
			}]
		});

		Zarafa.common.attachment.dialogs.ImportToFolderPanel.superclass.constructor.call(this, config);
	},

	/**
	* Event handler which is triggered when the user select a {@link Zarafa.hierarchy.data.MAPIFolderRecord folder}
	* from the {@link Zarafa.hierarchy.ui.Tree tree}. This will determine if a valid
	* {@link Zarafa.hierarchy.data.MAPIFolderRecord folder} is selected to which the {@link Zarafa.core.data.IPMRecord records}
	* can indeed be copied or moved to.
	* @param {DefaultSelectionModel} selectionModel The selectionModel for the treepanel
	* @param {TreeNode} node The selected tree node
	* @private
	*/
	onSelectionChange: function(selectionModel, node)
	{
		if (!Ext.isDefined(node) || (node.getFolder().isIPMSubTree() && this.objectType == Zarafa.core.mapi.ObjectType.MAPI_MESSAGE)) {
			this.importButton.disable();
			this.createFolderButton.disable();
		} else {
			this.importButton.enable();
			this.createFolderButton.enable();
		}
	},

	/**
	 * Event handler which is triggered when the user presses the import
	 * {@link Ext.Button button}. This will import given attachment into selected folder
	 * and will close the dialog when it is done.
	 * @private
	 */
	onImport: function()
	{
		var folder = this.hierarchyTree.getSelectionModel().getSelectedNode().getFolder();
		var records = this.record;

		if (!Ext.isDefined(folder) || Ext.isEmpty(this.record)) {
			return;
		}

		Ext.each(records, function(record, index) {
			record.store.importRecord(record, record.store.parentRecord, folder);
		}, this);

		this.dialog.selectFolder(folder);
		this.dialog.close();
	},

	/**
	 * Identify {@link Zarafa.common.data.FolderContentTypes content-type} based on
	 * the filename of given attachment to filter matching tree nodes only
	 * using {@link Zarafa.hierarchy.ui.Tree#IPMFilter}.
	 * @param {Zarafa.core.data.IPMAttachmentRecord} record The attachment record which should be imported.
	 * @return {String} The content type, undefined if no match available
	 * @private
	 */
	getIPMFilter: function(record)
	{
		let extension = record.get('extension').toLowerCase();

		if(record.isEmbeddedMessage()) {
			let messageClass = record.get('attach_message_class');
			if (Zarafa.core.MessageClass.isClass(messageClass, 'IPM.Note')) {
				extension = 'eml';
			} else if (Zarafa.core.MessageClass.isClass(messageClass, 'IPM.Contact')) {
				extension = 'vcf';
			}
		}

		switch (extension) {
			case 'eml':
				return Zarafa.common.data.FolderContentTypes.mail;
			case 'vcf':
				return Zarafa.common.data.FolderContentTypes.contact;
			case 'ics':
			case 'vcs':
				return Zarafa.common.data.FolderContentTypes.appointment;
		}

		return;
	}
});

Ext.reg('zarafa.importtofolderpanel', Zarafa.common.attachment.dialogs.ImportToFolderPanel);
Ext.namespace('Zarafa.common.attachment.dialogs');

/**
 * @class Zarafa.common.attachment.dialogs.MixAttachItemContentPanel
 * @extends Zarafa.core.ui.ContentPanel
 * @xtype zarafa.mixattachitemcontentpanel
 *
 * This content panel will be used to show a warning when user tries to download attachments of webapp
 * item containing embedded attachments as well.
 */
Zarafa.common.attachment.dialogs.MixAttachItemContentPanel = Ext.extend(Zarafa.core.ui.ContentPanel, {
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			xtype: 'zarafa.mixattachitemcontentpanel',
			layout: 'fit',
			title: _('Download as ZIP'),
			width: 400,
			height: 250,
			items: [{
				xtype: 'zarafa.mixattachitempanel',
				records: config.record,
				ref: 'mixAttachItemPanel',
				buttons: [{
					text: _('Continue'),
					handler: this.onContinue.createDelegate(this, [ config.downloadItem ], 1),
					scope: this
				},{
					text: _('Cancel'),
					ref: '../../dialogCancelButton',
					handler: this.onCancel,
					scope: this
				}]
			}]
		});

		Zarafa.common.attachment.dialogs.MixAttachItemContentPanel.superclass.constructor.call(this, config);

		// Register the check event to enable/disable 'Cancel' button
		this.mon(this.dontShowCheckBox, 'check', this.onDontShowCheck, this);
	},

	/**
	 * Event handler which is raised when the user clicks the "Continue" {@link Ext.Button button}
	 * This will go for making request to download attachments as ZIP, excluding embedded attachments
	 * and will close the panel.
	 * @param {Ext.Button} button Button which is clicked
	 * @param {Function} downloadItemFn The function to be called to make request to download attachments as ZIP.
	 * @private
	 */
	onContinue: function(button, downloadItemFn)
	{
		if (Ext.isEmpty(this.record)) {
			this.close();
			return;
		}
		this.saveState();
		downloadItemFn.call();

		this.close();
	},

	/**
	 * Event handler which is raised when the user clicks the "Cancel" {@link Ext.Button button}
	 *
	 * This will close the panel without saving
	 * @private
	 */
	onCancel: function()
	{
		this.close();
	},

	/**
	 * Handler function that will be called when user checks/unchecks checkbox of don't show this message again,
	 * @param {Ext.form.Checkbox} checkBox checkbox for don't show this message again.
	 * @param {Boolean} checked current state of the checkbox.
	 * @private
	 */
	onDontShowCheck: function(checkBox, checked)
	{
		this.dialogCancelButton.setDisabled(checked);
	},

	/**
	 * When {@link #stateful} the State object which should be saved into the
	 * {@link Ext.state.Manager}.
	 * @return {Object} The state object
	 * @protected
	 */
	getState: function()
	{
		var state = Zarafa.common.attachment.dialogs.MixAttachItemContentPanel.superclass.getState.call(this) || {};
		var checkboxValue = this.dontShowCheckBox.getValue();
		return Ext.apply(state, {
			dontshowagain: checkboxValue
		});
	}
});

Ext.reg('zarafa.mixattachitemcontentpanel', Zarafa.common.attachment.dialogs.MixAttachItemContentPanel);Ext.namespace('Zarafa.common.attachment.ui');

/**
 * @class Zarafa.common.attachment.ui.AttachmentBox
 * @extends Zarafa.common.ui.Box
 * @xtype zarafa.attachmentbox
 *
 * Extension to the normal {@link Zarafa.common.ui.Box} which must be used
 * together with the {@link Zarafa.common.attachment.ui.AttachmentField AttachmentField}.
 * This box offers some extra functionality concerning the displaying of attachments.
 */
Zarafa.common.attachment.ui.AttachmentBox = Ext.extend(Zarafa.common.ui.Box, {

	/**
	 * @cfg {String} validCls The CSS class which must be applied on {@link #el}
	 * when the attachment has been {@link #isValidRecord uploaded} or is
	 * otherwise present on the server.
	 */
	validCls: 'x-zarafa-boxfield-attachment-item-uploaded',

	/**
	 * @cfg {String} invalidCls The CSS class which must be applied on {@link #el}
	 * when the attachment has not been {@link #isValidRecord uploaded} and the
	 * attempt to upload the file has failed.
	 */
	invalidCls: 'x-zarafa-boxfield-attachment-item-invalid',

	/**
	 * @cfg {String} uploadingCls The CSS class which must be applied on {@link #el}
	 * when the attachment has not yet been uploaded to the server.
	 */
	uploadingCls: 'x-zarafa-boxfield-attachment-item-uploading',

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

		Ext.applyIf(config, {
			cls: 'x-zarafa-boxfield-attachment-item',
			ellipsisStringStartLength: 20,
			ellipsisStringEndLength: 20,
			textTpl:
				'<tpl if="!Ext.isEmpty(values.name)">' +
					'{name:htmlEncodeElide(this.ellipsisStringStartLength, this.ellipsisStringEndLength)}' +
				'</tpl>' +
				'<tpl if="!Ext.isEmpty(values.size) && values.size &gt; 0">' +
					'&nbsp;({values.size:fileSize})' +
				'</tpl>'
		});

		Zarafa.common.attachment.ui.AttachmentBox.superclass.constructor.call(this, config);
	},

	/**
	 * Check if the given {@link Ext.data.Record record} is valid. This function can be
	 * overridden by the childclasses to indicate if the given record is valid.
	 *
	 * This class will check if the given record is {@link Zarafa.core.data.IPMAttachmentRecord#isUploaded uploaded},
	 * or otherwise present on the server.
	 *
	 * @param {Zarafa.core.data.IPMAttachmentRecord} record The record to check
	 * @return {Boolean} True if the record is valid
	 * @protected
	 */
	isValidRecord: function(record)
	{
		return record.isUploaded();
	},

	/**
	 * Update the {@link #textEl inner HTML} of this component using the {@link #textTpl template}.
	 * @param {Ext.data.Record} record The Ext.data.Record which data must be applied to the template
	 */
	update: function(record)
	{
		Zarafa.common.attachment.ui.AttachmentBox.superclass.update.apply(this, arguments);

		if (this.isValidRecord(record)) {
			this.el.removeClass(this.uploadingCls);
			this.el.removeClass(this.invalidCls);
			this.el.addClass(this.validCls);
		} else if (record.attemptedToUpload()) {
			this.el.removeClass(this.uploadingCls);
			this.el.addClass(this.invalidCls);
			this.el.removeClass(this.validCls);
		} else {
			this.el.addClass(this.uploadingCls);
			this.el.removeClass(this.invalidCls);
			this.el.removeClass(this.validCls);
		}
	},

	/**
	 * Called when the user has clicked on the remove button.
	 * @private
	 */
	onClickRemove: function()
	{
		// Don't allow user to remove attachment where it is still uploading and
		// the browser is IE/Edge.
		if (this.record.isUploaded() || !(Ext.isIE || Ext.isEdge)) {
			Zarafa.common.attachment.ui.AttachmentBox.superclass.onClickRemove.apply(this, arguments);
		}
	}
});

Ext.reg('zarafa.attachmentbox', Zarafa.common.attachment.ui.AttachmentBox);
Ext.namespace('Zarafa.common.attachment.ui');

/**
 * @class Zarafa.common.attachment.ui.AttachmentContextMenu
 * @extends Zarafa.core.ui.menu.ConditionalMenu
 * @xtype zarafa.attachmentcontextmenu
 */
Zarafa.common.attachment.ui.AttachmentContextMenu = Ext.extend(Zarafa.core.ui.menu.ConditionalMenu, {
	// Insertion points for this class
	/**
	 * @insert common.contextmenu.attachment.actions
	 * Insertion point for adding actions menu items into the context menu
	 * @param {Zarafa.common.attachment.ui.AttachmentContextMenu} contextmenu This contextmenu
	 */
	/**
	 * @insert common.contextmenu.attachment.options
	 * Insertion point for adding options menu items into the context menu
	 * @param {Zarafa.common.attachment.ui.AttachmentContextMenu} contextmenu This contextmenu
	 */

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

		if (!Ext.isDefined(config.model) && Ext.isDefined(config.context)) {
			config.model = config.context.getModel();
		}
		Ext.applyIf(config, {
			items: [
				this.createContextActionItems(config.records),
				container.populateInsertionPoint('common.contextmenu.attachment.actions', this),
				{ xtype: 'menuseparator' },
				container.populateInsertionPoint('common.contextmenu.attachment.options', this)
			],
			defaults: {
				xtype: 'zarafa.conditionalitem',
				hideOnDisabled: false
			}
		});
		Zarafa.common.attachment.ui.AttachmentContextMenu.superclass.constructor.call(this, config);
	},

	/**
	 * Create the Action context menu items
	 * @param {Zarafa.core.data.IPMAttachmentRecord} Attachment record. Based on record type preview will be enabled or disabled.
	 * @return {Zarafa.core.ui.menu.ConditionalItem[]} The list of Action context menu items
	 * @private
	 */
	createContextActionItems: function(records)
	{
		return [{
			text: _('Preview'),
			iconCls: 'icon_attachment_preview',
			scope: this,
			handler: this.onPreviewItem,
			beforeShow: this.onPreviewBeforeShow
		}, {
			text: _('Download'),
			iconCls: 'icon_download',
			scope: this,
			handler: this.onDownloadItem,
			beforeShow: this.onDownloadBeforeShow
		}, {
			text: _('Download all as ZIP'),
			iconCls: 'icon_download_zip',
			scope: this,
			handler: this.onDownloadAllAsZip,
			beforeShow: this.onDownloadZipBeforeShow
		}, {
			text: _('Import to folder'),
			iconCls: 'icon_import_attachment',
			handler: this.onImportToFolder,
			beforeShow: this.onImportToFolderBeforeShow,
			afterRender: this.onImportToFolderAfterRender,
			scope: this
		}];
	},

	/**
	 * Function will be called before {@link Zarafa.common.attachment.ui.AttachmentContextMenu AttachmentContextMenu} is shown
	 * so we can decide which item should be disabled.
	 * @param {Zarafa.core.ui.menu.ConditionalItem} item context menu item
	 * @param {Zarafa.core.data.IPMAttachmentRecord} record attachment record on which context menu is shown
	 */
	onPreviewBeforeShow: function(item, record)
	{
		if (!Zarafa.common.Actions.isFilePreviewerEnabled()) {
			item.setVisible(false);
			return;
		}

		// get component that can preview the selected record
		var comp = container.getSharedComponent(Zarafa.core.data.SharedComponentType['common.view'], record);

		// component should be lightbox to make attachment previewable
		var disabled = !comp || comp instanceof Zarafa.common.CommonContext;
		// embedded messages can not be previewed
		disabled = disabled || record.isEmbeddedMessage();

		item.setDisabled(disabled);
	},

	/**
	 * Function will be called before {@link Zarafa.common.attachment.ui.AttachmentContextMenu AttachmentContextMenu} is shown
	 * so we can decide which item should be disabled.
	 * @param {Zarafa.core.ui.menu.ConditionalItem} item context menu item
	 * @param {Zarafa.core.data.IPMAttachmentRecord} record attachment record on which context menu is shown
	 */
	onDownloadBeforeShow: function(item, record)
	{
		// embedded messages can not be downloaded
		item.setDisabled(record.isEmbeddedMessage());
	},

	/**
	 * Function will be called before {@link Zarafa.common.attachment.ui.AttachmentContextMenu AttachmentContextMenu} is shown
	 * so we can decide which item should be disabled.
	 * @param {Zarafa.core.ui.menu.ConditionalItem} item context menu item
	 * @param {Zarafa.core.data.IPMAttachmentRecord} record attachment record on which context menu is shown
	 */
	onDownloadZipBeforeShow: function(item, record)
	{
		var normalAttachmentCounter = 0;
		// Check if there is more than one normal attachments.
		// Here, 'query' method of Ext.data.Store is useless in case where there is same id(-1) of all the unsaved attachments.
		if(record.store.getCount() > 1) {
			record.store.each(function(record){
				if(!record.get('hidden')) {
					normalAttachmentCounter++;
				}
			});
		}

		// embedded messages can not be downloaded as ZIP
		// check if there is more than one attachments.
		item.setDisabled(record.isEmbeddedMessage() || normalAttachmentCounter <= 1);
	},

	/**
	 * Function will be called before {@link Zarafa.common.attachment.ui.AttachmentContextMenu AttachmentContextMenu} is shown
	 * so we can decide which item should be disabled.
	 * @param {Zarafa.core.ui.menu.ConditionalItem} item context menu item
	 * @param {Zarafa.core.data.IPMAttachmentRecord} record attachment record on which context menu is shown
	 */
	onImportToFolderBeforeShow: function(item, record)
	{
		var store = record.getStore();
		var parentRecord = store.getParentRecord();
		item.setDisabled(!record.canBeImported() || parentRecord.phantom);
	},

	/**
	 * Function will be called after {@link Zarafa.common.attachment.ui.AttachmentContextMenu AttachmentContextMenu} gets rendered
	 * It helps to put qtip in case if the item is disabled.
	 * @param {Zarafa.core.ui.menu.ConditionalItem} item context menu item
	 */
	onImportToFolderAfterRender: function(item)
	{
		var serverConfig = container.getServerConfig();
		var attachRecord = this.getRecords();
		if (!serverConfig.isVCfImportSupported() && attachRecord.isVCFAttachment()) {
			var tooltip = _('In order to use the vCard import feature, upgrade your Gromox to version 0 or higher.');
			this.setTooltipOnImportButton(this.getEl(), tooltip);
		} else if (!serverConfig.isICSImportSupported() && attachRecord.isICSAttachment()) {
			var tooltip = _('In order to use the ICS / VCS import feature, upgrade your Gromox to version 0 or higher.');
			this.setTooltipOnImportButton(this.getEl(), tooltip);
		}
	},

	/**
	 * Helper function to set the tooltip on import button.
	 *
	 * @param {Ext.Element} itemElement The Element which encapsulates this Component.
	 * @param {String} tooltip The tooltip which is going to show on import button.
	 */
	setTooltipOnImportButton(itemElement, tooltip)
	{
		itemElement.dom.setAttribute('ext:qtip', tooltip);
		itemElement.dom.setAttribute('ext:qwidth', 'ext:qwidth="100%"');
	},

	/**
	 * Event handler which is called when the user selects the 'Preview'
	 * item in the context menu. This will open the item in a new dialog.
	 * @private
	 */
	onPreviewItem: function()
	{
		//should already have a component that has won the bid
		//invoke that component to open the preview
		Zarafa.core.data.UIFactory.openViewRecord(this.records, {modal: true, autoResize: true});
	},

	/**
	 * Event handler which is called when the user selects the 'Download'
	 * item in the context menu. This will open the print dialog.
	 * @private
	 */
	onDownloadItem: function()
	{
		Zarafa.common.Actions.downloadAttachment(this.records);
	},

	/**
	 * Event handler which is called when the user selects the 'Download all as ZIP'
	 * item in the context menu.
	 * @private
	 */
	onDownloadAllAsZip: function()
	{
		Zarafa.common.Actions.downloadAttachment(this.records, true);
	},

	/**
	 * Event handler which is called when the user selects the 'Import to folder'
	 * item in the context menu.
	 * @private
	 */
	onImportToFolder: function()
	{
		Zarafa.common.Actions.importToFolder(this.records);
	}
});

Ext.reg('zarafa.attachmentcontextmenu', Zarafa.common.attachment.ui.AttachmentContextMenu);
Ext.namespace('Zarafa.common.attachment.ui');

/**
 * @class Zarafa.common.attachment.ui.AttachmentField
 * @extends Zarafa.common.ui.BoxField
 * @xtype zarafa.attachmentfield
 *
 * Extension to the default {@link Zarafa.common.ui.BoxField} which
 * handles attachments as for rendering. It uses a
 * {@link Zarafa.core.data.IPMAttachmentStore IPMAttachmentStore} for storing
 * and resolving {@link Zarafa.core.data.IPMAttachmentRecord Attachments}.
 *
 * If the {@link Zarafa.core.plugins.RecordComponentUpdaterPlugin} is installed
 * in the {@link #plugins} array of this component, this component will automatically
 * load the {@link Zarafa.core.data.IPMAttachmentStore AttachmentStore} into the component.
 * Otherwise the user of this component needs to call {@link #setAttachmentStore}.
 */
Zarafa.common.attachment.ui.AttachmentField = Ext.extend(Zarafa.common.ui.BoxField, {
	/**
	 * @constructor
	 * @param config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			boxType: 'zarafa.attachmentbox',
			enableComboBox: false,
			height: 25,
			boxMinHeight: 25,
			boxMaxHeight: 75
		});
		config.wrapCls = config.wrapCls ? config.wrapCls + ' zarafa-attachmentfield' : 'zarafa-attachmentfield';

		Zarafa.common.attachment.ui.AttachmentField.superclass.constructor.call(this, config);

		this.on('boxdblclick', this.onBoxDblClick, this);
		this.on('boxcontextmenu', this.onBoxContextMenu, this);
	},

	/**
	 * Called during rendering of the component
	 * @param {Ext.Container} ct The container in which the component is placed
	 * @param {Number} position The position of the component in the container
	 * @private
	 */
	onRender: function(ct, position)
	{
		Zarafa.common.attachment.ui.AttachmentField.superclass.onRender.apply(this, arguments);

		// Hide the input element, editing of attachment
		// is not possible through the input field.
		this.el.addClass('x-hidden');

		// Initialize Drag & Drop events from desktop
		this.wrap.on('dragover', this.onBrowserDragOver, this);
		this.wrap.on('dragleave', this.onBrowserDragLeave, this);
		this.wrap.on('drop', this.onBrowserDrop, this);
	},

	/**
	 * Event handler for the 'dragover' event which happens if the user drags a file
	 * from the desktop to over the {@link #wrap} element.
	 * @param {Ext.EventObject} event The event object
	 * @private
	 */
	onBrowserDragOver: function(event)
	{
		if(!this.editable) {
			// if field is non editable then we should block drag & drop also
			return;
		}

		event.stopPropagation();
		event.preventDefault();

		this.wrap.addClass(this.wrapFocusClass);
	},

	/**
	 * Event handler for the 'dragleave' event which happens if the user drags a file
	 * from the desktop out of the {@link #wrap} element.
	 * @param {Ext.EventObject} event The event object
	 * @private
	 */
	onBrowserDragLeave: function(event)
	{
		if(!this.editable) {
			// if field is non editable then we should block drag & drop also
			return;
		}

		event.stopPropagation();
		event.preventDefault();

		this.wrap.removeClass(this.wrapFocusClass);
	},

	/**
	 * Event handler for the 'drop' event which happens if the user drops a file
	 * from the desktop to the {@link #wrap} element.
	 * @param {Ext.EventObject} event The event object
	 * @private
	 */
	onBrowserDrop: function(event)
	{
		if(!this.editable) {
			// if field is non editable then we should block drag & drop also
			return;
		}

		event.stopPropagation();
		event.preventDefault();

		this.wrap.removeClass(this.wrapFocusClass);

		var files = event.browserEvent.target.files || event.browserEvent.dataTransfer.files;

		// Test if the files can be uploaded, upload them if possible
		if (this.boxStore.canUploadFiles(files, { container: this.recordComponentUpdaterPlugin.rootContainer.getEl() })) {
			this.boxStore.uploadFiles(files);
		}
	},

	/**
	 * Set the store on this field. See {@link #setBoxStore}.
	 * @param {Zarafa.core.data.IPMAttachmentStore} store The store to set on this field
	 */
	setAttachmentStore: function(store)
	{
		return this.setBoxStore.apply(this, arguments);
	},

	/**
	 * Get the store attached to this field. See {@link #getBoxStore}.
	 * @return {Zarafa.core.data.IPMAttachmentStore} store
	 */
	getAttachmentStore: function()
	{
		return this.getBoxStore();
	},

	/**
	 * Update the components with the given record.
	 *
	 * @param {Zarafa.core.data.MAPIRecord} record The record to update in this component
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 */
	update: function(record, contentReset)
	{
		if (record && record instanceof Zarafa.core.data.MAPIRecord) {
			// In case the recordcomponentupdaterplugin is installed
			// we have a special action to update the component.
			if (contentReset && record.isOpened()) {
				this.setAttachmentStore(record.getAttachmentStore());
			}
		} else {
			// The recordcomponentupdaterplugin is not installed and the
			// caller really wants to perform the update() function. Probably
			// a bad move, but lets not disappoint the caller.
			Zarafa.common.attachment.ui.AttachmentField.superclass.update.apply(this, arguments);
		}
	},

	/**
	 * Called by {@link #filterRecords} to check if the given record
	 * must be visible in the field or not. For the attachmentfield,
	 * all records are filtered by their 'hidden' property.
	 * @param {Ext.data.Record} record The record to filter
	 * @return {Boolean} True if the record should be visible, false otherwise
	 * @protected
	 */
	filterRecord: function(record)
	{
		return record.get('hidden') !== true;
	},

	/**
	 * Event handler when a Box has been double-clicked.
	 * @param {Zarafa.common.attachment.ui.AttachmentField} field This field to which the box belongs
	 * @param {Zarafa.common.attachment.ui.AttachmentBox} box The box for which was double-clicked
	 * @param {Zarafa.core.data.IPMAttachmentRecord} record The record which is attached to the box
	 * @private
	 */
	onBoxDblClick: function(field, box, record)
	{
		Zarafa.common.Actions.openAttachmentRecord(record);
	},

	/**
	 * Event handler when the contextmenu is requested for a Box.
	 * @param {Zarafa.common.attachment.ui.AttachmentField} field This field to which the box belongs
	 * @param {Zarafa.common.attachment.ui.AttachmentBox} box The box for which the contextmenu is requested
	 * @param {Zarafa.core.data.IPMAttachmentRecord} record The record which is attached to the box
	 * @private
	 */
	onBoxContextMenu: function(field, box, record)
	{
		Zarafa.core.data.UIFactory.openDefaultContextMenu(record, {
			position: box.getEl().getXY()
		});
	},

	/**
	 * Event handler for the {@link #boxremove} event. This will remove
	 * the record belonging to the box from the {@link #boxStore}.
	 * The same needs to be overridden to abort the upload process if attachment is
	 * still uploading.
	 * @param {Zarafa.common.ui.BoxField} field The field which has fired the event
	 * @param {Zarafa.common.ui.Box} box The box which has been removed
	 * @param {Ext.data.Record} record The record which belongs to the given box
	 * @private
	 */
	onBoxRemove: function(field, box, record)
	{
		// If uploading is going on then abort the request first.
		if (!record.isUploaded() && record.requestId) {
			var xhrObj = container.getRequest().getActiveRequest(record.requestId);
			var count = 0;

			// Need to manually count as request object isn't providing length
			xhrObj.requestData.forEach(function(key){
				count++;
			});

			// Don't abort when there is more than one attachments considering that other
			// attachments are still being uploaded.
			if (count === 1) {
				container.getRequest().abortRequest(xhrObj);
			}
		}

		Zarafa.common.attachment.ui.AttachmentField.superclass.onBoxRemove.apply(this, arguments);
	},

	/**
	 * Callback function from {@link Zarafa.common.attachment.ui.AttachmentBox} which indicates that
	 * the box is being removed by the user. This will fire the {@link #boxremove}
	 * event only if the given attachment is gets uploaded in case of non-IE/Edge browser.
	 * @param {Zarafa.common.ui.Box} box The box which called this function
	 */
	doBoxRemove: function(box)
	{
		if (box.record.isUploaded() || !(Ext.isIE || Ext.isEdge)) {
			Zarafa.common.attachment.ui.AttachmentField.superclass.doBoxRemove.apply(this, arguments);
		}
	}
});

Ext.reg('zarafa.attachmentfield', Zarafa.common.attachment.ui.AttachmentField);Ext.namespace('Zarafa.common.categories.dialogs');

/**
 * @class Zarafa.common.categories.dialogs.CategoriesContentPanel
 * @extends Zarafa.core.ui.ContentPanel
 * @xtype zarafa.categoriescontentpanel
 *
 * ContentPanel for users to edit the categories on a given {@link Zarafa.core.data.IPMRecord record}
 */
Zarafa.common.categories.dialogs.CategoriesContentPanel = Ext.extend(Zarafa.core.ui.ContentPanel, {
	/**
	 * @cfg {Zarafa.core.ui.IPMRecord[]} record The record(s) for which the
	 * categories must be configured
	 */
	record: undefined,

	/**
	 * @cfg {Boolean} autoSave Automatically save all changes on the
	 * {@link Zarafa.core.data.IPMRecord IPMRecord} to the
	 * {@link Zarafa.core.data.IPMStore IPMStore}.
	 */
	autoSave: true,

	/**
	 * If the record(s) to which this dialog's actions will be applied, are copied to the
	 * {@link Zarafa.core.data.ShadowStore}, this will be set to true, so we know that we
	 * must delete them again when the dialog is destroyed.
	 * @property
	 * @type {Boolean}
	 */
	recordsCopied: false,

	/**
	 * @cfg {Function} callback the callback function to be called after applying categories to
	 * record. callback function is not empty function if original selected record or records are
	 * belongs to {@link Zarafa.advancesearch.AdvanceSearchStore AdvanceSearchStore}
	 */
	callback: Ext.emptyFn,

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

		if ( Ext.isDefined(config.record) ){
			if ( !Array.isArray(config.record) ){
				config.record = [config.record];
			}

			// Create copies of the record(s) in the ShadowStore or they will lose the store when
			// the mail list refreshes. This is not necessary for records in the ShadowStore.
			if ( config.record[0].getStore() !== container.getShadowStore() ){
				config.record = config.record.map(function(record){
					var copy = record.copy();
					container.getShadowStore().add(copy);
					return copy;
				});
				this.recordsCopied = true;
			}
		}

		config = Ext.applyIf(config, {
			// Override from Ext.Component
			xtype: 'zarafa.categoriescontentpanel',
			layout: 'fit',
			title: _('Manage Categories'),
			width: 400,
			height: 400,
			items: [{
				xtype: 'zarafa.categoriespanel',
				record: config.record,
				ref: 'categoriesPanel',
				buttons: [{
					text: _('Apply'),
					handler: this.onApply,
					scope: this
				},{
					text: _('New'),
					handler: this.onNew,
					scope: this
				},{
					text: _('Cancel'),
					handler: this.onCancel,
					scope: this
				}]
			}],
			listeners: {
				destroy: this.onDestroyPanel,
				scope: this
			}
		});

		Zarafa.common.categories.dialogs.CategoriesContentPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Event handler which is raised when the user clicks the "Apply" {@link Ext.Button button}
	 *
	 * This will apply the current categories to all {@link Zarafa.core.data.IPMRecord records}
	 * and will close the panel.
	 * @private
	 */
	onApply: function()
	{
		var categories = this.categoriesPanel.getSelectedCategories();
		var categoryString = categories.join('; ');

		if (Ext.isEmpty(this.record)) {
			this.close();
			return;
		}

		Ext.each(this.record, function(record) {
			record.set('categories', categoryString);
			// Since the new implementation of categories, labels are deprecated and will
			// also be displayed as categories. So if a label was set, we will need to remove it
			// otherwise it will be added again.
			if (record.get('label')) {
				record.set('label', 0);
			}
		}, this);

		if (this.autoSave) {
			this.record[0].getStore().save(this.record);
		}

		// Also save the changes that the user made to the categories
		var grid = this.categoriesPanel.categoriesGrid;
		var store = grid.getStore();
		// Check if standard categories are selected and it is
		// first time used then mark those categories to used.
		categories.forEach(function(category){
			var categoryIndex = store.findExactCaseInsensitive('category', category);
			var categoryRecord = store.getAt(categoryIndex);
			if(!Ext.isEmpty(categoryRecord.get('standardIndex')) && !categoryRecord.get('used')) {
				categoryRecord.set('used', true);
			}
		}, this);
		store.save();

		// Update the categoriesStore in Zarafa.common.categories.Util to make sure
		// the rendering is up to date.
		Zarafa.common.categories.Util.loadCategoriesStore();

		// Update the currently loaded stores
		if ( this.autoSave ){
			Zarafa.common.categories.Util.updateStoresAfterCategoryUpdate();
		}

		if (Ext.isFunction(this.callback)) {
			this.callback.call(this.scope || this);
		}
		this.close();
	},

	/**
	 * Event handler which is raised when the user clicks the "Cancel" {@link Ext.Button button}
	 *
	 * This will close the panel without saving
	 * @private
	 */
	onCancel: function()
	{
		this.close();
	},

	/**
	 * Event handler which is raised when the user clicks the "New" {@link Ext.Button button}
	 *
	 * This will open the dialog to create a new category
	 * @private
	 */
	onNew: function()
	{
		Zarafa.common.Actions.openNewCategoryContent({
			store: this.categoriesPanel.categoriesGrid.getStore(),
			grid: this.categoriesPanel.categoriesGrid
		});
	},

	/**
	 * Event handler for the destroy event of the panel. Will remove the records that
	 * were created in the {@link Zarafa.core.data.ShadowStore}
	 */
	onDestroyPanel: function()
	{
		// If we didn't copy the records, then we don't have to remove them
		if ( !this.recordsCopied ){
			return;
		}

		var shadowStore = container.getShadowStore();

		// Suspend the events to not fire the destroy event
		shadowStore.suspendEvents();
		shadowStore.remove(this.record);
		shadowStore.resumeEvents();
	}
});

Ext.reg('zarafa.categoriescontentpanel', Zarafa.common.categories.dialogs.CategoriesContentPanel);
Ext.namespace('Zarafa.common.categories.dialogs');

/**
 * @class Zarafa.common.categories.dialogs.NewCategoryPanel
 * @extends Zarafa.core.ui.ContentPanel
 * @xtype zarafa.newcategorypanel
 *
 * This content panel is used to create new categories.
 */
Zarafa.common.categories.dialogs.NewCategoryPanel = Ext.extend(Zarafa.core.ui.ContentPanel, {
	/**
	 * @cfg {Zarafa.common.categories.data.CategoriesStore} When this dialog is created from a
	 * {@link Zarafa.common.categories.dialogs.CategoriesContentPanel} this property will contain
	 * the store of that panel.
	 */
	store: null,

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

		Ext.applyIf(config, {
			xtype: 'zarafa.newcategorypanel',
			title: _('Create New Category'),
			cls: 'k-newcategorypanel',
			width: 250,
			height: 120,
			layout: 'fit',
			items: [{
				xtype: 'form',
				border: false,
				ref: 'formPanel',
				items: [{
					xtype: 'zarafa.compositefield',
					hideLabel: true,
					items: [{
						xtype: 'zarafa.colorpicker',
						hideLabel: true,
						name: 'color',
						ref: '../color',
						border: false
					},{
						xtype: 'textfield',
						name: 'name',
						ref: '../name',
						fieldLabel: _('Category name'),
						hideLabel: true,
						anchor: '100%',
						flex: 1,
						enableKeyEvents: true,
						listeners: {
							specialkey: this.onSpecialKey,
							keydown: this.onKeyDown,
							scope: this
						}
					}]
				},{
					xtype: 'checkbox',
					name: 'pin',
					ref: 'pin',
					ctCls: 'k-pin-check',
					hideLabel: true,
					boxLabel: _('Pin to quick access')
				}],
				buttons: [{
					text: _('Create'),
					handler: this.onCreate,
					scope: this
				},{
					text: _('Cancel'),
					handler: this.onCancel,
					scope: this
				}]
			}]
		});

		Zarafa.common.categories.dialogs.NewCategoryPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Event handler for the specialkey event. Will check if the user pressed ENTER and if
	 * so call the event handler for the create button
	 *
	 * @param {Ext.form.TextField} field The textfield for the name of the new category
	 * @param {Ext.EventObject} event Object with event data
	 */
	onSpecialKey: function(field, event)
	{
		if ( event.getKey() === event.ENTER ){
			event.preventDefault();
			this.onCreate();
		}
	},

	/**
	 * Event handler for the keydown event. Will prevent the user pressing COMMA or SEMICOLON,
	 * because we will not allow those characters in category names.
	 *
	 * @param {Ext.form.TextField} field The textfield for the name of the new category
	 * @param {Ext.EventObject} event Object with event data
	 */
	onKeyDown: function(field, event)
	{
		var key = event.browserEvent.key;
		if ( key === ';' || key === ',' ){ // Don't allow ; or , in a category name
			event.preventDefault();
		}
	},

	/**
	 * Event handler for the "create" button. Will check if the category does not
	 * exist yet, and create and save the new category. Will also update the grid
	 * of the manage categories dialog.
	 */
	onCreate: function()
	{
		var categoryName = this.formPanel.name.getValue().trim();
		// Don't allow empty category names
		if ( Ext.isEmpty(categoryName) ){
			return;
		}

		if ( !this.canCreateNewCategory(categoryName) ){
			return;
		}

		var categoryStore = new Zarafa.common.categories.data.CategoriesStore();
		var categoryColor = this.formPanel.color.getValue();
		var quickAccess = this.formPanel.pin.getValue();

		// Add the new category to our temporary store and use it to save it.
		categoryStore.addCategory(categoryName, categoryColor, quickAccess);
		categoryStore.save();

		// Also add the new category to the store of the grid in the manage category dialog
		if ( Ext.isDefined(this.store) ){
			this.store.addCategory(categoryName, categoryColor, quickAccess);

			// scroll the new category into view, put the focus on it and select it
			var rowIndex = this.store.findExact('category', categoryName);
			this.grid.getView().focusRow(rowIndex);
			this.grid.getSelectionModel().selectRow(rowIndex, true);
		}

		// Update the categoriesStore in Zarafa.common.categories.Util to make sure
		// the rendering is up to date.
		Zarafa.common.categories.Util.loadCategoriesStore();

		this.close();
	},

	/**
	 * Event handler for the click event of the Cancel button. Closes the dialog.
	 */
	onCancel: function()
	{
		this.close();
	},

	/**
	 * Checks if we can create a new category with the given name. Will show an error message
	 * to the user if it is not possible.
	 * @param {String} categoryName The name of the category that we try to create
	 * @return {Boolean} True if the category can be created, false otherwise.
	 */
	canCreateNewCategory: function(categoryName)
	{
		var categoryStore = new Zarafa.common.categories.data.CategoriesStore();

		// Check if the category does not exist yet (case insensitive)
		var catIndex = categoryStore.findExactCaseInsensitive('category', categoryName);
		var catGridIndex = this.store.findExactCaseInsensitive('category', categoryName);
		var matchingCategory;

		var errorMessage;
		if ( catIndex !== -1 ){
			if ( catGridIndex === -1 ){
				// User deleted or renamed the category in the manage categories dialog
				errorMessage = _('You must apply the changes you have made to the categories before you can create this category.');
			} else {
				matchingCategory = categoryStore.getAt(catIndex);
				errorMessage = String.format(
					_("You already have a category named '{0}'. Please use another name."),
					Ext.util.Format.htmlEncode(matchingCategory.get('category'))
				);
			}
		} else if ( catGridIndex !== -1 ){
			matchingCategory = this.store.getAt(catGridIndex);
			errorMessage = String.format(
				_("You already have a category named '{0}' in your pending changes. Please use another name."),
				Ext.util.Format.htmlEncode(matchingCategory.get('category'))
			);
		}

		// If we have an error we will show a message to the user and will return without creating the new category
		if ( !Ext.isEmpty(errorMessage) ){
			// show error message
			this.showErrorMessage(errorMessage);
			return false;
		}

		return true;
	},

	/**
	 * Shows an error message to the user
	 * @param {String} msg The error message that will be shown
	 */
	showErrorMessage: function(msg)
	{
		// Make sure the MessageBox can get big enough
		var maxWidth = Zarafa.common.dialogs.MessageBox.maxWidth;
		Zarafa.common.dialogs.MessageBox.maxWidth = 350;

		Zarafa.common.dialogs.MessageBox.alert(
			_('Rename Category'),
			msg,
			function(){
				// Set the normal width of the MessageBox back
				Zarafa.common.dialogs.MessageBox.maxWidth = maxWidth;
				this.formPanel.name.focus();
			},
			this
		);
	}
});

Ext.reg('zarafa.newcategorypanel', Zarafa.common.categories.dialogs.NewCategoryPanel);
Ext.namespace('Zarafa.common.categories.dialogs');

/**
 * @class Zarafa.common.categories.dialogs.RenameCategoryPanel
 * @extends Zarafa.core.ui.ContentPanel
 * @xtype zarafa.renamecategorypanel
 *
 * This content panel is used to rename the categories.
 */
Zarafa.common.categories.dialogs.RenameCategoryPanel = Ext.extend(Zarafa.core.ui.ContentPanel, {
	/**
	 * @cfg {Zarafa.common.categories.data.CategoriesStore} store categories store which
	 * contains the categories. default is null.
	 */
	store: null,

	/**
	 * @cfg {Boolean} isCategoryGrid When this dialog is created from a
	 * {@link Zarafa.common.categories.dialogs.CategoriesContentPanel} this property will be true
	 * else false.
	 */
	isCategoryGrid: false,

	/**
	 * @cfg {String} color Default color which requires to show in
	 * {@link Zarafa.common.ui.ColorPicker ColorPicker} when
	 * this dialog is instantiated.
	 */
	color: undefined,

	/**
	 * @cfg {String} categoryName Default category name which requires to show in text field
	 * when this dialog is instantiated.
	 */
	categoryName: undefined,

	/**
	 * @cfg {Zarafa.core.data.IPMStore} recordStore store which
	 * contains the {@link Zarafa.core.data.MAPIRecord record} on which
	 * category is going to apply. default is undefined.
	 */
	recordStore: undefined,

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

		if (Ext.isEmpty(config.store)) {
			config.store = new Zarafa.common.categories.data.CategoriesStore();
		}

		Ext.applyIf(config, {
			xtype: 'zarafa.renamecategorypanel',
			title: _('Rename Category'),
			cls: 'k-renamecategorypanel',
			width: 369,
			height: 120,
			layout: 'fit',
			items: [{
				xtype: 'form',
				border: false,
				ref: 'formPanel',
				items: [{
					xtype: 'displayfield',
					html: this.getDisplayText(config.categoryName),
					htmlEncode: true,
					hideLabel: true
				},{
					xtype: 'zarafa.compositefield',
					hideLabel: true,
					items: [{
						xtype: 'zarafa.colorpicker',
						hideLabel: true,
						name: 'color',
						ref: '../color',
						border: false,
						value: config.color
					},{
						xtype: 'textfield',
						name: 'name',
						ref: '../name',
						fieldLabel: _('Category name'),
						hideLabel: true,
						anchor: '100%',
						selectOnFocus: true,
						flex: 1,
						value: config.categoryName,
						enableKeyEvents: true,
						listeners: {
							specialkey: this.onSpecialKey,
							keydown: this.onKeyDown,
							scope: this
						}
					}]
				}],
				buttons: [{
					text: _('Rename'),
					handler: this.onRename,
					scope: this
				},{
					text: _('No'),
					handler: this.onClickNo,
					scope: this
				}]
			}]
		});

		Zarafa.common.categories.dialogs.RenameCategoryPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Function which compose message for rename dialog.
	 *
	 * @param {String} category category name which is used in message composition.
	 * @returns {String} return message for the rename dialog.
	 */
	getDisplayText: function (category)
	{
		var message = String.format(_('This is the first time you have used "{0}" category.'), category);
		message += '<BR/>';
		message += _('Do you want to rename it?');

		return message;
	},

	/**
	 * Event handler for the specialkey event. Will check if the user pressed ENTER and if
	 * so call the event handler for the rename button
	 *
	 * @param {Ext.form.TextField} field The textfield for the name of the new category
	 * @param {Ext.EventObject} event Object with event data
	 */
	onSpecialKey: function(field, event)
	{
		if ( event.getKey() === event.ENTER ){
			event.preventDefault();
			this.onRename();
		}
	},

	/**
	 * Event handler for the keydown event. Will prevent the user pressing COMMA or SEMICOLON,
	 * because we will not allow those characters in category names.
	 *
	 * @param {Ext.form.TextField} field The textfield for the name of the new category
	 * @param {Ext.EventObject} event Object with event data
	 */
	onKeyDown: function(field, event)
	{
		var key = event.browserEvent.key;
		if ( key === ';' || key === ',' ){
			event.preventDefault();
		}
	},

	/**
	 * Function which used to find the categories from
	 * {@link Zarafa.common.categories.data.CategoriesStore CategoriesStore}.
	 *
	 * @param {String} categoryName The category name which used to find
	 * from {@link Zarafa.common.categories.data.CategoriesStore CategoriesStore}.
	 * @return {Object} return if found then return categories object else undefined.
	 */
	findCategory: function(categoryName) {
		var categoryIndex = this.store.findExactCaseInsensitive('category', categoryName);
		return this.store.getAt(categoryIndex);
	},

	/**
	 * Event handler for the "Rename" button. which rename selected
	 * category by calling {@link #doRenameCategory} and also it Will
	 * show the {@link #showWarningMessage message box} if category already exist yet.
	 */
	onRename: function()
	{
		var categoryName = this.formPanel.name.getValue().trim();
		// Don't allow empty category names
		if ( Ext.isEmpty(categoryName)){
			return;
		}
		// check that category already exist in category store.
		// if it is exist then show message box else
		// call doRenameCategory function to rename the category.
		var foundCategory = this.findCategory(categoryName);
		if(this.formPanel.name.isDirty() && foundCategory !== undefined) {
			var msg = String.format(_('The category "{0}" already exists. Do you want to merge the "{1}" and "{2}" categories?'),
				foundCategory.get('category'), this.categoryName, foundCategory.get('category'));
				this.showWarningMessage(msg);
		} else {
			var categoryRecord = this.findCategory(this.categoryName);
			this.doRenameCategory(categoryRecord, categoryName);
			this.close();
		}
	},

	/**
	 * Event handler for the click event of the No button. Closes the dialog.
	 */
	onClickNo: function()
	{
		var category = this.findCategory(this.categoryName);
		category.set('used', true);
		category.commit();
		this.store.save();
		Zarafa.common.categories.Util.addCategory(this.records, this.categoryName, true, this.recordStore);
		this.close();
	},

	/**
	 * Shows a warning message to the user.
	 * @param {String} msg The warning message that will be shown
	 */
	showWarningMessage: function(msg)
	{
		Zarafa.common.dialogs.MessageBox.addCustomButtons({
			title: _('Categories'),
			msg: msg,
			fn: function(button) {
				if (button === 'merge') {
					this.onMergeCategory();
				} else {
					this.formPanel.name.focus();
				}
			},
			customButton: [{
				text: _('Merge'),
				name: 'merge'
			}, {
				text: _('No'),
				name: 'no'
			}],
			scope: this
		});
	},

	/**
	 * Function which is used to merge the categories.
	 */
	onMergeCategory: function()
	{
		var newCategoryName = this.formPanel.name.getValue().trim();
		var category = this.findCategory(newCategoryName);
		this.store.remove(category);

		// If merging category is standard category then
		// save it into merged_categories object of persistent setting model.
		if(!Ext.isEmpty(category.get('standardIndex'))) {
			container.getPersistentSettingsModel().set('grommunio/main/merged_categories/'+category.get('standardIndex'), category.get('category'));
		}
		var categoryRecord = this.findCategory(this.categoryName);
		this.doRenameCategory(categoryRecord, category.get('category'));
		this.close();
	},

	/**
	 * Function which is used to rename the selected category and
	 * reload the {@link Zarafa.common.categories.data.CategoriesStore CategoriesStore}.
	 *
	 * @param {Ext.data.Record} categoryRecord record which is going to rename.
	 * @param {String} categoryName new name which is apply to selected category.
	 */
	doRenameCategory: function (categoryRecord, categoryName)
	{
		categoryRecord.set('category', categoryName);
		categoryRecord.set('used', true);
		if(this.formPanel.color.isDirty()) {
			categoryRecord.set('color', '#'+this.formPanel.color.getValue());
		}
		categoryRecord.commit();
		this.store.save();

		Zarafa.common.categories.Util.loadCategoriesStore();

		// Don't apply category on record as this dialog is called
		// from managed category dialog. As managed category dialog
		// apply category on record by click on apply button
		if(!this.isCategoryGrid) {
			Zarafa.common.categories.Util.addCategory(this.records, categoryName, true, this.recordStore);
		}
		Zarafa.common.categories.Util.updateStoresAfterCategoryUpdate();
	}
});

Ext.reg('zarafa.renamecategorypanel', Zarafa.common.categories.dialogs.RenameCategoryPanel);

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

/**
 * @class Zarafa.common.checknames.dialogs.CheckNamesContentPanel
 * @extends Zarafa.core.ui.ContentPanel
 * @xtype zarafa.checknamescontentpanel
 */
Zarafa.common.checknames.dialogs.CheckNamesContentPanel = Ext.extend(Zarafa.core.ui.ContentPanel, {
	/**
	 * @cfg {Array} array store
	 * array store of checknames, that will be populated in ListBox
	 */
	checkNamesData: undefined,

	/**
	 * @cfg {Zarafa.core.data.IPMRecipientRecord} record
	 * recipient record for which the content panel is to be created
	 */
	record: undefined,

	/**
	 * @constructor
	 * @param config Configuration structure
	 */
	constructor: function(config)
	{
		config = config || {};
		config = Ext.applyIf(config, {
			xtype: 'zarafa.checknamescontentpanel',
			layout: 'fit',
			title: _('Check Names'),
			border: false,
			width: 320,
			height: 300,
			items:[{
				xtype	: 'zarafa.checknamespanel',
				buttons: [{
					text	: _('Ok'),
					tabIndex: 0,
					handler	: this.onOk,
					scope	: this
					},{
					text	: _('Cancel'),
					tabIndex: 0,
					handler	: this.onCancel,
					scope	: this
				}]
			}]
		});

		Zarafa.common.checknames.dialogs.CheckNamesContentPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Called automatically when the contentpanel is being rendered. This
	 * will load the {@link #record} and {@link #checknamesData} into
	 * the {@link Zarafa.common.checknames.dialogs.CheckNamesPanel CheckNamesPanel}.
	 * @private
	 */
	onRender: function()
	{
		Zarafa.common.checknames.dialogs.CheckNamesContentPanel.superclass.onRender.apply(this, arguments);

		this.get(0).update(this.record, this.checkNamesData);

		// Add focus trapping for Tab navigation
		this.getEl().on('keydown', function(e) {
			if (e.getKey() === Ext.EventObject.TAB) {
				const focusableElements = this.getEl().query('button, [tabindex]:not([tabindex="-1"])');
				const firstElement = focusableElements[0];
				const lastElement = focusableElements[focusableElements.length - 1];

				if (e.shiftKey && e.target === firstElement) {
					e.stopEvent();
					lastElement.focus();
				} else if (!e.shiftKey && e.target === lastElement) {
					e.stopEvent();
					firstElement.focus();
				}
			}
		}, this);

	},

	/**
	 * event handler for Ok button click for checkNames content panel
	 * this will set the selected display name to recipient record
	 * @param {Zarafa.core.data.IPMRecipientRecord} recipientrecord
	 * @private
	 */
	onOk: function()
	{
		if (this.get(0).updateRecord(this.record) !== false) {
			this.dialog.close();
		}
	},

	/**
	 * event handler for Cancel button click for checkNames content panel
	 * this will close the checkNames content panel
	 * @private
	 */
	onCancel: function()
	{
		this.markRecipientToInvalid();
		this.dialog.close();
	},

	/**
	 * Function used to mark the recipient to invalid.
	 */
	markRecipientToInvalid: function()
	{
		this.record.resolveAttemptAmbiguous = false;
		this.record.resolveAttempted = true;
		this.record.afterEdit();
	},

	/**
	 * Function has been called when check name dialog closed by close button.
	 * It will call {@link #markRecipientToInvalid} to mark the recipient to invalid.
	 * @override
	 */
	close: function()
	{
		this.markRecipientToInvalid();
		Zarafa.common.checknames.dialogs.CheckNamesContentPanel.superclass.close.apply(this, arguments);
	}
});

Ext.reg('zarafa.checknamescontentpanel', Zarafa.common.checknames.dialogs.CheckNamesContentPanel);
Ext.namespace('Zarafa.common.data');

/**
 * @class Zarafa.common.data.DataModes
 * @extends Zarafa.core.Enum
 *
 * Enum containing the different data modes.
 *
 * @singleton
 */
Zarafa.common.data.DataModes = Zarafa.core.Enum.create({
	/**
	 * View all context items from the selected folder(s).
	 *
	 * @property
	 * @type Number
	 */
	ALL: 0,
	/**
	 * View all found context items in the selected folder(s).
	 *
	 * @property
	 * @type Number
	 */
	SEARCH: 1
});
Ext.namespace('Zarafa.common.data');

/**
 * @class Zarafa.common.data.Filters
 * @extends Zarafa.core.Enum
 *
 * Enum containing the different filters for the stores.
 *
 * @singleton
 */
Zarafa.common.data.Filters = Zarafa.core.Enum.create({
	/**
	 * UNREAD which used to filter all unread items from store.
	 *
	 * @property
	 * @type Number
	 */
	UNREAD: 0
});
Ext.namespace('Zarafa.common.data');

/**
 * @class Zarafa.common.data.FolderContentTypes
 * @extends Zarafa.core.Enum
 *
 * Enumerates all possibles content names that are content by any folder.
 *
 * @singleton
 */
Zarafa.common.data.FolderContentTypes = Zarafa.core.Enum.create({
	/**
	 * For calendar items
	 *
	 * @property
	 * @type String
	 */
	mail: 'IPF.NOTE',
	/**
	 * For calendar items
	 *
	 * @property
	 * @type String
	 */
	appointment: 'IPF.APPOINTMENT',
	/**
	 * For contact items
	 *
	 * @property
	 * @type String
	 */
	contact: 'IPF.CONTACT',
	/**
	 * For journal items
	 *
	 * @property
	 * @type String
	 */
	journal: 'IPF.JOURNAL',
	/**
	 * For notes
	 *
	 * @property
	 * @type String
	 */
	note: 'IPF.STICKYNOTE',
	/**
	 * For task items
	 *
	 * @property
	 * @type String
	 */
	task: 'IPF.TASK',

	/**
	 * For IPM subtree
	 *
	 * @property
	 * @type String
	 */
	ipmsubtree: 'IPM.SUBTREE',

	/**
	 * Return the display name for the given content type
	 * @param String container_class container class of the folder
	 * @return {String} The display name of content type
	 */
	getContentName: function(container_class)
	{
		switch (container_class.toUpperCase()) {
			case Zarafa.common.data.FolderContentTypes.mail:
				return _('Mail and Post');
			case Zarafa.common.data.FolderContentTypes.appointment:
				return _('Calendar');
			case Zarafa.common.data.FolderContentTypes.contact:
				return _('Contact');
			case Zarafa.common.data.FolderContentTypes.journal:
				return _('Journal');
			case Zarafa.common.data.FolderContentTypes.note:
				return _('Note');
			case Zarafa.common.data.FolderContentTypes.task:
				return _('Task');
			default:
				return container_class;
		}
	},

	/**
	 * Return the Folder name for the given content type
	 * @param String container_class container class of the folder
	 * @return {String} The display name of Folder
	 */
	getFolderName: function(container_class)
	{
		switch (container_class.toUpperCase()) {
			case Zarafa.common.data.FolderContentTypes.mail:
				return _('Inbox');
			case Zarafa.common.data.FolderContentTypes.appointment:
				return _('Calendar');
			case Zarafa.common.data.FolderContentTypes.contact:
				return _('Contact');
			case Zarafa.common.data.FolderContentTypes.note:
				return _('Notes');
			case Zarafa.common.data.FolderContentTypes.task:
				return _('Task');
			case Zarafa.common.data.FolderContentTypes.ipmsubtree:
				return _('Entire Inbox');
			default:
				return container_class;
		}
	}
});
Ext.namespace('Zarafa.common.data');

/**
 * @class Zarafa.common.data.RulesFactoryType
 * @extends Zarafa.core.Enum
 *
 * Enum containing the different rule factory types.
 *
 * @singleton
 */
Zarafa.common.data.RulesFactoryType = Zarafa.core.Enum.create({

	/**
	 * Type of Condition factory.
	 * @property
	 * @type Number
	 */
	CONDITION: 0,

	/**
	 * Type of Action factory.
	 * @property
	 * @type Number
	 */
	ACTION: 1
});
Ext.namespace('Zarafa.common.data');

/**
 * @class Zarafa.common.data.SizeUnits
 * @extends Zarafa.core.Enum
 *
 * Enum containing the different sizes.
 *
 * @singleton
 */
Zarafa.common.data.SizeUnits = Zarafa.core.Enum.create({
  /**
   * For size in MB
   *
   * @property
   * @type String
   */
   MB: 'MB',

  /**
   * For size in KB
   *
   * @property
   * @type String
   */
   KB: 'KB',

  /**
   * For size in Bytes
   *
   * @property
   * @type String
   */
   BYTES: 'Bytes'
});
Ext.namespace('Zarafa.common.data');

/**
 * @class Zarafa.common.data.TimeFormat
 * @extends Zarafa.core.Enum
 *
 * Enum containing the different filters for the stores.
 *
 * @singleton
 */
Zarafa.common.data.TimeFormat = Zarafa.core.Enum.create({
	/**
	 * TWELVEHOUR which used to for 'g:i A' format.
	 *
	 * @property
	 * @type Number
	 */
  TWELVEHOUR: 'g:i A',

  /**
	 * TWENTYFOURHOUR which used to for 'G:i' format.
	 *
	 * @property
	 * @type Number
	 */
  TWENTYFOURHOUR: 'G:i'
});Ext.namespace('Zarafa.common.data');

/**
 * @class Zarafa.common.data.ViewModes
 * @extends Zarafa.core.Enum
 *
 * Enum containing the different viewing modes.
 *
 * @singleton
 */
Zarafa.common.data.ViewModes = Zarafa.core.Enum.create({
	/**
	 * Don't show the preview panel
	 * @property
	 * @type Number
	 */
	NO_PREVIEW: 0,

	/**
	 * Show the preview panel to the right
	 * @property
	 * @type Number
	 */
	RIGHT_PREVIEW: 1,

	/**
	 * Show the preview panel in the bottom
	 * @property
	 * @type Number
	 */
	BOTTOM_PREVIEW: 2,

	/**
	 * Show the search results
	 * @property
	 * @type Number
	 */
	SEARCH: 3,

	/**
	 * Show the live scroll results
	 * @property
	 * @type Number
	 */
	LIVESCROLL: 4,

	/**
	 * Function was used to check that current view mode
	 * was one of the view mode from {@link #NO_PREVIEW},
	 * {@link #RIGHT_PREVIEW} and {@link #BOTTOM_PREVIEW}.
	 *
	 * @param {Zarafa.common.data.ViewModes} viewMode the viewMode which currently selected.
	 * @return return true if current view mode is {@link #NO_PREVIEW}, {@link #RIGHT_PREVIEW}
	 * and {@link #BOTTOM_PREVIEW} else return false.
	 * @trurn {Boolean} return the true if view mode is {@link #NO_PREVIEW},
	 * {@link #RIGHT_PREVIEW} and {@link #BOTTOM_PREVIEW} else return false
	 */
	isMainViewMode: function(viewMode)
	{
		return viewMode === this.NO_PREVIEW || viewMode === this.RIGHT_PREVIEW || viewMode === this.BOTTOM_PREVIEW;
	}
});
Ext.namespace('Zarafa.common.data');

/**
 * @class Zarafa.common.data
 * @extends Zarafa.core.Enum
 *
 * Enum containing the different views of the context.
 *
 * @singleton
 */
Zarafa.common.data.Views = Zarafa.core.Enum.create({
	/**
	 * View all context items from the selected folder(s) in the 'list' view.
	 *
	 * @property
	 * @type Number
	 */
	LIST: 0,

	/**
	 * View all found search items from the folder(s) in the 'list' view.
	 *
	 * @property
	 * @type Number
	 */
	SEARCH: 1,

	/**
	 * View all updated batch of context items from the selected folder(s) in the 'list' view.
	 *
	 * @property
	 * @type Number
	 */
	LIVESCROLL: 2
});
Ext.namespace('Zarafa.common.delegates.data');

/**
 * @class Zarafa.common.delegates.data.DelegateStore
 * @extends Zarafa.core.data.MAPIStore
 *
 * Delegate store that will be used to load delegates information from server.
 */
Zarafa.common.delegates.data.DelegateStore = Ext.extend(Zarafa.core.data.MAPIStore, {
	/**
	 * @cfg {String} actionType type of action that should be used to send request to server,
	 * valid action types are defined in {@link Zarafa.core.Actions Actions}, default value is 'list'.
	 */
	actionType: undefined,

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

		// Apply default settings.
		Ext.applyIf(config, {
			// load data whenever instance of store is created
			autoLoad: true,
			batch: false,

			actionType: Zarafa.core.Actions['list'],

			writer: new Zarafa.core.data.JsonWriter(),
			reader: new Zarafa.common.delegates.data.JsonDelegateReader(),

			proxy: new Zarafa.core.data.IPMProxy({
				listModuleName: Zarafa.core.ModuleNames.getListName('DELEGATES'),
				itemModuleName: Zarafa.core.ModuleNames.getItemName('DELEGATES')
			})
		});

		Zarafa.common.delegates.data.DelegateStore.superclass.constructor.call(this, config);
	},

	load: function(options)
	{
		if (!Ext.isObject(options)) {
			options = {};
		}

		if (!Ext.isObject(options.params)) {
			options.params = {};
		}

		// By default 'load' must cancel the previous request.
		if (!Ext.isDefined(options.cancelPreviousRequest)) {
			options.cancelPreviousRequest = true;
		}

		Ext.applyIf(options, {
			actionType: this.actionType
		});

		return Zarafa.common.delegates.data.DelegateStore.superclass.load.call(this, options);
	}
});
Ext.namespace('Zarafa.common.delegates.data');

/**
 * @class Zarafa.common.delegates.data.JsonDelegateReader
 * @extends Zarafa.core.data.JsonReader
 */
Zarafa.common.delegates.data.JsonDelegateReader = Ext.extend(Zarafa.core.data.JsonReader, {
	/**
	 * @constructor
	 * @param {Object} meta Metadata configuration options.
	 * @param {Object} recordType (optional) Optional Record type matches the type
	 * which must be read from response. If no type is given, it will use the
	 * record type for the {@link Zarafa.core.data.RecordCustomObjectType.ZARAFA_DELEGATE}.
	 */
	constructor: function(meta, recordType)
	{
		meta = meta || {};

		Ext.applyIf(meta, {
			id: 'entryid',
			idProperty: 'entryid',
			dynamicRecord: false
		});

		// If no recordType is provided, force the type to be a delegate
		if (!Ext.isDefined(recordType)) {
			recordType = Zarafa.core.data.RecordFactory.getRecordClassByCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_DELEGATE);
		}

		Zarafa.common.delegates.data.JsonDelegateReader.superclass.constructor.call(this, meta, recordType);
	}
});Ext.namespace('Zarafa.common.dialogs');

/**
 * @class Zarafa.common.dialogs.CopyMoveContentPanel
 * @extends Zarafa.core.ui.ContentPanel
 * @xtype zarafa.copymovecontentpanel
 *
 * This will display a {@link Zarafa.core.ui.ContentPanel contentpanel}
 * for copying or moving {@link Zarafa.core.data.MAPIRecord records}
 * to a different {@link Zarafa.hierarchy.data.MAPIFolderRecord folder}.
 */
Zarafa.common.dialogs.CopyMoveContentPanel = Ext.extend(Zarafa.core.ui.ContentPanel, {
	/**
	 * @cfg {Zarafa.core.data.MAPIRecord} record The record(s) which are being
	 * copied or moved through this panel
	 */
	record: undefined,

	/**
	 * The MAPIFolder which was selected the last time this panel was opened.
	 * This is used when {@link #stateful} is enabled.
	 * @property
	 * @type Zarafa.hierarchy.data.MAPIFolderRecord
	 */
	last_selected_folder: undefined,

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

		var title;
		var objectType;
		if (config.record) {
			if (!Array.isArray(config.record)) {
				config.record = [ config.record ];
			}

			objectType = config.record[0].get('object_type');
			switch (objectType) {
				case Zarafa.core.mapi.ObjectType.MAPI_FOLDER:
					title = _('Copy/Move Folder');
					break;
				case Zarafa.core.mapi.ObjectType.MAPI_MESSAGE:
				/* falls through*/
				default:
					title = _('Copy/Move Messages');
					break;
			}
		}

		Ext.applyIf(config, {
			// Override from Ext.Component
			xtype: 'zarafa.copymovecontentpanel',
			// We don't need the autofocus plugin since we want the focus on the
			// selected tree node.
			useInputAutoFocusPlugin: false,
			layout: 'fit',
			title: title,
			width: 400,
			height: 350,
			cls: 'copymove-panel',
			items: [{
				xtype: 'zarafa.copymovepanel',
				ref: 'copyMovePanel',
				record: config.record,
				objectType: objectType
			}]
		});

		Zarafa.common.dialogs.CopyMoveContentPanel.superclass.constructor.call(this, config);

		this.mon(this.copyMovePanel.hierarchyTree.getSelectionModel(), 'selectionchange', this.onSelectionChange, this);
	},

	/**
	 * Event listener for the selectionchange event of the hierarchy tree. Will
	 * make sure this panel knows which folder is selected.
	 * @param {Ext.tree.DefaultSelectionModel} selModel The selection model of the tree
	 * @param {Ext.tree.TreeNode} node The selected tree node
	 */
	onSelectionChange: function(selModel, node) {
		this.selectFolder(node.attributes.folder);
	},

	/**
	 * Obtain the {@link Zarafa.hierarchy.data.MAPIFolderRecord folder} which should be selected by
	 * default. This will use {@link #last_selected_folder} if provided, otherwise will use the
	 * {@link #record} to obtain the corresponding default folder from the
	 * {@link Zarafa.core.Container#getHierarchyStore hierarchy}.
	 * @return {Zarafa.hierarchy.data.MAPIFolderRecord} The default selected folder
	 */
	getSelectedFolder: function()
	{
		if (!this.last_selected_folder && !Ext.isEmpty(this.record)) {
			var hierarchy = container.getHierarchyStore();
			var record = this.record[0];

			if (record.get('container_class')) {
				this.last_selected_folder = hierarchy.getDefaultFolderFromContainerClass(record.get('container_class'));
			} else if (record.get('message_class')) {
				this.last_selected_folder = hierarchy.getDefaultFolderFromMessageClass(record.get('message_class'));
			}
		}

		return this.last_selected_folder;
	},

	/**
	 * Mark the given folder as selected, this will update {@link #last_selected_folder}
	 * and will call {@link #saveState} if this panel is {@link #stateful}.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder The selected folder
	 */
	selectFolder: function(folder)
	{
		this.last_selected_folder = folder;
		if (this.stateful !== false) {
			this.saveState();
		}
	},

	/**
	 * When {@link #stateful} the State object which should be saved into the
	 * {@link Ext.state.Manager}.
	 * @return {Object} The state object
	 * @protected
	 */
	getState: function()
	{
		var state = Zarafa.common.dialogs.CopyMoveContentPanel.superclass.getState.call(this);

		if (this.last_selected_folder) {
			state.last_selected_folder = this.last_selected_folder.get('entryid');
		}

		return state;
	},

	/**
	 * Apply the given state to this object activating the properties which were previously
	 * saved in {@link Ext.state.Manager}.
	 * @param {Object} state The state object
	 * @protected
	 */
	applyState: function(state)
	{
		if (state && state.last_selected_folder) {
			this.last_selected_folder = container.getHierarchyStore().getFolder(state.last_selected_folder);
			delete state.last_selected_folder;
		}

		Zarafa.common.dialogs.CopyMoveContentPanel.superclass.applyState.call(this, state);
	}
});

Ext.reg('zarafa.copymovecontentpanel', Zarafa.common.dialogs.CopyMoveContentPanel);
/*
 * #dependsFile client/zarafa/core/KeyMapMgr.js
 */
Ext.namespace('Zarafa.common.dialogs');

/**
 * @class Zarafa.common.dialogs.KeyMapping
 * @extends Object
 *
 * The map of keys used in the Copy/Move Panel.
 * @singleton
 */
Zarafa.common.dialogs.CopyMovePanelKeyMapping = Ext.extend(Object, {
	/**
	 * @constructor
	 */
	constructor: function()
	{
		var keys = [{
			key: Ext.EventObject.ENTER,
			ctrl: false,
			alt: false,
			shift: false,
			stopEvent: true,
			handler: this.onMoveItems,
			enableGlobally: true,
			scope: this,
			basic: true
		}];

		Zarafa.core.KeyMapMgr.register('Zarafa.common.dialogs.CopyMovePanel', keys);
	},

	/**
	 * Event handler for the keydown event of the {@link Zarafa.core.KeyMap KeyMap} when the user wants to 
	 * move items.
	 * @param {Number} key Key code
	 * @param {Ext.EventObject} event The event
	 * @param {Zarafa.common.dialogs.CopyMovePanel} copyMovePanel The panel on which the key event is fired.
	 */
	onMoveItems: function(key, event, copyMovePanel)
	{
		var folder = copyMovePanel.hierarchyTree.getSelectionModel().getSelectedNode().getFolder();
		copyMovePanel.onMove(copyMovePanel.record, folder);
	}
});

Zarafa.common.dialogs.CopyMovePanelKeyMapping = new Zarafa.common.dialogs.CopyMovePanelKeyMapping();
Ext.namespace('Zarafa.common.export');

/**
 * @class Zarafa.common.export.ExportItemContextMenu
 * @extends Zarafa.core.ui.menu.ConditionalMenu
 * @xtype zarafa.exportitemcontextmenu
 *
 * The ExportContactContextMenu is the context menu that is shown as submenu
 * of the {@link Zarafa.core.ui.menu.ConditionalMenu ConditionalMenu}.
 */
Zarafa.common.export.ExportItemContextMenu = Ext.extend(Zarafa.core.ui.menu.ConditionalMenu, {
	/**
	 * @cfg {Zarafa.core.data.IPMRecord[]} The records to which the actions in
	 * this context menu will apply
	 */
	records: [],

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

		Ext.applyIf(config, {
			xtype: 'zarafa.exportitemcontextmenu',
			cls: 'k-categories',
			items: [{
				xtype: 'zarafa.conditionalitem',
				text: _('ICS'),
				iconCls: 'icon_saveaseml',
				hideOnDisabled: false,
				beforeShow: this.onExportMenuBeforeShow,
				handler: this.onContextItemICS,
				scope: this
			}]
		});

		Zarafa.common.export.ExportItemContextMenu.superclass.constructor.call(this, config);
	},

	/**
	 * Event handler which is called when the user selects the 'ICS'
	 * item in the context menu. This will request to download selected calendar item
	 * as file (RFC2445-formatted ics stream) with ics extension.
	 * @private
	 */
	onContextItemICS: function ()
	{
		Zarafa.common.Actions.openSaveEmlDialog(this.records, true);
	},

	/**
	 * Event handler which determines if menu items should be disable or not.
	 * It will disable respective menu item if record is not appointment.
	 *
	 * @param {Zarafa.core.ui.menu.ConditionalItem} item The item to enable/disable
	 * @param {Zarafa.core.data.IPMRecord[]} records The records which must be checked
	 * to see if the item must be enabled or disabled.
	 * @private
	 */
	onExportMenuBeforeShow: function (item, records)
	{
		item.setDisabled(!Ext.isEmpty(records) && records.some(function(record) {
				return !record.isMessageClass('IPM.appointment');
			})
		);
	}
});

Ext.reg('zarafa.exportitemcontextmenu', Zarafa.common.export.ExportItemContextMenu);
Ext.namespace('Zarafa.common.flags.dialogs');

/**
 * @class Zarafa.common.flags.dialogs.CustomFlagContentPanel
 * @extends Zarafa.core.ui.ContentPanel
 * @xtype zarafa.customflagcontentpanel
 *
 * Content panel for users for setting the custom flag and reminder on a {@link Zarafa.core.data.IPMRecord record}
 */
Zarafa.common.flags.dialogs.CustomFlagContentPanel = Ext.extend(Zarafa.core.ui.ContentPanel, {
	/**
	 * @cfg {Boolean} setFocusOnReminder True to set the focus on
	 * reminder checkbox.
	 */
	setFocusOnReminder: false,

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

		// Add the records to the shadow store, because otherwise we cannot
		// save them when the mail grid refreshes while we have this Content Panel open.
		var shadowStore = container.getShadowStore();
		config.record = config.record.map(function(record){
			record = record.copy();
			shadowStore.add(record);
			return record;
		});

		config = Ext.applyIf(config, {
			xtype: 'zarafa.customflagcontentpanel',
			layout: 'fit',
			title: _('Set custom flag'),
			width: 350,
			height: 160,
			items: [{
				xtype: 'zarafa.customflagpanel',
				ref:'customFlagPanel',
				records: config.record,
				buttons: [{
					text: _('Ok'),
					handler: this.onOk,
					scope: this
				},{
					text: _('Cancel'),
					handler: this.close,
					scope: this
				}]
			}],
			listeners: {
				afterrender: function () {
					if (this.setFocusOnReminder) {
						this.inputAutoFocusPlugin.setAutoFocus(this.customFlagPanel.reminderCheckbox);
					}
				},
				scope: this
			}
		});

		Zarafa.common.flags.dialogs.CustomFlagContentPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Event handler which is called when ok button is press. it will
	 * save the records.
	 */
	onOk: function ()
	{
		const flagProperties = Zarafa.common.flags.Util.getFlagBaseProperties();
		delete flagProperties.reminder;

		var dateRange = this.customFlagPanel.dateField.getValue();
		if(!Ext.isDate(dateRange.getStartDate()) || !Ext.isDate(dateRange.getDueDate())) {
			Ext.apply(flagProperties, {
				startdate: null,
				duedate: null
			});
			if(!this.record[0].get('reminder')) {
				Ext.apply(flagProperties, {
					reminder:		false,
					reminder_time:		null,
					flag_due_by:		null
				});
			}
		}

		this.record.forEach(function (record) {
			record.beginEdit();
			for ( var property in flagProperties ){
				record.set(property, flagProperties[property]);
			}
			record.endEdit();
			record.save();
		}, this);
		this.close();
	},

	/**
	 * Event handler for the destroy event of the component. Will remove the records that
	 * were copied from the shadow store.
	 */
	onDestroy: function()
	{
		container.getShadowStore().remove(this.record);
	}
});
Ext.reg('zarafa.customflagcontentpanel', Zarafa.common.flags.dialogs.CustomFlagContentPanel);
Ext.namespace('Zarafa.common.flags.ui');

/**
 * @class Zarafa.common.flags.ui.FlagsMenu
 * @extends Zarafa.core.ui.menu.ConditionalMenu
 * @xtype zarafa.flagsmenu
 *
 * The FlagsMenu is the menu that is shown for flags.
 */
Zarafa.common.flags.ui.FlagsMenu = Ext.extend(Zarafa.core.ui.menu.ConditionalMenu, {
	/**
	 * @cfg {Zarafa.core.data.IPMRecord[]} The records to which the actions in
	 * this menu will apply
	 */
	records: [],

	/**
	 * @cfg {Boolean} shadowEdit True to add {@link #records} into
	 * {@link Zarafa.core.data.ShadowStore}.
	 */
	shadowEdit: true,

	/**
	 * @cfg {Zarafa.core.data.IPMStore} store which contains
	 * records on which flag will be apply.
	 */
	store: null,

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

		if ( !Array.isArray(config.records) ){
			config.records = [config.records];
		}

		if (config.shadowEdit !== false) {
			// Add the records to the shadow store, because otherwise we cannot
			// save them when the mail grid refreshes while we have this context
			// menu open.
			var shadowStore = container.getShadowStore();
			config.records = config.records.map(function(record){
				record = record.copy();
				shadowStore.add(record);
				return record;
			});
		}

		Ext.applyIf(config, {
			xtype: 'zarafa.flagsmenu',
			cls: 'k-flags-menu',
			items: this.createMenuItems(),
			listeners: {
				scope: this,
				destroy: this.onDestroy
			}
		});

		Zarafa.common.flags.ui.FlagsMenu.superclass.constructor.call(this, config);
	},

	/**
	 * Create the menu items that should be shown in the flags menu
	 * @return {Zarafa.core.ui.menu.ConditionalItem[]} The list of menu items of
	 * the flags menu
	 * @private
	 */
	createMenuItems: function()
	{
		return [{
			text: _('Today'),
			iconCls: 'icon_flag_red',
			action: 'today',
			handler: this.setFlag,
			scope: this
		}, {
			text: _('Tomorrow'),
			iconCls: 'icon_flag_orange_dark',
			action: 'tomorrow',
			handler: this.setFlag,
			scope: this
		}, {
			text: _('This week'),
			iconCls: 'icon_flag_orange',
			action: 'this_week',
			handler: this.setFlag,
			scope: this
		}, {
			text: _('Next week'),
			iconCls: 'icon_flag_yellow',
			action: 'next_week',
			handler: this.setFlag,
			scope: this
		}, {
			text: _('No date'),
			iconCls: 'icon_flag_red',
			action: 'no_date',
			handler: this.setFlag,
			scope: this
		},{
			xtype: 'zarafa.conditionalitem',
			text: _('Custom'),
			iconCls: 'icon_flag_red',
			beforeShow: this.onFollowUpItemBeforeShow,
			action: 'custom',
			handler: this.onSetCustomFlag,
			scope: this
		}, {
			xtype: 'menuseparator'
		}, {
			xtype: 'zarafa.conditionalitem',
			text: _('Set reminder'),
			iconCls: 'icon_flag_Reminder',
			action: 'set_reminder',
			beforeShow: this.onFollowUpItemBeforeShow,
			handler: this.onSetCustomFlag,
			scope: this
		}, {
			text: _('Complete'),
			iconCls: 'icon_flag_complete',
			action: 'complete',
			handler: this.setFlag,
			scope: this
		}, {
			xtype: 'zarafa.conditionalitem',
			text: _('None'),
			action: 'none',
			beforeShow: this.onFollowUpItemBeforeShow,
			iconCls: 'icon_flag',
			handler: this.setFlag,
			scope: this
		}];
	},

	/**
	 * Event handler triggers before the item shows. If selected
	 * record is task record then don't show 'Set reminder', 'Custom' and 'None'
	 * options in flags context menu.
	 *
	 * @param {Zarafa.core.ui.menu.ConditionalItem} item The item to enable/disable
	 * @param {Zarafa.core.data.IPMRecord[]} records The records which must be checked
	 * to see if the item must be enabled or disabled.
	 * @private
	 */
	onFollowUpItemBeforeShow: function (item, records)
	{
		if(!Array.isArray(records)) {
			records = [records];
		}
		var hasTaskRecord = records.some(function (record) {
			return record.isMessageClass('IPM.Task');
		});
		item.setDisabled(hasTaskRecord);
	},

	/**
	 * Event handler for the destroy event of the component. Will remove the records that
	 * were copied from the shadow store.
	 */
	onDestroy: function()
	{
		if (this.shadowEdit !== false) {
			container.getShadowStore().remove(this.record);
		}
	},

	/**
	 * Event handler which is triggered when either custom
	 * or set reminder flag context menu item was clicked.
	 * @param {Ext.menu.Item} menuItem The menu item that was clicked
	 */
	onSetCustomFlag: function (menuItem)
	{
		Zarafa.common.Actions.openCustomFlagContent(this.records,{
			setFocusOnReminder: menuItem.action === 'set_reminder'
		});
	},

	/**
	 * Event handler for the click event of the items in the flag menu. Will set the required properties
	 * on the selected records.
	 *
	 * @param {Zarafa.core.ui.menu.ConditionalItem} menuItem The menu item that was clicked
	 */
	setFlag: function(menuItem)
	{
		// If there was an old-style flag, we must set the category before changing the flag
		Zarafa.common.flags.Util.updateCategories(this.records);

		const flagProperties = Zarafa.common.flags.Util.getFlagBaseProperties();

		switch ( menuItem.action ) {
			case 'no_date':
				Ext.apply(flagProperties, Zarafa.common.flags.Util.getFlagPropertiesNoDate());
				break;
			case 'today':
				Ext.apply(flagProperties, Zarafa.common.flags.Util.getFlagPropertiesToday());
				break;
			case 'tomorrow':
				Ext.apply(flagProperties, Zarafa.common.flags.Util.getFlagPropertiesTomorrow());
				break;
			case 'this_week':
				Ext.apply(flagProperties, Zarafa.common.flags.Util.getFlagPropertiesThisWeek());
				break;
			case 'next_week':
				Ext.apply(flagProperties, Zarafa.common.flags.Util.getFlagPropertiesNextWeek());
				break;
			case 'complete':
				Ext.apply(flagProperties, Zarafa.common.flags.Util.getFlagPropertiesComplete());
				break;
			case 'none':
				Ext.apply(flagProperties, Zarafa.common.flags.Util.getFlagPropertiesRemoveFlag());
				break;
		}

		// Now set the properties an all selected records
		this.setFlagProperties(this.records, flagProperties);
	},

	/**
	 * Set necessary flag related properties into given record(s).
	 *
	 * @param {Zarafa.core.data.IPMRecord} records The record(s) for which configured flag needs to be identified.
	 * @param {Object} flagProperties Necessary flag properties
	 */
	setFlagProperties: function(records, flagProperties)
	{
		records.forEach(function(record){
			record.beginEdit();
			for ( var property in flagProperties ){
				record.set(property, flagProperties[property]);
			}
			record.endEdit();
			record.save();
		}, this);
	}
});

Ext.reg('zarafa.flagsmenu', Zarafa.common.flags.ui.FlagsMenu);
Ext.namespace('Zarafa.common.freebusy.data');

/**
 * @class Zarafa.common.freebusy.data.FreebusyProxy
 * @extends Zarafa.core.data.MAPIProxy
 */
Zarafa.common.freebusy.data.FreebusyProxy = Ext.extend(Zarafa.core.data.MAPIProxy,
{
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			listModuleName: Zarafa.core.ModuleNames.getListName('freebusy')
		});

		Zarafa.common.freebusy.data.FreebusyProxy.superclass.constructor.call(this, config);
	},

	/**
	 * This will create a {@link Zarafa.core.data.ProxyResponseHandler ProxyResponseHandler} object
	 * which will be used by the {@link Zarafa.core.data.ResponseRouter ResponseRouter} when the
	 * response for the given request has returned.
	 *
	 * @param {String} modulename The modulename which is being accessed with this request
	 * @param {Zarafa.core.Actions} serverAction The action to perform on the server.
	 * @param {Ext.data.Api.action} action name of the action to perform.
	 * @param {Ext.data.Record[]} records list of records to operate on.
	 * @param {Object} parameters object containing user parameters such as range (pagination) information, sorting information, etc.
	 * @param {Ext.data.DataReader} reader data reader. Converts raw JavaScript objects (in our case) to instances of {@link Ext.data.Record}
	 * @param {Function} callback call back function to call when the request has finished successfully.
	 * @param {Object} scope scope for the call back function.
	 * @param {Object} args arguments object. This will be passed to the call back function on successful read.
	 * @return {Object} An instance of the {@link Zarafa.core.data.ProxyResponseHandler ProxyResponseHandler}
	 * which should be used for this request.
	 * @private
	 */
	getResponseHandlerForRequest: function(modulename, serverAction, action, records, parameters, reader, callback, scope, args)
	{
		return new Zarafa.common.freebusy.data.FreebusyResponseHandler({
			proxy: this,
			action: action,
			reader: reader,
			options: args,
			callback: callback,
			scope: scope
		});
	}
});
Ext.namespace('Zarafa.common.freebusy.ui');

/**
 * @class Zarafa.common.freebusy.ui.FreebusyContextMenu
 * @extends Zarafa.core.ui.menu.ConditionalMenu
 * @xtype zarafa.freebusycontextmenu
 *
 * Context menu special for changing the attendee status
 */
Zarafa.common.freebusy.ui.FreebusyContextMenu = Ext.extend(Zarafa.core.ui.menu.ConditionalMenu, {
	// Insertion points for this class
	/**
	 * @insert context.freebusy.userlist.contextmenu
	 * Insertion point for adding items to the context menu that is triggered when right-clicking a user row.
	 * @param {Zarafa.common.freebusy.ui.FreebusyContextMenu} contextmenu This contextmenu
	 */

	/**
	 * @cfg {Boolean} editable Allow the record to be edited
	 */
	editable: true,

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

		var editable = Ext.isDefined(config.editable) ? config.editable : this.editable;
		var organizer = false;
		var resolved = false;
		if (config.records) {
			if (Array.isArray(config.records)) {
				config.records = config.records[0];
			}
			organizer = config.records.isMeetingOrganizer();
			resolved = config.records.isResolved();
		}

		Ext.applyIf(config, {
			items: [{
				xtype: 'zarafa.conditionalitem',
				text: _('Edit recipient'),
				hidden: !editable || resolved,
				handler: this.onContextMenuEditRecipientClick,
				scope: this
			},{
				xtype: 'zarafa.conditionalitem',
				text: _('Show Details'),
				hidden: !resolved,
				handler: this.openDetailsContent,
				scope: this
			},{
				xtype: 'zarafa.conditionalitem',
				text: _('Copy email address'),
				iconCls: 'icon_copy',
				hidden: !resolved,
				handler: this.copyEmail,
				scope: this
			},'-',{
				xtype: 'zarafa.conditionalitem',
				text: _('Set as required'),
				hidden: !editable || organizer,
				recipientType: Zarafa.core.mapi.RecipientType.MAPI_TO,
				handler: this.onRecipientTypeChange,
				scope: this
			},{
				xtype: 'zarafa.conditionalitem',
				text: _('Set as optional'),
				hidden: !editable || organizer,
				recipientType: Zarafa.core.mapi.RecipientType.MAPI_CC,
				handler: this.onRecipientTypeChange,
				scope: this
			},{
				xtype: 'zarafa.conditionalitem',
				text: _('Set as resource'),
				hidden: !editable || organizer,
				recipientType: Zarafa.core.mapi.RecipientType.MAPI_BCC,
				handler: this.onRecipientTypeChange,
				scope: this
			},
			// Add insertion points
			container.populateInsertionPoint('context.freebusy.userlist.contextmenu', this)
			]
		});

		Zarafa.common.freebusy.ui.FreebusyContextMenu.superclass.constructor.call(this, config);
	},

	/**
	 * Handler for the "Edit Recipient" option. This will open the edit content panel.
	 * @param {Zarafa.core.ui.ConditionalItem} item The item that was selected
	 * @private
	 */
	onContextMenuEditRecipientClick: function(item)
	{
		Zarafa.core.data.UIFactory.openCreateRecord(this.records);
	},

	/**
	 * Handler for the "Show Details" option. This will convert
	 * the {@link Zarafa.core.dat.IPMRecipientRecord recipient}
	 * into a {@link Zarafa.core.data.MAPIRecord addressbook record}
	 * which can be opened in the Addressbook details content panel.
	 *
	 * @private
	 */
	openDetailsContent: function()
	{
		Zarafa.common.Actions.openViewRecipientContent(this.records);
	},

	/**
	 * Called when selected on of the options set in the recipient type contextmenu.
	 * @param {Zarafa.core.ui.ConditionalItem} item The item that was selected
	 * @private
	 */
	onRecipientTypeChange: function(item)
	{
		this.records.set('recipient_type', item.recipientType);
	},

	/**
	 * Handler for the "Copy email address" option. This will
	 * copy email address of the resolved recipient.
	 */
	copyEmail: function ()
	{
		Zarafa.common.Actions.copyEmailAddress(this.records);
	}
});

Ext.reg('zarafa.freebusycontextmenu', Zarafa.common.freebusy.ui.FreebusyContextMenu);
Ext.namespace('Zarafa.common.freebusy.ui');

/**
 * @class Zarafa.common.freebusy.ui.FreebusyTimelineBodyContextMenu
 * @extends Zarafa.core.ui.menu.ConditionalMenu
 * @xtype zarafa.freebusytimelinebodycontextmenu
 */
Zarafa.common.freebusy.ui.FreebusyTimelineBodyContextMenu = Ext.extend(Zarafa.core.ui.menu.ConditionalMenu, {
	// Insertion points for this class
	/**
	 * @insert context.freebusy.timelinebody.contextmenu
	 * Insertion point for adding items to the contextmenu that is triggered when right-clicking on the body of the timeline.
	 * @param {Zarafa.common.freebusy.ui.FreebusyTimelineBodyContextMenu} contextmenu This contextmenu
	 */

	/**
	 * @cfg {Zarafa.common.freebusy.data.FreebusyModel} model The model which holds all freebusy information.
	 */
	model: undefined,

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

		Ext.applyIf(config, {
			items: container.populateInsertionPoint('context.freebusy.timelinebody.contextmenu', this)
		});

		Zarafa.common.freebusy.ui.FreebusyTimelineBodyContextMenu.superclass.constructor.call(this, config);
	}
});

Ext.reg('zarafa.freebusytimelinebodycontextmenu', Zarafa.common.freebusy.ui.FreebusyTimelineBodyContextMenu);
Ext.namespace('Zarafa.common.freebusy.ui');

/**
 * @class Zarafa.common.freebusy.ui.FreebusyTimelineHeaderContextMenu
 * @extends Zarafa.core.ui.menu.ConditionalMenu
 * @xtype zarafa.freebusytimelineheadercontextmenu
 */
Zarafa.common.freebusy.ui.FreebusyTimelineHeaderContextMenu = Ext.extend(Zarafa.core.ui.menu.ConditionalMenu, {
	// Insertion points for this class
	/**
	 * @insert context.freebusy.timelineheader.contextmenu
	 * Insertion point for adding items to the contextmenu that is triggered when right-clicking on the header of the timeline.
	 * @param {Zarafa.common.freebusy.ui.FreebusyTimelineHeaderContextMenu} contextmenu This contextmenu
	 */

	/**
	 * @cfg {Zarafa.common.freebusy.data.FreebusyModel} model The model which holds all freebusy information.
	 */
	model: undefined,

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

		Ext.applyIf(config, {
			items: [{
				xtype: 'zarafa.conditionalitem',
				text: _('Show non-working hours'),
				showNonWorkingHours: true,
				hidden: config.model ? config.model.showOnlyWorkingHours() === false : false,
				handler: this.onHeaderContextMenuToggleNonWorkingHours,
				scope: this
			},{
				xtype: 'zarafa.conditionalitem',
				text: _('Hide non-working hours'),
				showNonWorkingHours: false,
				hidden: config.model ? config.model.showOnlyWorkingHours() === true : false,
				handler: this.onHeaderContextMenuToggleNonWorkingHours,
				scope: this
			},
			container.populateInsertionPoint('context.freebusy.timelineheader.contextmenu', this)
			]
		});

		Zarafa.common.freebusy.ui.FreebusyTimelineHeaderContextMenu.superclass.constructor.call(this, config);
	},

	/**
	 * Called when the context menuitem "show/hide non-working hours" in the timeline is used.
	 * @param {Zarafa.core.ui.menu.ConditionalItem} item The item which was clicked
	 * @private
	 */
	onHeaderContextMenuToggleNonWorkingHours: function(item)
	{
		this.model.hideNonWorkingHours(!item.showNonWorkingHours);
	}
});

Ext.reg('zarafa.freebusytimelineheadercontextmenu', Zarafa.common.freebusy.ui.FreebusyTimelineHeaderContextMenu);
Ext.namespace('Zarafa.common.manageCc.ui');

/**
 * @class Zarafa.common.manageCc.ui.ManageCcGridContextMenu
 * @extends Zarafa.core.ui.menu.ConditionalMenu
 * @xtype zarafa.manageccgridcontextmenu
 *
 * Context menu provides the buttons to enable/disable the Cc recipients in new or reply mails.
 */
Zarafa.common.manageCc.ui.ManageCcGridContextMenu = Ext.extend(Zarafa.core.ui.menu.ConditionalMenu, {
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor : function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			items: [{
				xtype: 'zarafa.conditionalitem',
				iconCls : 'icon_new_email',
				name : 'new_mail',
				beforeShow: this.onBeforeShowMenuItem,
				handler : this.onClickMenuItem,
				scope: this
			},{
				xtype: 'zarafa.conditionalitem',
				name : 'reply_mail',
				iconCls : 'icon_mail_replied',
				beforeShow: this.onBeforeShowMenuItem,
				handler : this.onClickMenuItem,
				scope: this
			}]
		});

		Zarafa.common.manageCc.ui.ManageCcGridContextMenu.superclass.constructor.call(this, config);
	},

	/**
	 * Event handler which update the text for the menu item if
	 *
	 * @param {Zarafa.core.ui.menu.ConditionalItem} item The menu item text needs to update.
	 * @param {Zarafa.common.manageCc.data.IPMCcRecipientRecord} record The record which is selected.
	 */
	onBeforeShowMenuItem : function(item, record)
	{
		var menuItemText;
		if (item.name === 'new_mail') {
			menuItemText = record.get(item.name) ?  _('Disable for new mail') : _('Enable for new mail');
		} else {
			menuItemText = record.get(item.name) ?  _('Disable for replies') : _('Enable for replies');
		}
		item.setText(menuItemText);
	},

	/**
	 * Event handler triggered when "set/unset for new/reply mail" menu item clicked.
	 *
	 * @param {Zarafa.core.ui.menu.ConditionalItem} item The menu item which is selected.
	 */
	onClickMenuItem : function (item)
	{
		var record = this.records;
		record.set(item.name, !record.get(item.name));
	}
});

Ext.reg('zarafa.manageccgridcontextmenu', Zarafa.common.manageCc.ui.ManageCcGridContextMenu);
Ext.ns('Zarafa.common.plugins');

/**
 * @class Zarafa.common.plugins.NumberSpinner
 * @extends Zarafa.common.plugins.SpinnerPlugin
 * @ptype zarafa.numberspinner
 * Extension to the {@link Zarafa.common.plugins.SpinnerPlugin Spinner} object,
 * to correctly parse numbers This is used by the
 * {@link Zarafa.common.ui.SpinnerField SpinnerField}
 */
Zarafa.common.plugins.NumberSpinner = Ext.extend(Zarafa.common.plugins.SpinnerPlugin, {
	/**
	 * @cfg {Boolean} allowDecimals False to disallow decimal values (defaults to true)
	 */
	allowDecimals: true,
	/**
	 * @cfg {String} decimalSeparator Character(s) to allow as the decimal separator (defaults to '.')
	 */
	decimalSeparator: '.',
	/**
	 * @cfg {Number} decimalPrecision The maximum precision to display after the decimal separator (defaults to 2)
	 */
	decimalPrecision: 2,
	/**
	 * @cfg {Boolean} allowNegative False to prevent entering a negative sign (defaults to true)
	 */
	allowNegative: true,
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		Ext.applyIf(this, config);

		Zarafa.common.plugins.NumberSpinner.superclass.constructor.call(this, config);
	},

	/**
	 * Initializes the {@link Zarafa.common.ui.SpinnerField SpinnerField} to which
	 * this plugin has been hooked.
	 * @param {Zarafa.common.ui.SpinnerField} The parent field to which this component is connected
	 */
	init: function(field)
	{
		Zarafa.common.plugins.NumberSpinner.superclass.init.call(this, field);

		// Enforce all default values to the int format. If they are not
		// defined obtain them from the Ext.from.NumberField.
		if (Ext.isDefined(field.defaultValue) && Ext.isString(field.defaultValue)) {
			field.defaultValue = parseFloat(field.defaultValue);
		}
		if (!Ext.isDefined(field.minValue)) {
			field.minValue = Ext.form.NumberField.minValue;
		} else if (Ext.isString(field.minValue)) {
			field.minValue = parseFloat(field.minValue);
		}
		if (!Ext.isDefined(field.maxValue)) {
			field.maxValue = Ext.form.NumberField.maxValue;
		} else if (Ext.isString(field.maxValue)) {
			field.maxValue = parseFloat(field.maxValue);
		}

		// Enforce all values which were set on the plugin to be present
		// on the field.
		if (!Ext.isDefined(field.allowDecimals)) {
			field.allowDecimals = this.allowDecimals;
		}
		if (!Ext.isDefined(field.decimalSeparator)) {
			field.decimalSeparator = this.decimalSeparator;
		}
		if (!Ext.isDefined(field.decimalPrecision)) {
			field.decimalPrecision = this.decimalPrecision;
		}
		if (!Ext.isDefined(field.allowNegative)) {
			field.allowNegative = this.allowNegative;
		}

		var allowed = '0123456789';
		if (field.allowDecimals) {
			allowed += field.decimalSeparator;
		}
		if (field.allowNegative) {
			allowed += '-';
		}
		field.maskRe = new RegExp('[' + Ext.escapeRe(allowed) + ']');

		field.value = this.floatToString(field.defaultValue);

		// Update the getValue/setValue functions with our custom
		// functions which handle the Date value type.
		this.setStringValue = field.setValue.createDelegate(field);
		this.getStringValue = field.getValue.createDelegate(field);
		field.setValue = this.setValue.createDelegate(this);
		field.getValue = this.getValue.createDelegate(this);
	},

	/**
	 * Reference to the original {@link Ext.form.Field#setValue} function
	 * for setting the inner value as String.
	 * @param {String} value The value to set
	 * @private
	 */
	setStringValue: Ext.emptyFn,

	/**
	 * Reference to the original {@link Ext.form.Field#getValue} function
	 * for getting the inner value as String.
	 * @return {String} value The value to set
	 * @private
	 */
	getStringValue: Ext.emptyFn,

	/**
	 * Convert a String into a Float. This will convert the value from {@link #getStringValue}
	 * to a Float value.
	 * @param {String} value The string to convert to a float
	 * @return {Float} The float value representing the string
	 * @protected
	 */
	stringToFloat: function(value)
	{
		return parseFloat(value);
	},

	/**
	 * Convert a Float to a String. This will convert the value for {@link #setStringValue}.
	 * @param {Float} value The float value which must be converted
	 * @return {String} The stringified value represented as percentage
	 * @private
	 */
	floatToString: function(value)
	{
		if (Ext.isDefined(value)) {
			return value.toString();
		}
	},

	/**
	 * Sets a data value into the field and validates it.
	 * To set the value directly without validation see {@link Ext.form.Field#setRawValue}.
	 * @param {Number/String} value The value to set
	 * @private
	 */
	setValue: function(value)
	{
		this.setStringValue(this.floatToString(this.fixBoundries(value)));
	},

	/**
	 * Returns the normalized data value (undefined or emptyText will be returned as '').
	 * To return the raw value see {@link Ext.form.Field#getRawValue}.
	 * @return {Number} The selected number
	 * @protected
	 */
	getValue: function()
	{
		var stringValue = this.getStringValue();
		if (Ext.isEmpty(stringValue)) {
			return undefined;
		}
		return this.stringToFloat(stringValue);
	},

	/**
	 * The default spin action when the value inside the
	 * {@link Zarafa.common.ui.SpinnerField SpinnerField} must be changed.
	 *
	 * @param {Boolean} down True when the value must be decreased
	 * @param {Boolean} alternate True when the alternate change has been requested
	 * @private
	 */
	spin: function(down, alternate)
	{
		if (this.field.rendered) {
			this.field.onFocus();
		}

		// Always parse the value from the parent field, in case the user typed in an update
		var v = this.field.getValue();

		// Determine the method to change the value
		var incr = (alternate === true) ? this.field.alternateIncrementValue : this.field.incrementValue;

		v += (down ? -1 : 1) * incr;
		v = (isNaN(v)) ? this.defaultValue : v;
		v = this.fixBoundries(v);

		// Format the Data back into a string for displaying.
		this.field.setValue(v);
	}
});

// Register as plugin, so that it can be used using lazy loading
// with the Zarafa.common.ui.SpinnerField
Ext.preg('zarafa.numberspinner', Zarafa.common.plugins.NumberSpinner);
Ext.ns('Zarafa.common.plugins');

/**
 * @class Zarafa.common.plugins.TimeSpinner
 * @extends Zarafa.common.plugins.SpinnerPlugin
 * @ptype zarafa.timespinner
 * Extension to the {@link Zarafa.common.plugins.SpinnerPlugin} object,
 * to correctly parse times. This is used by the
 * {@link Zarafa.common.ui.SpinnerField SpinnerField}
 */
Zarafa.common.plugins.TimeSpinner = Ext.extend(Zarafa.common.plugins.SpinnerPlugin, {
	/**
	 * @cfg {String} format The string format which is applied for
	 * displaying the time in the {@link Zarafa.common.ui.SpinnerField SpinnerField}.
	 */
	format: _('G:i'),
	/**
	 * @cfg {String} alternateIncrementField The Date field which must be incremented
	 * when the alternate increment/decrement option is used (default: Date.HOUR).
	 */
	alternateIncrementField: Date.HOUR,
	/**
	 * @cfg {String} incrementField The Date field which must be incremented when the
	 * normal increment/decrement option is used (default: Date.MINUTE).
	 */
	incrementField: Date.MINUTE,
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		Ext.applyIf(this, config);

		Zarafa.common.plugins.TimeSpinner.superclass.constructor.call(this, config);
	},

	/**
	 * Initializes the {@link Zarafa.common.ui.SpinnerField SpinnerField} to which
	 * this plugin has been hooked.
	 * @param {Zarafa.common.ui.SpinnerField} The parent field to which this component is connected
	 */
	init: function(field)
	{
		Zarafa.common.plugins.TimeSpinner.superclass.init.call(this, field);

		// Enforce all default values to the date format.
		if (Ext.isDefined(field.defaultValue) && !Ext.isDate(field.defaultValue)) {
			field.defaultValue = Date.parseDate(field.defaultValue, this.format);
		}
		if (Ext.isDefined(field.minValue) && !Ext.isDate(field.minValue)) {
			field.minValue = Date.parseDate(field.minValue, this.format);
		}
		if (Ext.isDefined(field.maxValue) && !Ext.isDate(field.maxValue)) {
			field.maxValue = Date.parseDate(field.maxValue, this.format);
		}

		// Initialize our values
		this.dateValue = field.defaultValue;
		if (Ext.isDate(this.dateValue)) {
			field.value = this.dateValue.format(this.format);
		}

		// Update the getValue/setValue functions with our custom
		// functions which handle the Date value type.
		this.setStringValue = field.setValue.createDelegate(field);
		this.getStringValue = field.getValue.createDelegate(field);
		field.setValue = this.setValue.createDelegate(this);
		field.getValue = this.getValue.createDelegate(this);
		field.isValidTimeString = this.isValidTimeString.createDelegate(this);
	},

	/**
	 * Reference to the original {@link Ext.form.Field#setValue} function
	 * for setting the inner value as String.
	 * @param {String} value The value to set
	 * @private
	 */
	setStringValue: Ext.emptyFn,

	/**
	 * Reference to the original {@link Ext.form.Field#getValue} function
	 * for getting the inner value as String.
	 * @return {String} value The value to set
	 * @private
	 */
	getStringValue: Ext.emptyFn,

	/**
	 * Sets a data value into the field and validates it.
	 * To set the value directly without validation see {@link Ext.form.Field#setRawValue}.
	 * @param {Date/String} value The value to set
	 * @private
	 */
	setValue: function(value)
	{
		if (!Ext.isDefined(value) || Ext.isEmpty(value)) {
			// If no value is provided, we clear the field.
			this.dateValue = null;
			this.setStringValue('');
		} else if (Ext.isDate(value)) {
			// The value is a date, clone it to obtain a local copy.
			this.dateValue = value.clone();
			this.setStringValue(value.format(this.format));
		} else if (Ext.isDate(this.dateValue)) {
			// The value is a String, but the dateValue is a Date.
			// Use the dateValue to determine the date (Day, Month, Year)
			// part which must be applied to the value (which only contains hours and minutes).
			var tmpDate = Date.parseDate(value, this.format);
			value = this.dateValue.clone();
			value.setHours(tmpDate.getHours());
			value.setMinutes(tmpDate.getMinutes());
			this.dateValue = value;
			this.setStringValue(value.format(this.format));
		} else {
			// The value is a String, dateValue is useless.
			// We parse the date and assume that the Day, Month, Year
			// should be set for today.
			this.dateValue = Date.parseDate(value, this.format);
			this.setStringValue(value);
		}
	},

	/**
	 * Returns the normalized data value (undefined or emptyText will be returned as '').
	 * To return the raw value see {@link Ext.form.Field#getRawValue}.
	 * @return {Date} The selected Date
	 * @private
	 */
	getValue: function()
	{
		this.formatTimeString();
		var stringValue = this.isValidTimeString() ? this.getStringValue() : this.field.value;
		if (Ext.isEmpty(stringValue) || Ext.isEmpty(this.dateValue)) {
			return null;
		}

		var dateValue = Date.parseDate(stringValue, this.format);

		this.dateValue.setHours(dateValue.getHours());
		this.dateValue.setMinutes(dateValue.getMinutes());

		return this.dateValue.clone();
	},

	/**
	 * Function which is used to correct the format of the given time string in the spinner.
	 */
	formatTimeString: function()
	{
		var currentValue = this.getStringValue();
		var defaultValue = this.field.value;
		var hours, minutes, timePeriod;

		if (currentValue === defaultValue) {
			return;
		}

		if (Ext.isEmpty(currentValue)) {
			return null;
		}

		var regExAmPm = /[AaPp][Mm]/g;
		var regExSpecialChar = /[^0-9]/g;

		// Check if the current value has 'AM/am' or 'PM/pm'
		if (regExAmPm.test(currentValue)) {
			timePeriod = currentValue.match(regExAmPm);
			currentValue = currentValue.replace(timePeriod,'');
			timePeriod = timePeriod.toString().toUpperCase();
		}

		// Remove all special characters used as separators
		currentValue = currentValue.replace(regExSpecialChar, '');

		switch (currentValue.length) {
			case 4:
				break;
			case 3:
				// If the string has 3 digits, append '0' at the beginning
				// For example, 900 -> 0900
				currentValue = '0' + currentValue;
				break;
			case 2:
				// If the string has 2 digits, append '00' at the end
				// For example, 12 -> 1200
				currentValue = currentValue + '00';
				break;
			case 1:
				// If the string has 1 digit, append a '0' to the beginning
				// and '00' at the end.
				// For example, 8 -> 0800
				currentValue = '0' + currentValue + '00';
				break;
			default:
				// The defaultValue might be something like '7:00',
				// so to extract the hours and minutes, we need to change it
				// to the desired format.
				currentValue = defaultValue.replace(regExSpecialChar, '');
				if (currentValue.length === 3) {
					currentValue = '0' + currentValue;
				}
				break;
		}
		hours = currentValue.slice(0,2);
		minutes = currentValue.slice(2,4);

		var newDate = new Date();
		// Set the hours and minutes to the new Date object
		newDate.setHours(hours);
		newDate.setMinutes(minutes);

		// Format it according to the current format
		newDate = newDate.format(this.format);
		// Replace the time period if given in input by the user
		if (hours <= 12 && timePeriod) {
			newDate = newDate.replace(regExAmPm, timePeriod);
		}
		this.setStringValue(newDate);
	},

	/**
	 * The default spin action when the value inside the
	 * {@link Zarafa.common.ui.SpinnerField SpinnerField} must be changed.
	 *
	 * @param {Boolean} down True when the value must be decreased
	 * @param {Boolean} alternate True when the alternate change has been requested
	 * @private
	 */
	spin: function(down, alternate)
	{
		var oldValue;

		if (this.field.rendered) {
			this.field.onFocus();
		}

		// Always parse the value from the parent field, in case the user typed in an update
		var v = this.field.getValue();

		// Determine the method to change the value
		var incr = (alternate === true) ? this.field.alternateIncrementValue : this.field.incrementValue;
		var incrField = (alternate === true) ? this.alternateIncrementField : this.incrementField;

		oldValue = v;
		if (v) {
			v = v.add(incrField, down ? (-1 * incr) : incr);
		}

		// When spinning up, we could at one point increase the time into the Daylight Saving area.
		// With switch to Daylight savings between 2 and 3 AM, all values within that lost hour, are
		// rounded down by Javascript. Meaning that we can't use the spinbox to step over the given
		// hour. So when the new value is lower then the old value, while we are spinning down, we
		// assume this is the DST switch, and we must add 2 hours to step over the lost hour and
		// make the step as logical as possible.
		if (!down && v && (v <= oldValue)) {
			v = v.add(Date.HOUR, 2);
		}

		v = (!Ext.isDate(v)) ? this.field.defaultValue : v;
		v = this.fixBoundries(v);

		// Format the Date back into a string for displaying.
		this.field.setValue(v);
	},

	/**
	 * Update the value to a correctly formatted float.
	 * Disable this function, since it must be a noop for
	 * Date values.
	 * @param {Mixed} value The value to fix
	 * @private
	 */
	fixPrecision: function(value)
	{
		return value;
	},

	/**
	 * Function which is used to validate time string.
	 * It will return true if the value of the field is in a HH:MM format.
	 * @returns {boolean} True if time string value is valid, false otherwise
	 */
	isValidTimeString: function ()
	{
		var regEx = /^([0-1]?[0-9]|2[0-4]):([0-5][0-9])(:[0-5][0-9])?$/;
		return regEx.test(this.getStringValue());
	}
});

// Register as plugin, so that it can be used using lazy loading
// with the Zarafa.common.ui.SpinnerField
Ext.preg('zarafa.timespinner', Zarafa.common.plugins.TimeSpinner);
Ext.namespace('Zarafa.common.previewer.ui');

/**
 * @class Zarafa.common.previewer.ui.ViewerContainer
 * @extends Zarafa.core.ui.ContentPanel
 * @xtype zarafa.ViewerContentPanel
 *
 * ViewerContainer is provide the container for the pdf and odf file previewer.
 */
Zarafa.common.previewer.ui.ViewerContainer = Ext.extend(Zarafa.core.ui.ContentPanel, {

	/**
	 * @cfg {String} viewerjsPath Path to the ViewerJS installation.
	 * This is a relative path starting from the plugin root.
	 */
	viewerjsPath: 'client/filepreviewer/ViewerJS/index.html',

	/**
	 * @cfg {String} pdfjsPath Path to the pdfjs installation.
	 * This is a relative path starting from the plugin root.
	 */
	pdfjsPath: 'client/filepreviewer/pdfjs/web/viewer.html',

	/**
	 * @cfg {String} src The Iframe source. This will be applied if no record is set.
	 * If a records was passed to this panel, the return value of the "getInlineImageUrl" function
	 * will be used.
	 */
	src: '',

	/**
	 * @cfg {String} title The panel title. This will be applied if no record is set.
	 * If a records was passed to this panel, the "name" or "filename" property of the record
	 * will be used.
	 */
	title: '',

	/**
	 * @cfg {Boolean|Ext.Component} autoResize This flag specifies if the panel gets automatically resized if the window size has changed.
	 * If it is set to true the panel will listen on the Window update event. Otherwise it will listen to the component
	 * resize event.
	 * Defaults to false.
	 */
	autoResize: false,

	/**
	 * @cfg {Number} defaultScale The default scaling of the panel.
	 * Defaults to 0.8 meaning 80% of the browser width.
	 */
	defaultScale: 0.8,

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

		var src = config.src;
		this.title = config.title;
		var record = config.record;

		if (Ext.isDefined(record)) {
			this.title = record.get('name') || record.get('filename');

			var extension = this.title.split('.').pop().toLowerCase();
			src = this.generateUrl(record.getInlineImageUrl(), extension);
		}

		if (Zarafa.isDeskApp) {
			// Render a webview for DeskApp, to make it more secure
			// (it also solves issues with namespace collisions)
			this.tag = 'webview';
			// First load a blank page to be able to inject the cookies with credentials
			// Store the src of the viewer so we can redirect to it after the cookies have
			// been injected
			this.viewerSrc = src;
			src = Ext.SSL_SECURE_URL;
		} else {
			this.tag = 'iframe';
		}

		var viewSize = Ext.getBody().getViewSize();

    Ext.applyIf(config, {
			xtype: 'zarafa.viewercontainer',
			layout: 'anchor',
			anchor: '100%',
      title: Ext.util.Format.htmlEncode(this.title),
			height: viewSize.height * this.defaultScale,
			width: viewSize.width * this.defaultScale,
			defaultScale: config.defaultScale || this.defaultScale,
			autoResize: config.autoResize || false,
			items: [{
				xtype: 'component',
				ref: 'iframeComponent',
				autoEl: {
					tag: this.tag,
					src: src,
					style: {
						width: '100%',
						height: '100%'
					},
					frameborder: 0,
					allowfullscreen: true
				},
				listeners: {
					afterrender: this.onAfterRender,
					scope: this
				}
			}]
    });

		Zarafa.common.previewer.ui.ViewerContainer.superclass.constructor.call(this, config);

		if (this.autoResize === true) {
			Ext.EventManager.onWindowResize(this.onResizeWindow, this);
		}
	},

	/**
	 * Removes the resize event listener. This function is called when the panel gets destroyed.
	 * @private
	 */
	onDestroy: function ()
	{
		if (this.autoResize === true) {
			Ext.EventManager.removeResizeListener(this.onResizeWindow, this);
		}

		Zarafa.common.previewer.ui.ViewerContainer.superclass.onDestroy.apply(this, arguments);
	},

	/**
	 * This functions resizes the panel to the given width. It will be called on the resize event if autoResize is enabled.
	 *
	 * @param {Number} adjWidth The window width.
	 * @param {Number} adjHeight The window height.
	 * @private
	 */
	onResizeWindow: function(adjWidth, adjHeight)
	{
		this.setWidth(adjWidth * this.defaultScale);
		this.setHeight(adjHeight * this.defaultScale);

		// center the panel
		if (this.ownerCt instanceof Ext.Window) {
			this.ownerCt.center();
		}
	},

	/**
	 * Generate the complete Iframe URL including the ViewerJS path.
	 *
	 * @param {String} url The inline url of the attachment to view.
	 * @param {String} extension The fileextension of the attachment. Optional.
	 * @returns {string}
	 */
	generateUrl: function (url, extension)
	{
		var root = container.getBasePath();
		var options = '';
		var pdfJSlang = container.getSettingsModel().get('zarafa/v1/main/language').split('.')[0].replace('_', '-');
		if(Ext.isDefined(extension)) {
			if (extension === 'pdf') {
				options += '#zoom=' + container.getSettingsModel().get('zarafa/v1/main/file_previewer/pdf_zoom');
				// Add the filename to the url to make sure that the pdfjs viewer will use it when downloaded.
				// Replace the hashes because otherwise pdfjs viewer will strip everything after it.
				url += '&filename=' + this.title.replace('#', '-') + '&locale=' + pdfJSlang;
				return root + this.pdfjsPath + '?file=' + encodeURIComponent(url) + options;
			} else if((/(od[tps]|docx|xlsx)$/i).test(extension)) {
				options += '?zoom=' + container.getSettingsModel().get('zarafa/v1/main/file_previewer/odf_zoom') + '&locale=' + pdfJSlang;
			} else {
				// ViewerJS does not provide zooming options for images, instead sets the width. 
				// Hence, we only keep the 'auto' option for images.
				options += '?zoom=auto' + '&locale=' + pdfJSlang;
			}
			options += '&type=' + extension;
		}

		return root + this.viewerjsPath + options + '#' + url;
	},

	/**
	 * Event handler which is fired after the {@link Ext.Component#afterrender rendered}.
	 * This will register the load event handler on {@link Ext.Component IFrame}.
	 * @param {Ext.Component} component The component that encapsulates the iframe
	 */
	onAfterRender: function (component)
	{
		var componentEl = component.getEl();
		// Add a load event handler for the iframe
		componentEl.on('load', this.onLoadFrame, component.getEl(), { single: true});
		// Add a load event handler for the webview
		this.getEl().on('contentload', this.onLoadWebview, this, { single: true});

		// Add some event listeners when we are using webviews (i.e. in DeskApp)
		if ( this.tag === 'webview' ){
			componentEl.dom.addEventListener('permissionrequest', this.handlePermissionRequests);
			componentEl.dom.addEventListener('dialog', this.handleDialogRequests, this);
			componentEl.dom.addEventListener('loadstop', this.onWebViewLoadstop, componentEl.dom);
			componentEl.dom.addEventListener('newwindow', this.newWindowHandler);
		}
	},

	/**
	 * Handler for the 'loadstop' event of WEBVIEW, fired after the content has been loaded
	 * in WEBVIEW. This function is currently used for injecting a script into WEBVIEW context
	 * to set default target of links reside in pdf to '_blank'.
	 */
	onWebViewLoadstop: function()
	{
		// We are adding a script tag at the end of all scripts in WEBVIEW dom so that
		// we can have access of all global variables exported by all the other scripts
		// while this script runs.
		// We also don't want unnecessary script tag in dom. So to keep dom clean we remove script tag.
		this.executeScript({ code: "var script = document.createElement('script');" +
				"script.textContent = '(' + function() " +
				"{if(window.PDFJS) {window.PDFJS.externalLinkTarget = window.PDFJS.LinkTarget.BLANK;}}" +
				" + ')();';" +
				"(document.head||document.documentElement).appendChild(script);" +
				"script.remove();"
		});
	},

	/**
	 * Handler for the 'load' event of iframe, fired immediately after iframe is loaded.
	 * Passes the keydown event for the Escape key to the parent window, so it can close
	 * the preview window.
	 * Also set the target for links in pdf to new tab.
	 * Get the {@link Ext.Button download button} of iframe body and
	 * it will skip showing the requester on {@link Ext.Button download button} click event
	 */
	onLoadFrame: function ()
	{
		var self = this;
		var frameDom = this.dom.contentDocument;
		var frameWindow = this.dom.contentWindow;

		frameDom.addEventListener('webviewerloaded', function () {
			if (frameWindow.PDFViewerApplicationOptions) {
				frameWindow.PDFViewerApplicationOptions.set('externalLinkTarget', 2);
				frameWindow.PDFViewerApplicationOptions.set('disablePreferences', true);
			}
		});

		frameDom.addEventListener('keydown', function(origEvent) {
			// Pass the esc keydown event to the parent window
			if ( origEvent.keyCode === 27 ) {
				self.dom.dispatchEvent(new KeyboardEvent(origEvent.type, origEvent));
			}
		});

		// The pdf.js viewer download button is 'downloadButton' (pdf.js >= 4);
		// fall back to the legacy 'download' id for older viewer builds.
		var download = frameDom.getElementById('downloadButton') || frameDom.getElementById('download');
		if (download) {
			download = Ext.get(download);
			download.on('click', function () {
				Zarafa.core.Util.skipRequester = true;
			});
		}
	},

	/**
	 * Handler for the contentload event of the webview. Will inject the grommunio Web cookies
	 * into the webview, so documents can be loaded
	 */
	onLoadWebview: function()
	{
		var iframeCookieStoreId = this.iframeComponent.el.dom.getCookieStoreId();
		var cookieCounter = 0;
		window.chrome.cookies.getAll({url: location.href}, function(cookies) {
			cookies.forEach( function(c) {
				var cookie = {
					url: location.href,
					name: c.name,
					value: c.value,
					path: c.path,
					secure: c.secure,
					httpOnly: c.httpOnly,
					sameSite: c.sameSite,
					storeId: iframeCookieStoreId
				};
				window.chrome.cookies.set(cookie, function() {
					if ( ++cookieCounter === cookies.length ) {
						this.iframeComponent.el.dom.src = this.viewerSrc;
					}
				}.bind(this));
			}.bind(this));
		}.bind(this));
	},

	/**
	 * Handler of the newwindow event of WEBVIEW element.
	 * Chrome is blocking new window from WEBVIEW. So this handler will handle new window request
	 * from WEBVIEW and open link in new window.
	 * @param {Event} event The newwindow event
	 */
	newWindowHandler: function(event)
	{
		switch (event.windowOpenDisposition) {
			case 'new_window':
			case 'new_background_tab':
			case 'new_foreground_tab':
			case 'new_popup':
				var newWindow = window.open(event.targetUrl, '_blank');
				if (newWindow) {
					newWindow.focus();
				}
				return;
			// Ignore other cases like "save_to_disk", "current_tab", "ignore" for the request of new window.
			default: return;
		}
	},

	/**
	 * Handler for the dialog event of WEBVIEW elements. Will handle alert, prompt,
	 * and confirm dialogs
	 * @param {Event} e The dialog event
	 */
	handleDialogRequests: function(e)
	{
		switch (e.messageType) {
			case "alert":
				window.alert(e.messageText); // eslint-disable-line no-alert
			break;
			case "confirm":
				var confirmation = window.confirm(e.messageText); // eslint-disable-line no-alert
				if (confirmation === true) {
					e.dialog.ok();
				} else {
					e.dialog.cancel();
				}
			break;
			case "prompt":
				var wprompt = window.prompt(e.messageText); // eslint-disable-line no-alert
				if (wprompt !== null) {
					e.dialog.ok(wprompt);
				} else {
					e.dialog.cancel();
				}
			break;
		}
	},

	/**
	 * Handler for the permissionrequest event of WEBVIEW elements. Will handle the request
	 * by its type.
	 * Possible types are media, geolocation, pointerLock, download, loadplugin and fullscreen.
	 * For now we deny geolocation, fullscreen and pointerLock requests.
	 * @param {Event} e The permissionrequest event
	 */
	handlePermissionRequests: function(e)
	{
		e.preventDefault();
		switch (e.permission) {
			// Allow
			case 'download':
			case 'media':
			case 'loadplugin':
			case 'fullscreen':
				e.request.allow();
			break;
			// Deny
			case 'pointerLock':
			case 'geolocation':
				e.request.deny();
			break;
			// also deny all other, not yet known, requests
			default:
				e.request.deny();
			break;

		}
	}
});

Ext.reg('zarafa.viewercontainer', Zarafa.common.previewer.ui.ViewerContainer);
// -*- coding: utf-8; indent-tabs-mode: nil -*-
Ext.namespace('Zarafa.common.printer.renderers');

/**
 * @class Zarafa.common.printer.renderers.RecordRenderer
 * @extends Zarafa.common.printer.renderers.BaseRenderer
 * Utility functions for a record renderer class. Don't use this directly, use a subclass instead.
 */
Zarafa.common.printer.renderers.RecordRenderer = Ext.extend(Zarafa.common.printer.renderers.BaseRenderer, {
	/**
	 * @property customStylesheetPath
	 * @type String
	 * The path at which the print stylesheets can be found for the record renderer
	 */
	customStylesheetPath: 'client/resources/css/external/print.record.css',

	/**
	 * Print a single table row
	 * @param {String} header to place in the table before the record value
	 * @param {String} data value
	 * @private
	 */
	addRow: function(header, data) {
		var args = Ext.toArray(arguments);
		var tpl = '<tr>';

		tpl += '<th valign="top">' + header + (Ext.isEmpty(header) ? '' : ':') + '</th>';

		for (var i = 1, len = args.length; i < len; i++) {
			tpl += '<td valign="top">' + args[i] + '</td>';
		}

		tpl += '</tr>\n';

		return tpl;
	},

	/**
	 * Helper function to print an optional column in the email header table
	 * @param {Zarafa.core.data.MAPIRecord} record to get data value from
	 * @param {String} header to place in the table before the record value
	 * @param {String} tag to get from the record
	 * @param {String} format The template string which should be added
	 * @private
	 */
	optionalRow: function(header, tag, format) {
		var html = '';
		html += '<tpl if="!Ext.isEmpty(values.'+tag+')">';
		html += this.addRow(header, format);
		html += '</tpl>';
		return html;
	},

	/**
	 * Prepares data for any record for use in the XTemplate
	 * @param {Zarafa.core.data.MAPIRecord} record The mapi record to print
	 * @return {Array} Data suitable for use in the XTemplate
	 */
	prepareData: function(record) {
		// copy all properties
		var data = Ext.apply({}, record.data);
		data['fullname'] = container.getUser().getDisplayName();

		// HTML Escape all data
		for (var key in data) {
			if(Ext.isString(data[key])) {
				data[key] = Ext.util.Format.htmlEncode(data[key]);
			}
		}

		// remove default values
		var opt = record.get('sensitivity');
		if (!Ext.isEmpty(opt) && opt === 0) {
			delete data['sensitivity'];
		}
		opt = record.get('importance');
		if (!Ext.isEmpty(opt) && opt == 1) {
			delete data['importance'];
		}

		// add printer specific tags
		data['attachment_names'] = Ext.util.Format.htmlEncode(record.getAttachmentNames());

		return data;
	}
});
Ext.namespace('Zarafa.common.recipientfield.data');

/**
 * @class Zarafa.common.recipientfield.data.SuggestionListProxy
 * @extends Zarafa.core.data.MAPIProxy
 */
Zarafa.common.recipientfield.data.SuggestionListProxy = Ext.extend(Zarafa.core.data.MAPIProxy,
{
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

		var module = Zarafa.core.ModuleNames.getModule('suggestemailaddress');

		Ext.applyIf(config, {
			listModuleName: module.list,
			itemModuleName: module.item
		});

		Zarafa.common.recipientfield.data.SuggestionListProxy.superclass.constructor.call(this, config);
	},

	/**
	 * This will create a {@link Zarafa.core.data.ProxyResponseHandler ProxyResponseHandler} object
	 * which will be used by the {@link Zarafa.core.data.ResponseRouter ResponseRouter} when the
	 * response for the given request has returned.
	 *
	 * @param {String} modulename The modulename which is being accessed with this request
	 * @param {Zarafa.core.Actions} serverAction The action to perform on the server.
	 * @param {Ext.data.Api.action} action name of the action to perform.
	 * @param {Ext.data.Record[]} records list of records to operate on.
	 * @param {Object} parameters object containing user parameters such as range (pagination) information, sorting information, etc.
	 * @param {Ext.data.DataReader} reader data reader. Converts raw JavaScript objects (in our case) to instances of {@link Ext.data.Record}
	 * @param {Function} callback call back function to call when the request has finished successfully.
	 * @param {Object} scope scope for the call back function.
	 * @param {Object} args arguments object. This will be passed to the call back function on successful read.
	 * @return {Object} An instance of the {@link Zarafa.core.data.ProxyResponseHandler ProxyResponseHandler}
	 * which should be used for this request.
	 * @private
	 */
	getResponseHandlerForRequest: function(modulename, serverAction, action, records, parameters, reader, callback, scope, args)
	{
		return new Zarafa.common.recipientfield.data.SuggestionListResponseHandler({
			proxy: this,
			action: action,
			reader: reader,
			options: args,
			callback: callback,
			scope: scope
		});
	}
});
Ext.namespace('Zarafa.common.recipientfield.ui');

/**
 * @class Zarafa.common.recipientfield.ui.EditRecipientContentPanel
 * @extends Zarafa.core.ui.ContentPanel
 * @xtype zarafa.editrecipientcontentpanel
 *
 * This content panel allows for simple editing of a recipient.
 */
Zarafa.common.recipientfield.ui.EditRecipientContentPanel = Ext.extend(Zarafa.core.ui.ContentPanel, {
	/**
	 * @cfg {Zarafa.core.data.IPMRecipientRecord} record The recipient which
	 * is being edited by this panel.
	 */
	record: undefined,

	/**
	 * The form panel which is loaded inside this panel.
	 * @property
	 * @type Ext.form.FormPanel
	 */
	formPanel: undefined,

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

		Ext.applyIf(config, {
			title: _('Edit recipient'),
			layout: 'fit',
			width: 350,
			height: 110,
			stateful: false,
			items: [{
				xtype: 'form',
				layout: 'form',
				border: false,
				ref: 'formPanel',
				items: [{
					xtype: 'textfield',
					fieldLabel: _('Display name'),
					name: 'display_name',
					anchor: '100%'
				},{
					xtype: 'textfield',
					fieldLabel: _('Email address'),
					allowBlank: false,
					vtype: 'email',
					name: 'smtp_address',
					anchor: '100%'
				}],
				buttons: [{
					text: _('Ok'),
					handler: this.onOk,
					scope: this
				},{
					text: _('Cancel'),
					handler: this.onCancel,
					scope: this
				}]
			}]
		});

		Zarafa.common.recipientfield.ui.EditRecipientContentPanel.superclass.constructor.call(this, config);

		this.on('afterlayout', this.onAfterFirstLayout, this, { single: true });
	},

	/**
	 * Event handler which is fired when {@link #afterlayout} has been called for the first time.
	 * This will load the {@link #record} into {@link #formPanel}.
	 * @private
	 */
	onAfterFirstLayout: function()
	{
		this.formPanel.getForm().loadRecord(this.record);
	},

	/**
	 * Event handler which is raised when the user clicks the "Ok" {@link Ext.Button button}
	 * @private
	 */
	onOk: function()
	{
		this.formPanel.getForm().updateRecord(this.record);
		this.close();
	},

	/**
	 * Event handler which is raised when the user clicks the "Cancel" {@link Ext.Button button}
	 * @private
	 */
	onCancel: function()
	{
		this.close();
	}
});

Ext.reg('zarafa.editrecipientcontentpanel', Zarafa.common.recipientfield.ui.EditRecipientContentPanel);
Ext.namespace('Zarafa.common.recipientfield.ui');

/**
 * @class Zarafa.common.recipientfield.ui.RecipientBox
 * @extends Zarafa.common.ui.Box
 * @xtype zarafa.recipientbox
 *
 * Extension to the normal {@link Zarafa.common.ui.Box} which must be used
 * together with the {@link Zarafa.common.recipientfield.ui.RecipientField RecipientField}.
 * This box offers some extra functionality concerning the displaying of recipients.
 */
Zarafa.common.recipientfield.ui.RecipientBox = Ext.extend(Zarafa.common.ui.Box, {
	/**
	 * @cfg {String} validCls The CSS class which must be applied on {@link #el}
	 * when the recipient is {@link #isValidRecord}.
	 */
	validCls: 'x-zarafa-boxfield-recipient-item-resolved',

	/**
	 * @cfg {String} ambiguousCls The CSS class which must be applied on {@link #el}
	 * when the recipient is ambiguous
	 */
	ambiguousCls: 'x-zarafa-boxfield-recipient-item-ambiguous',

	/**
	 * @cfg {String} pendingCls The CSS class which must be applied on {@link #el}
	 * when the recipient is pending resolving.
	 */
	pendingCls: 'x-zarafa-boxfield-recipient-item-pending',

	/**
	 * @cfg {String} invalidCls The CSS class which must be applied on {@link #el}
	 * when the recipient is invalid.
	 */
	invalidCls: 'x-zarafa-boxfield-recipient-item-invalid',

	/**
	 * The {@link Ext.Element} which contains the expand button for this box.
	 * @property
	 * @type Ext.Element
	 */
	expandBtnEl: undefined,

	/**
	 * @cfg {String} expandBtnCls The CSS class which will be applied to the {@link #expandBtnEl} when
	 * the component is rendered.
	 */
	expandBtnCls: 'x-zarafa-boxfield-item-expand',

	/**
	 * @cfg {String} expandBtnHoverCls The CSS class which will be applied to the {@link #expandBtnEl} when
	 * the cursor is hovering over the button.
	 */
	expandBtnHoverCls: 'x-zarafa-boxfield-item-expand-hover',

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

		Ext.applyIf(config, {
			cls: 'x-zarafa-boxfield-recipient-item',
			textTpl:
				'<tpl if="!Ext.isEmpty(values.display_name)">' +
					'{display_name:htmlEncodeElide(this.ellipsisStringStartLength, this.ellipsisStringEndLength)}' +
				'</tpl>'
		});

		Zarafa.common.recipientfield.ui.RecipientBox.superclass.constructor.call(this, config);
	},

	/**
	 * Setup the buttons of the box. This function will only be called when {@link #enableButtons} is true.
	 * It is specially overridden to add expand button.
	 * @override
	 * @private
	 */
	renderButtons: function()
	{
		Zarafa.common.recipientfield.ui.RecipientBox.superclass.renderButtons.apply(this, arguments);

		// Render the expand button only if the recipient is distribution list.
		if(this.record.get('object_type') == Zarafa.core.mapi.ObjectType.MAPI_DISTLIST) {
			this.renderExpandButton();
		}
	},

	/**
	 * Function called after the {@link #render rendering} of this component.
	 * This will hide the {@link #expandBtnEl} when the {@link #editable} flag is false
	 * @param {Ext.Container} ct The container in which the component is being rendered.
	 * @override
	 * @private
	 */
	afterRender: function(ct)
	{
		Zarafa.common.recipientfield.ui.RecipientBox.superclass.afterRender.call(this, ct);

		if(Ext.isDefined(this.expandBtnEl)){
			this.expandBtnEl.setVisible(this.editable);
		}
	},

	/**
	 * Set the {@link #editable} flag, making the box editable or non-editable.
	 * @param {Boolean} value The new editable status.
	 * @override
	 */
	setEditable: function(value)
	{
		Zarafa.common.recipientfield.ui.RecipientBox.superclass.setEditable.call(this, value);

		if (Ext.isDefined(this.expandBtnEl)) {
			this.expandBtnEl.setVisible(this.editable);
		}
	},

	/**
	 * Create expand button and sets event listeners on that expand button of the box.
	 * @private
	 */
	renderExpandButton: function()
	{
		this.expandBtnEl = this.el.insertFirst({
			tag: 'span',
			cls: this.expandBtnCls
		});
		this.expandBtnEl.set({
			'role': 'button',
			'tabindex': '0',
			'aria-label': _('Expand distribution list')
		});

		this.expandBtnEl.addClassOnOver(this.expandBtnHoverCls);
		this.mon(this.expandBtnEl, 'click', this.onClickExpand, this);
		this.mon(this.expandBtnEl, 'keydown', function(e) {
			if (e.getKey() === e.ENTER || e.getKey() === e.SPACE) {
				e.stopEvent();
				this.onClickExpand();
			}
		}, this);
	},

	/**
	 * Called when the user has clicked on the expand button of any recipient.
	 * @private
	 */
	onClickExpand: function()
	{
		Ext.MessageBox.show({
			title: _('Expand distribution list'),
			msg:_('Distribution list will be replaced with its members. You will not be able to collapse it again.'),
			record: this.record,
			fn: this.doExpand,
			scope: this,
			buttons: Ext.MessageBox.OKCANCEL
		});
	},

	/**
	 * Handler called when user press any button from {@link Ext.MessageBox mesagebox}.
	 * @param {String} buttonClicked The ID of the button pressed,
	 * here, one of: ok cancel.
	 * @param {String} text Value of the input field, not useful here
	 * @private
	 */
	doExpand: function(buttonClicked, text)
	{
		if (buttonClicked == 'ok') {
			var store = this.parent.getBoxStore();
			store.expand(this.record);

			// Remove the distribution list from store
			Zarafa.common.recipientfield.ui.RecipientBox.superclass.onClickRemove.apply(this, arguments);
		}
	},

	/**
	 * Function which can be overridden to provide custom formatting for the given {@link Ext.data.Record}
	 * to the {@link #update} function. The data object returned here is used by the {@link #textTpl template}
	 * to render the contents of the box.
	 * @param {Ext.data.Record} record The record which is going to be rendered
	 * @return {Object} The data object which can be passed to {@link #textTpl}.
 	 * @private
	 */
	prepareData: function(record)
	{
		var prepared = {};

		prepared.display_name = record.get('display_name');
		prepared.smtp_address = record.get('smtp_address') || record.get('email_address');
		prepared.object_type = record.get('object_type');
		prepared.entryid = record.get('entryid');

		return prepared;
	},

	/**
	 * Function which can be overridden to provide custom icon rendering for the given {@link Ext.data.Record}
	 * to the {@link #iconEl} element. The string returned here is the CSS class which will be set on the
	 * {@link #iconEl}.
	 * @param {Ext.data.Record} record The record which is going to be rendered
	 * @return {String} The CSS class which must be applied to the {@link #iconEl}.
	 * @private
	 */
	prepareIcon: function(record)
	{
		if (record.isResolved() || (record.attemptedToResolve() && record.isValidSMTP())) {
			return Zarafa.common.recipientfield.ui.RecipientBox.superclass.prepareIcon.apply(this, arguments);
		}
	},

	/**
	 * Check if the given {@link Ext.data.Record record} is valid. This function can be
	 * overridden by the childclasses to indicate if the given record is valid.
	 *
	 * This class will check if the given record is {@link Zarafa.core.data.IPMRecipientRecord#isResolved resolved},
	 * or if the user is a valid {@link Zarafa.core.data.IPMRecipientRecord#isValidSMTP SMTP address}.
	 *
	 * @param {Zarafa.core.data.IPMRecipientRecord} record The record to check
	 * @return {Boolean} True if the record is valid
	 * @protected
	 */
	isValidRecord: function(record)
	{
		return record.isResolved() || record.attemptedToResolve() && record.isValidSMTP();
	},

	/**
	 * Update the {@link #textEl inner HTML} of this component using the {@link #textTpl template}.
	 * @param {Ext.data.Record} record The Ext.data.Record which data must be applied to the template
	 */
	update: function(record)
	{
		Zarafa.common.recipientfield.ui.RecipientBox.superclass.update.apply(this, arguments);

		// Valid recipients are those that have been resolved, or those that could
		// not be resolved but at least have an valid SMTP address.
		if (this.isValidRecord(record)) {
			this.el.removeClass(this.invalidCls);
			this.el.removeClass(this.pendingCls);
			this.el.removeClass(this.ambiguousCls);
			this.el.addClass(this.validCls);
		} else if (record.isAmbiguous()) {
			this.el.removeClass(this.invalidCls);
			this.el.removeClass(this.pendingCls);
			this.el.addClass(this.ambiguousCls);
			this.el.removeClass(this.validCls);
		} else if (!record.isResolved() && !record.attemptedToResolve() && record.dirty) {
			// will mark the RecipientBox as pending only
			// when the user is busy resolving after the user typed in new data

			this.el.removeClass(this.invalidCls);
			this.el.addClass(this.pendingCls);
			this.el.removeClass(this.ambiguousCls);
			this.el.removeClass(this.validCls);
		} else {
			this.el.addClass(this.invalidCls);
			this.el.removeClass(this.pendingCls);
			this.el.removeClass(this.ambiguousCls);
			this.el.removeClass(this.validCls);
		}

		// When record gets updated by resolve request, it may be possible that it is a DistList.
		// Render the expand button only if the recipient is distribution list.
		if(this.record.get('object_type') == Zarafa.core.mapi.ObjectType.MAPI_DISTLIST) {
			if (this.enableButtons === true && !Ext.isDefined(this.expandBtnEl)) {
				this.renderExpandButton();
			}
		}
	}
});

Ext.reg('zarafa.recipientbox', Zarafa.common.recipientfield.ui.RecipientBox);
Ext.namespace('Zarafa.common.recipientfield.ui');

/**
 * @class Zarafa.common.recipientfield.ui.RecipientContextMenu
 * @extends Zarafa.core.ui.menu.ConditionalMenu
 * @xtype zarafa.recipientcontextmenu
 */
Zarafa.common.recipientfield.ui.RecipientContextMenu = Ext.extend(Zarafa.core.ui.menu.ConditionalMenu, {
	// Insertion points for this class
	/**
	 * @insert context.common.recipientfield.contextmenu.actions
	 * @param {Zarafa.common.recipientfield.ui.RecipientContextMenu} contextmenu This contextmenu
	 */

	/**
	 * @cfg {Boolean} editable
	 * False to prevent any menu item to edit the record on which this menu is shown.
	 */
	editable: true,

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

		var editable = Ext.isDefined(config.editable) ? config.editable : this.editable;
		var resolved = false;
		if (config.records) {
			if (Array.isArray(config.records)) {
				config.records = config.records[0];
			}

			resolved = config.records.isResolved();
		}

		Ext.applyIf(config, {
			items: [{
				xtype: 'zarafa.conditionalitem',
				text: _('Edit Recipient'),
				iconCls: 'icon_edit_recipient',
				hidden: !editable || resolved,
				handler: this.editRecipient,
				scope: this
			},{
				xtype: 'zarafa.conditionalitem',
				text: _('Show Details'),
				iconCls: 'icon_contact',
				hidden: !resolved,
				handler: this.openDetailsContent,
				scope: this
			},{
				xtype: 'zarafa.conditionalitem',
				text: _('Copy email address'),
				iconCls: 'icon_copy',
				hidden: !resolved,
				handler: this.copyEmail,
				scope: this
			},{
				xtype: 'zarafa.conditionalitem',
				text: _('Copy email addresses'),
				iconCls: 'icon_copy_all',
				beforeShow: this.onMenuItemBeforeShow,
				name: 'copyEmailAddresses',
				handler: this.copyEmail,
				scope: this
			},{
				xtype: 'zarafa.conditionalitem',
				text: _('Send email'),
				iconCls: 'icon_new_email',
				handler: this.onEmailRecipient,
				scope: this
			},
				container.populateInsertionPoint('context.common.recipientfield.contextmenu.actions', this),
			{
				xtype: 'menuseparator'
			}]
		});

		Zarafa.common.recipientfield.ui.RecipientContextMenu.superclass.constructor.call(this, config);
	},

	/**
	 * Handler for the "Edit Recipient" option. This will open the edit contentpanel.
	 *
	 * @private
	 */
	editRecipient: function()
	{
		Zarafa.core.data.UIFactory.openCreateRecord(this.records, {
			manager: Ext.WindowMgr
		});
	},

	/**
	 * Handler for the "Show Details" option. This will convert
	 * the {@link Zarafa.core.dat.IPMRecipientRecord recipient}
	 * into a {@link Zarafa.core.data.MAPIRecord addressbook record}
	 * which can be opened in the Addressbook details content panel.
	 *
	 * @private
	 */
	openDetailsContent: function()
	{
		Zarafa.common.Actions.openViewRecipientContent(this.records);
	},

	/**
	 * Event handler which determines if menu items should be visible or not.
	 * It will hide the menu item if only one recipient on selected filed (To, Cc, BCc).
	 *
	 * @param {Zarafa.core.ui.menu.ConditionalItem} item The item to enable/disable
	 * @private
	 */
	onMenuItemBeforeShow: function(item)
	{
		var record = this.records;
		// Hide the context menu if selected recipient is not resolved.
		if (!record.isResolved()) {
			item.setVisible(false);
			return;
		}

		var recipients = Zarafa.common.Actions.getRecipientsByType(record.store, record.get('recipient_type'));
		item.setVisible(recipients.length !== 1);
	},

	/**
	 * Handler for the "Copy email address" and 'Copy email addresses' option. This will
	 * copy email address(es) of the resolved recipient(s).
	 * @param {Zarafa.core.ui.menu.ConditionalItem} item The item which was clicked.
	 */
	copyEmail: function (item)
	{
		var copyAll = item.name === 'copyEmailAddresses';
		Zarafa.common.Actions.copyEmailAddress(this.records, this.records.store, copyAll);
	},

	/**
	 * Handler for the "Email Recipient" option. This will create
	 * a new {@link Zarafa.core.data.MAPIRecord mail record}
	 * and generate new phantom recipient for new mail. Then
	 * it will open a new MailCreate ContentPanel for the new mail
	 *
	 * @private
	 */
	onEmailRecipient: function()
	{
		Zarafa.common.Actions.onEmailRecipient(this.records);
	}
});

Ext.reg('zarafa.recipientcontextmenu', Zarafa.common.recipientfield.ui.RecipientContextMenu);
Ext.namespace('Zarafa.common.recipientfield.ui');

/**
 * @class Zarafa.common.recipientfield.ui.RecipientDropZone
 * @extends Zarafa.common.ui.BoxFieldDropZone
 * @xtype zarafa.recipientdropzone
 *
 * A {@link Ext.dd.DropZone DropZone} installed on a {@link Zarafa.common.recipientfield.ui.RecipientField RecipientField}
 * Ensures cooperation between different RecipientFields' drag and drop zones in order to enable the dragging
 * of recipients e.g. from the TO to the CC field of an email.
 */
Zarafa.common.recipientfield.ui.RecipientDropZone = Ext.extend(Zarafa.common.ui.BoxFieldDropZone, {
	/**
	 * Called when the DropZone determines that a {@link Ext.dd.DragSource} has been dropped onto
	 * the drop node.
	 * Check if the dragged record is of a different recipient type than the current field's, and change it if it is.
	 * In this case return true to prevent drop repair, false otherwise.
	 *
	 * @param {Object} nodeData The custom data associated with the drop node (this is the same value returned from
	 * {@link #getTargetFromEvent} for this node)
	 * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
	 * @param {Event} e The event
	 * @param {Object} data An object containing arbitrary data supplied by the drag source
	 * @return {Boolean} True if the drop was valid, else false
	 */
	onNodeDrop: function(nodeData, dd, e, data)
	{
		if (!this.isValidDropPoint(nodeData, dd, e, data)) {
			return false;
		}

		// If dragging to a field with a different store - e.g. from FROM to TO
		if (dd.field.boxStore !== this.field.boxStore) {
			// recipient_type is between the remove and add, otherwise the box may flicker in the wrong field
			dd.field.boxStore.remove(data.draggedRecord);
			if (this.field.defaultRecipientType) {
				data.draggedRecord.set('recipient_type', this.field.defaultRecipientType);
			}

			this.field.boxStore.add(data.draggedRecord);
			return true;

		} else {
			if (data.draggedRecord.get('recipient_type') === this.field.defaultRecipientType) {
				return false;
			}

			data.draggedRecord.set('recipient_type', this.field.defaultRecipientType);
			return true;
		}
	},

	/**
	 * Called while the DropZone determines that a {@link Ext.dd.DragSource} is over a drop node
	 * that has either been registered or detected by a configured implementation of {@link #getTargetFromEvent}.
	 * Overridden to use {@link #isValidDropPoint}
	 *
	 * @param {Object} nodeData The custom data associated with the drop node (this is the same value returned from
	 * {@link #getTargetFromEvent} for this node)
	 * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
	 * @param {Event} e The event
	 * @param {Object} data An object containing arbitrary data supplied by the drag source
	 * @return {String} status The CSS class that communicates the drop status back to the source so that the
	 * underlying {@link Ext.dd.StatusProxy} can be updated
	 * @override
	 */
	onNodeOver: function(target, dd, e, data)
	{
		return this.isValidDropPoint(target, dd, e, data) ? Ext.dd.DropZone.prototype.dropAllowed : Ext.dd.DropZone.prototype.dropNotAllowed;
	},

	/**
	 * Determines whether this dropzone allows dropping at this moment
	 * This implementation checks whether the boxLimit has been exceeded
	 *
	 * @param {Object} nodeData The custom data associated with the drop node (this is the same value returned from
	 * {@link #getTargetFromEvent} for this node)
	 * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
	 * @param {Event} e The event
	 * @param {Object} data An object containing arbitrary data supplied by the drag source
	 * @return {Boolean} isValid A boolean indicating whether the DropZone allows dropping
	 */
	isValidDropPoint: function(target, dd, e, data)
	{
		return (!this.field.boxLimit || this.field.items.getCount() < this.field.boxLimit);
	}
});

Ext.reg('zarafa.recipientdropzone', Zarafa.common.recipientfield.ui.RecipientDropZone);
Ext.namespace('Zarafa.common.recipientfield.ui');

/**
 * @class Zarafa.common.recipientfield.ui.ViewRecipientContentPanel
 * @extends Zarafa.core.ui.ContentPanel
 * @xtype zarafa.viewrecipientcontentpanel
 *
 * This content panel allows for simple viewing of a recipient.
 */
Zarafa.common.recipientfield.ui.ViewRecipientContentPanel = Ext.extend(Zarafa.core.ui.ContentPanel, {
	/**
	 * @cfg {Zarafa.core.data.IPMRecipientRecord} record The recipient which
	 * is being viewed by this panel.
	 */
	record: undefined,

	/**
	 * The form panel which is loaded inside this panel.
	 * @property
	 * @type Ext.form.FormPanel
	 */
	formPanel: undefined,

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

		Ext.applyIf(config, {
			title: _('View recipient'),
			layout: 'fit',
			width: 350,
			height: 100,
			items: [{
				xtype: 'form',
				layout: 'form',
				border: false,
				bodyStyle: 'padding: 5px; background-color: inherit;',
				ref: 'formPanel',
				items: [{
					xtype: 'textfield',
					fieldLabel: _('Display name'),
					name: 'display_name',
					anchor: '100%',
					readOnly: true
				},{
					xtype: 'textfield',
					fieldLabel: _('Email address'),
					name: 'smtp_address',
					anchor: '100%',
					readOnly: true
				}],
				buttons: [{
					text: _('Ok'),
					handler: this.onOk,
					scope: this
				},{
					text: _('Cancel'),
					handler: this.onCancel,
					scope: this
				}]
			}]
		});

		Zarafa.common.recipientfield.ui.ViewRecipientContentPanel.superclass.constructor.call(this, config);

		this.on('afterlayout', this.onAfterFirstLayout, this, { single: true });
	},

	/**
	 * Event handler which is fired when {@link #afterlayout} has been called for the first time.
	 * This will load the {@link #record} into {@link #formPanel}.
	 * @private
	 */
	onAfterFirstLayout: function()
	{
		this.formPanel.getForm().loadRecord(this.record);
	},

	/**
	 * Event handler which is raised when the user clicks the "Ok" {@link Ext.Button button}
	 * @private
	 */
	onOk: function()
	{
		this.close();
	},

	/**
	 * Event handler which is raised when the user clicks the "Cancel" {@link Ext.Button button}
	 * @private
	 */
	onCancel: function()
	{
		this.close();
	}
});

Ext.reg('zarafa.viewrecipientcontentpanel', Zarafa.common.recipientfield.ui.ViewRecipientContentPanel);
Ext.namespace('Zarafa.common.recurrence.data');

/**
 * @class Zarafa.common.recurrence.data.RecurrenceEnd
 * @extends Zarafa.core.Enum
 *
 * Enumerates all possible recurrence endings
 *
 * @singleton
 */
Zarafa.common.recurrence.data.RecurrenceEnd = Zarafa.core.Enum.create({
  /**
	 * Recurrence never ends
	 *
	 * @property
	 * @type Object
	 */
	NEVER: 0x23,
  /**
	 * Recurrence ends after N occurrences
	 *
	 * @property
	 * @type Object
	 */
	N_OCCURRENCES: 0x22,
  /**
	 * Recurrence ends on date
	 *
	 * @property
	 * @type Object
	 */
	ON_DATE: 0x21
});
Ext.namespace('Zarafa.common.recurrence.data');

/**
 * @class Zarafa.common.recurrence.data.RecurrenceSubtype
 * @extends Zarafa.core.Enum
 *
 * Enumerates all possible recurrence subtypes
 *
 * @singleton
 */
Zarafa.common.recurrence.data.RecurrenceSubtype = Zarafa.core.Enum.create({
  /**
	 * For daily recurrence, recur every N days.
	 * Use in combination with {@link Zarafa.common.recurrence.data.RecurrenceType.DAILY DAILY}.
	 *
	 * @property
	 * @type Object
	 */
	DAILY_EVERY_N_DAYS: { type: 0, regen: 0 },
  /**
	 * For daily recurrence, recur every weekday.
	 * Use in combination with {@link Zarafa.common.recurrence.data.RecurrenceType.DAILY DAILY}.
	 *
	 * @property
	 * @type Object
	 */
	DAILY_WEEKDAYS: { type: 1, regen: 0 },
  /**
	 * For daily recurrence, recur every N days after completion.
	 * Use in combination with {@link Zarafa.common.recurrence.data.RecurrenceType.DAILY DAILY}.
	 *
	 * @property
	 * @type Object
	 */
	DAILY_REGENERATE: { type: 0, regen: 1 },
  /**
	 * For weekly recurrence, recur every N weeks after completion.
	 * Use in combination with {@link Zarafa.common.recurrence.data.RecurrenceType.WEEKLY WEEKLY}.
	 *
	 * @property
	 * @type Object
	 */
	WEEKLY_REGENERATE: { type: 0, regen: 1 },
  /**
	 * For weekly recurrence, recur every N weeks.
	 * Use in combination with {@link Zarafa.common.recurrence.data.RecurrenceType.WEEKLY WEEKLY}.
	 *
	 * @property
	 * @type Object
	 */
	WEEKLY: { type: 1, regen: 0 },
  /**
	 * For monthly recurrence, recur Nth day of the month.
	 * Use in combination with {@link Zarafa.common.recurrence.data.RecurrenceType.MONTHLY MONTHLY}.
	 *
	 * @property
	 * @type Object
	 */
	MONTHLY_N_DAY_OF_MONTH: { type: 2, regen: 0 },
  /**
	 * For monthly recurrence, recur Nth weekday of the month.
	 * Use in combination with {@link Zarafa.common.recurrence.data.RecurrenceType.MONTHLY MONTHLY}.
	 *
	 * @property
	 * @type Object
	 */
	MONTHLY_N_WEEKDAY_OF_MONTH: { type: 3, regen: 0 },
  /**
	 * For monthly recurrence, recur N months after completion.
	 * Use in combination with {@link Zarafa.common.recurrence.data.RecurrenceType.MONTHLY MONTHLY}.
	 *
	 * @property
	 * @type Object
	 */
	MONTHLY_REGENERATE: { type: 2, regen: 1 },
  /**
	 * For yearly recurrence, recur Every Nth day of month M.
	 * Use in combination with {@link Zarafa.common.recurrence.data.RecurrenceType.YEARLY YEARLY}.
	 *
	 * @property
	 * @type Object
	 */
	YEARLY_MONTH: { type: 2, regen: 0 },
  /**
	 * For yearly recurrence, recur Nth weekday of month M.
	 * Use in combination with {@link Zarafa.common.recurrence.data.RecurrenceType.YEARLY YEARLY}.
	 *
	 * @property
	 * @type Object
	 */
	YEARLY_N_WEEKDAY: { type: 3, regen: 0 },
  /**
	 * For yearly recurrence, recur N years after completion.
	 * Use in combination with {@link Zarafa.common.recurrence.data.RecurrenceType.YEARLY YEARLY}.
	 *
	 * @property
	 * @type Object
	 */
	YEARLY_REGENERATE: { type: 2, regen: 1 },
  /**
	 * For yearly recurrence, every N years.
	 * Use in combination with {@link Zarafa.common.recurrence.data.RecurrenceType.YEARLY YEARLY}.
	 *
	 * @property
	 * @type Object
	 */
	YEARLY_N: { type: 2, regen: 0 }
});
Ext.namespace('Zarafa.common.recurrence.data');

/**
 * @class Zarafa.common.recurrence.data.RecurrenceType
 * @extends Zarafa.core.Enum
 *
 * Enumerates all possible recurrence types
 *
 * @singleton
 */
Zarafa.common.recurrence.data.RecurrenceType = Zarafa.core.Enum.create({
	/**
	 * No recurrence
	 *
	 * @property
	 * @type Number
	 */
	NONE: 0,
  /**
	 * Daily recurrence
	 *
	 * @property
	 * @type Number
	 */
	DAILY: 10,
  /**
	 * Weekly recurrence
	 *
	 * @property
	 * @type Number
	 */
	WEEKLY: 11,
  /**
	 * Monthly recurrence
	 *
	 * @property
	 * @type Number
	 */
	MONTHLY: 12,
  /**
	 * Yearly recurrence
	 *
	 * @property
	 * @type Number
	 */
	YEARLY: 13
});

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

/**
 * @class Zarafa.common.recurrence.dialogs.DailyPanel
 * @extends Zarafa.common.recurrence.dialogs.RecurrenceSubPanel
 * @xtype zarafa.recurrencedailypanel
 *
 * The Panel used for configuring a Daily Recurrence
 */
Zarafa.common.recurrence.dialogs.DailyPanel = Ext.extend(Zarafa.common.recurrence.dialogs.RecurrenceSubPanel, {
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			xtype: 'zarafa.recurrencedailypanel',
			recurrenceType: Zarafa.common.recurrence.data.RecurrenceType.DAILY,
			recurrenceSubtypes: [
				Zarafa.common.recurrence.data.RecurrenceSubtype.DAILY_EVERY_N_DAYS,
				Zarafa.common.recurrence.data.RecurrenceSubtype.DAILY_WEEKDAYS,
				Zarafa.common.recurrence.data.RecurrenceSubtype.DAILY_REGENERATE
			],
			defaults: {
				border: false,
				bodyStyle: 'background-color: inherit;',
				height: 30
			},
			items: [
				this.createEveryNDaysPanel(),
				this.createEveryWeekdayPanel(),
				this.createRegeneratePanel()
			]
		});

		Zarafa.common.recurrence.dialogs.DailyPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Creates the configuration object for the "Every N Days Panel",
	 * this panel allows Messages to recur every N number of days.
	 *
	 * @return {Object} Configuration object for the panel
	 * @private
	 */
	createEveryNDaysPanel: function()
	{
		return {
			xtype: 'panel',
			layout: 'column',
			items: [{
				xtype: 'radio',
				name: 'daily_pattern',
				patternValue: Zarafa.common.recurrence.data.RecurrenceSubtype.DAILY_EVERY_N_DAYS,
				hideLabel: true,
				width: 25,
				listeners: {
					check: this.onSubtypeChange,
					scope: this
				}
			},{
				xtype: 'zarafa.compositefield',
				plugins: [ 'zarafa.splitfieldlabeler' ],
				fieldLabel: _('Every {A} day(s)'),
				labelWidth: 100,
				columnWidth: 1,
				items: [{
					xtype: 'zarafa.spinnerfield',
					plugins: [ 'zarafa.numberspinner' ],
					ref: '../../everyNDaysSpinner',
					name: 'recurrence_everyn',
					labelSplitter: '{A}',
					allowNegative: false,
					minValue: 1,
					width: 65,
					listeners: {
						change: this.onEveryNDaysChange,
						scope: this
					}
				}]
			}]
		};
	},

	/**
	 * Creates the configuration object for the "Every Weekday Panel",
	 * this panel allows Messages to recur every weekday.
	 *
	 * @return {Object} Configuration object for the panel
	 * @private
	 */
	createEveryWeekdayPanel: function()
	{
		return {
			xtype: 'panel',
			layout: 'column',
			items: [{
				xtype: 'radio',
				name: 'daily_pattern',
				patternValue: Zarafa.common.recurrence.data.RecurrenceSubtype.DAILY_WEEKDAYS,
				hideLabel: true,
				width: 25,
				listeners: {
					check: this.onSubtypeChange,
					scope: this
				}
			},{
				xtype: 'displayfield',
				value: _('Every weekday'),
				hideLabel: true
			}]
		};
	},

	/**
	 * Creates the configuration object for the "Regenerate Panel",
	 * this panel allows Tasks to recur N days after the given task
	 * is completed.
	 *
	 *return @return {Object} Configuration object for the panel
	 * @private
	 */
	createRegeneratePanel: function()
	{
		return {
			xtype: 'panel',
			layout: 'column',
			ref: 'regeneratePanel',
			items: [{
				xtype: 'radio',
				name: 'daily_pattern',
				patternValue: Zarafa.common.recurrence.data.RecurrenceSubtype.DAILY_REGENERATE,
				hideLabel: true,
				width: 25,
				listeners: {
					check: this.onSubtypeChange,
					scope: this
				}
			},{
				xtype: 'zarafa.compositefield',
				plugins: [ 'zarafa.splitfieldlabeler' ],
				fieldLabel: _('Regenerate new task {A} day(s) after each task is completed'),
				labelWidth: 400,
				columnWidth: 1,
				items: [{
					xtype: 'zarafa.spinnerfield',
					plugins: [ 'zarafa.numberspinner' ],
					ref: '../../regenNDaysSpinner',
					name: 'recurrence_everyn',
					labelSplitter: '{A}',
					allowNegative: false,
					minValue: 1,
					width: 50,
					listeners: {
						change: this.onDailyRegenerateChange,
						scope: this
					}
				}]
			}]
		};
	},

	/**
	 * Event handler which is fired when one of the components belonging to the
	 * {@link Zarafa.common.recurrence.data.RecurrenceSubtype#DAILY_EVERY_N_DAYS} recurrence type has been changed.
	 * This will call {@link #onSubtypePropertyChange}.
	 * @param {Ext.form.Field} field The field which was changed
	 * @param {Mixed} value The new value of the field
	 * @private
	 */
	onEveryNDaysChange: function(field, value)
	{
		this.onSubtypePropertyChange(Zarafa.common.recurrence.data.RecurrenceSubtype.DAILY_EVERY_N_DAYS, field, value * (24 * 60));
	},

	/**
	 * Event handler which is fired when one of the components belonging to the
	 * {@link Zarafa.common.recurrence.data.RecurrenceSubtype#DAILY_REGENERATE} recurrence type has been changed.
	 * This will call {@link #onSubtypePropertyChange}.
	 * @param {Ext.form.Field} field The field which was changed
	 * @param {Mixed} value The new value of the field
	 * @private
	 */
	onDailyRegenerateChange: function(field, value)
	{
		this.onSubtypePropertyChange(Zarafa.common.recurrence.data.RecurrenceSubtype.DAILY_REGENERATE, field, value * (24 * 60));
	},

	/**
	 * Apply the values for the "Every N Days Panel",
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record which is loaded in this panel
	 * @param {Boolean} useDefaultValues True if defaultValues should be used rather then the
	 * data from the Record.
	 * @private
	 */
	updateEveryNDaysValues: function(record, useDefaultValues)
	{
		// Convert everyn value from minutes, to days
		var everyn = useDefaultValues ? 1: Math.floor(record.get('recurrence_everyn') / (24 * 60));
		this.everyNDaysSpinner.setValue(everyn);
	},

	/**
	 * Apply the values for the "Regenerate Panel",
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record which is loaded in this panel
	 * @param {Boolean} useDefaultValues True if defaultValues should be used rather then the
	 * data from the Record.
	 * @private
	 */
	updateRegenerateValues: function(record, useDefaultValues)
	{
		// Convert everyn value from minutes, to days
		var everyn = useDefaultValues ? 1: Math.floor(record.get('recurrence_everyn') / (24 * 60));
		this.regenNDaysSpinner.setValue(everyn);
	},

	/**
	 * Enable/disable/hide/unhide all {@link Ext.Component Components} within the {@link Ext.Panel Panel}
	 * using the given {@link Zarafa.core.data.IPMRecord IPMRecord}.
	 * @param {Zarafa.core.data.IPMRecord} record The record to update the panel with
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 * @private
	 */
	updateUI: function(record, contentReset)
	{
		var layout = false;

		if (contentReset === true || record.isModifiedSinceLastUpdate('message_class')) {
			if (record.isMessageClass('IPM.TaskRequest', true)) {
				this.regeneratePanel.show();
			} else {
				this.regeneratePanel.hide();
			}

			layout = true;
		}

		if (layout) {
			this.doLayout();
		}
	},

	/**
	 * Update the {@link Ext.Component components} which belong to the given
	 * {@link Zarafa.common.recurrence.data.RecurrenceSubtype subtype}.
	 * @param {Zarafa.core.data.MAPIRecord} record The record from where the values must be read
	 * @param {Zarafa.common.recurrence.data.RecurrenceSubtype} subtype The subtype for which the UI
	 * components must be updated
	 * @param {Boolean} useDefault True if default values should be used rather then the data from
	 * the given record.
	 * @protected
	 */
	updateSubtype: function(record, pattern, useDefaultValues)
	{
		var subTypes = Zarafa.common.recurrence.data.RecurrenceSubtype;

		if (this.isSubtype(subTypes.DAILY_EVERY_N_DAYS, pattern)) {
			this.updateEveryNDaysValues(record, useDefaultValues);
		} else if (this.isSubtype(subTypes.DAILY_REGENERATE, pattern)) {
			this.updateRegenerateValues(record, useDefaultValues);
		}
	},

	/**
	 * Called by {@link #updateRecordSubType} to indicate that the record must be updated for the
	 * given {@link Zarafa.common.recurrence.data.RecurrenceSubtype recurrence subtype}. The record
	 * itself is already in {@link Zarafa.core.data.MAPIRecord#editing editing} mode.
	 * @param {Zarafa.core.data.MAPIRecord} record The record which must be updated from the UI
	 * @param {Zarafa.common.recurrence.data.RecurrenceSubtype} pattern The Subtype which is
	 * currently enabled. Only the components for this subtype must be used to update the record.
	 * @protected
	 */
	updateRecordSubType: function(record, pattern)
	{
		var subTypes = Zarafa.common.recurrence.data.RecurrenceSubtype;

		if (this.isSubtype(subTypes.DAILY_EVERY_N_DAYS, pattern)) {
			record.set('recurrence_everyn', this.everyNDaysSpinner.getValue() * (24 * 60));
		} else if (this.isSubtype(subTypes.DAILY_WEEKDAYS, pattern)) {
			record.set('recurrence_everyn', 1);
		} else if (this.isSubtype(subTypes.DAILY_REGENERATE, pattern)) {
			record.set('recurrence_everyn', this.regenNDaysSpinner.getValue() * (24 * 60));
		}

		// Unset all unused properties for this recurrence type.
		record.set('recurrence_weekdays', undefined);
		record.set('recurrence_month', undefined);
		record.set('recurrence_monthday', undefined);
		record.set('recurrence_nday', undefined);
	}
});

Ext.reg('zarafa.recurrencedailypanel', Zarafa.common.recurrence.dialogs.DailyPanel);
Ext.namespace('Zarafa.common.recurrence.dialogs');

/**
 * @class Zarafa.common.recurrence.dialogs.MonthlyPanel
 * @extends Zarafa.common.recurrence.dialogs.RecurrenceSubPanel
 * @xtype zarafa.recurrencemonthlypanel
 *
 * The Panel used for configuring a Monthly Recurrence
 */
Zarafa.common.recurrence.dialogs.MonthlyPanel = Ext.extend(Zarafa.common.recurrence.dialogs.RecurrenceSubPanel, {
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			xtype: 'zarafa.recurrencemonthlypanel',
			recurrenceType: Zarafa.common.recurrence.data.RecurrenceType.MONTHLY,
			recurrenceSubtypes: [
				Zarafa.common.recurrence.data.RecurrenceSubtype.MONTHLY_N_DAY_OF_MONTH,
				Zarafa.common.recurrence.data.RecurrenceSubtype.MONTHLY_N_WEEKDAY_OF_MONTH,
				Zarafa.common.recurrence.data.RecurrenceSubtype.MONTHLY_REGENERATE
			],
			defaults: {
				border: false,
				bodyStyle: 'background-color: inherit;',
				height: 30
			},
			items: [
				this.createEveryNDayOfMonthPanel(),
				this.createEveryNWeekDayOfMonthPanel(),
				this.createRegeneratePanel()
			]
		});

		Zarafa.common.recurrence.dialogs.MonthlyPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Creates the configuration object for the "Every N Day of Month Panel",
	 * this panel allows Messages to recur every Nth day of the Month.
	 *
	 * @return {Object} Configuration object for the panel
	 * @private
	 */
	createEveryNDayOfMonthPanel: function()
	{
		return {
			xtype: 'panel',
			layout: 'column',
			items: [{
				xtype: 'radio',
				name: 'monthly_pattern',
				patternValue: Zarafa.common.recurrence.data.RecurrenceSubtype.MONTHLY_N_DAY_OF_MONTH,
				hideLabel: true,
				width: 25,
				listeners: {
					check: this.onSubtypeChange,
					scope: this
				}
			},{
				xtype: 'zarafa.compositefield',
				plugins: [ 'zarafa.splitfieldlabeler' ],
				fieldLabel: _('Day {A} of every {B} month(s)'),
				labelWidth: 250,
				columnWidth: 1,
				items: [{
					xtype: 'zarafa.spinnerfield',
					plugins: [ 'zarafa.numberspinner' ],
					ref: '../../nthDaySpinner',
					name: 'recurrence_monthday',
					labelSplitter: '{A}',
					allowNegative: false,
					minValue: 1,
					maxValue: 31,
					width: 65,
					listeners: {
						change: this.onMonthlyNDayOfMonthChange,
						scope: this
					}
				},{
					xtype: 'zarafa.spinnerfield',
					plugins: [ 'zarafa.numberspinner' ],
					ref: '../../everyNMonthsSpinner',
					name: 'recurrence_everyn',
					labelSplitter: '{B}',
					allowNegative: false,
					minValue: 1,
					width: 65,
					listeners: {
						change: this.onMonthlyNDayOfMonthChange,
						scope: this
					}
				}]
			}]
		};
	},

	/**
	 * Creates the configuration object for the "Every N Weekday of Month Panel",
	 * this panel allows Messages to recur every Nth weekday of the Month.
	 *
	 * @return {Object} Configuration object for the panel
	 * @private
	 */
	createEveryNWeekDayOfMonthPanel: function()
	{
		return {
			xtype: 'panel',
			layout: 'column',
			items: [{
				xtype: 'radio',
				name: 'monthly_pattern',
				patternValue: Zarafa.common.recurrence.data.RecurrenceSubtype.MONTHLY_N_WEEKDAY_OF_MONTH,
				hideLabel: true,
				width: 25,
				listeners: {
					check: this.onSubtypeChange,
					scope: this
				}
			},{
				xtype: 'zarafa.compositefield',
				plugins: [ 'zarafa.splitfieldlabeler' ],
				fieldLabel: _('The {A} {B} of every {C} month(s)'),
				labelWidth: 220,
				columnWidth: 1,
				items: [{
					xtype: 'combo',
					ref: '../../weekdayRankCombo',
					name: 'recurrence_nday',
					labelSplitter: '{A}',
					width: 110,
					store: {
						xtype: 'jsonstore',
						fields: ['name', 'value'],
						data: Zarafa.common.recurrence.data.DayRankData
					},
					mode: 'local',
					triggerAction: 'all',
					displayField: 'name',
					valueField: 'value',
					lazyInit: false,
					forceSelection: true,
					editable: false,
					autoSelect: true,
					listeners: {
						change: this.onMonthlyNWeekdayOfMonthChange,
						scope: this
					}
				},{
					xtype: 'combo',
					ref: '../../weekdayCombo',
					name: 'recurrence_weekdays',
					labelSplitter: '{B}',
					width: 130,
					store: {
						xtype: 'jsonstore',
						fields: ['name', 'value'],
						data: Zarafa.common.recurrence.data.DayData
					},
					mode: 'local',
					triggerAction: 'all',
					displayField: 'name',
					valueField: 'value',
					lazyInit: false,
					forceSelection: true,
					editable: false,
					autoSelect: true,
					listeners: {
						change: this.onMonthlyNWeekdayOfMonthChange,
						scope: this
					}
				},{
					xtype: 'zarafa.spinnerfield',
					plugins: [ 'zarafa.numberspinner' ],
					ref: '../../weekdayEveryNMonthsSpinner',
					name: 'recurrence_everyn',
					labelSplitter: '{C}',
					allowNegative: false,
					minValue: 1,
					width: 65,
					listeners: {
						change: this.onMonthlyNWeekdayOfMonthChange,
						scope: this
					}
				}]
			}]
		};
	},

	/**
	 * Creates the configuration object for the "Regenerate Panel",
	 * this panel allows Tasks to recur N months after the given task
	 * is completed.
	 *
	 * @return {Object} Configuration object for the panel
	 * @private
	 */
	createRegeneratePanel: function()
	{
		return {
			xtype: 'panel',
			layout: 'column',
			ref: 'regeneratePanel',
			items: [{
				xtype: 'radio',
				name: 'monthly_pattern',
				patternValue: Zarafa.common.recurrence.data.RecurrenceSubtype.MONTHLY_REGENERATE,
				hideLabel: true,
				width: 25,
				listeners: {
					check: this.onSubtypeChange,
					scope: this
				}
			},{
				xtype: 'zarafa.compositefield',
				plugins: [ 'zarafa.splitfieldlabeler' ],
				fieldLabel: _('Regenerate new task {A} month(s) after each task is completed'),
				labelWidth: 400,
				columnWidth: 1,
				items: [{
					xtype: 'zarafa.spinnerfield',
					plugins: [ 'zarafa.numberspinner' ],
					ref: '../../regenNMonthsSpinner',
					name: 'recurrence_everyn',
					labelSplitter: '{A}',
					allowNegative: false,
					minValue: 1,
					width: 50,
					listeners: {
						change: this.onMonthlyRegenerateChange,
						scope: this
					}
				}]
			}]
		};
	},

	/**
	 * Event handler which is fired when one of the components belonging to the
	 * {@link Zarafa.common.recurrence.data.RecurrenceSubtype#MONTHLY_N_DAY_OF_MONTH} recurrence type has been changed.
	 * This will call {@link #onSubtypePropertyChange}.
	 * @param {Ext.form.Field} field The field which was changed
	 * @param {Mixed} value The new value of the field
	 * @private
	 */
	onMonthlyNDayOfMonthChange: function(field, value)
	{
		this.onSubtypePropertyChange(Zarafa.common.recurrence.data.RecurrenceSubtype.MONTHLY_N_DAY_OF_MONTH, field, value);
	},

	/**
	 * Event handler which is fired when one of the components belonging to the
	 * {@link Zarafa.common.recurrence.data.RecurrenceSubtype#MONTHLY_N_WEEKDAY_OF_MONTH} recurrence type has been changed.
	 * This will call {@link #onSubtypePropertyChange}.
	 * @param {Ext.form.Field} field The field which was changed
	 * @param {Mixed} value The new value of the field
	 * @private
	 */
	onMonthlyNWeekdayOfMonthChange: function(field, value)
	{
		this.onSubtypePropertyChange(Zarafa.common.recurrence.data.RecurrenceSubtype.MONTHLY_N_WEEKDAY_OF_MONTH, field, value);
	},

	/**
	 * Event handler which is fired when one of the components belonging to the
	 * {@link Zarafa.common.recurrence.data.RecurrenceSubtype#MONTHLY_REGENERATE} recurrence type has been changed.
	 * This will call {@link #onSubtypePropertyChange}.
	 * @param {Ext.form.Field} field The field which was changed
	 * @param {Mixed} value The new value of the field
	 * @private
	 */
	onMonthlyRegenerateChange: function(field, value)
	{
		this.onSubtypePropertyChange(Zarafa.common.recurrence.data.RecurrenceSubtype.MONTHLY_REGENERATE, field, value);
	},

	/**
	 * Apply the values for the "Every N Day of Month Panel",
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record which is loaded in this panel
	 * @param {Boolean} useDefaultValues True if defaultValues should be used rather then the
	 * data from the Record.
	 * @private
	 */
	updateEveryNDayOfMonthValues: function(record, useDefaultValues)
	{
		var startdate = record.get('startdate') || new Date();
		var monthday = useDefaultValues ? startdate.getDate() : record.get('recurrence_monthday');
		var everyn = useDefaultValues ? 1: record.get('recurrence_everyn');

		this.nthDaySpinner.setValue(monthday);
		this.everyNMonthsSpinner.setValue(everyn);
	},

	/**
	 * Apply the values for the "Every N Weekday of Month Panel",
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record which is loaded in this panel
	 * @param {Boolean} useDefaultValues True if defaultValues should be used rather then the
	 * data from the Record.
	 * @private
	 */
	updateEveryNWeekDayOfMonthValues: function(record, useDefaultValues)
	{
		var dayRank;
		var weekday;
		var everyn;

		if (useDefaultValues) {
			var today = record.get('startdate') || new Date();

			dayRank = Math.ceil(today.getDate() / 7);
			weekday = Math.pow(2, today.getDay());
			everyn = 1;
		} else {
			dayRank = record.get('recurrence_nday');
			weekday = record.get('recurrence_weekdays');
			everyn = record.get('recurrence_everyn');
		}

		this.weekdayRankCombo.setValue(dayRank);
		this.weekdayCombo.setValue(weekday);
		this.weekdayEveryNMonthsSpinner.setValue(everyn);
	},

	/**
	 * Apply the values for the "Regenerate Panel",
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record which is loaded in this panel
	 * @param {Boolean} useDefaultValues True if defaultValues should be used rather then the
	 * data from the Record.
	 * @private
	 */
	updateRegenerateValues: function(record, useDefaultValues)
	{
		// Convert everyn value from minutes, to months
		var everyn = useDefaultValues ? 1: record.get('recurrence_everyn');
		this.regenNMonthsSpinner.setValue(everyn);
	},

	/**
	 * Enable/disable/hide/unhide all {@link Ext.Component Components} within the {@link Ext.Panel Panel}
	 * using the given {@link Zarafa.core.data.IPMRecord IPMRecord}.
	 * @param {Zarafa.core.data.IPMRecord} record The record to update the panel with
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 * @private
	 */
	updateUI: function(record, contentReset)
	{
		var layout = false;

		if (contentReset === true || record.isModifiedSinceLastUpdate('message_class')) {
			if (record.isMessageClass('IPM.TaskRequest', true)) {
				this.regeneratePanel.show();
			} else {
				this.regeneratePanel.hide();
			}

			layout = true;
		}

		if (layout) {
			this.doLayout();
		}
	},

	/**
	 * Update the {@link Ext.Component components} which belong to the given
	 * {@link Zarafa.common.recurrence.data.RecurrenceSubtype subtype}.
	 * @param {Zarafa.core.data.MAPIRecord} record The record from where the values must be read
	 * @param {Zarafa.common.recurrence.data.RecurrenceSubtype} subtype The subtype for which the UI
	 * components must be updated
	 * @param {Boolean} useDefault True if default values should be used rather then the data from
	 * the given record.
	 * @protected
	 */
	updateSubtype: function(record, pattern, useDefaultValues)
	{
		var subTypes = Zarafa.common.recurrence.data.RecurrenceSubtype;

		if (this.isSubtype(subTypes.MONTHLY_N_DAY_OF_MONTH, pattern)) {
			this.updateEveryNDayOfMonthValues(record, useDefaultValues);
		} else if (this.isSubtype(subTypes.MONTHLY_N_WEEKDAY_OF_MONTH, pattern)) {
			this.updateEveryNWeekDayOfMonthValues(record, useDefaultValues);
		} else if (this.isSubtype(subTypes.MONTHLY_REGENERATE, pattern)) {
			this.updateRegenerateValues(record, useDefaultValues);
		}
	},

	/**
	 * Called by {@link #updateRecordSubType} to indicate that the record must be updated for the
	 * given {@link Zarafa.common.recurrence.data.RecurrenceSubtype recurrence subtype}. The record
	 * itself is already in {@link Zarafa.core.data.MAPIRecord#editing editing} mode.
	 * @param {Zarafa.core.data.MAPIRecord} record The record which must be updated from the UI
	 * @param {Zarafa.common.recurrence.data.RecurrenceSubtype} pattern The Subtype which is
	 * currently enabled. Only the components for this subtype must be used to update the record.
	 * @protected
	 */
	updateRecordSubType: function(record, pattern)
	{
		var subTypes = Zarafa.common.recurrence.data.RecurrenceSubtype;

		if (this.isSubtype(subTypes.MONTHLY_N_DAY_OF_MONTH, pattern)) {
			record.set('recurrence_monthday', this.nthDaySpinner.getValue());
			record.set('recurrence_nday', undefined);
			record.set('recurrence_weekdays', undefined);
			record.set('recurrence_everyn', this.everyNMonthsSpinner.getValue());
		} else if (this.isSubtype(subTypes.MONTHLY_N_WEEKDAY_OF_MONTH, pattern)) {
			record.set('recurrence_monthday', undefined);
			record.set('recurrence_nday', this.weekdayRankCombo.getValue());
			record.set('recurrence_weekdays', this.weekdayCombo.getValue());
			record.set('recurrence_everyn', this.weekdayEveryNMonthsSpinner.getValue());
		} else if (this.isSubtype(subTypes.MONTHLY_REGENERATE, pattern)) {
			record.set('recurrence_monthday', 1);
			record.set('recurrence_nday', undefined);
			record.set('recurrence_weekdays', undefined);
			record.set('recurrence_everyn', this.regenNMonthsSpinner.getValue());
		}

		// Unset all unused properties for this recurrence type.
		record.set('recurrence_month', undefined);
	}
});

Ext.reg('zarafa.recurrencemonthlypanel', Zarafa.common.recurrence.dialogs.MonthlyPanel);
Ext.namespace('Zarafa.common.recurrence.dialogs');

/**
 * @class Zarafa.common.recurrence.dialogs.WeeklyPanel
 * @extends Zarafa.common.recurrence.dialogs.RecurrenceSubPanel
 * @xtype zarafa.recurrenceweeklypanel
 *
 * The Panel used for configuring a Weekly Recurrence
 */
Zarafa.common.recurrence.dialogs.WeeklyPanel = Ext.extend(Zarafa.common.recurrence.dialogs.RecurrenceSubPanel, {
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			xtype: 'zarafa.recurrenceweeklypanel',
			recurrenceType: Zarafa.common.recurrence.data.RecurrenceType.WEEKLY,
			recurrenceSubtypes: [
				Zarafa.common.recurrence.data.RecurrenceSubtype.WEEKLY_REGENERATE,
				Zarafa.common.recurrence.data.RecurrenceSubtype.WEEKLY
			],
			defaults: {
				border: false,
				bodyStyle: 'background-color: inherit;',
				height: 30
			},
			items: [
				this.createEveryNWeeksPanel(),
				this.createDaySelectionPanel(),
				this.createRegeneratePanel()
			]
		});

		Zarafa.common.recurrence.dialogs.WeeklyPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Creates the configuration object for the "Every N Weeks Panel",
	 * this panel allows Messages to recur the configured days every N weeks.
	 *
	 * @return {Object} Configuration object for the panel
	 * @private
	 */
	createEveryNWeeksPanel: function()
	{
		return {
			xtype: 'panel',
			layout: 'column',
			items: [{
				xtype: 'radio',
				name: 'weekly_pattern',
				patternValue: Zarafa.common.recurrence.data.RecurrenceSubtype.WEEKLY,
				hideLabel: true,
				width: 25,
				listeners: {
					check: this.onSubtypeChange,
					scope: this
				}
			},{
				xtype: 'zarafa.compositefield',
				plugins: [ 'zarafa.splitfieldlabeler' ],
				fieldLabel: _('Every {A} week(s) on') + ':',
				labelWidth: 140,
				columnWidth: 1,
				items: [{
					xtype: 'zarafa.spinnerfield',
					plugins: [ 'zarafa.numberspinner' ],
					ref: '../../everyNWeeksSpinner',
					name: 'recurrence_everyn',
					labelSplitter: '{A}',
					allowNegative: false,
					minValue: 1,
					width: 65,
					listeners: {
						change: this.onWeeklyChange,
						scope: this
					}
				}]
			}]
		};
	},

	/**
	 * Creates the configuration object for the "Day Selection Panel",
	 * this panel allows Messages to recur on specific weekdays (used
	 * in conjunction with the "Every N Weeks Panel"
	 *
	 * @return {Object} Configuration object for the panel
	 * @private
	 */
	createDaySelectionPanel: function()
	{
		var weekStart = container.getSettingsModel().get('zarafa/v1/main/week_start');

		return {
			xtype: 'checkboxgroup',
			ref: 'weeklyDaySelect',
			name: 'recurrence_weekdays',
			columns: 3,
			style: 'padding-left: 25px',
			anchor: '100% 100%',
			height: 'auto',
			// The array of checkboxes depends on the first day of the
			// week. If this is sunday, the first checkbox must be sunday,
			// while when the first day of the week is monday, the first
			// checkbox must be monday. The way we are going to do this,
			// is my simply start counting from the weekStart index,
			// and limit the value to the <0, 6>. Each value belonging to
			// the checkbox is also depending on the day, but this can be
			// calculated the same way, but taking the power of two of the
			// used dayNames index.
			items: [{
				xtype: 'checkbox',
				boxLabel: Date.dayNames[(weekStart + 0) % 7],
				dayValue: Math.pow(2, (weekStart + 0) % 7),
				width: 250,
				listeners: {
					change: this.onWeeklyDayChange,
					scope: this
				}
			},{
				xtype: 'checkbox',
				boxLabel: Date.dayNames[(weekStart + 1) % 7],
				dayValue: Math.pow(2, (weekStart + 1) % 7),
				width: 250,
				listeners: {
					change: this.onWeeklyDayChange,
					scope: this
				}
			},{
				xtype: 'checkbox',
				boxLabel: Date.dayNames[(weekStart + 2) % 7],
				dayValue: Math.pow(2, (weekStart + 2) % 7),
				width: 250,
				listeners: {
					change: this.onWeeklyDayChange,
					scope: this
				}
			},{
				xtype: 'checkbox',
				boxLabel: Date.dayNames[(weekStart + 3) % 7],
				dayValue: Math.pow(2, (weekStart + 3) % 7),
				width: 250,
				listeners: {
					change: this.onWeeklyDayChange,
					scope: this
				}
			},{
				xtype: 'checkbox',
				boxLabel: Date.dayNames[(weekStart + 4) % 7],
				dayValue: Math.pow(2, (weekStart + 4) % 7),
				width: 250,
				listeners: {
					change: this.onWeeklyDayChange,
					scope: this
				}
			},{
				xtype: 'checkbox',
				boxLabel: Date.dayNames[(weekStart + 5) % 7],
				dayValue: Math.pow(2, (weekStart + 5) % 7),
				width: 250,
				listeners: {
					change: this.onWeeklyDayChange,
					scope: this
				}
			},{
				xtype: 'checkbox',
				boxLabel: Date.dayNames[(weekStart + 6) % 7],
				dayValue: Math.pow(2, (weekStart + 6) % 7),
				width: 250,
				listeners: {
					change: this.onWeeklyDayChange,
					scope: this
				}
			}]
		};
	},

	/**
	 * Creates the configuration object for the "Regenerate Panel",
	 * this panel allows Tasks to recur N weeks after the given task
	 * is completed.
	 *
	 * @return {Object} Configuration object for the panel
	 * @private
	 */
	createRegeneratePanel: function()
	{
		return {
			xtype: 'panel',
			layout: 'column',
			ref: 'regeneratePanel',
			items: [{
				xtype: 'radio',
				name: 'weekly_pattern',
				patternValue: Zarafa.common.recurrence.data.RecurrenceSubtype.WEEKLY_REGENERATE,
				hideLabel: true,
				width: 25,
				listeners: {
					check: this.onSubtypeChange,
					scope: this
				}
			},{
				xtype: 'zarafa.compositefield',
				plugins: [ 'zarafa.splitfieldlabeler' ],
				fieldLabel: _('Regenerate new task {A} week(s) after each task is completed'),
				labelWidth: 400,
				columnWidth: 1,
				items: [{
					xtype: 'zarafa.spinnerfield',
					plugins: [ 'zarafa.numberspinner' ],
					ref: '../../regenNWeeksSpinner',
					name: 'recurrence_everyn',
					labelSplitter: '{A}',
					allowNegative: false,
					minValue: 1,
					width: 50,
					listeners: {
						change: this.onWeeklyRegenerateChange,
						scope: this
					}
				}]
			}]
		};
	},

	/**
	 * Event handler which is fired when one of the components belonging to the
	 * {@link Zarafa.common.recurrence.data.RecurrenceSubtype#WEEKLY} recurrence type has been changed.
	 * This will call {@link #onSubtypePropertyChange}.
	 * @param {Ext.form.Field} field The field which was changed
	 * @param {Mixed} value The new value of the field
	 * @private
	 */
	onWeeklyChange: function(field, value)
	{
		this.onSubtypePropertyChange(Zarafa.common.recurrence.data.RecurrenceSubtype.WEEKLY, field, value);
	},

	/**
	 * Event handler which is fired when one of the components belonging to the
	 * {@link Zarafa.common.recurrence.data.RecurrenceSubtype#WEEKLY} recurrence type has been changed.
	 * This will call {@link #onSubtypePropertyChange}.
	 * @param {Ext.form.Field} field The field which was changed
	 * @param {Mixed} value The new value of the field
	 * @private
	 */
	onWeeklyDayChange: function(field, value)
	{
		var oldValue = this.record.get(field.name);

		if (value) {
			oldValue |= field.dayValue;
		} else {
			oldValue &= ~field.dayValue;
		}

		this.onSubtypePropertyChange(Zarafa.common.recurrence.data.RecurrenceSubtype.WEEKLY, field, oldValue);
	},

	/**
	 * Event handler which is fired when one of the components belonging to the
	 * {@link Zarafa.common.recurrence.data.RecurrenceSubtype#WEEKLY_REGENERATE} recurrence type has been changed.
	 * This will call {@link #onSubtypePropertyChange}.
	 * @param {Ext.form.Field} field The field which was changed
	 * @param {Mixed} value The new value of the field
	 * @private
	 */
	onWeeklyRegenerateChange: function(field, value)
	{
		this.onSubtypePropertyChange(Zarafa.common.recurrence.data.RecurrenceSubtype.WEEKLY_REGENERATE, field, value * (7 * 24 * 60));
	},

	/**
	 * Apply the values for the "Every N Weeks Panel",
	 *
   * @param {Zarafa.core.data.IPMRecord} record The record which is loaded in this panel
	 * @param {Boolean} useDefaultValues True if defaultValues should be used rather then the
	 * data from the Record.
	 * @private
	 */
	updateEveryNWeeksValues: function(record, useDefaultValues)
	{
		var everyn = useDefaultValues ? 1: record.get('recurrence_everyn');
		this.everyNWeeksSpinner.setValue(everyn);
	},

	/**
	 * Apply the values for the "Day Selection Panel",
	 *
   * @param {Zarafa.core.data.IPMRecord} record The record which is loaded in this panel
	 * @param {Boolean} useDefaultValues True if defaultValues should be used rather then the
	 * data from the Record.
	 * @private
	 */
	updateDaySelectionValues: function(record, useDefaultValues)
	{
		// Detect the start of the occurrence
		var startdate = record.get('startdate') || new Date();
		var weekdays = useDefaultValues ? Math.pow(2, startdate.getDay()) : record.get('recurrence_weekdays');
		this.weeklyDaySelect.items.each(function(radio) {
			radio.setValue(!!(radio.dayValue & weekdays));
		});
	},

	/**
	 * Apply the values for the "Regenerate Panel",
	 *
   * @param {Zarafa.core.data.IPMRecord} record The record which is loaded in this panel
	 * @param {Boolean} useDefaultValues True if defaultValues should be used rather then the
	 * data from the Record.
	 * @private
	 */
	updateRegenerateValues: function(record, useDefaultValues)
	{
		// Convert everyn value from minutes, to weeks
		var everyn = useDefaultValues ? 1: Math.floor(record.get('recurrence_everyn') / (7 * 24 * 60));
		this.regenNWeeksSpinner.setValue(everyn);
	},

	/**
	 * Enable/disable/hide/unhide all {@link Ext.Component Components} within the {@link Ext.Panel Panel}
	 * using the given {@link Zarafa.core.data.IPMRecord IPMRecord}.
	 * @param {Zarafa.core.data.IPMRecord} record The record to update the panel with
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 * @private
	 */
	updateUI: function(record, contentReset)
	{
		var layout = false;

		if (contentReset === true || record.isModifiedSinceLastUpdate('message_class')) {
			if (record.isMessageClass('IPM.TaskRequest', true)) {
				this.regeneratePanel.show();
			} else {
				this.regeneratePanel.hide();
			}

			layout = true;
		}

		if (layout) {
			this.doLayout();
		}
	},

	/**
	 * Update the {@link Ext.Component components} which belong to the given
	 * {@link Zarafa.common.recurrence.data.RecurrenceSubtype subtype}.
	 * @param {Zarafa.core.data.MAPIRecord} record The record from where the values must be read
	 * @param {Zarafa.common.recurrence.data.RecurrenceSubtype} subtype The subtype for which the UI
	 * components must be updated
	 * @param {Boolean} useDefault True if default values should be used rather then the data from
	 * the given record.
	 * @protected
	 */
	updateSubtype: function(record, pattern, useDefaultValues)
	{
		var subTypes = Zarafa.common.recurrence.data.RecurrenceSubtype;

		if (this.isSubtype(subTypes.WEEKLY, pattern)) {
			this.updateEveryNWeeksValues(record, useDefaultValues);
			this.updateDaySelectionValues(record, useDefaultValues);
		} else if (this.isSubtype(subTypes.WEEKLY_REGENERATE, pattern)) {
			this.updateRegenerateValues(record, useDefaultValues);
		}
	},

	/**
	 * Called by {@link #updateRecordSubType} to indicate that the record must be updated for the
	 * given {@link Zarafa.common.recurrence.data.RecurrenceSubtype recurrence subtype}. The record
	 * itself is already in {@link Zarafa.core.data.MAPIRecord#editing editing} mode.
	 * @param {Zarafa.core.data.MAPIRecord} record The record which must be updated from the UI
	 * @param {Zarafa.common.recurrence.data.RecurrenceSubtype} pattern The Subtype which is
	 * currently enabled. Only the components for this subtype must be used to update the record.
	 * @protected
	 */
	updateRecordSubType: function(record, pattern)
	{
		var subTypes = Zarafa.common.recurrence.data.RecurrenceSubtype;

		if (this.isSubtype(subTypes.WEEKLY, pattern)) {
			record.set('recurrence_everyn', this.everyNWeeksSpinner.getValue());

			var weekdays = 0;
			Ext.each(this.weeklyDaySelect.getValue(), function(radio) {
				weekdays |= radio.dayValue;
			});
			record.set('recurrence_weekdays', weekdays);
		} else if (this.isSubtype(subTypes.WEEKLY_REGENERATE, pattern)) {
			record.set('recurrence_everyn', this.regenNWeeksSpinner.getValue() * (7 * 24 * 60));
		}

		// Unset all unused properties for this recurrence type.
		record.set('recurrence_month', undefined);
		record.set('recurrence_monthday', undefined);
		record.set('recurrence_nday', undefined);
	}
});

Ext.reg('zarafa.recurrenceweeklypanel', Zarafa.common.recurrence.dialogs.WeeklyPanel);
Ext.namespace('Zarafa.common.recurrence.dialogs');

/**
 * @class Zarafa.common.recurrence.dialogs.YearlyPanel
 * @extends Zarafa.common.recurrence.dialogs.RecurrenceSubPanel
 * @xtype zarafa.recurrenceyearlypanel
 *
 * The Panel used for configuring a Yearly Recurrence
 */
Zarafa.common.recurrence.dialogs.YearlyPanel = Ext.extend(Zarafa.common.recurrence.dialogs.RecurrenceSubPanel, {
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			xtype: 'zarafa.recurrenceyearlypanel',
			recurrenceType: Zarafa.common.recurrence.data.RecurrenceType.YEARLY,
			recurrenceSubtypes: [
				Zarafa.common.recurrence.data.RecurrenceSubtype.YEARLY_MONTH,
				Zarafa.common.recurrence.data.RecurrenceSubtype.YEARLY_N_WEEKDAY,
				Zarafa.common.recurrence.data.RecurrenceSubtype.YEARLY_REGENERATE
			],
			defaults: {
				border: false,
				bodyStyle: 'background-color: inherit;',
				height: 30
			},
			items: [
				this.createEveryNYearssPanel(),
				this.createEveryNWeekdayPanel(),
				this.createEveryMonthPanel(),
				this.createRegeneratePanel()
			]
		});

		Zarafa.common.recurrence.dialogs.YearlyPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Creates the configuration object for the "Every N Years Panel",
	 * this panel allows Messages to recur the every N years.
	 *
	 * @return {Object} Configuration object for the panel
	 * @private
	 */
	createEveryNYearssPanel: function()
	{
		return {
			xtype: 'panel',
			layout: 'column',
			items: [{
				xtype: 'zarafa.compositefield',
				plugins: [ 'zarafa.splitfieldlabeler' ],
				fieldLabel: _('Every {A} years(s) on') + ':',
				labelWidth: 140,
				columnWidth: 1,
				items: [{
					xtype: 'zarafa.spinnerfield',
					plugins: [ 'zarafa.numberspinner' ],
					ref: '../../everyNYearsSpinner',
					name: 'recurrence_everyn',
					labelSplitter: '{A}',
					allowNegative: false,
					minValue: 1,
					width: 65,
					listeners: {
						change: this.onYearlyChange,
						scope: this
					}
				}]
			}]
		};
	},

	/**
	 * Creates the configuration object for the "Every N day Panel",
	 * this panel allows Messages to recur every Nth day.
	 *
	 * @return {Object} Configuration object for the panel
	 * @private
	 */
	createEveryNWeekdayPanel: function()
	{
		return {
			xtype: 'panel',
			layout: 'column',
			items: [{
				xtype: 'radio',
				name: 'yearly_pattern',
				patternValue: Zarafa.common.recurrence.data.RecurrenceSubtype.YEARLY_MONTH,
				hideLabel: true,
				width: 25,
				listeners: {
					change: this.onSubtypeChange,
					scope: this
				}
			},{
				xtype: 'zarafa.compositefield',
				plugins: [ 'zarafa.splitfieldlabeler' ],
				fieldLabel: _('{A} {B}'),
				labelWidth: 50,
				columnWidth: 1,
				items: [{
					xtype: 'combo',
					ref: '../../monthCombo',
					name: 'recurrence_month',
					labelSplitter: '{A}',
					width: 130,
					store: {
						xtype: 'jsonstore',
						fields: ['name', 'value'],
						data: Zarafa.common.recurrence.data.MonthData
					},
					mode: 'local',
					triggerAction: 'all',
					displayField: 'name',
					valueField: 'value',
					lazyInit: false,
					forceSelection: true,
					editable: false,
					autoSelect: true,
					listeners: {
						change: this.onYearlyMonthChange,
						select: this.onEveryNWeekdayMonthSelect,
						scope: this
					}
				},{
					xtype: 'zarafa.spinnerfield',
					plugins: [ 'zarafa.numberspinner' ],
					ref: '../../monthDaySpinner',
					name: 'recurrence_monthday',
					labelSplitter: '{B}',
					allowNegative: false,
					minValue: 1,
					maxValue: 31,
					width: 65,
					listeners: {
						change: this.onYearlyMonthChange,
						scope: this
					}
				}]
			}]
		};
	},

	/**
	 * Creates the configuration object for the "Every N Day of the Month Panel",
	 * this panel allows Messages to recur every Nth day of the Month.
	 *
	 * @return {Object} Configuration object for the panel
	 * @private
	 */
	createEveryMonthPanel: function()
	{
		return {
			xtype: 'panel',
			layout: 'column',
			items: [{
				xtype: 'radio',
				name: 'yearly_pattern',
				patternValue: Zarafa.common.recurrence.data.RecurrenceSubtype.YEARLY_N_WEEKDAY,
				hideLabel: true,
				width: 25,
				listeners: {
					change: this.onSubtypeChange,
					scope: this
				}
			},{
				xtype: 'zarafa.compositefield',
				plugins: [ 'zarafa.splitfieldlabeler' ],
				fieldLabel: _('The {A} {B} of {C}'),
				labelWidth: 85,
				columnWidth: 1,
				items: [{
					xtype: 'combo',
					ref: '../../dayRankCombo',
					name: 'recurrence_nday',
					labelSplitter: '{A}',
					width: 110,
					store: {
						xtype: 'jsonstore',
						fields: ['name', 'value'],
						data: Zarafa.common.recurrence.data.DayRankData
					},
					mode: 'local',
					triggerAction: 'all',
					displayField: 'name',
					valueField: 'value',
					lazyInit: false,
					forceSelection: true,
					editable: false,
					autoSelect: true,
					listeners: {
						change: this.onYearlyNWeekdayChange,
						scope: this
					}
				},{
					xtype: 'combo',
					ref: '../../weekdayCombo',
					name: 'recurrence_weekdays',
					labelSplitter: '{B}',
					width: 130,
					store: {
						xtype: 'jsonstore',
						fields: ['name', 'value'],
						data: Zarafa.common.recurrence.data.DayData
					},
					mode: 'local',
					triggerAction: 'all',
					displayField: 'name',
					valueField: 'value',
					lazyInit: false,
					forceSelection: true,
					editable: false,
					autoSelect: true,
					listeners: {
						change: this.onYearlyNWeekdayChange,
						scope: this
					}
				},{
					xtype: 'combo',
					ref: '../../monthWeekdayCombo',
					name: 'recurrence_month',
					labelSplitter: '{C}',
					width: 130,
					store: {
						xtype: 'jsonstore',
						fields: ['name', 'value'],
						data: Zarafa.common.recurrence.data.MonthData
					},
					mode: 'local',
					triggerAction: 'all',
					displayField: 'name',
					valueField: 'value',
					lazyInit: false,
					forceSelection: true,
					editable: false,
					autoSelect: true,
					listeners: {
						change: this.onYearlyNWeekdayChange,
						scope: this
					}
				}]
			}]
		};
	},

	/**
	 * Creates the configuration object for the "Regenerate Panel",
	 * this panel allows Tasks to recur N years after the given task
	 * is completed.
	 *
	 * @return {Object} Configuration object for the panel
	 * @private
	 */
	createRegeneratePanel: function()
	{
		return {
			xtype: 'panel',
			layout: 'column',
			ref: 'regeneratePanel',
			items: [{
				xtype: 'radio',
				name: 'yearly_pattern',
				patternValue: Zarafa.common.recurrence.data.RecurrenceSubtype.YEARLY_REGENERATE,
				hideLabel: true,
				width: 25,
				listeners: {
					change: this.onSubtypeChange,
					scope: this
				}
			},{
				xtype: 'zarafa.compositefield',
				plugins: [ 'zarafa.splitfieldlabeler' ],
				fieldLabel: _('Regenerate new task {A} year(s) after each task is completed'),
				labelWidth: 400,
				columnWidth: 1,
				items: [{
					xtype: 'zarafa.spinnerfield',
					plugins: [ 'zarafa.numberspinner' ],
					ref: '../../regenNYearsSpinner',
					name: 'recurrence_everyn',
					labelSplitter: '{A}',
					allowNegative: false,
					minValue: 1,
					width: 50,
					listeners: {
						change: this.onYearlyRegenerateChange,
						scope: this
					}
				}]
			}]
		};
	},

	/**
	 * Event handler which is fired when one of the components belonging to the
	 * {@link Zarafa.common.recurrence.data.RecurrenceSubtype#YEARLY_N} recurrence type has been changed.
	 * This will call {@link #onSubtypePropertyChange}.
	 * @param {Ext.form.Field} field The field which was changed
	 * @param {Mixed} value The new value of the field
	 * @private
	 */
	onYearlyChange: function(field, value)
	{
		this.onSubtypePropertyChange(Zarafa.common.recurrence.data.RecurrenceSubtype.YEARLY_N, field, value);
	},

	/**
	 * Event handler which is fired when one of the components belonging to the
	 * {@link Zarafa.common.recurrence.data.RecurrenceSubtype#YEARLY_MONTH} recurrence type has been changed.
	 * This will call {@link #onSubtypePropertyChange}.
	 * @param {Ext.form.Field} field The field which was changed
	 * @param {Mixed} value The new value of the field
	 * @private
	 */
	onYearlyMonthChange: function(field, value)
	{
		this.onSubtypePropertyChange(Zarafa.common.recurrence.data.RecurrenceSubtype.YEARLY_MONTH, field, value);
	},

	/**
	 * Event handler which is fired when one of the components belonging to the
	 * {@link Zarafa.common.recurrence.data.RecurrenceSubtype#YEARLY_N_WEEKDAY} recurrence type has been changed.
	 * This will call {@link #onSubtypePropertyChange}.
	 * @param {Ext.form.Field} field The field which was changed
	 * @param {Mixed} value The new value of the field
	 * @private
	 */
	onYearlyNWeekdayChange: function(field, value)
	{
		this.onSubtypePropertyChange(Zarafa.common.recurrence.data.RecurrenceSubtype.YEARLY_N_WEEKDAY, field, value);
	},

	/**
	 * Event handler which is fired when one of the components belonging to the
	 * {@link Zarafa.common.recurrence.data.RecurrenceSubtype#YEARLY_REGENERATE} recurrence type has been changed.
	 * This will call {@link #onSubtypePropertyChange}.
	 * @param {Ext.form.Field} field The field which was changed
	 * @param {Mixed} value The new value of the field
	 * @private
	 */
	onYearlyRegenerateChange: function(field, value)
	{
		this.onSubtypePropertyChange(Zarafa.common.recurrence.data.RecurrenceSubtype.YEARLY_REGENERATE, field, value);
	},

	/**
	 * Event handler which is triggered when a Month has been selected in the "Every N Weekday in Month Panel".
	 * With the selected month known, we can limit the allowed maximum value for the day spinnerbox which
	 * is connected with the month selection.
	 *
	 * @param {Ext.form.ComboBox} combo The combobox which was selected
	 * @param {Ext.data.Record} record The selected record
	 * @param {Number} index The index of the selected record
	 * @private
	 */
	onEveryNWeekdayMonthSelect: function(combo, record, index)
	{
		var maxDaysInMonth = [31, 29 /* Include leap year */, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
		var value = maxDaysInMonth[index];

		if (this.monthDaySpinner.getValue() > value) {
			this.monthDaySpinner.setValue(value);
		}
		this.monthDaySpinner.maxValue = value;
	},

	/**
	 * Apply the values for the "Every N Years Panel",
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record which is loaded in this panel
	 * @param {Boolean} useDefaultValues True if defaultValues should be used rather then the
	 * data from the Record.
	 * @private
	 */
	updateEveryNYearsValues: function(record, useDefaultValues)
	{
		var everyn = useDefaultValues ? 1: record.get('recurrence_everyn') / 12;
		this.everyNYearsSpinner.setValue(everyn);
	},

	/**
	 * Apply the values for the "Every N day Panel",
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record which is loaded in this panel
	 * @param {Boolean} useDefaultValues True if defaultValues should be used rather then the
	 * data from the Record.
	 * @private
	 */
	updateEveryNWeekdayValues: function(record, useDefaultValues)
	{
		var month;
		var monthday;

		if (useDefaultValues) {
			var today = record.get('startdate') || new Date();

			var monthStore = this.monthCombo.getStore();
			month = monthStore.getAt(today.getMonth()).get('value'); // Month index matches store contents...

			monthday = today.getDate();
		} else {
			month = record.get('recurrence_month');
			monthday = record.get('recurrence_monthday');
		}

		this.monthCombo.setValue(month);
		this.monthDaySpinner.setValue(monthday);
	},

	/**
	 * Apply the values for the "Every N Day of the Month Panel",
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record which is loaded in this panel
	 * @param {Boolean} useDefaultValues True if defaultValues should be used rather then the
	 * data from the Record.
	 * @private
	 */
	updateEveryMonthValues: function(record, useDefaultValues)
	{
		var nday;
		var weekdays;
		var month;

		if (useDefaultValues) {
			var today = record.get('startdate') || new Date();

			nday = Math.ceil(today.getDate() / 7);
			weekdays = Math.pow(2, today.getDay());

			var monthStore = this.monthWeekdayCombo.getStore();
			month = monthStore.getAt(today.getMonth()).get('value'); // Month index matches store contents...
		} else {
			nday = record.get('recurrence_nday');
			weekdays = record.get('recurrence_weekdays');
			month = record.get('recurrence_month');
		}

		this.dayRankCombo.setValue(nday);
		this.weekdayCombo.setValue(weekdays);
		this.monthWeekdayCombo.setValue(month);
	},

	/**
	 * Apply the values for the "Regenerate Panel",
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record which is loaded in this panel
	 * @param {Boolean} useDefaultValues True if defaultValues should be used rather then the
	 * data from the Record.
	 * @private
	 */
	updateRegenerateValues: function(record, useDefaultValues)
	{
		// Convert everyn value from minutes, to days
		this.regenNYearsSpinner.setValue(1);
	},

	/**
	 * Enable/disable/hide/unhide all {@link Ext.Component Components} within the {@link Ext.Panel Panel}
	 * using the given {@link Zarafa.core.data.IPMRecord IPMRecord}.
	 * @param {Zarafa.core.data.IPMRecord} record The record to update the panel with
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 * @private
	 */
	updateUI: function(record, contentReset)
	{
		var layout = false;

		if (contentReset === true || record.isModifiedSinceLastUpdate('message_class')) {
			if (record.isMessageClass('IPM.TaskRequest', true)) {
				this.regeneratePanel.show();
			} else {
				this.regeneratePanel.hide();
			}

			layout = true;
		}

		if (layout) {
			this.doLayout();
		}
	},

	/**
	 * Update the {@link Ext.Component components} which belong to the given
	 * {@link Zarafa.common.recurrence.data.RecurrenceSubtype subtype}.
	 * @param {Zarafa.core.data.MAPIRecord} record The record from where the values must be read
	 * @param {Zarafa.common.recurrence.data.RecurrenceSubtype} subtype The subtype for which the UI
	 * components must be updated
	 * @param {Boolean} useDefault True if default values should be used rather then the data from
	 * the given record.
	 * @protected
	 */
	updateSubtype: function(record, pattern, useDefaultValues)
	{
		var subTypes = Zarafa.common.recurrence.data.RecurrenceSubtype;

		if (this.isSubtype(subTypes.YEARLY_N, pattern)) {
			this.updateEveryNYearsValues(record, useDefaultValues);
		}
		if (this.isSubtype(subTypes.YEARLY_MONTH, pattern)) {
			this.updateEveryNWeekdayValues(record, useDefaultValues);
		} else if (this.isSubtype(subTypes.YEARLY_N_WEEKDAY, pattern)) {
			this.updateEveryMonthValues(record, useDefaultValues);
		} else if (this.isSubtype(subTypes.YEARLY_REGENERATE, pattern)) {
			this.updateRegenerateValues(record, useDefaultValues);
		}
	},

	/**
	 * Called by {@link #updateRecordSubType} to indicate that the record must be updated for the
	 * given {@link Zarafa.common.recurrence.data.RecurrenceSubtype recurrence subtype}. The record
	 * itself is already in {@link Zarafa.core.data.MAPIRecord#editing editing} mode.
	 * @param {Zarafa.core.data.MAPIRecord} record The record which must be updated from the UI
	 * @param {Zarafa.common.recurrence.data.RecurrenceSubtype} pattern The Subtype which is
	 * currently enabled. Only the components for this subtype must be used to update the record.
	 * @protected
	 */
	updateRecordSubType: function(record, pattern)
	{
		var subTypes = Zarafa.common.recurrence.data.RecurrenceSubtype;

		if (this.isSubtype(subTypes.YEARLY_MONTH, pattern)) {
			record.set('recurrence_everyn', this.everyNYearsSpinner.getValue());
			record.set('recurrence_nday', undefined);
			record.set('recurrence_weekdays', undefined);
			record.set('recurrence_month', this.monthCombo.getValue());
			record.set('recurrence_monthday', this.monthDaySpinner.getValue());
		} else if (this.isSubtype(subTypes.YEARLY_N_WEEKDAY, pattern)) {
			record.set('recurrence_everyn', this.everyNYearsSpinner.getValue());
			record.set('recurrence_nday', this.dayRankCombo.getValue());
			record.set('recurrence_weekdays', this.weekdayCombo.getValue());
			record.set('recurrence_month', this.monthWeekdayCombo.getValue());
			record.set('recurrence_monthday', undefined);
		} else if (this.isSubtype(subTypes.YEARLY_REGENERATE, pattern)) {
			record.set('recurrence_everyn', this.regenNYearsSpinner.getValue());
			record.set('recurrence_nday', undefined);
			record.set('recurrence_weekdays', undefined);
			record.set('recurrence_month', undefined);
			record.set('recurrence_monthday', 1);
		}
	}
});

Ext.reg('zarafa.recurrenceyearlypanel', Zarafa.common.recurrence.dialogs.YearlyPanel);
Ext.namespace('Zarafa.common.reminder.data');
/**
 * @class Zarafa.common.reminder.data.ReminderProxy
 * @extends Zarafa.core.data.MAPIProxy
 */
Zarafa.common.reminder.data.ReminderProxy = Ext.extend(Zarafa.core.data.MAPIProxy, {

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

		var module = Zarafa.core.ModuleNames.getModule('reminder');

		Ext.applyIf(config, {
			listModuleName: module.list,
			itemModuleName: module.item
		});

		Zarafa.common.reminder.data.ReminderProxy.superclass.constructor.call(this, config);
	},

	/**
	 * This will create a {@linkZarafa.core.data.IPMResponseHandler IPMResponseHandler} object
	 * which will be used by the {@link Zarafa.core.data.ResponseRouter ResponseRouter} when the
	 * response for the given request has returned.
	 *
	 * @param {String} modulename The modulename which is being accessed with this request
	 * @param {Zarafa.core.Actions} serverAction The action to perform on the server.
	 * @param {Ext.data.Api.action} action name of the action to perform.
	 * @param {Ext.data.Record[]} records list of records to operate on.
	 * @param {Object} parameters object containing user parameters such as range (pagination) information, sorting information, etc.
	 * @param {Ext.data.DataReader} reader data reader. Converts raw JavaScript objects (in our case) to instances of {@link Ext.data.Record}
	 * @param {Function} callback call back function to call when the request has finished successfully.
	 * @param {Object} scope scope for the call back function.
	 * @param {Object} args arguments object. This will be passed to the call back function on successful read.
	 * @return {Object} An instance of the {@link Zarafa.core.data.IPMResponseHandler IPMResponseHandler}
	 * which should be used for this request.
	 * @private
	 */
	getResponseHandlerForRequest: function(modulename, serverAction, action, records, parameters, reader, callback, scope, args)
	{
		return new Zarafa.core.data.IPMResponseHandler({
			proxy: this,
			action: action,
			reader: reader,
			options: args,
			callback: callback,
			scope: scope
		});
	}
});
Ext.namespace('Zarafa.common.reminder.dialogs');

/**
 * @class Zarafa.common.reminder.dialogs.ReminderContentPanel
 * @extends Zarafa.core.ui.ContentPanel
 * @xtype zarafa.remindercontentpanel
 */
Zarafa.common.reminder.dialogs.ReminderContentPanel = Ext.extend(Zarafa.core.ui.ContentPanel, {
	/**
	 * @cfg {Zarafa.common.reminder.data.ReminderStore} store store that will be used to get reminder information.
	 */
	store: undefined,

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

		if (!Ext.isDefined(config.store)) {
			config.store = container.getReminderStore();
		}

		config = Ext.applyIf(config, {
			xtype: 'zarafa.remindercontentpanel',
			layout: 'fit',
			title: _('Reminders'),
			border: false,
			width: 420,
			minWidth: 420,
			maxWidth: 420,
			height: 400,
			useInputAutoFocusPlugin: false,
			forceFullyOpenInMainWindow: true,
			items: [{
				xtype: 'zarafa.reminderpanel',
				store: config.store
			}]
		});

		Zarafa.common.reminder.dialogs.ReminderContentPanel.superclass.constructor.call(this, config);
	}
});

Ext.reg('zarafa.remindercontentpanel', Zarafa.common.reminder.dialogs.ReminderContentPanel);
Ext.namespace('Zarafa.common.reminder.dialogs');

/**
 * @class Zarafa.common.reminder.dialogs.ReminderGrid
 * @extends Zarafa.common.ui.grid.GridPanel
 * @xtype zarafa.remindergrid
 */
Zarafa.common.reminder.dialogs.ReminderGrid = Ext.extend(Zarafa.common.ui.grid.GridPanel, {
	/**
	 * @constructor
	 * @param {Object} config Configuration structure
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			xtype: 'zarafa.remindergrid',
			ariaLabel: _('Reminders'),
			border: true,
			columns: this.initColumnModel(),
			selModel: this.initSelectionModel()
		});

		Zarafa.common.reminder.dialogs.ReminderGrid.superclass.constructor.call(this, config);
	},

	/**
	 * Initialize the event handlers
	 * @protected
	 */
	initEvents : function()
	{
		this.on('rowdblclick', this.onRowDblClick, this);
	},

	/**
	 * creates and returns a column model object, used in {@link Ext.grid.GridPanel.colModel colModel} config
	 * @return {Ext.grid.ColumnModel} column model object
	 * @private
	 */
	initColumnModel: function()
	{
		return [{
				header: '<p class="icon_index">&nbsp;<span class="title">Icon</span></p>',
				headerCls: 'zarafa-icon-column',
				dataIndex: 'icon_index',
				width: 24,
				renderer: Zarafa.common.ui.grid.Renderers.icon,
				fixed: true,
				menuDisabled: true
			},{
				dataIndex: 'subject',
				renderer: Zarafa.common.ui.grid.Renderers.subject,
				header: _('Subject'),
				menuDisabled: true
			},{
				dataIndex: 'reminder_time',
				width: 150,
				header: _('Due In'),
				renderer: Zarafa.common.ui.grid.Renderers.dueBy,
				menuDisabled: true
			}];
	},

	/**
	 * creates and returns a selection model object, used in {@link Ext.grid.GridPanel.selModel selModel} config
	 * @return {Ext.grid.RowSelectionModel} selection model object
	 * @private
	 */
	initSelectionModel: function()
	{
		return new Ext.grid.RowSelectionModel({
			multiSelect: true
		});
	},

	/**
	 * Event handler which is fired when a row in the grid has been double-clicked.
	 * This will open the selected appointment/task item in a new dialog.
	 * @param {Ext.grid.GridPanel} grid The grid which fired the event
	 * @param {Number} rowIndex The index of the row which was double clicked
	 * @param {Ext.EventObject} event The event
	 */
	onRowDblClick: function(grid, rowIndex, event)
	{
		var record = this.store.getAt(rowIndex);
		if (record) {
			Zarafa.common.Actions.openReminderRecord(record);
			this.dialog.close();
		}
	}
});

Ext.reg('zarafa.remindergrid', Zarafa.common.reminder.dialogs.ReminderGrid);
Ext.namespace('Zarafa.common.reminder.dialogs');

/**
 * @class Zarafa.common.reminder.dialogs.ReminderGridContextMenu
 * @extends Zarafa.core.ui.menu.ConditionalMenu
 * @xtype zarafa.remindergridcontextmenu
 */
Zarafa.common.reminder.dialogs.ReminderGridContextMenu = Ext.extend(Zarafa.core.ui.menu.ConditionalMenu, {
	// Insertion points for this class
	/**
	 * @insert context.common.reminder.contentpanel.contextmenu.actions
	 * Insertion point for adding actions menu items into the context menu
	 * @param {Zarafa.common.reminder.dialogs.ReminderGridContextMenu} contextmenu This contextmenu
	 */
	/**
	 * @insert context.common.reminder.contentpanel.contextmenu.options
	 * Insertion point for adding options menu items into the context menu
	 * @param {Zarafa.common.reminder.dialogs.ReminderGridContextMenu} contextmenu This contextmenu
	 */

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

		Ext.applyIf(config, {
			items: [
				this.createContextActionItems(),
				{ xtype: 'menuseparator' },
				container.populateInsertionPoint('context.common.reminder.contentpanel.contextmenu.actions', this),
				{ xtype: 'menuseparator' },
				container.populateInsertionPoint('context.common.reminder.contentpanel.contextmenu.options', this)
			]
		});

		Zarafa.common.reminder.dialogs.ReminderGridContextMenu.superclass.constructor.call(this, config);
	},

	/**
	 * Create the Action context menu items.
	 * @return {Zarafa.core.ui.menu.ConditionalItem[]} The list of Action context menu items
	 * @private
	 */
	createContextActionItems : function()
	{
		return [{
			xtype : 'zarafa.conditionalitem',
			text : _('Open'),
			iconCls : 'icon_open',
			singleSelectOnly : true,
			handler : this.onContextItemOpen,
			scope : this
		}];
	},

	/**
	 * Event handler which is called when the user selects the 'Open'
	 * item in the context menu. This will open the item in a new dialog.
	 * @private
	 */
	onContextItemOpen : function()
	{
		Zarafa.common.Actions.openReminderRecord(this.records);
	}
});

Ext.reg('zarafa.remindergridcontextmenu', Zarafa.common.reminder.dialogs.ReminderGridContextMenu);
Ext.namespace('Zarafa.common.rules.data');

/**
 * @class Zarafa.common.rules.data.ActionFlags
 * @extends Zarafa.core.Enum
 * @singleton
 */
Zarafa.common.rules.data.ActionFlags = Zarafa.core.Enum.create({
	/**
	 * Indicates that the action is unknown/unsupported
	 * @property
	 * @type Number
	 */
	UNKNOWN: -1,

	/**
	 * Indicates that the "Move Message To" action is selected
	 * @property
	 * @type Number
	 */
	MOVE: 1,

	/**
	 * Indicates that the "Copy Message To" action is selected
	 * @property
	 * @type Number
	 */
	COPY: 2,

	/**
	 * Indicates that the "Move Message to Deleted Items" action is selected
	 * @property
	 * @type Number
	 */
	DELETE: 3,

	/**
	 * Indicates that the "Redirect Message To" action is selected
	 * @property
	 * @type Number
	 */
	REDIRECT: 4,

	/**
	 * Indicates that the "Forward Message To" action is selected
	 * @property
	 * @type Number
	 */
	FORWARD: 5,

	/**
	 * Indicates that the "Forward Message as Attachment To" action is selected
	 * @property
	 * @type Number
	 */
	FORWARD_ATTACH: 6,

	/**
	 * Indicates that the "Mark As Read" action is selected
	 * @property
	 * @type Number
	 * */
	MARK_AS_READ: 7
});
Ext.namespace('Zarafa.common.rules.data');

/**
 * @class Zarafa.common.rules.data.ConditionFlags
 * @extends Zarafa.core.Enum
 * @singleton
 */
Zarafa.common.rules.data.ConditionFlags = Zarafa.core.Enum.create({
	/**
	 * Indicates that the condition is unknown/unsupported
	 * @property
	 * @type Number
	 */
	UNKNOWN: -1,

	/**
	 * Indicates that the condition checks whom has sent it
	 * @property
	 * @type Number
	 */
	RECEIVED_FROM: 1,

	/**
	 * Indicates that the condition checks to whom it was sent
	 * @property
	 * @type Number
	 */
	SENT_TO: 2,

	/**
	 * Indicates that the condition checks which words are in the subject
	 * @property
	 * @type Number
	 */
	SUBJECT_WORDS: 3,

	/**
	 * Indicates that the condition checks which words are in the body
	 * @property
	 * @type Number
	 */
	BODY_WORDS: 4,

	/**
	 * Indicates that the condition checks which words are in the sender
	 * @property
	 * @type Number
	 */
	SENDER_WORDS: 5,

	/**
	 * Indicates that the condition checks if the user is in the To or CC field
	 * @property
	 * @type Number
	 */
	NAME_TO_CC: 6,

	/**
	 * Indicates that the condition checks if the message was sent with a certain importance
	 * @property
	 * @type Number
	 */
	IMPORTANCE: 7,

	/**
	 * Indicates that the condition checks if the message was sent only to the user
	 * @property
	 * @type Number
	 */
	SENT_TO_ME_ONLY: 8,

	/**
	 * Indicates that the condition checks if the message has an attachment
	 * @property
	 * @type Number
	 */
	ATTACHMENT: 9,

	/**
	 * Indicates that the condition checks if the message was sent with a certain sensitivity
	 * @property
	 * @type Number
	 */
	SENSITIVITY: 10,

	/**
	 * Indicates that the condition checks if the user is in the To field
	 * @property
	 * @type Number
	 */
	SENT_TO_ME: 11,

	/**
	 * Indicates that the condition checks if the message has my name in the Cc field
	 * @property
	 * @type Number
	 */
	SENT_CC_ME: 12,

	/**
	 * Indicates that the condition checks if the message was received before a certain date range
	 * @property
	 * @type Number
	 */
	RECEIVED_BEFORE: 13,

	/**
	 * Indicates that the condition checks if the message was received after a specific date range
	 * @property
	 * @type Number
	 */
	RECEIVED_AFTER: 14,

	/**
	 * Indicates that no condition will be applied. The rule always matches.
	 * @property
	 * @type Number
	 */
	NONE: 15,

	/**
	* Indicates that the condition checks which words are in the message-transport-header
	* @property
	* @type Number
	*/
	TRANSPORTHEADER_WORDS: 16,

	/**
	 * Indicates that the condition checks which words are in the recipient
	 * @property
	 * @type Number
	 */
	RECIPIENT_WORDS: 17,

	/**
	 * Indicates that the condition checks if the user is in the Bcc field
	 * @property
	 * @type Number
	 */
	NAME_BCC: 18,

	/** Indicates that the condition checks that message size should be equal to or more than given size in this condition.
	 * @property
	 * @type Number
	 */
	ATLEAST_SIZE: 19,

	/**
	 * Indicates that the condition checks that message size should be equal to or lesser than given size in this condition.
	 * @property
	 * @type Number
	 */
	ATMOST_SIZE: 20
});
Ext.namespace('Zarafa.common.rules.data');

/**
 * @class Zarafa.common.rules.data.ExceptionsConstants
 * @extends Zarafa.core.Enum
 * @singleton
 */
Zarafa.common.rules.data.ExceptionsConstants = Zarafa.core.Enum.create({
    /**
     * Indicates that the exception is invalid
     * @property
     * @type Number
     */
    INVALID_EXCEPTION: -1
});
Ext.namespace('Zarafa.common.rules.data');

/**
 * @class Zarafa.common.rules.data.JsonRulesReader
 * @extends Zarafa.core.data.JsonReader
 */
Zarafa.common.rules.data.JsonRulesReader = Ext.extend(Zarafa.core.data.JsonReader, {
	/**
	 * @constructor
	 * @param {Object} meta Metadata configuration options.
	 * @param {Object} recordType (optional) Optional Record type matches the type
	 * which must be read from response. If no type is given, it will use the
	 * record type for the {@link Zarafa.core.data.RecordCustomObjectType#ZARAFA_RULE}.
	 */
	constructor: function(meta, recordType)
	{
		meta = meta || {};

		Ext.applyIf(meta, {
			id: 'rule_id',
			idProperty: 'rule_id',
			dynamicRecord: false
		});

		// If no recordType is provided, force the type to be a rule
		if (!Ext.isDefined(recordType)) {
			recordType = Zarafa.core.data.RecordFactory.getRecordClassByCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_RULE);
		}

		Zarafa.common.rules.data.JsonRulesReader.superclass.constructor.call(this, meta, recordType);
	}
});Ext.namespace('Zarafa.common.rules.data');

/**
 * @class Zarafa.common.rules.data.RulesStore
 * @extends Zarafa.core.data.MAPIStore
 *
 * Delegate store that will be used to load delegates information from server.
 */
Zarafa.common.rules.data.RulesStore = Ext.extend(Zarafa.core.data.MAPIStore, {
	/**
	 * @cfg {String} actionType type of action that should be used to send request to server,
	 * valid action types are defined in {@link Zarafa.core.Actions Actions}, default value is 'list'.
	 */
	actionType: undefined,

	/**
	 * {String} storeEntryId the store to be used for reading and writing the rules.
	 */
	storeEntryId: undefined,

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

		// Apply default settings.
		Ext.applyIf(config, {
			// load data whenever instance of store is created
			autoLoad: true,

			batch: true,

			actionType: Zarafa.core.Actions['list'],

			writer: new Zarafa.core.data.JsonWriter({
				writeAllFields: true
			}),
			reader: new Zarafa.common.rules.data.JsonRulesReader(),

			proxy: new Zarafa.common.rules.data.RulesProxy({
				listModuleName: Zarafa.core.ModuleNames.getListName('RULES'),
				itemModuleName: Zarafa.core.ModuleNames.getItemName('RULES')
			})
		});

		Zarafa.common.rules.data.RulesStore.superclass.constructor.call(this, config);
	},

	/**
	 * Event handler which is raised when a {@link Zarafa.common.rules.data.RulesRecord RulesRecord} has been added
	 * to this {@link Zarafa.common.rules.data.RulesStore RulesStore}. This will generate unique value for rule_sequence
	 * property.
	 *
	 * @param {Zarafa.common.rules.data.RulesStore} store The {@link Zarafa.common.rules.data.RulesStore} to which the store was added.
	 * @param {Zarafa.common.rules.data.RulesRecord[]} records The array of {@link Zarafa.common.rules.data.RulesRecord records} which have been added.
	 * @param {Number} index The index at which the record(s) were added
	 * @private
	 */
	createRecords: function(store, records, index)
	{
		for (var i = 0, len = records.length; i < len; i++) {
			var record = records[i];
			if (record.phantom) {
				// generate sequence for the new record
				var seq = 10;

				store.each(function(rec) {
					var newSeq = rec.get('rule_sequence');

					if (!Ext.isEmpty(newSeq)) {
						seq = Math.max(seq, newSeq);
					}
				}, this);

				record.data.rule_sequence = (seq + 1);
			}
		}

		Zarafa.common.rules.data.RulesStore.superclass.createRecords.apply(this, arguments);
	},

	/**
	 * 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 (!this.writer) {
			throw new Ext.data.Store.Error('writer-undefined');
		}

		var queue = [],
		    len,
		    trans,
		    batch,
		    data = {},
		    i;

		var rs = this.getModifiedRecords();

		this.setBaseParam("store_entryid", this.storeEntryId);

		// If there are no modified records in the store, and none where
		// deleted, we don't need to save anything as there are
		// no changes.
		if (rs.length === 0 && this.removed.length === 0) {
			return;
		}


		// We are going to save all rules in a single batch,
		// the server will handle all updates/deletes automatically
		rs = this.getRange().filter(function(record) {
			return record.isValid();
		});

		// Add the store entryid to the request.
		rs.forEach(function(record) {
			record.addMessageAction("store_entryid", this.storeEntryId);
		}, this);

		// Put all records in the update batch, note that we don't care if this is empty,
		// as that will resolve on the server to a "Delete everything" action.
		// Use 'update' rather than 'create' because the server replaces all rules
		// atomically (delete-all + re-add). The response is a full refresh, not a
		// 1:1 correlation to phantom records, so reader.update (forgiving) is
		// appropriate instead of reader.realize (which throws on mismatches).
		queue.push(['update', rs]);

		len = queue.length;
		if (len) {
			batch = ++this.batchCounter;
			for (i = 0; i < len; ++i) {
				trans = queue[i];
				data[trans[0]] = trans[1];
			}
			if (this.fireEvent('beforesave', this, data) !== false) {
				for (i = 0; i < len; ++i) {
					trans = queue[i];
					this.doTransaction(trans[0], trans[1], batch);
				}
				return batch;
			}
		}
		return -1;
	},

	/**
	 * <p>Loads the Record cache from the configured <tt>{@link #proxy}</tt> using the configured <tt>{@link #reader}</tt>.</p>
	 * <br><p>Notes:</p><div class="mdetail-params"><ul>
	 * <li><b><u>Important</u></b>: loading is asynchronous! This call will return before the new data has been
	 * loaded. To perform any post-processing where information from the load call is required, specify
	 * the <tt>callback</tt> function to be called, or use a {@link Ext.util.Observable#listeners a 'load' event handler}.</li>
	 * <li>If using {@link Ext.PagingToolbar remote paging}, the first load call must specify the <tt>start</tt> and <tt>limit</tt>
	 * properties in the <code>options.params</code> property to establish the initial position within the
	 * dataset, and the number of Records to cache on each read from the Proxy.</li>
	 * <li>If using {@link #remoteSort remote sorting}, the configured <code>{@link #sortInfo}</code>
	 * will be automatically included with the posted parameters according to the specified
	 * <code>{@link #paramNames}</code>.</li>
	 * </ul></div>
	 * @param {Object} options An object containing properties which control loading options:<ul>
	 * <li><b><tt>params</tt></b>:Object<div class="sub-desc"><p>An object containing properties to pass as HTTP
	 * parameters to a remote data source. <b>Note</b>: <code>params</code> will override any
	 * <code>{@link #baseParams}</code> of the same name.</p>
	 * <p>Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode}.</p></div></li>
	 * <li><b>callback</b>: Function<div class="sub-desc"><p>A function to be called after the Records
	 * have been loaded. The callback is called after the load event is fired, and is passed the following arguments:<ul>
	 * <li>r: Ext.data.Record[] An Array of Records loaded.</li>
	 * <li>options: Options object from the load call.</li>
	 * <li>success: Boolean success indicator.</li></ul></p></div></li>
	 * <li><b>scope</b>: Object<div class="sub-desc"><p>Scope with which to call the callback (defaults
	 * to the Store object)</p></div></li>
	 * <li><b>add</b>: Boolean<div class="sub-desc"><p>Indicator to append loaded records rather than
	 * replace the current cache.  <b>Note</b>: see note for <tt>{@link #loadData}</tt></p></div></li>
	 * </ul>
	 * @return {Boolean} If the <i>developer</i> provided <tt>{@link #beforeload}</tt> event handler returns
	 * <tt>false</tt>, the load call will abort and will return <tt>false</tt>; otherwise will return <tt>true</tt>.
	 */
	load: function(options)
	{
		if (!Ext.isObject(options)) {
			options = {};
		}

		options.params = Ext.applyIf(options.params||{},{
			'store_entryid': this.storeEntryId
		});

		// By default 'load' must cancel the previous request.
		if (!Ext.isDefined(options.cancelPreviousRequest)) {
			options.cancelPreviousRequest = true;
		}

		Ext.applyIf(options, {
			actionType: this.actionType
		});

		return Zarafa.common.rules.data.RulesStore.superclass.load.call(this, options);
	}
});
/**
 * @class Zarafa.common.rules.dialogs.AtLeatSizeLink
 * @extends Zarafa.common.rules.dialogs.BaseLink
 * @xtype zarafa.atleatsizelink
 *
 * Condition component for the {@link Zarafa.common.rules.data.ConditionFlags#ATLEAST_SIZE}
 * condition. This will allow the user to input a message size and select a size unit. This will generate a proper
 * condition for it and set 'rule_msg_atleast_size_unit' record property.
 */
Zarafa.common.rules.dialogs.AtLeatSizeLink = Ext.extend(Zarafa.common.rules.dialogs.BaseLink, {
  /**
   * Size value for the message size for this condition which is '5' by default.
   * This is changed during {@link #getCondition} if user has changed input value.
   * @property
   * @type Number
   */
  sizeValue: 5,

  /**
   * Size unit value for the message size for this condition which is 'MB' by default.
   * This is changed during {@link #getCondition} if user has selected another value from combobox.
   * @property
   * @type String
   */
  comboValue: Zarafa.common.data.SizeUnits.MB,

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

    Ext.applyIf(config, {
      xtype: 'container',
      style: {
        height: '100%',
        display: 'flex',
      },
      width: 600,
      items: [{
        xtype: 'zarafa.spinnerfield',
        plugins: [ 'zarafa.numberspinner' ],
        fieldLabel: _('Message size'),
        width: 60,
        minValue: 0,
        defaultValue: 5,
        ref: 'msgSize',
        listeners: {
          change: this.setModified,
          scope: this
        }
      },{
        xtype: 'combo',
        ref: 'sizeCombo',
        fieldLabel: _('Size unit'),
        typeAhead: true,
        editable: false,
        triggerAction: 'all',
        lazyRender: true,
        mode: 'local',
        width: 80,
        style: {
          margin: '0 0 0 5px'
        },
        store: {
          xtype: 'jsonstore',
          fields: [ 'id', 'size' ],
          data: this.createComboData()
        },
        valueField: 'size',
        displayField: 'size',
        listeners: {
          select: this.setModified,
          scope: this
        }
      }]
    });

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

  /**
   * Function returns Data for store of size unit combo box.
   **/
  createComboData: function ()
  {
    return [{
      id: 0,
      size: Zarafa.common.data.SizeUnits.MB
    },{
      id: 1,
      size: Zarafa.common.data.SizeUnits.KB
    },{
      id: 2,
      size: Zarafa.common.data.SizeUnits.BYTES
    }];
  },

  /**
   * Handler for input box and combo box value change. It will set component's isModified to true.
   */
  setModified: function()
  {
    this.isModified = true;
  },

  /**
   * Apply an action onto the DataView, this will parse the condition and show
   * the contents in a user-friendly way to the user.
   * @param {Zarafa.common.rules.data.ConditionFlags} conditionFlag The condition type
   * which identifies the exact type of the condition.
   * @param {Object} condition The condition to apply
   * @param {String} sizeUnit which user selected for this condition
   */
  setCondition: function(conditionFlag, condition, sizeUnit)
  {
    if (condition) {
      var conditionSizeValue = condition[1][Zarafa.core.mapi.Restrictions.VALUE]['PR_MESSAGE_SIZE'];
      // If Condition/Exception were added from OUTLOOK, sizeUnit param will be undefined.
      // For that case select 'KB' as default unit like in OUTLOOK.
      sizeUnit = sizeUnit ? sizeUnit : Zarafa.common.data.SizeUnits.KB;
      this.sizeValue = Zarafa.core.Util.convertBytesToKBorMB(conditionSizeValue, sizeUnit);
      this.comboValue = sizeUnit;
    }

    this.msgSize.setValue(this.sizeValue);
    this.sizeCombo.setValue(this.comboValue);

    Zarafa.common.rules.dialogs.AtLeatSizeLink.superclass.setCondition.apply(this, arguments);
  },

  /**
   * Obtain the condition as configured by the user
   * @return {Object} The condition
   */
  getCondition: function()
  {
    if (this.isModified !== true) {
      return this.condition;
    }

    var useInpSize = this.msgSize.getValue();
    //If user has not selected any value then set default value.
    useInpSize = useInpSize ? useInpSize : this.sizeValue;

    this.sizeValue = Number(useInpSize);
    this.comboValue = this.sizeCombo.getValue();

    var isValidInput = this.isSafeToConvertIntoBytes(this.sizeValue, this.comboValue);

    // To avoid Integer range problem while converting to bytes,
    // check if user input is valid according to selected size unit.
    // If not than return rule as false.
    if (!isValidInput) {
      this.msgSize.markInvalid();
      return false;
    }

    var convertedSizeValue = Zarafa.core.Util.convertToBytes(this.sizeValue, this.comboValue);
    var conditionFactory = container.getRulesFactoryByType(Zarafa.common.data.RulesFactoryType.CONDITION);
    var conditionDefinition = conditionFactory.getConditionById(this.conditionFlag);
    return conditionDefinition({value: convertedSizeValue});
  },

  /**
   * This function set 'rule_msg_atleast_size_unit' / 'rule_msg_atmost_size_unit' or
   * 'rule_exception_atmost_size_unit' / 'rule_exception_atleast_size_unit' record property
   * with currently selected size unit comboValue.
   * @param {Zarafa.common.rules.data.RulesRecord} record which needs to be updated.
   * @param {Boolean} overwrite should be true to reset 'rule_msg_atleast_size_unit' / 'rule_msg_atmost_size_unit' or
   * rule_exception_atmost_size_unit' / 'rule_exception_atleast_size_unit' property in record.
   * @param {Boolean} isException which will indicate whether this is Exception component or Condition component.
   * */
  setSizeUnit: function(record, overwrite, isException)
  {
    var sizeunit, atleastSizeProp, atmostSizeProp;

    if (isException) {
      atleastSizeProp = 'rule_exception_atleast_size_unit';
      atmostSizeProp = 'rule_exception_atmost_size_unit';
    } else {
      atleastSizeProp = 'rule_msg_atleast_size_unit';
      atmostSizeProp = 'rule_msg_atmost_size_unit';
    }

    sizeunit = this.atMostSizeLink ? record.get(atmostSizeProp) : record.get(atleastSizeProp);

    // For the case where we have multiple at least or at most size condition,
    // if value already exists than concat that value with ";" and new value.
    if (sizeunit && !overwrite) {
      sizeunit = sizeunit +';'+ this.comboValue;
    } else {
      sizeunit = this.comboValue;
    }

    if (this.atMostSizeLink) {
		record.set(atmostSizeProp, sizeunit);
	} else {
		record.set(atleastSizeProp, sizeunit);
	}
  },

  /**
   * Function which will check if given input is safe to convert into Bytes size unit
   * from MB/KB size unit.
   * @param {Number} input the number in KB/MB/Bytes size unit which needs to be checked for compatibility.
   * @param {Zarafa.common.data.SizeUnits} sizeUnit string which should indicate size unit of an input.
   * @return {Boolean} true if input is valid to convert into Bytes else false.
   */
  isSafeToConvertIntoBytes: function (input, sizeUnit)
  {
    var maxNumber = Math.pow(2,31)-1;

    switch (sizeUnit) {
      case Zarafa.common.data.SizeUnits.MB:
        var maxMB = parseInt(maxNumber / Math.pow(1024,2));
        if (input > maxMB) {
          return false;
        }
        break;
      case Zarafa.common.data.SizeUnits.KB:
        var maxKB = parseInt(maxNumber / 1024);
        if (input > maxKB) {
          return false;
        }
        break;
      case Zarafa.common.data.SizeUnits.BYTES:
        if (this.sizeValue > maxNumber) {
          this.msgSize.markInvalid();
          return false;
        }
        break;
      default: return false;
    }

    return true;
  }

});

Ext.reg('zarafa.atleatsizelink', Zarafa.common.rules.dialogs.AtLeatSizeLink);
/*
 * #dependsFile client/zarafa/common/rules/dialogs/BaseLink.js
 */
Ext.namespace('Zarafa.common.rules.dialogs');

/**
 * @class Zarafa.common.rules.dialogs.AttachmentLink
 * @extends Zarafa.common.rules.dialogs.BaseLink
 * @xtype zarafa.attachmentlink
 *
 * Condition component for the {@link Zarafa.common.rules.data.ConditionFlags#ATTACHMENT ATTACHMENT}
 * verifies if the message has an attachment.
 */
Zarafa.common.rules.dialogs.AttachmentLink = Ext.extend(Zarafa.common.rules.dialogs.BaseLink, {
	/**
	 * Obtain the condition as configured by the user
	 * @return {Object} The condition
	 */
	getCondition: function()
	{
		if (this.isModified !== true) {
			return this.condition;
		}

		// Invalid conditionFlag
		if (this.conditionFlag !== Zarafa.common.rules.data.ConditionFlags.ATTACHMENT) {
			return false;
		}

		var conditionFactory = container.getRulesFactoryByType(Zarafa.common.data.RulesFactoryType.CONDITION);
		var conditionDefinition = conditionFactory.getConditionById(this.conditionFlag);
		return conditionDefinition();
	}
});

Ext.reg('zarafa.attachmentlink', Zarafa.common.rules.dialogs.AttachmentLink);
Ext.namespace('Zarafa.common.rules.dialogs');

/**
 * @class Zarafa.common.rules.dialogs.DeleteLink
 * @extends Zarafa.common.rules.dialogs.FolderSelectionLink
 * @xtype zarafa.deletelink
 *
 * Extension of the {@link Zarafa.common.rules.dialogs.FolderSelectionLink Folder Selection Link}
 * especially for the {@link Zarafa.common.rules.data.ActionFlags#DELETE DELETE ActionFlag}.
 *
 * This will not show anything to the user, but during {@link #setAction} this will force
 * the {@link #actionFlag} to be {@link Zarafa.common.rules.data.ActionFlags#MOVE MOVE}
 * and the {@link #folder} to be set to the 'Deleted Items' folder.
 */
Zarafa.common.rules.dialogs.DeleteLink = Ext.extend(Zarafa.common.rules.dialogs.FolderSelectionLink, {

	/**
	 * Called when user clicks on a {@link Zarafa.common.rules.dialogs.FolderSelectionLink}
	 * It opens hierarchy folder selection dialog
	 * @param {Ext.DataView} dataView Reference to this object
	 * @param {Number} index The index of the target node
	 * @param {HTMLElement} node The target node
	 * @param {Ext.EventObject} evt The mouse event
	 * @protected
	 */
	onClick: function(dataView, index, node, evt)
	{
		// Don't perform any action
	},

	/**
	 * Apply an action onto the DataView, this will parse the action and show
	 * the contents in a user-friendly way to the user.
	 * @param {Zarafa.common.rules.data.ActionFlags} actionFlag The action type
	 * which identifies the exact type of the action.
	 * @param {Object} action The action to apply
	 */
	setAction: function(actionFlag, action)
	{
		// This component only handles the 'DELETE' action,
		// that equals the 'MOVE' action in combination
		// with the 'Deleted Items' folder.
		this.actionFlag = Zarafa.common.rules.data.ActionFlags.MOVE;
		this.action = action;
		this.isModified = !Ext.isDefined(action);

		var hierarchyStore = container.getHierarchyStore();
		var index = hierarchyStore.findExact('store_entryid', this.storeEntryId);

		this.folder = hierarchyStore.getAt(index).getDefaultFolder('wastebasket');
	},

	/**
	 * Update the contents of this dataview, this will apply the {@link #tpl} for
	 * the {@link #folder}.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder The folder to show
	 */
	update: function(folder)
	{
		// Don't show anything
	}
});

Ext.reg('zarafa.deletelink', Zarafa.common.rules.dialogs.DeleteLink);
Ext.namespace('Zarafa.common.rules.dialogs');

/**
 * @class Zarafa.common.rules.dialogs.ImportanceLink
 * @extends Zarafa.common.rules.dialogs.BaseLink
 * @xtype zarafa.importancelink
 *
 * Condition component for the {@link Zarafa.common.rules.data.ConditionFlags#IMPORTANCE IMPORTANCE}
 * condition. This will allow the user to select the preferred importance and can generate a proper
 * condition for it.
 */
Zarafa.common.rules.dialogs.ImportanceLink = Ext.extend(Zarafa.common.rules.dialogs.BaseLink, {
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			items: [{
				xtype: 'combo',
				ref: 'importanceCombo',
				width: 100,
				store: {
					xtype: 'jsonstore',
					fields: [ 'name', 'value' ],
					data: Zarafa.common.data.ImportanceFlags.flags
				},
				mode: 'local',
				triggerAction: 'all',
				displayField: 'name',
				valueField: 'value',
				lazyInit: false,
				forceSelection: true,
				editable: false,
				listeners: {
					select: function() { this.isModified = true; },
					scope: this
				}
			}]
		});

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

	/**
	 * Apply an action onto the DataView, this will parse the condition and show
	 * the contents in a user-friendly way to the user.
	 * @param {Zarafa.common.rules.data.ConditionFlags} conditionFlag The condition type
	 * which identifies the exact type of the condition.
	 * @param {Object} condition The condition to apply
	 */
	setCondition: function(conditionFlag, condition)
	{
		var importance = Zarafa.core.mapi.Importance['NORMAL'];

		if (condition) {
			importance = condition[1][Zarafa.core.mapi.Restrictions.VALUE]['PR_IMPORTANCE'];
		}

		this.importanceCombo.setValue(importance);
		Zarafa.common.rules.dialogs.ImportanceLink.superclass.setCondition.apply(this, arguments);
	},

	/**
	 * Obtain the condition as configured by the user
	 * @return {Object} The condition
	 */
	getCondition: function()
	{
		if (this.isModified !== true) {
			return this.condition;
		}

		var value = this.importanceCombo.getValue();
		var conditionFactory = container.getRulesFactoryByType(Zarafa.common.data.RulesFactoryType.CONDITION);
		var conditionDefinition = conditionFactory.getConditionById(this.conditionFlag);
		return conditionDefinition({value: value});
	}
});

Ext.reg('zarafa.importancelink', Zarafa.common.rules.dialogs.ImportanceLink);
Ext.namespace('Zarafa.common.rules.dialogs');

/**
 * @class Zarafa.common.rules.dialogs.NameBccLink
 * @extends Zarafa.common.rules.dialogs.BaseLink
 * @xtype zarafa.namebcclink
 *
 * Condition component for the {@link Zarafa.common.rules.data.ConditionFlags#NAME_BCC NAME_BCC}
 * condition Flag. This will not show anything to the user, but does generate a proper
 * restriction in {@link #getCondition}.
 */
Zarafa.common.rules.dialogs.NameBccLink = Ext.extend(Zarafa.common.rules.dialogs.BaseLink, {
  /**
   * Obtain the condition as configured by the user
   * @return {Object} The condition
   */
  getCondition: function()
  {
    if (this.isModified !== true) {
      return this.condition;
    }

    // Invalid conditionFlag
    if (this.conditionFlag !== Zarafa.common.rules.data.ConditionFlags.NAME_BCC) {
      return false;
    }
    var currCoreVersion = container.getVersion().getZCP();
    var versionCompare = container.versionRecord.versionCompare(currCoreVersion,"8.7.2");

    var recipMeValue = versionCompare === -1 ? false : true;
    var conditionFactory = container.getRulesFactoryByType(Zarafa.common.data.RulesFactoryType.CONDITION);
    var conditionDefinition = conditionFactory.getConditionById(this.conditionFlag);
    return conditionDefinition({value: recipMeValue});

  }
});

Ext.reg('zarafa.namebcclink', Zarafa.common.rules.dialogs.NameBccLink);
Ext.namespace('Zarafa.common.rules.dialogs');

/**
 * @class Zarafa.common.rules.dialogs.NameToCcLink
 * @extends Zarafa.common.rules.dialogs.BaseLink
 * @xtype zarafa.senttomelink
 *
 * Condition component for the {@link Zarafa.common.rules.data.ConditionFlags#NAME_TO_CC NAME_TO_CC}
 * condition Flag. This will not show anything to the user, but does generate a proper
 * restriction in {@link #getCondition}.
 */
Zarafa.common.rules.dialogs.NameToCcLink = Ext.extend(Zarafa.common.rules.dialogs.BaseLink, {
	/**
	 * Obtain the condition as configured by the user
	 * @return {Object} The condition
	 */
	getCondition: function()
	{
		if (this.isModified !== true) {
			return this.condition;
		}

		// Invalid conditionFlag
		if (this.conditionFlag !== Zarafa.common.rules.data.ConditionFlags.NAME_TO_CC) {
			return false;
		}

		var conditionFactory = container.getRulesFactoryByType(Zarafa.common.data.RulesFactoryType.CONDITION);
		var conditionDefinition = conditionFactory.getConditionById(this.conditionFlag);
		return conditionDefinition();
	}
});

Ext.reg('zarafa.nametocclink', Zarafa.common.rules.dialogs.NameToCcLink);
Ext.namespace('Zarafa.common.rules.dialogs');

/**
 * @class Zarafa.common.rules.dialogs.ReceivedAfterLink
 * @extends Zarafa.common.rules.dialogs.BaseLink
 * @xtype zarafa.receivedafterlink
 *
 * Condition component for the {@link Zarafa.common.rules.data.ConditionFlags#RECEIVED_AFTER RECEIVED_AFTER}
 * condition. This will allow the user to select a date and can generate a proper
 * condition for it.
 */
Zarafa.common.rules.dialogs.ReceivedAfterLink = Ext.extend(Zarafa.common.rules.dialogs.BaseLink, {
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			items: [{
				xtype: 'datefield',
				width: 120,
				ref: 'receivedDate',
				listeners: {
					select: function() { this.isModified = true; },
					scope: this
				}
			}]
		});

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

	/**
	 * Apply an action onto the DataView, this will parse the condition and show
	 * the contents in a user-friendly way to the user.
	 * @param {Zarafa.common.rules.data.ConditionFlags} conditionFlag The condition type
	 * which identifies the exact type of the condition.
	 * @param {Object} condition The condition to apply
	 */
	setCondition: function(conditionFlag, condition)
	{
		if (condition) {
			var date = condition[1][Zarafa.core.mapi.Restrictions.VALUE]['PR_MESSAGE_DELIVERY_TIME'];
			this.receivedDate.setValue(new Date(date * 1000));
		} else {
			this.receivedDate.setValue(new Date());
		}

		Zarafa.common.rules.dialogs.ReceivedAfterLink.superclass.setCondition.apply(this, arguments);
	},

	/**
	 * Obtain the condition as configured by the user
	 * @return {Object} The condition
	 */
	getCondition: function()
	{
		if (this.isModified !== true) {
			return this.condition;
		}

		var date = this.receivedDate.getValue();
		date.setHours(23);
		date.setMinutes(59);
		date.setSeconds(59);
		var value = date.getTime() / 1000;

		var conditionFactory = container.getRulesFactoryByType(Zarafa.common.data.RulesFactoryType.CONDITION);
		var conditionDefinition = conditionFactory.getConditionById(this.conditionFlag);
		return conditionDefinition({value: value});
	}
});

Ext.reg('zarafa.receivedafterlink', Zarafa.common.rules.dialogs.ReceivedAfterLink);
Ext.namespace('Zarafa.common.rules.dialogs');

/**
 * @class Zarafa.common.rules.dialogs.RulesConditionContainer
 * @extends Zarafa.common.rules.dialogs.BaseContainer
 * @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(Zarafa.common.rules.dialogs.BaseContainer, {

	/**
	 * Array which holds selected Conditions by user.
	 * This Array is changed by {@link #update}.
	 * @property
	 * @type Array
	 * @private
	 */
	conditions: [],

	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = Ext.apply(config || {},{
			id: 'rule-condition-container',
			addBtnName: _('Add condition'),
			removeBtnName: _('Remove condition')
		});

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

	/**
	 * Generic function to create containers in which a condition is represented. This consists of
	 * two 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
	 */
	createComboBoxContainer: 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 Zarafa.common.rules.dialogs.RulesConditionContainer.superclass.createComboBoxContainer.call(this, id, profileStore);
	},

	/**
	 * Function that can be used to remove a condition from a rule dialog.
	 * This will always remove the last condition.
	 * @private
	 */
	removeComboBoxContainer: function()
	{
		if (this.boxContainerCount > 1) {
			// if removed condition was at least / at most size condition then reset the size unit property in the record accordingly.
			var conditionBoxToRemove = this.get(this.items.getCount() - 2).get(0);
			var conditionFlag = conditionBoxToRemove.getValue();
			if (conditionFlag === Zarafa.common.rules.data.ConditionFlags.ATMOST_SIZE) {
				this.record.set('rule_msg_atmost_size_unit', '');
			} else if (conditionFlag === Zarafa.common.rules.data.ConditionFlags.ATLEAST_SIZE) {
				this.record.set('rule_msg_atleast_size_unit', '');
			}

			// 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.boxContainerCount--;

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

			this.doLayout();
		}
	},

	/**
	 * 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;
		var conditions = record.getConditions(this);

		// If conditions exist in record then only compare with config property conditions.
		var isConditionsChanged = conditions ? !conditions.equals(this.conditions) : false;
		if (contentReset || isConditionsChanged) {
			//update conditions config with the latest conditions.
			this.conditions = conditions;

			if (Ext.isEmpty(conditions)) {
				this.setBoxContainerCount(1);
				return;
			}

			// 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.setBoxContainerCount(count);

			var atleastSizeUnit = record.get('rule_msg_atleast_size_unit');
			var atmostSizeUnit = record.get('rule_msg_atmost_size_unit');

			var atleastSizes = atleastSizeUnit.split(';');
			var atmostSizes = atmostSizeUnit.split(';');

			for (var i = 0, len = conditions.length; i < len; i++) {
				// Apply the action to the corresponding container
				if (conditions[i]) {
					var conditionFlag = this.getConditionFlagFromCondition(conditions[i]);
					var isAtleastSizeCondition = conditionFlag === Zarafa.common.rules.data.ConditionFlags.ATLEAST_SIZE;
					var isAtmostSizeCondition = conditionFlag === Zarafa.common.rules.data.ConditionFlags.ATMOST_SIZE;
					if (isAtleastSizeCondition) {
						//sending sizeUnits values according to condition in order.
						this.applyCondition(this.get(i), conditions[i], atleastSizes.shift());
					} else if (isAtmostSizeCondition) {
						//sending sizeUnits values according to condition in order.
						this.applyCondition(this.get(i), conditions[i], atmostSizes.shift());
					} else {
						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;

		// initAtleastCond and initAtmostCond are flags for initial at least and at most conditions respectively.
		var initAtleastCond = true;
		var initAtmostCond = true;

		for (var i = 0; i < this.boxContainerCount; 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();
			}

			// For conditions Atleast and Atmost, for first time in loop 'rule_msg_atleast_size_unit' and
			// 'rule_msg_atmost_size_unit' props in record will be overwritten.
			if (activeItem.id.indexOf('atleastsize') >= 0) {
				activeItem.setSizeUnit(record, initAtleastCond);
				if (initAtleastCond) {
					initAtleastCond = false;
				}
			} else if (activeItem.id.indexOf('atmostsize') >= 0) {
				activeItem.setSizeUnit(record, initAtmostCond);
				if (initAtmostCond) {
					initAtmostCond = false;
				}
			}

			// 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);
		}
		record.setConditionsValid(conditionsValid);

		// Set latest Conditions in the record.
		this.updateConditionsInRecord(record, conditions);
	}
});

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

/**
 * @class Zarafa.common.rules.dialogs.RulesExceptionContainer
 * @extends Zarafa.common.rules.dialogs.BaseContainer
 * @xtype zarafa.rulesexceptionscontainer
 *
 * The container in which all exceptions can be edited. This container
 * can be expanded to include multiple exceptions, and is able to parse
 * the rules_conditions property of a {@link Zarafa.common.rules.data.RulesRecord rule}.
 */
Zarafa.common.rules.dialogs.RulesExceptionContainer = Ext.extend(Zarafa.common.rules.dialogs.BaseContainer, {
  /**
   * Array which holds selected Exceptions by user.
   * This Array is changed by {@link #update}.
   * @property
   * @type Array
   * @private
   */
  exceptions: [],

  /**
   * @constructor
   * @param {Object} config Configuration object
   */
  constructor: function(config)
  {
    config = Ext.apply(config || {},{
      id: 'rule-exception-container',
      addBtnName: _('Add exception'),
      removeBtnName: _('Remove exception'),
    });

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

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

    return Zarafa.common.rules.dialogs.RulesExceptionContainer.superclass.createComboBoxContainer.call(this, id, profileStore);
  },

  /**
   * The event handler for the {@link Ext.form.ComboBox#select} event for the combobox for
   * a particular action. This will 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
   */
  onComboSelect: function(combo, record, index)
  {
    // 'Remove exception' button would be disabled when there's no exceptions selected.
    // Enable 'Remove exception' button on select of any exception.
    // So, user can remove exception when there is only one exception.
    if (this.removeBtn.disabled) {
      this.removeBtn.setDisabled(false);
    }

    Zarafa.common.rules.dialogs.RulesExceptionContainer.superclass.onComboSelect.call(this, combo, record, index);
  },

  /**
   * 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;
    var exceptions = record.getExceptions(this);
    var isExceptionChanged = false;

    if (!this.exceptions && exceptions) {
      isExceptionChanged = true;
    } else if (this.exceptions && exceptions) {
      isExceptionChanged = !exceptions.equals(this.exceptions);
    }

    // If exceptions value has been changed only then update view.
    if (contentReset || isExceptionChanged) {
      this.exceptions = exceptions;

      // When the rule doesn't have any exception specified,
      // we will create an empty exception.
      if (Ext.isEmpty(exceptions)) {
        this.setBoxContainerCount(1);
        return;
      }

      // We have to ensure that there are sufficient exception fields
      // present in the container.
      var count = Math.max(1, exceptions.length);
      this.setBoxContainerCount(count);

      var atleastSizeUnit = record.get('rule_exception_atleast_size_unit');
      var atmostSizeUnit = record.get('rule_exception_atmost_size_unit');
      var atleastSizes = atleastSizeUnit.split(';');
      var atmostSizes = atmostSizeUnit.split(';');

      for (var i = 0, len = exceptions.length; i < len; i++) {
        var exceptionFlag = this.getExceptionFlagFromCondition(exceptions[i]);
        var isAtleastSizeException = exceptionFlag === Zarafa.common.rules.data.ConditionFlags.ATLEAST_SIZE;
        var isAtmostSizeException = exceptionFlag === Zarafa.common.rules.data.ConditionFlags.ATMOST_SIZE;
        if (isAtleastSizeException) {
          //sending sizeUnits values according to condition in order.
          this.applyException(this.get(i), exceptions[i], atleastSizes.shift());
        } else if (isAtmostSizeException) {
          //sending sizeUnits values according to condition in order.
          this.applyException(this.get(i), exceptions[i], atmostSizes.shift());
        } else {
          if (exceptions[i] && exceptions[i] !== Zarafa.common.rules.data.ExceptionsConstants.INVALID_EXCEPTION) {
            this.applyException(this.get(i), exceptions[i]);
          }
        }
      }
    }
  },

  /**
   * Function that can be used to remove a exception from a rule dialog.
   * This will always remove the last exception.
   * @private
   */
  removeComboBoxContainer: function()
  {
    if (this.boxContainerCount >= 1) {
      // if removed condition was atleast / atmost size condition then reset the size unit property in the record accordingly.
      var conditionBoxToRemove = this.get(this.items.getCount() - 2).get(0);
      var exceptionFlag = conditionBoxToRemove.getValue();
      if (exceptionFlag === Zarafa.common.rules.data.ConditionFlags.ATMOST_SIZE) {
        this.record.set('rule_exception_atmost_size_unit', '');
      } else if (exceptionFlag === Zarafa.common.rules.data.ConditionFlags.ATLEAST_SIZE) {
        this.record.set('rule_exception_atleast_size_unit', '');
      }

      this.remove(this.get(this.items.getCount() - 2));
      this.boxContainerCount--;
      // Exception is optional. So user can remove all Exceptions but
      // We need to add empty combo box if all the exceptions are removed.
      if (this.boxContainerCount < 1) {
        this.addComboBoxContainer();
      }
      this.doLayout();
    }
  },

  /**
   * Read a exception object as located in the {@link Zarafa.common.rules.data.RulesRecord Rule}
   * and convert it to the corresponding ExceptionFlag which properly represents the exception.
   * @param {Object} exception The exception which should be converted to a Exception Flag
   * @return {Zarafa.common.rules.data.ConditionFlags} The Exception Flag
   * @private
   */
  getExceptionFlagFromCondition: function(exception)
  {
    if (exception === Zarafa.common.rules.data.ExceptionsConstants.INVALID_EXCEPTION) {
      return Zarafa.common.rules.data.ExceptionsConstants.INVALID_EXCEPTION;
    }

    // We are using ConditionFlags as ExceptionFlags.
    var condition = exception[1];
    return this.getConditionFlagFromCondition(condition);
  },

  /**
   * 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 exceptions = [];
    var exceptionsValid = true;
    var RestrictionFactory = Zarafa.core.data.RestrictionFactory;

    // initAtleastException and initAtmostException are flags for initial atleast and atmost conditions respectively.
    var initAtleastException = true;
    var initAtmostException = true;

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

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

      // For exceptions Atleast and Atmost, for first time in loop 'rule_exception_atleast_size_unit' and
      // 'rule_exception_atmost_size_unit' props in record will be overwritten.
      if (activeItem.id.indexOf('atleastsize') >= 0) {
        activeItem.setSizeUnit(record, initAtleastException, true);
        if (initAtleastException) {
          initAtleastException = false;
        }
      } else if (activeItem.id.indexOf('atmostsize') >= 0) {
        activeItem.setSizeUnit(record, initAtmostException, true);
        if (initAtmostException) {
          initAtmostException = false;
        }
      }

      // If no exceptions exist then mark combobox invalid
      // and set value to {@Zarafa.common.rules.data.ExceptionsConstants.INVALID_EXCEPTION}.
      // Also if only one combobox available and exception value is null,
      // then we are not setting it invalid because exceptions are not mandatory.
      if (exception === false || (this.boxContainerCount > 1 && exception === null)) {
        combo.markInvalid();
        exceptionsValid = false;
        exception = Zarafa.common.rules.data.ExceptionsConstants.INVALID_EXCEPTION;
      } else if (this.boxContainerCount === 1 && exception === null) {
        break;
      }

      // If a proper condition exist only then convert it into exception.
      if (exception && exception !== Zarafa.common.rules.data.ExceptionsConstants.INVALID_EXCEPTION) {
        exception = RestrictionFactory.createResNot(exception);
      }

      // preparing final conditions by adding Exceptions after it.
      exceptions.push(exception);
    }

    // Only if any exception is invalid, setConditionsValid to false.
    // This will prevent unnecessary update
    // of 'rule_condition' property of {@link Zarafa.core.data.IPMRecord record}
    // Because this property is shared between Conditions and Exceptions.
    if(!exceptionsValid) {
      record.setConditionsValid(exceptionsValid);
    }

    // Set latest Exceptions in the the record.
    this.updateConditionsInRecord(record, exceptions, true);
  },

  /**
   * Load a Exception from a {@Link Zarafa.common.rules.data.RulesRecord} and apply it
   * onto the {@link Ext.Container} which was created by {@link #addComboBoxContainer}.
   * @param {Ext.Container} panel The container on which the exception will be loaded
   * @param {Object} exception The exception which should be loaded
   * @param {Zarafa.common.data.SizeUnits} sizeUnit selected size unit for the atleast and atmost size exception components.
   * @private
   */
  applyException: function(panel, exception, sizeUnit)
  {
    // Parsing original condition by removing NOT part of the condition.
    // And sending to {@link Zarafa.common.rules.dialogs.RuleConditionsContainer}
    Zarafa.common.rules.dialogs.RulesExceptionContainer.superclass.applyCondition.call(this, panel, exception[1], sizeUnit);
  }

});

Ext.reg('zarafa.rulesexceptionscontainer', Zarafa.common.rules.dialogs.RulesExceptionContainer);
Ext.namespace('Zarafa.common.rules.dialogs');

/**
 * @class Zarafa.common.rules.dialogs.RulesWordsEditContentPanel
 * @extends Zarafa.core.ui.ContentPanel
 * @xtype zarafa.ruleswordseditcontentpanel
 *
 * Content Panel to edit the {@link Zarafa.common.rules.data.ConditionFlags#SUBJECT_WORDS} condition
 * for a {@link Zarafa.common.rules.data.RulesRecord Rule}.
 */
Zarafa.common.rules.dialogs.RulesWordsEditContentPanel = Ext.extend(Zarafa.core.ui.ContentPanel, {
	/**
	 * @cfg {Ext.data.Store} store The store which contains the words on which
	 * is being filtered.
	 */
	store: undefined,

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

		// Add in some standard configuration data.
		Ext.applyIf(config, {
			// Override from Ext.Component
			xtype: 'zarafa.ruleswordseditcontentpanel',
			// Override from Ext.Component
			layout: 'fit',
			width: 500,
			height: 300,
			title: _('Specify Words or Phrases'),
			items: [{
				xtype: 'zarafa.ruleswordseditpanel',
				ref: 'rulesWordsEditPanel',
				store: config.store,
				buttons: [{
					text: _('Ok'),
					handler: this.onOk,
					scope: this
				},{
					text: _('Cancel'),
					handler: this.onCancel,
					scope: this
				}]
			}]
		});

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

	/**
	 * Event handler which is fired when the user pressed the
	 * 'Ok' button. This will call {@link #callback} and {@link #close}
	 * the content panel.
	 * @private
	 */
	onOk: function()
	{
		// In case word field still contain some word to add.
		if (this.rulesWordsEditPanel.onWordAdd() !== false) {
			if (Ext.isFunction(this.callback)) {
				this.callback.call(this.scope || this, this.store);
			}
			this.close();
		}
	},

	/**
	 * Event handler which is fired when the user pressed the
	 * 'Cancel' button. This will discard all changes and {@link #close}
	 * the content panel.
	 * @private
	 */
	onCancel: function()
	{
		this.close();
	}
});
Ext.reg('zarafa.ruleswordseditcontentpanel', Zarafa.common.rules.dialogs.RulesWordsEditContentPanel);
Ext.namespace('Zarafa.common.rules.dialogs');

/**
 * @class Zarafa.common.rules.dialogs.SensitivityLink
 * @extends Zarafa.common.rules.dialogs.BaseLink
 * @xtype zarafa.sensitivitylink
 *
 * Condition component for the {@link Zarafa.common.rules.data.ConditionFlags#IMPORTANCE IMPORTANCE}
 * condition. This will allow the user to select the preferred sensitivity and can generate a proper
 * condition for it.
 */
Zarafa.common.rules.dialogs.SensitivityLink = Ext.extend(Zarafa.common.rules.dialogs.BaseLink, {
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			items: [{
				xtype: 'combo',
				ref: 'sensitivityCombo',
				width: 100,
				store: {
					xtype: 'jsonstore',
					fields: [ 'name', 'value' ],
					data: Zarafa.common.data.SensitivityFlags.flags
				},
				mode: 'local',
				triggerAction: 'all',
				displayField: 'name',
				valueField: 'value',
				lazyInit: false,
				forceSelection: true,
				editable: false,
				listeners: {
					select: function() { this.isModified = true; },
					scope: this
				}
			}]
		});

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

	/**
	 * Apply an action onto the DataView, this will parse the condition and show
	 * the contents in a user-friendly way to the user.
	 * @param {Zarafa.common.rules.data.ConditionFlags} conditionFlag The condition type
	 * which identifies the exact type of the condition.
	 * @param {Object} condition The condition to apply
	 */
	setCondition: function(conditionFlag, condition)
	{
		var sensitivity = Zarafa.core.mapi.Sensitivity['NONE'];

		if (condition) {
			sensitivity = condition[1][Zarafa.core.mapi.Restrictions.VALUE]['PR_SENSITIVITY'];
		}

		this.sensitivityCombo.setValue(sensitivity);
		Zarafa.common.rules.dialogs.SensitivityLink.superclass.setCondition.apply(this, arguments);
	},

	/**
	 * Obtain the condition as configured by the user
	 * @return {Object} The condition
	 */
	getCondition: function()
	{
		if (this.isModified !== true) {
			return this.condition;
		}

		var value = this.sensitivityCombo.getValue();
		var conditionFactory = container.getRulesFactoryByType(Zarafa.common.data.RulesFactoryType.CONDITION);
		var conditionDefinition = conditionFactory.getConditionById(this.conditionFlag);

		return conditionDefinition({value: value});
	}
});

Ext.reg('zarafa.sensitivitylink', Zarafa.common.rules.dialogs.SensitivityLink);
Ext.namespace('Zarafa.common.rules.dialogs');

/**
 * @class Zarafa.common.rules.dialogs.SentCcMeLink
 * @extends Zarafa.common.rules.dialogs.BaseLink
 * @xtype zarafa.senttomelink
 *
 * Condition component for the {@link Zarafa.common.rules.data.ConditionFlags#SENT_TO_ME_ONLY SENT_TO_ME_ONLY}
 * condition Flag. This will not show anything to the user, but does generate a proper
 * restriction in {@link #getCondition}.
 */
Zarafa.common.rules.dialogs.SentCcMeLink = Ext.extend(Zarafa.common.rules.dialogs.BaseLink, {
	/**
	 * Obtain the condition as configured by the user
	 * @return {Object} The condition
	 */
	getCondition: function()
	{
		if (this.isModified !== true) {
			return this.condition;
		}

		// Invalid conditionFlag
		if (this.conditionFlag !== Zarafa.common.rules.data.ConditionFlags.SENT_CC_ME) {
			return false;
		}

		// Create a restriction
		var conditionFactory = container.getRulesFactoryByType(Zarafa.common.data.RulesFactoryType.CONDITION);
		var conditionDefinition = conditionFactory.getConditionById(this.conditionFlag);
		return conditionDefinition();
	}
});

Ext.reg('zarafa.sentccmelink', Zarafa.common.rules.dialogs.SentCcMeLink);
Ext.namespace('Zarafa.common.rules.dialogs');

/**
 * @class Zarafa.common.rules.dialogs.SentToLink
 * @extends Zarafa.common.rules.dialogs.BaseLink
 * @xtype zarafa.senttolink
 *
 * Condition component for the {@link Zarafa.common.rules.data.ConditionFlags#SENT_TO_ME SENT_TO_ME}
 * condition Flag. Which checks if the user is in the TO field.
 */
Zarafa.common.rules.dialogs.SentToLink = Ext.extend(Zarafa.common.rules.dialogs.BaseLink, {
	/**
	 * Obtain the condition as configured by the user
	 * @return {Object} The condition
	 */
	getCondition: function()
	{
		if (this.isModified !== true) {
			return this.condition;
		}

		// Invalid conditionFlag
		if (this.conditionFlag !== Zarafa.common.rules.data.ConditionFlags.SENT_TO_ME) {
			return false;
		}

		var conditionFactory = container.getRulesFactoryByType(Zarafa.common.data.RulesFactoryType.CONDITION);
		var conditionDefinition = conditionFactory.getConditionById(this.conditionFlag);
		return conditionDefinition();
	}
});

Ext.reg('zarafa.senttolink', Zarafa.common.rules.dialogs.SentToLink);
Ext.namespace('Zarafa.common.rules.dialogs');

/**
 * @class Zarafa.common.rules.dialogs.SentToMeLink
 * @extends Zarafa.common.rules.dialogs.BaseLink
 * @xtype zarafa.senttomelink
 *
 * Condition component for the {@link Zarafa.common.rules.data.ConditionFlags#SENT_TO_ME_ONLY SENT_TO_ME_ONLY}
 * condition Flag. This will not show anything to the user, but does generate a proper
 * restriction in {@link #getCondition}.
 */
Zarafa.common.rules.dialogs.SentToMeLink = Ext.extend(Zarafa.common.rules.dialogs.BaseLink, {
	/**
	 * Obtain the condition as configured by the user
	 * @return {Object} The condition
	 */
	getCondition: function()
	{
		if (this.isModified !== true) {
			return this.condition;
		}

		// Invalid conditionFlag
		if (this.conditionFlag !== Zarafa.common.rules.data.ConditionFlags.SENT_TO_ME_ONLY) {
			return false;
		}

		// Create a restriction
		var conditionFactory = container.getRulesFactoryByType(Zarafa.common.data.RulesFactoryType.CONDITION);
		var conditionDefinition = conditionFactory.getConditionById(this.conditionFlag);
		return conditionDefinition();
	}
});

Ext.reg('zarafa.senttomelink', Zarafa.common.rules.dialogs.SentToMeLink);
Ext.namespace('Zarafa.common.rules.ui');

/**
 * @class Zarafa.common.rules.ui.RulesGrid
 * @extends Zarafa.common.ui.grid.GridPanel
 * @xtype zarafa.rulesgrid
 *
 * {@link Zarafa.common.rules.ui.RulesGrid RulesGrid} will be used to display
 * rules information of the current user.
 */
Zarafa.common.rules.ui.RulesGrid = Ext.extend(Zarafa.common.ui.grid.GridPanel, {
	/**
	 * @constructor
	 * @param {Object} config Configuration structure
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			xtype: 'zarafa.rulesgrid',
			cls: 'k-rulesgrid',
			border: false,
			enableHdMenu: false,
			autoExpandColumn: 'rule_name',
			viewConfig: {
				emptyText: '<div class=\'emptytext\'>' + _('No rule exists') + '</div>'
			},
			loadMask: this.initLoadMask(),
			sm: new Ext.grid.RowSelectionModel({
				singleSelect: true
			}),
			columns: this.initColumnModel(),
			listeners: {
				rowdblclick: this.onRowDblClick,
				scope: this
			},
			tbar: this.initToolbar()
		});

		Zarafa.common.rules.ui.RulesGrid.superclass.constructor.call(this, config);
	},

	/**
	 * initialize events for the grid panel.
	 * @private
	 */
	initEvents: function()
	{
		Zarafa.common.rules.ui.RulesGrid.superclass.initEvents.call(this);

		// register event to enable/disable buttons based on selection
		this.mon(this.getSelectionModel(), 'selectionchange', this.onGridSelectionChange, this);
	},

	/**
	 * Creates a {@link Ext.Toolbar} config object to be used with {@link #tbar}.
	 * @return {Ext.Toolbar} toolbar config object
	 * @private
	 */
	initToolbar: function()
	{
		return [{
			xtype: 'button',
			text: _('New'),
			handler: this.onRuleAdd,
			ref: '../addButton',
			scope: this
		}, {
			xtype: 'button',
			text: _('Delete'),
			disabled: true,
			ref: '../removeButton',
			handler: this.onRuleRemove,
			scope: this
		}, {
			xtype: 'button',
			text: _('Edit'),
			disabled: true,
			ref: '../editButton',
			handler: this.onRuleEdit,
			scope: this
		}, {
			xtype: 'button',
			iconCls: 'zarafa-rules-sequence-up',
			tooltip: _('Move Up'),
			disabled: true,
			ref: '../upButton',
			handler: this.onRuleSequenceUp,
			scope: this
		}, {
			xtype: 'button',
			iconCls: 'zarafa-rules-sequence-down',
			tooltip: _('Move Down'),
			disabled: true,
			ref: '../downButton',
			handler: this.onRuleSequenceDown,
			scope: this
		},
			container.populateInsertionPoint('settings.rules.action.last')
		];
	},

	/**
	 * Creates a column model object, used in {@link #colModel} config
	 * @return {Ext.grid.ColumnModel} column model object
	 * @private
	 */
	initColumnModel: function()
	{
		return [{
			xtype: 'checkcolumn',
			dataIndex: 'rule_state',
			header: _('Active'),
			fixed: true,
			width: 60,
			headerCls: 'k-unsortable',
			sortable: false,
			// override renderer to show active state of rule
			renderer: this.ruleStateRenderer.createDelegate(this),
			// override processEvent so we can save the change in the record
			processEvent: this.onRuleStateColumnProcessEvent.createDelegate(this)
		}, {
			dataIndex: 'rule_state',
			header: _('Out of office'),
			headerCls: 'k-unsortable',
			sortable: false,
			fixed: true,
			width: 130,
			renderer: this.ruleOutOfOfficeStateRenderer
		},{
			id: 'rule_name',
			dataIndex: 'rule_name',
			header: _('Rule'),
			headerCls: 'k-unsortable',
			sortable: false,
			renderer: Zarafa.common.ui.grid.Renderers.text
		}];
	},

	/**
	 * Render the cell as Rule state, setting an Out of office icon in
	 * out of office column to indicate that rule is active when
	 * user is out of office rule state.
	 *
	 * @param {Object} value The data value for the cell.
	 * @param {Object} p An object with metadata
	 * @param {Ext.data.record} record The {Ext.data.Record} from which the data was extracted.
	 */
	ruleOutOfOfficeStateRenderer: function(value, p, record)
	{
		var stateOnlyWhenOOF = value & Zarafa.core.mapi.RuleStates.ST_ONLY_WHEN_OOF;

		if (stateOnlyWhenOOF === Zarafa.core.mapi.RuleStates.ST_ONLY_WHEN_OOF) {
			p.css += ' icon_rule_oof';
		}
		return '';
	},


	/**
	 * Render the cell as Rule state checkbox, function will call {@link Ext.ux.grid.CheckColumn#renderer CheckColumn#renderer}
	 * after setting boolean value for rule state.
	 *
	 * @param {Object} value The data value for the cell.
	 * @param {Object} p An object with metadata
	 * @param {Ext.data.record} record The {Ext.data.Record} from which the data was extracted.
	 */
	ruleStateRenderer: function(value, p, record)
	{
		value = ((value & Zarafa.core.mapi.RuleStates.ST_ENABLED) === Zarafa.core.mapi.RuleStates.ST_ENABLED);

		return Ext.ux.grid.CheckColumn.prototype.renderer.apply(this, arguments);
	},

	/**
	 * Function will be called when user toggles active/inactive state of rule.
	 * This will update the corresponding property to indicate active state of the rule.
	 * @param {String} name name of the event which triggered this function
	 * @param {Ext.EventObject} e event object
	 * @param {Zarafa.common.ui.grid.GridPanel} grid grid panel which holds this {@link Zarafa.common.ui.grid.ColumnModel ColumnModel}
	 * @param {Number} rowIndex index of the row which is toggled
	 * @param {Number} colIndex index of the column which is toggled
	 * @private
	 */
	onRuleStateColumnProcessEvent: function(name, e, grid, rowIndex, colIndex)
	{
		if(name === 'mousedown') {
			var record = grid.getStore().getAt(rowIndex);
			var rule_state = record.get('rule_state');

			if ((rule_state & Zarafa.core.mapi.RuleStates.ST_ENABLED) === Zarafa.core.mapi.RuleStates.ST_ENABLED) {
				record.set('rule_state', rule_state & ~Zarafa.core.mapi.RuleStates.ST_ENABLED);
			} else {
				record.set('rule_state', rule_state | Zarafa.core.mapi.RuleStates.ST_ENABLED);
			}
		}
	},

	/**
	 * Initialize the {@link Ext.grid.GridPanel.loadMask} field
	 * @return {Ext.LoadMask} The configuration object for {@link Ext.LoadMask}
	 * @private
	 */
	initLoadMask: function()
	{
		return {
			msg: _('Loading rules') + '...'
		};
	},

	/**
	 * Event handler for the {@link #viewready} event.
	 * This will {@link Ext.grid.RowSelectionModel#selectFirstRow select the first row} in the grid.
	 * @private
	 */
	onViewReady: function()
	{
		Zarafa.common.rules.ui.RulesGrid.superclass.onViewReady.apply(this, arguments);
		this.getSelectionModel().selectFirstRow();
	},

	/**
	 * Called when {@link Ext.grid.GridPanel#store store} on the {@link #field} is
	 * loading new data.
	 * This will {@link Ext.grid.RowSelectionModel#selectFirstRow select the first row} in the grid.
	 *
	 * @param {Zarafa.core.data.MAPIStore} store The store which being loaded.
	 * @param {Ext.data.Record[]} records The records which have been loaded from the store
	 * @param {Object} options The loading options that were specified (see {@link Ext.data.Store#load load} for details)
	 * @private
	 */
	onStoreLoad: function(store, records, options)
	{
		Zarafa.common.rules.ui.RulesGrid.superclass.onStoreLoad.apply(this, arguments);
		if (!this.getSelectionModel().hasSelection()) {
			this.getSelectionModel().selectFirstRow();
		}
	},

	/**
	 * Event handler which is fired when the {@link Zarafa.common.rules.ui.RulesGrid RulesGrid} is double clicked.
	 * @private
	 */
	onRowDblClick: function(grid, rowIndex)
	{
		// open rule edit dialog
		Zarafa.common.Actions.openRulesEditContent(grid.getStore().getAt(rowIndex));
	},

	/**
	 * Event handler will be called when selection in {@link Zarafa.common.rules.ui.RulesGrid RulesGrid}
	 * has been changed
	 * @param {Ext.grid.RowSelectionModel} selectionModel selection model that fired the event
	 * @private
	 */
	onGridSelectionChange: function(selectionModel)
	{
		var noSelection = (selectionModel.hasSelection() === false);

		this.removeButton.setDisabled(noSelection);
		this.editButton.setDisabled(noSelection);

		this.upButton.setDisabled(!selectionModel.hasPrevious());
		this.downButton.setDisabled(!selectionModel.hasNext());
	},

	/**
	 * Handler function will be called when user clicks on 'Add' button
	 * @private
	 */
	onRuleAdd: function()
	{
		// open rule edit dialog
		var ruleRecord = Zarafa.core.data.RecordFactory.createRecordObjectByCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_RULE);
		this.getStore().add(ruleRecord);

		Zarafa.common.Actions.openRulesEditContent(ruleRecord, { removeRecordOnCancel: true });
	},

	/**
	 * Handler function will be called when user clicks on 'Remove' button.
	 * @private
	 */
	onRuleRemove: function()
	{
		var store = this.getStore();
		var sm = this.getSelectionModel();

		var ruleRecord = sm.getSelected();
		if (!ruleRecord) {
			container.getNotifier().notify('error.rules', _('Error'), _('Please select a rule.'));
			return;
		}

		// remove rule from store
		store.remove(ruleRecord);
	},

	/**
	 * Handler function will be called when user clicks on 'Edit' button.
	 * @private
	 */
	onRuleEdit: function()
	{
		var ruleRecord = this.getSelectionModel().getSelected();
		if (!ruleRecord) {
			container.getNotifier().notify('error.rules', _('Error'), _('Please select a rule.'));
			return;
		}

		// open rule edit dialog
		Zarafa.common.Actions.openRulesEditContent(ruleRecord);
	},

	/**
	 * Handler function will be called when user clicks on 'Up' button
	 * This will determine which rules to swap and call {@link #swapRules}.
	 * @private
	 */
	onRuleSequenceUp: function()
	{
		var store = this.getStore();
		var sm = this.getSelectionModel();
		var rule = sm.getSelected();

		if (!rule) {
			container.getNotifier().notify('error.rules', _('Error'), _('Please select a rule.'));
			return;
		}

		if (!sm.hasPrevious()) {
			container.getNotifier().notify('error.rules', _('Error'), _('Rule already has highest priority.'));
			return;
		}

		// Start looking for the first sequence number which is lower then
		// the current sequence number. Note that we want the rule_sequence
		// which is closest to the current rule_sequence, hence the rule:
		//  rule.get('rule_sequence') > record.get('rule_sequence') > swapRule.get('rule_sequence')
		var swapRule;
		store.each(function(record) {
			if (rule.get('rule_sequence') > record.get('rule_sequence')) {
				if (!swapRule || record.get('rule_sequence') > swapRule.get('rule_sequence')) {
					swapRule = record;
				}
			}
		}, this);

		this.swapRules(rule, swapRule);
	},

	/**
	 * Handler function will be called when user clicks on 'Down' button
	 * This will determine which rules to swap and call {@link #swapRules}.
	 * @private
	 */
	onRuleSequenceDown: function()
	{
		var store = this.getStore();
		var sm = this.getSelectionModel();
		var rule = sm.getSelected();

		if (!rule) {
			container.getNotifier().notify('error.rules', _('Error'), _('Please select a rule.'));
			return;
		}

		if (!sm.hasNext()) {
			container.getNotifier().notify('error.rules', _('Error'), _('Rule already has lowest priority.'));
			return;
		}

		// Start looking for the first sequence number which is higher then
		// the current sequence number. Note that we want the rule_sequence
		// which is closest to the current rule_sequence, hence the rule:
		//  rule.get('rule_sequence') < record.get('rule_sequence') < swapRule.get('rule_sequence')
		var swapRule;
		store.each(function(record) {
			if (rule.get('rule_sequence') < record.get('rule_sequence')) {
				if (!swapRule || record.get('rule_sequence') < swapRule.get('rule_sequence')) {
					swapRule = record;
				}
			}
		}, this);

		this.swapRules(rule, swapRule);
	},

	/**
	 * Swap to rules by changing the 'rule_sequence' property
	 * for both rules, and {@link Ext.data.Store#sort sort}
	 * the {@link #store}.
	 * @param {Zarafa.common.rules.data.RulesRecord} a The first rule
	 * @param {Zarafa.common.rules.data.RulesRecord} b The second rule
	 * @private
	 */
	swapRules: function(a, b)
	{
		var aSeq = a.get('rule_sequence');
		var bSeq = b.get('rule_sequence');

		// Swap the 2 rules
		a.set('rule_sequence', bSeq);
		b.set('rule_sequence', aSeq);

		// Reapply the sorting, this will update the UI
		this.getStore().sort('rule_sequence', 'ASC');

		// Update the 'up'/'down' button
		var sm = this.getSelectionModel();
		this.upButton.setDisabled(!sm.hasPrevious());
		this.downButton.setDisabled(!sm.hasNext());
	}
});

Ext.reg('zarafa.rulesgrid', Zarafa.common.rules.ui.RulesGrid);
Ext.namespace('Zarafa.common.rules.ui');

/**
 * @class Zarafa.common.rules.ui.RulesContextMenu
 * @extends Zarafa.core.ui.menu.ConditionalMenu
 * @xtype zarafa.rulescontextmenu
 *
 * The RulesContextMenu is the context menu that is shown as submenu
 * of the {@link Zarafa.mail.ui.MailGridContextMenu MailGridContextMenu}.
 */
Zarafa.common.rules.ui.RulesContextMenu = Ext.extend(Zarafa.core.ui.menu.ConditionalMenu, {

	/**
	 * @cfg {Zarafa.common.rules.data.RulesStore} store store to use for adding rules
	 */
	store: undefined,

	/**
	 * @cfg {Zarafa.core.data.MAPIStore} store contains {@link #records} on which
	 * rules are going to apply.
	 */
	mailStore: undefined,

	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function (config)
	{
		var senderName, subject, mailStore;
		if (Array.isArray(config.records)) {
			var record = config.records[0];
			mailStore = record.getStore();
			senderName = record.get('sender_name');
			var sentRepresentingName = record.get('sent_representing_name');

			// In case of any mail which has been sent on behalf of other user then show the name of that user.
			if (sentRepresentingName && sentRepresentingName!== senderName) {
				senderName = record.get('sent_representing_name');
			}

			senderName = senderName.bold();
			subject = record.get('subject');
		} else {
			mailStore = config.records.getStore();
		}

		Ext.applyIf(config || {}, {
			xtype: 'zarafa.rulescontextmenu',
			defaults: {
				cls: 'k-rules-menu-item'
			},
			mailStore: mailStore,
			store: new Zarafa.common.rules.data.RulesStore({
				'storeEntryId': container.getHierarchyStore().getDefaultStore().get('store_entryid')
			}),
			items: [{
				xtype: 'zarafa.conditionalitem',
				text: String.format(_('Always move messages from {0}'), senderName),
				name: 'ruleForSender',
				handler: this.onCreateRuleForSender,
				beforeShow: this.onBeforeshowHandler,
				scope: this
			},{
				xtype: 'zarafa.conditionalitem',
				text: _('Always move messages that are sent to <recipients>'),
				name: 'ruleForRecipient',
				handler: this.onCreateRuleForRecipient,
				beforeShow: this.onBeforeshowHandler,
				scope: this
			},{
				text: _('Always move messages with this subject'),
				hidden: Ext.isEmpty(subject),
				handler: this.onCreateRuleForSubject,
				scope: this
			},{
				text: _('Create rule…'),
				handler: this.onCreateRule,
				scope: this
			}]
		});

		Zarafa.common.rules.ui.RulesContextMenu.superclass.constructor.call(this, config);
		this.store.on('save', this.onSaveRecord, this);
		this.mon(this.mailStore,'load', this.onLoad, this);
	},

	/**
	 * Event handler triggers before the item shows. Disable the item ruleForRecipient
	 * If there is no recipients available other than loggedIn user.
	 * If It's Report mail then disable ruleForSender and ruleForRecipient menu items.
	 *
	 * @param {Zarafa.core.ui.menu.ConditionalItem} item The item to enable/disable
	 * @param {Zarafa.core.data.IPMRecord[]} records The records which must be checked
	 * to see if the item must be enabled or disabled.
	 * @private
	 */
	onBeforeshowHandler: function(item, records)
	{
		var itemName = item.name;
		var record = records[0];
		var isReportMessageClass = record.isMessageClass('REPORT.IPM', true);

		// If It's Report mail then Don't show sender and recipient rules.
		if (isReportMessageClass) {
			item.setDisabled(true);
			return;
		}

		if (itemName === 'ruleForRecipient') {
			var ccRecipients = record.get('display_to');
			var toRecipients = record.get('display_cc');
			var bccRecipients = record.get('display_bcc');
			var ownerName = record.get('received_by_name');
			// If mail is in sentItems then receiver name will be empty.
			if (Ext.isEmpty(ownerName)) {
				ownerName = record.get('sender_name');
			}

			var totalRecipients = ccRecipients.split(';');
			totalRecipients = totalRecipients.concat(toRecipients.split(';'));
			totalRecipients = totalRecipients.concat(bccRecipients.split(';'));

			totalRecipients = totalRecipients.filter(function(recipient) {
				return !Ext.isEmpty(recipient) && recipient.indexOf(ownerName) < 0;
			});

			// If no one other than owner of mailbox is present in To and CC then disable this menu item.
			if (Ext.isEmpty(totalRecipients)) {
				item.setDisabled(true);
			}
		}
	},

	/**
	 * Show a {@link Zarafa.core.ui.notifier.Notifier Notification} to the user indicating that Rule
	 * was saved successfully.
	 */
	onSaveRecord: function()
	{
		container.getNotifier().notify('info.saved','Saved', 'Rule saved successfully');
	},

	/**
	 * Event handler which is called when the user selects the "Create rule…"
	 * item in the sub context menu. This will open the rule dialog for creating new rule.
	 * @private
	 */
	onCreateRule: function()
	{
		// FIXME: Need to hide context menu for the case where User keep on clicking the same context menu
		// while this handler is being executed.
		this.hide();

		var ruleRecord = this.createRuleRecord();

		this.store.add(ruleRecord);
		Zarafa.common.Actions.openRulesEditContent(ruleRecord, { removeRecordOnCancel: true , autoSave: true});
	},

	/**
	 * Event handler which is called when the user selects the 'Always move messages from'
	 * item in the sub context menu. This will open the rule dialog for creating new rule
	 * with condition as of type {@link Zarafa.common.rules.data.ConditionFlags.RECEIVED_FROM RECEIVED_FROM}
	 * and action as of type {@link Zarafa.common.rules.data.ActionFlags.MOVE MOVE}.
   *
   * @param {Ext.menu.Item} button which is clicked.
	 * @private
	 */
	onCreateRuleForSender: function(button)
	{
		// FIXME: Need to hide context menu for the case where User keep on clicking the same context menu
		// while this handler is being executed. This will create multiple conditions and actions in a Rule dialog.
		this.hide();

		var mailRecord = this.records[0];

		if (!mailRecord.isOpened()) {
			this.openRecord(mailRecord, button.name);
			return;
		}

		var ruleRecord = this.createRuleRecord();
		var userStore = mailRecord.getSubStore('reply-to');
		var conditionId = Zarafa.common.rules.data.ConditionFlags.RECEIVED_FROM;
		var actionId = Zarafa.common.rules.data.ActionFlags.MOVE;
		var definitions = this.getDefinitions(conditionId, actionId);

		var condition = definitions.conditionDefinition({store: userStore});
		var action = definitions.actionDefinition();
		ruleRecord.set('rule_condition', condition);
		ruleRecord.set('rule_actions', action);
		this.store.add(ruleRecord);

		Zarafa.common.Actions.openRulesEditContent(ruleRecord, { removeRecordOnCancel: true , autoSave: true});
	},

	/**
	 * Event handler which is called when the user selects the 'Always move messages that are sent to <recipient>'
	 * item in the sub context menu. This will open the rule dialog for creating new rule
	 * with condition as of type {@link Zarafa.common.rules.data.ConditionFlags.SENT_TO SENT_TO}
	 * and action as of type {@link Zarafa.common.rules.data.ActionFlags.MOVE MOVE}.
	 *
	 * @param {Ext.menu.Item} button which is clicked.
	 * @private
	 */
	onCreateRuleForRecipient: function(button)
	{
		//FIXME: Need to hide context menu for the case where User keep on clicking the same context menu
		// while this handler is being executed. This will create multiple conditions and actions in a Rule dialog.
		this.hide();

		var mailRecord = this.records[0];

		if (!mailRecord.isOpened()) {
			this.openRecord(mailRecord, button.name);
			return;
		}

		// Exclude owner of mailbox from recipients list
		var defaultUserEntryId = mailRecord.get('received_by_entryid');
		// If mail is in sentItems then owner will be sender.
		if (Ext.isEmpty(defaultUserEntryId)) {
			defaultUserEntryId = mailRecord.get('sender_entryid');
		}

		var userStore = new Zarafa.core.data.IPMRecipientStore();
		var recepientSubStore = mailRecord.getSubStore('recipients');
		recepientSubStore.each(function(recipient) {
			var recipientEntryId = recipient.get('entryid');
			if(!Zarafa.core.EntryId.compareEntryIds(defaultUserEntryId, recipientEntryId)) {
				var isUserAlreadyAdded = !Ext.isEmpty(userStore.getById(recipientEntryId));
				if(!isUserAlreadyAdded) {
					userStore.add(recipient.copy(recipientEntryId));
				}
			}
		});

		var ruleRecord = this.createRuleRecord();
		var conditionId = Zarafa.common.rules.data.ConditionFlags.SENT_TO;
		var actionId = Zarafa.common.rules.data.ActionFlags.MOVE;
		var definitions = this.getDefinitions(conditionId, actionId);

		var condition = definitions.conditionDefinition({store: userStore});
		var action = definitions.actionDefinition();

		ruleRecord.set('rule_condition', condition);
		ruleRecord.set('rule_actions', action);
		this.store.add(ruleRecord);

		Zarafa.common.Actions.openRulesEditContent(ruleRecord, { removeRecordOnCancel: true , autoSave: true});
	},

	/**
	 * Event handler which is called when the user selects the 'Always move messages with this subject'
	 * item in the sub context menu. This will open the rule dialog for creating new rule
	 * with condition as of type {@link Zarafa.common.rules.data.ConditionFlags.SUBJECT_WORDS SUBJECT_WORDS}
	 * and action as of type {@link Zarafa.common.rules.data.ActionFlags.MOVE MOVE}.
	 * @private
	 */
	onCreateRuleForSubject: function()
	{
		//FIXME: Need to hide context menu for the case where User keep on clicking the same context menu
		// while this handler is being executed. This will create multiple conditions and actions in a Rule dialog.
		this.hide();

		var mailRecord = this.records[0];
		var subject = mailRecord.get('subject');
		var wordStore = new Ext.data.Store({ fields: [ 'words' ] });
		wordStore.add(new Ext.data.Record({ words: subject }));

		var ruleRecord = this.createRuleRecord();
		var conditionId = Zarafa.common.rules.data.ConditionFlags.SUBJECT_WORDS;
		var actionId = Zarafa.common.rules.data.ActionFlags.MOVE;
		var definitions = this.getDefinitions(conditionId, actionId);

		var condition = definitions.conditionDefinition({store: wordStore});
		var action = definitions.actionDefinition();
		ruleRecord.set('rule_name', subject);
		ruleRecord.set('rule_condition', condition);
		ruleRecord.set('rule_actions', action);
		this.store.add(ruleRecord);

		Zarafa.common.Actions.openRulesEditContent(ruleRecord, {removeRecordOnCancel: true , autoSave: true});
	},

	/**
	 * General Function to get required condition and action definitions.
	 *
	 * @param {Zarafa.common.rules.data.ConditionFlags} conditionId for which we need definition function.
	 * @param {Zarafa.common.rules.data.ActionFlags} actionId for which we need definition function.
	 * @return {Object} returns Object that will contain condition and action definitions.
	 * @private
	 */
	getDefinitions: function(conditionId, actionId)
	{
		var conditionFactory = container.getRulesFactoryByType(Zarafa.common.data.RulesFactoryType.CONDITION);
		var conditionDefinition = conditionFactory.getConditionById(conditionId);

		var actionFactory = container.getRulesFactoryByType(Zarafa.common.data.RulesFactoryType.ACTION);
		var actionDefinition = actionFactory.getActionById(actionId);

		return {
			conditionDefinition: conditionDefinition,
			actionDefinition: actionDefinition
		};
	},

	/**
	 * General Function to create and get new {@link Zarafa.common.rules.data.RulesRecord RulesRecord}.
	 *
	 * @return {Zarafa.common.rules.data.RulesRecord} returns newly created Rule's Record.
	 * @private
	 */
	createRuleRecord: function() {
		return Zarafa.core.data.RecordFactory.createRecordObjectByCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_RULE);
	},

	/**
	 * Function which is used to open the mail record item and attach given handler for 'open' event with record's store.
	 *
	 * @param {Zarafa.core.data.IPMRecord} mailRecord which needs to be opened.
	 * @param {String} buttonName of clicked menu item.
	 * @private
	 */
	openRecord: function(mailRecord, buttonName)
	{
		var store = mailRecord.getStore();
		var handler = function(store, record) {
			if (!Zarafa.core.EntryId.compareEntryIds(mailRecord.get('entryid'), record.get('entryid'))) {
				return;
			}
			store.un('open', handler, this);

			switch (buttonName) {
				case 'ruleForSender':
					this.onCreateRuleForSender();
					break;
				case 'ruleForRecipient':
					this.onCreateRuleForRecipient();
					break;
			}
		};

		store.on('open', handler, this);
		mailRecord.open();
	}
});

Ext.reg('zarafa.rulescontextmenu', Zarafa.common.rules.ui.RulesContextMenu);
Ext.ns('Zarafa.common.searchfield.ui');

/**
 * @class Zarafa.common.searchfield.ui.SearchTextField
 * @extends Ext.form.TextField
 * @xtype zarafa.searchtextfield
 *
 * Token-based search input. The original {@code <input>} is hidden and replaced
 * by a composite container that renders KQL prefixes as visual chiplets with
 * inline value inputs. A tail input at the end accepts new text and auto-converts
 * KQL patterns (e.g. typing "from:") into chips.
 *
 * The hidden input always holds the full KQL query string so that
 * {@link #getValue} returns what the server expects.
 *
 * #dependsFile client/zarafa/common/searchfield/ui/SearchDropdownPanel.js
 */
Zarafa.common.searchfield.ui.SearchTextField = Ext.extend(Ext.form.TextField, {

	/**
	 * @cfg {String} searchIndicatorClass CSS class applied during active search.
	 */
	searchIndicatorClass: 'zarafa-tbar-loading',

	/**
	 * @cfg {Boolean} searchPanelRendered Whether the search content panel is open.
	 */
	searchPanelRendered: false,

	/**
	 * The dropdown panel instance.
	 * @property
	 * @type Zarafa.common.searchfield.ui.SearchDropdownPanel
	 */
	dropdownPanel: null,

	/**
	 * Whether the dropdown is being interacted with (prevents blur-hide).
	 * @property
	 * @type Boolean
	 */
	dropdownInteracting: false,

	/**
	 * Ordered array of token objects.
	 * Each token: { type: 'filter'|'operator'|'text', key: String, value: String }
	 * @property
	 * @type Array
	 */
	tokens: null,

	/**
	 * Index of the token currently being edited inline, or -1 if none.
	 * @property
	 * @type Number
	 */
	editingTokenIndex: -1,

	/**
	 * The visible container that replaces the hidden input.
	 * @property
	 * @type Ext.Element
	 */
	tokenWrapEl: null,

	/**
	 * The input at the end of the token container for new text.
	 * @property
	 * @type Ext.Element
	 */
	tailInputEl: null,

	/**
	 * Display labels for filter keys.
	 * @property
	 * @type Object
	 */
	filterLabels: null,

	/**
	 * Supported KQL field names.
	 * @property
	 * @type Array
	 */
	kqlFields: ['subject','from','to','cc','bcc','body','sender','attachment','category','unread','type','date'],

	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		Ext.apply(config, {
			validationEvent: false,
			validateOnBlur: false,
			cls: 'k-search-input',
			ref: 'searchTextField',
			enableKeyEvents: true
		});

		this.tokens = [];
		this.filterLabels = {
			'subject': _('Subject'),
			'from': _('From'),
			'to': _('To'),
			'cc': _('Cc'),
			'bcc': _('Bcc'),
			'body': _('Body'),
			'sender': _('Sender'),
			'attachment': _('Attachment'),
			'category': _('Category'),
			'unread': _('Unread'),
			'type': _('Type'),
			'date': _('Date')
		};

		this.addEvents(
			'beforestart',
			'start',
			'beforestop',
			'stop',
			/**
			 * @event chipchange
			 * Fired when filter/operator tokens are added or removed.
			 * @param {Zarafa.common.searchfield.ui.SearchTextField} field
			 * @param {Array} chips Current array of filter chip objects
			 */
			'chipchange'
		);

		Zarafa.common.searchfield.ui.SearchTextField.superclass.constructor.call(this, config);
	},

	/**
	 * @override
	 */
	initComponent: function()
	{
		Zarafa.common.searchfield.ui.SearchTextField.superclass.initComponent.call(this);
		this.on('afterrender', this.onAfterRenderSearch, this);
	},

	/**
	 * After the Ext field renders, hide its native input and build the
	 * composite token input on top of it.
	 * @private
	 */
	onAfterRenderSearch: function()
	{
		// Hide the native input — it becomes the KQL value store
		this.el.setDisplayed(false);

		// Build the visible token container as a sibling inside x-form-field-wrap
		this.tokenWrapEl = Ext.DomHelper.insertAfter(this.el.dom, {
			tag: 'div',
			cls: 'k-search-token-input',
			role: 'search',
			cn: [{
				tag: 'input',
				type: 'text',
				cls: 'k-search-tail-input',
				autocomplete: 'off',
				'aria-label': _('Search')
			},{
				tag: 'span',
				cls: 'k-search-submit-btn',
				title: _('Search'),
				role: 'button',
				'aria-label': _('Search'),
				html: '&#9166;'
			}]
		}, true);

		this.tailInputEl = this.tokenWrapEl.child('.k-search-tail-input');
		this.tailInputEl.set({ placeholder: _('Search\u2026') });

		// Search submit button
		this.submitBtnEl = this.tokenWrapEl.child('.k-search-submit-btn');
		this.submitBtnEl.on('click', function() {
			if (this.tailInputEl.dom.value.trim()) {
				this.commitTailInput();
			}
			this.hideDropdown();
			this.syncHiddenInput();
			this.fireEvent('chipchange', this, this.getFilterChips());
			var textValue = this.buildQuery().trim();
			var specialCharsFilter = /[#\$%\^\&*\)\(]/g;
			if (Ext.isEmpty(textValue) || textValue.match(specialCharsFilter)) {
				this.stopSearch();
			} else {
				this.onTriggerClick();
			}
		}, this);
		this.submitBtnEl.on('mousedown', function(e) { e.preventDefault(); }, this);
		this.submitBtnEl.set({ 'tabindex': '0' });
		this.submitBtnEl.on('keydown', function(e) {
			if (e.getKey() === e.ENTER || e.getKey() === e.SPACE) {
				e.preventDefault();
				this.submitBtnEl.fireEvent('click', e);
			}
		}, this);

		// Tail input events
		this.tailInputEl.on('focus', this.onSearchFocus, this);
		this.tailInputEl.on('blur', this.onSearchBlur, this);
		this.tailInputEl.on('keydown', this.onTailKeyDown, this);
		this.tailInputEl.on('input', this.onTailInput, this);

		// Click anywhere on the wrapper → focus tail (unless clicking a chip label, edit input, or submit)
		this.tokenWrapEl.on('click', function(e) {
			var t = Ext.fly(e.getTarget());
			if (!t.hasClass('k-search-chip-remove') &&
				!t.hasClass('k-search-chip-label') &&
				!t.hasClass('k-search-chip-edit') &&
				!t.hasClass('k-search-submit-btn')) {
				this.tailInputEl.focus();
			}
		}, this);

		// Create the dropdown panel
		this.dropdownPanel = new Zarafa.common.searchfield.ui.SearchDropdownPanel({
			searchTextField: this,
			searchFolderCombo: this.searchContainer.searchFolderCombo
		});

		// Prevent blur when interacting with the dropdown.
		// mousedown prevents the default focus-shift; we keep
		// dropdownInteracting true until the click handler has
		// finished and the focus has settled back inside the
		// token wrapper.
		// The panel is already rendered (renderTo: Ext.getBody()),
		// so bind directly on its element.
		this.dropdownPanel.el.on('mousedown', function(e) {
			e.preventDefault();
			this.dropdownInteracting = true;
		}, this);
		this.dropdownPanel.el.on('mouseup', function() {
			// Defer reset so that the click handler (which fires
			// after mouseup) can still run with the flag set.
			(function() {
				this.dropdownInteracting = false;
			}).defer(100, this);
		}, this);

		// If the hidden input already has a value (set via setValue before
		// afterrender), parse it into tokens now that the UI is ready.
		var existingValue = this.el.dom.value;
		if (existingValue) {
			this.tokens = this.parseQueryToTokens(existingValue);
			var remaining = this.getPlainTextRemainder(existingValue);
			this.renderTokens();
			if (this.tailInputEl) {
				this.tailInputEl.dom.value = remaining;
			}
			this.fireEvent('chipchange', this, this.getFilterChips());
		}
	},

	// ===================================================================
	// Value management
	// ===================================================================

	/**
	 * @override
	 * Returns the full KQL query from the hidden input.
	 */
	getValue: function()
	{
		return this.el ? this.el.dom.value : '';
	},

	/**
	 * @override
	 */
	getRawValue: function()
	{
		return this.el ? this.el.dom.value : '';
	},

	/**
	 * @override
	 * Parses the given value into tokens and renders them.
	 */
	setValue: function(v)
	{
		if (this.el) {
			this.el.dom.value = v || '';
		}
		if (this.tokenWrapEl) {
			this.tokens = this.parseQueryToTokens(v || '');
			var remaining = this.getPlainTextRemainder(v || '');
			this.renderTokens();
			if (this.tailInputEl) {
				this.tailInputEl.dom.value = remaining;
			}
			this.syncHiddenInput();
			this.fireEvent('chipchange', this, this.getFilterChips());
		}
		return this;
	},

	/**
	 * Returns an array of virtual token objects (type, date, unread)
	 * that aren't part of the KQL string but need to be transported.
	 * @return {Array}
	 */
	getVirtualTokens: function()
	{
		var result = [];
		for (var i = 0; i < this.tokens.length; i++) {
			var t = this.tokens[i];
			if (t.type !== 'filter') {
				continue;
			}
			var isVirtual = this.virtualFilterKeys.indexOf(t.key) !== -1;
			var isBooleanEmpty = this.booleanFilterKeys.indexOf(t.key) !== -1 && !t.value;
			if (isVirtual || isBooleanEmpty) {
				result.push({ type: 'filter', key: t.key, value: t.value });
			}
		}
		return result;
	},

	/**
	 * Merges virtual tokens into the current token array.
	 * Respects singleton and duplicate rules.
	 * @param {Array} virtualTokens
	 */
	applyVirtualTokens: function(virtualTokens)
	{
		if (!virtualTokens || !virtualTokens.length || !this.tokenWrapEl) {
			return;
		}
		// Batch insert — add tokens directly, then render+sync once
		for (var i = 0; i < virtualTokens.length; i++) {
			var vt = virtualTokens[i];
			var isSingleton = this.singletonFilterKeys.indexOf(vt.key) !== -1;

			// Check for existing token with same key
			var found = false;
			if (isSingleton) {
				for (var s = 0; s < this.tokens.length; s++) {
					if (this.tokens[s].type === 'filter' && this.tokens[s].key === vt.key) {
						this.tokens[s].value = vt.value || '';
						found = true;
						break;
					}
				}
			}

			// Check for duplicate: by key:value for value filters, by key alone for boolean filters
			if (!found) {
				var isBooleanKey = this.booleanFilterKeys.indexOf(vt.key) !== -1 && !vt.value;
				for (var d = 0; d < this.tokens.length; d++) {
					if (this.tokens[d].type === 'filter' && this.tokens[d].key === vt.key) {
						if (isBooleanKey || (vt.value && this.tokens[d].value === vt.value)) {
							found = true;
							break;
						}
					}
				}
			}

			if (!found) {
				var last = this.tokens.length > 0 ? this.tokens[this.tokens.length - 1] : null;
				if (last && last.type !== 'operator') {
					this.tokens.push({ type: 'operator', key: 'AND' });
				}
				this.tokens.push({ type: 'filter', key: vt.key, value: vt.value || '' });
			}
		}

		this.renderTokens();
		this.syncHiddenInput();
		this.fireEvent('chipchange', this, this.getFilterChips());
	},

	/**
	 * Virtual filter keys that are synced to toolbox panel controls
	 * rather than included in the KQL query string.
	 * @property
	 * @type Array
	 */
	virtualFilterKeys: ['unread', 'type', 'date'],

	/**
	 * Reconstructs the full KQL string from tokens + tail input.
	 * Virtual filter tokens (unread, type, date) are excluded since
	 * they are handled by the toolbox panel controls.
	 * @return {String}
	 */
	/**
	 * Filter keys that should be excluded from the KQL query when
	 * they have no value, because they are handled as boolean
	 * restrictions by the toolbox panel (e.g. "has attachments").
	 * @property
	 * @type Array
	 */
	booleanFilterKeys: ['attachment', 'unread'],

	buildQuery: function()
	{
		// First pass: mark virtual token indices
		var isVirtual = [];
		for (var i = 0; i < this.tokens.length; i++) {
			var t = this.tokens[i];
			var isVirtualKey = this.virtualFilterKeys.indexOf(t.key) !== -1;
			var isBooleanEmpty = t.type === 'filter' &&
				this.booleanFilterKeys.indexOf(t.key) !== -1 && !t.value;
			isVirtual[i] = (t.type === 'filter' && (isVirtualKey || isBooleanEmpty));
		}

		// Also mark operators that only connect virtual tokens
		for (var j = 0; j < this.tokens.length; j++) {
			if (this.tokens[j].type === 'operator') {
				var prevReal = false, nextReal = false;
				for (var p = j - 1; p >= 0; p--) {
					if (this.tokens[p].type !== 'operator') { prevReal = !isVirtual[p]; break; }
				}
				for (var n = j + 1; n < this.tokens.length; n++) {
					if (this.tokens[n].type !== 'operator') { nextReal = !isVirtual[n]; break; }
				}
				if (!prevReal || !nextReal) {
					isVirtual[j] = true;
				}
			}
		}

		// Second pass: build query from non-virtual tokens
		var parts = [];
		for (var k = 0; k < this.tokens.length; k++) {
			if (isVirtual[k]) {
				continue;
			}
			var tok = this.tokens[k];
			if (tok.type === 'filter') {
				var val = tok.value || '';
				if (val.indexOf(' ') !== -1) {
					val = '"' + val + '"';
				}
				parts.push(tok.key + ':' + val);
			} else if (tok.type === 'operator') {
				parts.push(tok.key);
			} else if (tok.type === 'text') {
				var tv = tok.value;
				if (tv.indexOf(' ') !== -1) {
					tv = '"' + tv + '"';
				}
				parts.push(tv);
			}
		}
		var tail = this.tailInputEl ? this.tailInputEl.dom.value.trim() : '';
		if (tail) {
			parts.push(tail);
		}
		return parts.join(' ');
	},

	/**
	 * Writes the current query into the hidden input and fires a
	 * synthetic DOM {@code input} event so external listeners
	 * (e.g. SearchToolBoxPanel) are notified.
	 * @private
	 */
	syncHiddenInput: function()
	{
		var query = this.buildQuery();
		this.el.dom.value = query;
		try {
			var evt = document.createEvent('Event');
			evt.initEvent('input', true, true);
			this.el.dom.dispatchEvent(evt);
		} catch (e) {}
	},

	// ===================================================================
	// Token parsing
	// ===================================================================

	/**
	 * Parses a KQL query string into an ordered array of tokens.
	 * Only field:value pairs and AND/OR operators become tokens;
	 * plain text is left for the tail input.
	 *
	 * @param {String} query
	 * @return {Array}
	 */
	parseQueryToTokens: function(query)
	{
		if (!query) {
			return [];
		}

		var tokens = [];
		var fieldsPattern = this.kqlFields.join('|');
		var regex = new RegExp(
			'\\b(' + fieldsPattern + '):(\"[^\"]*\"|\'[^\']*\'|\\S*)|\\b(AND|OR|NOT)\\b|(\"[^\"]*\"|\'[^\']*\')|\\S+', 'gi'
		);

		var match;
		while ((match = regex.exec(query)) !== null) {
			if (match[1]) {
				var key = match[1].toLowerCase();
				var val = match[2] || '';
				if (val.length >= 2 &&
					((val.charAt(0) === '"' && val.charAt(val.length - 1) === '"') ||
					 (val.charAt(0) === "'" && val.charAt(val.length - 1) === "'"))) {
					val = val.substring(1, val.length - 1);
				}
				tokens.push({ type: 'filter', key: key, value: val });
			} else if (match[3]) {
				tokens.push({ type: 'operator', key: match[3].toUpperCase() });
			} else {
				var text = match[0];
				if (text.length >= 2 &&
					((text.charAt(0) === '"' && text.charAt(text.length - 1) === '"') ||
					 (text.charAt(0) === "'" && text.charAt(text.length - 1) === "'"))) {
					text = text.substring(1, text.length - 1);
				}
				tokens.push({ type: 'text', value: text });
			}
		}

		return tokens;
	},

	/**
	 * Returns remaining plain text not captured as tokens.
	 * With the new chip-based model, all text is captured as tokens.
	 * @param {String} query
	 * @return {String}
	 */
	getPlainTextRemainder: function()
	{
		return '';
	},

	// ===================================================================
	// Token rendering
	// ===================================================================

	/**
	 * Re-renders the entire token container from {@link #tokens}.
	 * Inserts implicit AND chips between adjacent non-operator tokens.
	 * @private
	 */
	renderTokens: function()
	{
		if (!this.tokenWrapEl || !this.tailInputEl) {
			return;
		}

		// Remove everything except the tail input and submit button
		var dom = this.tokenWrapEl.dom;
		var submitDom = this.submitBtnEl ? this.submitBtnEl.dom : null;
		var children = dom.childNodes;
		var toRemove = [];
		for (var i = 0; i < children.length; i++) {
			if (children[i] !== this.tailInputEl.dom && children[i] !== submitDom) {
				toRemove.push(children[i]);
			}
		}
		for (var j = 0; j < toRemove.length; j++) {
			dom.removeChild(toRemove[j]);
		}

		var insertBefore = this.tailInputEl.dom;

		for (var k = 0; k < this.tokens.length; k++) {
			var token = this.tokens[k];

			// Implicit AND between adjacent non-operator tokens
			if (k > 0 && token.type !== 'operator' && this.tokens[k - 1].type !== 'operator') {
				dom.insertBefore(this.createOperatorChipEl('AND', true), insertBefore);
			}

			if (this.editingTokenIndex === k) {
				// Render inline edit input instead of chip
				dom.insertBefore(this.createEditInput(token, k), insertBefore);
			} else if (token.type === 'filter') {
				dom.insertBefore(this.createFilterChipEl(token, k), insertBefore);
			} else if (token.type === 'operator') {
				dom.insertBefore(this.createOperatorChipEl(token.key, false, k), insertBefore);
			} else if (token.type === 'text') {
				dom.insertBefore(this.createTextChipEl(token, k), insertBefore);
			}
		}

		// Placeholder: only show when no tokens exist
		this.tailInputEl.set({
			placeholder: this.tokens.length > 0 ? '' : _('Search\u2026')
		});
	},

	/**
	 * Creates a chip DOM element for a filter token.
	 * Shows "Label: Value" in a single chip. Clicking the label
	 * enters inline edit mode (unless the filter is virtual/boolean).
	 * @private
	 */
	createFilterChipEl: function(token, index)
	{
		var isVirtual = this.virtualFilterKeys.indexOf(token.key) !== -1;
		var isBooleanEmpty = this.booleanFilterKeys.indexOf(token.key) !== -1 && !token.value;
		var isEditable = !isVirtual && !isBooleanEmpty;

		var chip = document.createElement('span');
		chip.className = 'k-search-chip' +
			((isVirtual || isBooleanEmpty) ? ' k-search-chip-value' : ' k-search-chip-filter');
		chip.setAttribute('data-token-index', index);

		var label = document.createElement('span');
		label.className = 'k-search-chip-label';
		var keyLabel = this.filterLabels[token.key] || token.key;
		if (token.value) {
			var valLabels = this.valueLabels[token.key];
			var valLabel = (valLabels && valLabels[token.value]) || token.value;
			label.textContent = keyLabel + ': ' + valLabel;
		} else {
			label.textContent = keyLabel;
		}
		chip.appendChild(label);

		var remove = document.createElement('span');
		remove.className = 'k-search-chip-remove';
		remove.textContent = '\u00d7';
		chip.appendChild(remove);

		chip.addEventListener('mousedown', function(e) { e.preventDefault(); });

		var self = this;
		remove.addEventListener('click', function(e) {
			e.stopPropagation();
			self.removeTokenAt(index);
		});

		if (isEditable) {
			label.style.cursor = 'text';
			label.addEventListener('click', function() {
				self.startEditToken(index);
			});
		}

		return chip;
	},

	/**
	 * Human-readable labels for virtual filter values.
	 * @property
	 * @type Object
	 */
	valueLabels: {
		'type': {
			'mail': _('Mails'),
			'appointment': _('Appointments'),
			'contact': _('Contacts'),
			'task': _('Tasks'),
			'note': _('Notes')
		},
		'date': {
			'past_week': _('Past week'),
			'past_two_weeks': _('Past 2 weeks'),
			'past_month': _('Past month'),
			'past_six_month': _('Past 6 month'),
			'past_year': _('Past year')
		},
		'unread': {
			'true': _('Yes')
		}
	},

	/**
	 * Creates a chip for a plain text token.
	 * Clicking the label enters inline edit mode.
	 * @param {Object} token
	 * @param {Number} index
	 * @return {HTMLElement}
	 * @private
	 */
	createTextChipEl: function(token, index)
	{
		var chip = document.createElement('span');
		chip.className = 'k-search-chip k-search-chip-text';
		chip.setAttribute('data-token-index', index);

		var label = document.createElement('span');
		label.className = 'k-search-chip-label';
		label.textContent = token.value;
		label.style.cursor = 'text';
		chip.appendChild(label);

		var remove = document.createElement('span');
		remove.className = 'k-search-chip-remove';
		remove.textContent = '\u00d7';
		chip.appendChild(remove);

		chip.addEventListener('mousedown', function(e) { e.preventDefault(); });

		var self = this;
		remove.addEventListener('click', function(e) {
			e.stopPropagation();
			self.removeTokenAt(index);
		});

		label.addEventListener('click', function() {
			self.startEditToken(index);
		});

		return chip;
	},

	/**
	 * Creates a chip DOM element for a boolean operator.
	 * @param {String} operator 'AND' or 'OR'
	 * @param {Boolean} implicit True for auto-inserted AND chips
	 * @private
	 */
	/**
	 * Operator cycle order for click-to-toggle.
	 * @property
	 * @type Array
	 */
	operatorCycle: ['AND', 'OR', 'NOT'],

	createOperatorChipEl: function(operator, implicit, tokenIndex)
	{
		var chip = document.createElement('span');
		chip.className = 'k-search-chip k-search-chip-operator';
		if (implicit) {
			chip.className += ' k-search-chip-implicit';
		}

		var label = document.createElement('span');
		label.className = 'k-search-chip-label';
		label.textContent = operator;
		label.style.cursor = 'pointer';
		label.title = _('Click to cycle: AND \u2192 OR \u2192 NOT');
		chip.appendChild(label);

		var self = this;

		// Click label to cycle operator (works for both implicit and explicit)
		label.addEventListener('click', function(e) {
			e.stopPropagation();
			var cur = label.textContent;
			var idx = self.operatorCycle.indexOf(cur);
			var next = self.operatorCycle[(idx + 1) % self.operatorCycle.length];
			label.textContent = next;

			if (implicit) {
				// Implicit AND → promote to explicit token
				// Find the right token index: count non-operator tokens before this chip
				var chipsBefore = chip.parentNode.querySelectorAll('.k-search-chip');
				var pos = Array.prototype.indexOf.call(chipsBefore, chip);
				// Find adjacent tokens and insert operator between them
				// Count visible tokens to find insertion point
				var tokenIdx = 0;
				var insertAt = -1;
				var nodes = chip.parentNode.childNodes;
				for (var n = 0; n < nodes.length; n++) {
					if (nodes[n] === chip) {
						insertAt = tokenIdx;
						break;
					}
					if (nodes[n].classList &&
						(nodes[n].classList.contains('k-search-chip') ||
						 nodes[n].classList.contains('k-search-chip-edit')) &&
						!nodes[n].classList.contains('k-search-chip-implicit')) {
						tokenIdx++;
					}
				}
				if (insertAt >= 0 && insertAt <= self.tokens.length) {
					self.tokens.splice(insertAt, 0, { type: 'operator', key: next });
					self.renderTokens();
					self.syncHiddenInput();
					self.fireEvent('chipchange', self, self.getFilterChips());
				}
			} else if (typeof tokenIndex === 'number') {
				self.tokens[tokenIndex].key = next;
				self.syncHiddenInput();
			}
		});

		// Explicit operator chips get a remove button
		if (!implicit && typeof tokenIndex === 'number') {
			var remove = document.createElement('span');
			remove.className = 'k-search-chip-remove';
			remove.textContent = '\u00d7';
			chip.appendChild(remove);

			remove.addEventListener('click', function(e) {
				e.stopPropagation();
				self.removeTokenAt(tokenIndex);
			});
		}

		chip.addEventListener('mousedown', function(e) { e.preventDefault(); });

		return chip;
	},

	/**
	 * Starts inline editing of a chip at the given token index.
	 * @param {Number} index
	 * @private
	 */
	startEditToken: function(index)
	{
		this.editingTokenIndex = index;
		this.renderTokens();
		var editInput = this.tokenWrapEl.dom.querySelector('.k-search-chip-edit');
		if (editInput) {
			editInput.focus();
			editInput.setSelectionRange(editInput.value.length, editInput.value.length);
		}
	},

	/**
	 * Creates an inline edit input that replaces a chip during editing.
	 * @param {Object} token The token being edited
	 * @param {Number} index Token index
	 * @return {HTMLElement}
	 * @private
	 */
	createEditInput: function(token, index)
	{
		var input = document.createElement('input');
		input.type = 'text';
		input.className = 'k-search-chip-edit';

		// Build raw representation for editing
		if (token.type === 'filter') {
			var val = token.value || '';
			if (val.indexOf(' ') !== -1) {
				val = '"' + val + '"';
			}
			input.value = token.key + ':' + val;
		} else if (token.type === 'text') {
			var tv = token.value || '';
			if (tv.indexOf(' ') !== -1) {
				tv = '"' + tv + '"';
			}
			input.value = tv;
		} else if (token.type === 'operator') {
			input.value = token.key;
		}

		input.style.width = Math.max(4, input.value.length + 1) + 'ch';

		var self = this;

		input.addEventListener('input', function() {
			input.style.width = Math.max(4, input.value.length + 1) + 'ch';
			// Auto-commit on closing quote
			var text = input.value;
			if (text.length >= 2) {
				var lastChar = text.charAt(text.length - 1);
				if ((lastChar === '"' || lastChar === "'") && !self.isInsideQuotes(text)) {
					self.commitEdit(index);
					self.tailInputEl.focus();
					return;
				}
			}
		});

		input.addEventListener('keydown', function(e) {
			if (e.keyCode === 13) { // Enter — commit and search
				e.preventDefault();
				self.commitEdit(index);
				self.tailInputEl.focus();
				// Trigger search after commit
				self.syncHiddenInput();
				self.fireEvent('chipchange', self, self.getFilterChips());
				self.onTriggerClick();
			} else if (e.keyCode === 9) { // Tab — commit and move to tail
				e.preventDefault();
				self.commitEdit(index);
				self.tailInputEl.focus();
			} else if (e.keyCode === 27) { // Escape — cancel
				e.preventDefault();
				self.cancelEdit();
			} else if (e.keyCode === 32) { // Space — commit if not in quotes
				if (!self.isInsideQuotes(input.value)) {
					e.preventDefault();
					self.commitEdit(index);
					self.tailInputEl.focus();
				}
			}
		});

		input.addEventListener('blur', function() {
			setTimeout(function() {
				if (self.editingTokenIndex === index) {
					self.commitEdit(index);
				}
			}, 100);
		});

		input.addEventListener('focus', function() { self.onSearchFocus(); });

		return input;
	},

	/**
	 * Commits the inline edit — parses the edit input text and updates the token.
	 * @param {Number} index Token index
	 * @private
	 */
	commitEdit: function(index)
	{
		if (this.editingTokenIndex !== index) {
			return;
		}

		var editInput = this.tokenWrapEl.dom.querySelector('.k-search-chip-edit');
		if (!editInput) {
			this.editingTokenIndex = -1;
			this.renderTokens();
			return;
		}

		var text = editInput.value.trim();
		this.editingTokenIndex = -1;

		if (!text) {
			this.removeTokenAt(index);
			return;
		}

		var parsed = this.parseInputText(text);
		this.tokens[index] = parsed;

		this.renderTokens();
		this.syncHiddenInput();
		this.fireEvent('chipchange', this, this.getFilterChips());
	},

	/**
	 * Cancels the inline edit — restores the chip unchanged.
	 * @private
	 */
	cancelEdit: function()
	{
		this.editingTokenIndex = -1;
		this.renderTokens();
		this.tailInputEl.focus();
	},

	/**
	 * Parses a single input text string into a token object.
	 * @param {String} text
	 * @return {Object} token
	 * @private
	 */
	parseInputText: function(text)
	{
		// Strip outer quotes
		var raw = text;
		if (raw.length >= 2 &&
			((raw.charAt(0) === '"' && raw.charAt(raw.length - 1) === '"') ||
			 (raw.charAt(0) === "'" && raw.charAt(raw.length - 1) === "'"))) {
			raw = raw.substring(1, raw.length - 1);
		}

		// Check for operator
		var upper = raw.toUpperCase();
		if (upper === 'AND' || upper === 'OR' || upper === 'NOT') {
			return { type: 'operator', key: upper };
		}

		// Check for field:value
		var colonIdx = raw.indexOf(':');
		if (colonIdx > 0) {
			var key = raw.substring(0, colonIdx).toLowerCase();
			if (this.kqlFields.indexOf(key) !== -1) {
				var value = raw.substring(colonIdx + 1);
				if (value.length >= 2 &&
					((value.charAt(0) === '"' && value.charAt(value.length - 1) === '"') ||
					 (value.charAt(0) === "'" && value.charAt(value.length - 1) === "'"))) {
					value = value.substring(1, value.length - 1);
				}
				return { type: 'filter', key: key, value: value };
			}
		}

		// Plain text
		return { type: 'text', value: raw };
	},

	/**
	 * Checks whether the given text has unmatched quotes (i.e. cursor is inside quotes).
	 * @param {String} text
	 * @return {Boolean}
	 * @private
	 */
	isInsideQuotes: function(text)
	{
		var dq = 0, sq = 0;
		for (var i = 0; i < text.length; i++) {
			if (text.charAt(i) === '"') { dq++; }
			else if (text.charAt(i) === "'") { sq++; }
		}
		return (dq % 2 === 1) || (sq % 2 === 1);
	},

	/**
	 * Commits the current tail input text as one or more tokens.
	 * Called when Space, Tab, or closing quote triggers chip creation.
	 * @private
	 */
	commitTailInput: function()
	{
		var text = this.tailInputEl.dom.value.trim();
		if (!text) {
			return;
		}

		this.tailInputEl.dom.value = '';

		var parsed = this.parseInputText(text);
		this.tokens.push(parsed);

		this.renderTokens();
		this.syncHiddenInput();
		this.fireEvent('chipchange', this, this.getFilterChips());
	},

	// ===================================================================
	// Token manipulation
	// ===================================================================

	/**
	 * Removes the token at the given index, cleans up orphaned operators,
	 * and re-renders.
	 * @param {Number} index
	 * @param {Boolean} silent When true, suppress focus changes (used by panel sync)
	 */
	removeTokenAt: function(index, silent)
	{
		if (index < 0 || index >= this.tokens.length) {
			return;
		}

		this.tokens.splice(index, 1);

		// Clean up orphaned operators: leading operators, trailing operators,
		// and consecutive operators (keep the first one).
		this.cleanupOperators();

		this.renderTokens();
		this.syncHiddenInput();
		this.fireEvent('chipchange', this, this.getFilterChips());
		if (!silent) {
			this.focusTailSilent();
		}

		// If everything is empty, stop the active search to clear results
		if (this.tokens.length === 0 && !this.tailInputEl.dom.value.trim()) {
			this.stopSearch();
		}
	},

	/**
	 * Removes orphaned operators from the token array:
	 * - Leading operators (no filter/text before them)
	 * - Trailing operators (no filter/text after them)
	 * - Consecutive operators (keep first, remove rest)
	 * @private
	 */
	cleanupOperators: function()
	{
		// Remove leading operators
		while (this.tokens.length > 0 && this.tokens[0].type === 'operator') {
			this.tokens.splice(0, 1);
		}

		// Remove trailing operators
		while (this.tokens.length > 0 && this.tokens[this.tokens.length - 1].type === 'operator') {
			this.tokens.splice(this.tokens.length - 1, 1);
		}

		// Remove consecutive operators (keep first)
		for (var i = this.tokens.length - 1; i > 0; i--) {
			if (this.tokens[i].type === 'operator' && this.tokens[i - 1].type === 'operator') {
				this.tokens.splice(i, 1);
			}
		}
	},

	/**
	 * Filter keys where only one instance makes sense.
	 * A new insert replaces the existing one.
	 * @property
	 * @type Array
	 */
	singletonFilterKeys: ['date', 'unread'],

	/**
	 * Adds a new filter chip (called from the dropdown or panel sync).
	 * @param {String} filterKey
	 * @param {String} value Optional preset value for the filter
	 * @param {Boolean} silent When true, suppress focus changes (used by panel sync)
	 */
	insertFilter: function(filterKey, value, silent)
	{
		var isVirtual = this.virtualFilterKeys.indexOf(filterKey) !== -1;
		var isBooleanFilter = this.booleanFilterKeys.indexOf(filterKey) !== -1;

		// Singleton filters: replace existing token with same key
		if (this.singletonFilterKeys.indexOf(filterKey) !== -1) {
			for (var s = this.tokens.length - 1; s >= 0; s--) {
				if (this.tokens[s].type === 'filter' && this.tokens[s].key === filterKey) {
					this.tokens[s].value = value || '';
					this.renderTokens();
					this.syncHiddenInput();
					this.fireEvent('chipchange', this, this.getFilterChips());
					if (!silent) {
						this.focusTailSilent();
					}
					return;
				}
			}
		}

		// Duplicate prevention for value-based filters (e.g. type:mail)
		if (value && isVirtual) {
			for (var d = 0; d < this.tokens.length; d++) {
				if (this.tokens[d].type === 'filter' &&
					this.tokens[d].key === filterKey &&
					this.tokens[d].value === value) {
					return;
				}
			}
		}

		// For regular filters with no value from dropdown: put key: in tail for user to type value
		if (!isVirtual && !isBooleanFilter && !value && !silent) {
			this.tailInputEl.dom.value = filterKey + ':';
			this.tailInputEl.focus();
			var len = this.tailInputEl.dom.value.length;
			this.tailInputEl.dom.setSelectionRange(len, len);
			return;
		}

		// Auto-insert an explicit AND between adjacent non-operator tokens
		var last = this.tokens.length > 0 ? this.tokens[this.tokens.length - 1] : null;
		if (last && last.type !== 'operator') {
			this.tokens.push({ type: 'operator', key: 'AND' });
		}
		this.tokens.push({ type: 'filter', key: filterKey, value: value || '' });
		this.renderTokens();
		this.syncHiddenInput();
		this.fireEvent('chipchange', this, this.getFilterChips());
		if (!silent) {
			this.focusTailSilent();
		}
	},

	/**
	 * Adds a boolean operator chip (called from the dropdown).
	 * @param {String} operator 'AND' or 'OR'
	 */
	insertOperator: function(operator)
	{
		this.tokens.push({ type: 'operator', key: operator });
		this.renderTokens();
		this.syncHiddenInput();
		this.fireEvent('chipchange', this, this.getFilterChips());
		this.tailInputEl.focus();
	},

	/**
	 * Removes the first filter token that matches the given key.
	 * Called externally by SearchToolBoxPanel when a checkbox is unchecked.
	 * @param {String} filterKey
	 */
	removeFilterChip: function(filterKey, silent)
	{
		for (var i = this.tokens.length - 1; i >= 0; i--) {
			if (this.tokens[i].type === 'filter' && this.tokens[i].key === filterKey) {
				this.removeTokenAt(i, silent);
				return;
			}
		}
	},

	/**
	 * Checks if a filter chip is preceded by a NOT operator.
	 * @param {String} filterKey The filter key to check
	 * @return {Boolean} True if the filter is negated
	 */
	isFilterNegated: function(filterKey)
	{
		for (var i = 0; i < this.tokens.length; i++) {
			if (this.tokens[i].type === 'filter' && this.tokens[i].key === filterKey) {
				return (i > 0 &&
					this.tokens[i - 1].type === 'operator' &&
					this.tokens[i - 1].key === 'NOT');
			}
		}
		return false;
	},

	/**
	 * Removes the NOT operator before a filter chip, effectively
	 * un-negating it. The NOT becomes AND or is removed if at the start.
	 * @param {String} filterKey The filter key to un-negate
	 * @param {Boolean} silent When true, suppress focus changes
	 */
	removeFilterNegation: function(filterKey, silent)
	{
		for (var i = 0; i < this.tokens.length; i++) {
			if (this.tokens[i].type === 'filter' && this.tokens[i].key === filterKey) {
				if (i > 0 && this.tokens[i - 1].type === 'operator' &&
					this.tokens[i - 1].key === 'NOT') {
					// Change NOT to AND, or remove if it's the first operator
					if (i > 1) {
						this.tokens[i - 1].key = 'AND';
					} else {
						this.tokens.splice(i - 1, 1);
					}
					this.renderTokens();
					this.syncHiddenInput();
					this.fireEvent('chipchange', this, this.getFilterChips());
				}
				return;
			}
		}
	},

	/**
	 * Starts editing the last editable token.
	 * @private
	 */
	editLastToken: function()
	{
		for (var i = this.tokens.length - 1; i >= 0; i--) {
			var t = this.tokens[i];
			if (t.type === 'text' ||
				(t.type === 'filter' &&
				 this.virtualFilterKeys.indexOf(t.key) === -1 &&
				 !(this.booleanFilterKeys.indexOf(t.key) !== -1 && !t.value))) {
				this.startEditToken(i);
				return;
			}
		}
	},

	/**
	 * Focus the tail input without triggering the dropdown.
	 * Used by programmatic operations (chip sync, token removal).
	 * @private
	 */
	focusTailSilent: function()
	{
		this.suppressDropdown = true;
		this.tailInputEl.focus();
		this.suppressDropdown = false;
	},

	/**
	 * Returns an array of filter chip descriptors for external consumers.
	 * @return {Array} e.g. [{key:'from', label:'From', type:'filter'}, ...]
	 */
	getFilterChips: function()
	{
		var chips = [];
		for (var i = 0; i < this.tokens.length; i++) {
			if (this.tokens[i].type === 'filter') {
				// Check if preceded by a NOT operator
				var negated = (i > 0 &&
					this.tokens[i - 1].type === 'operator' &&
					this.tokens[i - 1].key === 'NOT');
				chips.push({
					key: this.tokens[i].key,
					value: this.tokens[i].value || '',
					label: this.filterLabels[this.tokens[i].key] || this.tokens[i].key,
					type: 'filter',
					negated: negated
				});
			}
		}
		return chips;
	},

	/**
	 * Returns active filter key strings.
	 * @return {Array}
	 */
	getActiveFilterKeys: function()
	{
		var keys = [];
		for (var i = 0; i < this.tokens.length; i++) {
			if (this.tokens[i].type === 'filter') {
				keys.push(this.tokens[i].key);
			}
		}
		return keys;
	},

	// ===================================================================
	// Tail input handling
	// ===================================================================

	/**
	 * Monitors the tail input for KQL patterns and auto-converts them
	 * to chips.
	 * @private
	 */
	onTailInput: function()
	{
		// Reset history highlight when user types
		if (this.dropdownPanel) {
			this.dropdownPanel.resetHistoryHighlight();
		}
		var text = this.tailInputEl.dom.value;

		// Auto-commit on closing quote
		if (text.length >= 2) {
			var lastChar = text.charAt(text.length - 1);
			if ((lastChar === '"' || lastChar === "'") && !this.isInsideQuotes(text)) {
				this.commitTailInput();
				return;
			}
		}

		// Auto-convert pasted content with multiple terms (contains spaces)
		if (text.indexOf(' ') !== -1 && text.indexOf(':') !== -1) {
			var parsed = this.parseQueryToTokens(text);
			if (parsed.length > 1) {
				this.tailInputEl.dom.value = '';
				for (var i = 0; i < parsed.length; i++) {
					this.tokens.push(parsed[i]);
				}
				this.renderTokens();
				this.syncHiddenInput();
				this.fireEvent('chipchange', this, this.getFilterChips());
				return;
			}
		}

		// Plain text — just sync the hidden input
		this.syncHiddenInput();
	},

	/**
	 * Handles special keys in the tail input.
	 * @private
	 */
	onTailKeyDown: function(e)
	{
		if (e.getKey() === Ext.EventObject.ENTER) {
			// If a history row is highlighted, apply it instead
			if (this.dropdownPanel && this.dropdownPanel.isVisible()) {
				var entry = this.dropdownPanel.getHighlightedEntry();
				if (entry) {
					e.preventDefault();
					this.dropdownPanel.applyHistoryEntry(entry);
					return;
				}
			}
			// Commit any uncommitted tail text first
			if (this.tailInputEl.dom.value.trim()) {
				this.commitTailInput();
			}
			this.hideDropdown();
			this.syncHiddenInput();
			this.fireEvent('chipchange', this, this.getFilterChips());
			var textValue = this.buildQuery().trim();
			var specialCharsFilter = /[#\$%\^\&*\)\(]/g;
			if (Ext.isEmpty(textValue) || textValue.match(specialCharsFilter)) {
				this.stopSearch();
			} else {
				this.onTriggerClick();
			}
		} else if (e.getKey() === Ext.EventObject.ESC) {
			this.hideDropdown();
		} else if (e.getKey() === Ext.EventObject.DOWN) {
			if (this.dropdownPanel && this.dropdownPanel.isVisible()) {
				e.preventDefault();
				this.dropdownPanel.moveHistoryHighlight(1);
			}
		} else if (e.getKey() === Ext.EventObject.UP) {
			if (this.dropdownPanel && this.dropdownPanel.isVisible()) {
				e.preventDefault();
				this.dropdownPanel.moveHistoryHighlight(-1);
			}
		} else if (e.getKey() === Ext.EventObject.TAB) {
			// Tab always commits current text as a chip
			if (this.tailInputEl.dom.value.trim()) {
				e.preventDefault();
				this.commitTailInput();
			}
		} else if (e.getKey() === 32) { // SPACE
			var tailText = this.tailInputEl.dom.value;
			if (tailText.trim() && !this.isInsideQuotes(tailText)) {
				e.preventDefault();
				this.commitTailInput();
			}
		} else if (e.getKey() === Ext.EventObject.BACKSPACE) {
			if (!this.tailInputEl.dom.value && this.tokens.length > 0) {
				e.preventDefault();
				this.removeTokenAt(this.tokens.length - 1);
			}
		} else if (e.getKey() === Ext.EventObject.LEFT) {
			if (this.tailInputEl.dom.selectionStart === 0 && this.tokens.length > 0) {
				e.preventDefault();
				this.editLastToken();
			}
		}
	},

	// ===================================================================
	// Focus / blur / dropdown
	// ===================================================================

	/**
	 * @private
	 */
	onSearchFocus: function()
	{
		if (this.tokenWrapEl) {
			this.tokenWrapEl.addClass('k-search-focused');
		}
		if (!this.suppressDropdown) {
			this.showDropdown();
		}
	},

	/**
	 * Hides the dropdown only if focus leaves the entire token container.
	 * @private
	 */
	onSearchBlur: function()
	{
		if (this.dropdownInteracting) {
			return;
		}
		// Defer so we can check if focus moved to another element
		// inside the same token wrapper (value input → tail, etc.)
		(function() {
			var active = document.activeElement;
			if (this.tokenWrapEl && this.tokenWrapEl.dom.contains(active)) {
				return;
			}
			if (this.tokenWrapEl) {
				this.tokenWrapEl.removeClass('k-search-focused');
			}
			this.hideDropdown();
		}).defer(50, this);
	},

	/**
	 * Shows the dropdown aligned below the token container.
	 */
	showDropdown: function()
	{
		if (this.dropdownPanel && this.tokenWrapEl) {
			this.dropdownPanel.showBelow(this.tokenWrapEl);
		}
	},

	/**
	 * Hides the search dropdown panel.
	 */
	hideDropdown: function()
	{
		if (this.dropdownInteracting) {
			return;
		}
		if (this.dropdownPanel) {
			this.dropdownPanel.resetHistoryHighlight();
			this.dropdownPanel.hide();
		}
	},

	// ===================================================================
	// Search actions
	// ===================================================================

	/**
	 * Stops the active search.
	 * @protected
	 */
	stopSearch: function()
	{
		if (this.fireEvent('beforestop', this) !== false) {
			this.hideMask();
			this.fireEvent('stop', this);
		}
	},

	/**
	 * Starts a search.
	 * @protected
	 */
	onTriggerClick: function()
	{
		if (this.fireEvent('beforestart', this) !== false) {
			if (Ext.isEmpty(this.getValue())) {
				this.focus();
				return false;
			}
			var searchFolder = this.searchContainer.searchFolderCombo.getValue();

			if (!container.getHierarchyStore().getFolder(searchFolder)) {
				Ext.MessageBox.show({
					title: _('Folder does not exist'),
					msg: _("The folder you are searching through no longer exists. Please select another folder"),
					buttons: Ext.MessageBox.OK
				});
				return false;
			}

			// Save search to history
			if (this.dropdownPanel) {
				this.dropdownPanel.addToHistory(
					this.getValue(),
					this.tokens.slice(0),
					this.getVirtualTokens()
				);
			}

			if (!this.searchPanelRendered) {
				var componentType = Zarafa.core.data.SharedComponentType['common.search'];
				Zarafa.core.data.UIFactory.openLayerComponent(componentType, [], {
					'searchText': this.getValue(),
					'virtualTokens': this.getVirtualTokens(),
					'parentSearchField': this,
					'parentSearchFolderCombo': this.searchContainer.searchFolderCombo
				});
			}

			if (this.tokenWrapEl) {
				this.tokenWrapEl.addClass(this.searchIndicatorClass);
			}
			this.fireEvent('start', this);
		}
	},

	/**
	 * Hides the loading indicator.
	 */
	hideMask: function()
	{
		if (this.tokenWrapEl) {
			this.tokenWrapEl.removeClass([this.searchIndicatorClass]);
		}
	},

	// ===================================================================
	// Overrides for layout compatibility
	// ===================================================================

	/**
	 * @override — focus the tail input, not the hidden field.
	 */
	focus: function()
	{
		if (this.tailInputEl) {
			this.tailInputEl.focus();
		}
	},

	/**
	 * @override — propagate width to the visible token container.
	 */
	setWidth: function(w)
	{
		Zarafa.common.searchfield.ui.SearchTextField.superclass.setWidth.call(this, w);
		if (this.tokenWrapEl) {
			this.tokenWrapEl.setWidth(w);
		}
		return this;
	},

	/**
	 * @override
	 */
	onDestroy: function()
	{
		if (this.dropdownPanel) {
			this.dropdownPanel.destroy();
			this.dropdownPanel = null;
		}
		if (this.tokenWrapEl) {
			this.tokenWrapEl.remove();
			this.tokenWrapEl = null;
		}
		Zarafa.common.searchfield.ui.SearchTextField.superclass.onDestroy.call(this);
	}
});

Ext.reg('zarafa.searchtextfield', Zarafa.common.searchfield.ui.SearchTextField);
Ext.namespace('Zarafa.common.settings');

/**
 * @class Zarafa.common.settings.SettingsDelegateCategory
 * @extends Zarafa.settings.ui.SettingsCategory
 * @xtype zarafa.settingsdelegatecategory
 *
 * The delegates category for users which will allow the user to configure delegation settings.
 */
Zarafa.common.settings.SettingsDelegateCategory = Ext.extend(Zarafa.settings.ui.SettingsCategory, {
	// Insertion points for this class
	/**
	 * @insert context.settings.category.delegate
	 * Insertion point to register new {@link Zarafa.settings.ui.SettingsWidget widgets}
	 * for the {@link Zarafa.common.settings.SettingsDelegateCategory Delegate Category}.
	 * @param {Zarafa.common.settings.SettingsDelegateCategory} category The delegate
	 * category to which the widgets will be added.
	 */

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

		Ext.applyIf(config, {
			title: _('Delegates'),
			categoryIndex: 5,
			xtype: 'zarafa.settingsdelegatecategory',
			iconCls: 'zarafa-settings-category-delegate',
			items: [{
					xtype: 'zarafa.settingsdelegatewidget',
					settingsContext: config.settingsContext
				},
				container.populateInsertionPoint('context.settings.category.delegate', this)
			]
		});

		Zarafa.common.settings.SettingsDelegateCategory.superclass.constructor.call(this, config);
	},

	/**
	 * Called by superclass when the Category has been deselected and is hidden from the user,
	 * this will unregister the {@link #onBeforeSaveRules} event handler.
	 * @private
	 */
	onHide: function()
	{
		Zarafa.common.settings.SettingsDelegateCategory.superclass.onHide.apply(this, arguments);

		// Unregister the 'beforesave' event. This could be lingering when
		// 'savesettings' was fired but it was cancelled by one of the
		// event handlers.
		var store = this.get(0).getDelegateStore();
		this.mun(store, 'beforesave', this.onBeforeSaveDelegate, this);
	},

	/**
	 * Event handler for the
	 * {@link Zarafa.settings.SettingsContextModel ContextModel}#{@link Zarafa.settings.SettingsContextModel#beforesavesettings beforesavesettings}
	 * event. It will reset the {@link #savingElCounter} and his will register the event handler for
	 * {@link Zarafa.settings.SettingsModel#beforesave beforesave} event.
	 * @private
	 */
	onBeforeSaveSettingsModel: function()
	{
		Zarafa.common.settings.SettingsDelegateCategory.superclass.onBeforeSaveSettingsModel.apply(this, arguments);

		var store = this.get(0).getDelegateStore();
		this.mon(store, 'beforesave', this.onBeforeSaveDelegate, this, { single: true });
	},

	/**
	 * Event handler which is fired when the {@link Zarafa.common.delegates.data.DelegateStore Delegate Store}
	 * fires the 'beforesave' event. This will {@link #displaySavingMask show a notification} and register the
	 * event handlers for the completion of the save.
	 * @private
	 */
	onBeforeSaveDelegate: function()
	{
		this.displaySavingMask();

		var store = this.get(0).getDelegateStore();
		this.mon(store, 'save', this.onDelegateSave, this);
		this.mon(store, 'exception', this.onDelegateException, this);
	},

	/**
	 * Event handler which is fired when the {@link Zarafa.common.delegates.data.DelegateStore Delegate Store}
	 * fires the 'save' event indicating the successful save of the delegates. This will
	 * {@link #hideSavingMask hide the notification}.
	 * @private
	 */
	onDelegateSave: function()
	{
		this.hideSavingMask(true);

		var store = this.get(0).getDelegateStore();
		this.mun(store, 'save', this.onDelegateSave, this);
		this.mun(store, 'exception', this.onDelegateException, this);
	},

	/**
	 * Event handler which is fired when the {@link Zarafa.common.delegates.data.DelegateStore Delegate Store}
	 * fires the 'exception' event indicating a failing save of the delegates. This will
	 * {@link #hideSavingMask hide the notification}.
	 * @private
	 */
	onDelegateException: function()
	{
		this.hideSavingMask(false);

		var store = this.get(0).getDelegateStore();
		this.mun(store, 'save', this.onDelegateSave, this);
		this.mun(store, 'exception', this.onDelegateException, this);
	}
});

Ext.reg('zarafa.settingsdelegatecategory', Zarafa.common.settings.SettingsDelegateCategory);
Ext.namespace('Zarafa.common.settings');

/**
 * @class Zarafa.common.settings.SettingsDelegateWidget
 * @extends Zarafa.settings.ui.SettingsWidget
 * @xtype zarafa.settingsdelegatewidget
 *
 * The {@link Zarafa.settings.ui.SettingsWidget widget} for configuring
 * delegation options in the {@link Zarafa.common.settings.SettingsDelegateCategory delegate category}.
 */
Zarafa.common.settings.SettingsDelegateWidget = Ext.extend(Zarafa.settings.ui.SettingsWidget, {

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

		Ext.applyIf(config, {
			height: 400,
			title: _('Delegate settings'),
			cls: 'zarafa-settings-widget k-settings-nogap',
			xtype: 'zarafa.settingsdelegatewidget',
			layout: {
				// override from SettingsWidget
				type: 'fit'
			},
			items: [{
				xtype: 'zarafa.delegatespanel',
				ref: 'delegatesPanel'
			}]
		});

		Zarafa.common.settings.SettingsDelegateWidget.superclass.constructor.call(this, config);
	},

	/**
	 * @return {Zarafa.common.delegates.data.DelegateStore} The store which is holds all delegates
	 */
	getDelegateStore: function()
	{
		return this.delegatesPanel.store;
	},

	/**
	 * initialize events for the {@link Zarafa.common.settings.SettingsDelegateWidget SettingsDelegateWidget}.
	 * @private
	 */
	initEvents: function()
	{
		Zarafa.common.settings.SettingsDelegateWidget.superclass.initEvents.call(this);

		// listen to savesettings and discardsettings to save/discard delegation data
		var contextModel = this.settingsContext.getModel();

		this.mon(contextModel, 'savesettings', this.onSaveSettings, this);
		this.mon(contextModel, 'discardsettings', this.onDiscardSettings, this);

		this.mon(this.getDelegateStore(), {
			'remove': this.doStoreRemove,
			'update': this.doStoreUpdate,
			scope: this
		});
	},

	/**
	 * Event handler for the {@link Ext.data.Store#remove} event which is fired
	 * by the {@link Ext.data.Store} inside the {@link #delegatesPanel}.
	 * This will mark the {@link Zarafa.settings.SettingsContextModel} as
	 * {@link Zarafa.settings.SettingsContextModel#setDirty dirty}.
	 * @param {Ext.data.Store} store The store which fired the event
	 * @param {Ext.data.Record} record The record which was updated
	 * @private
	 */
	doStoreRemove: function(store, record)
	{
		if(!record.phantom) {
			this.settingsContext.getModel().setDirty();
		}
	},

	/**
	 * Event handler for the {@link Ext.data.Store#update} event which is fired
	 * by the {@link Ext.data.Store} inside the {@link #delegatesPanel}.
	 * This will mark the {@link Zarafa.settings.SettingsContextModel} as
	 * {@link Zarafa.settings.SettingsContextModel#setDirty dirty}.
	 * @param {Ext.data.Store} store The store which fired the event
	 * @param {Ext.data.Record} record The record which was updated
	 * @param {String} operation The update operation being performed.
	 * @private
	 */
	doStoreUpdate: function(store, record, operation)
	{
		if (operation !== Ext.data.Record.COMMIT) {
			this.settingsContext.getModel().setDirty();
		}
	},

	/**
	 * Event handler will be called when {@link Zarafa.settings.SettingsContextModel#savesettings} event is fired.
	 * This will relay this event to {@link Zarafa.common.delegates.ui.DelegatesPanel DelegatesPanel} so it can
	 * save delegation data.
	 * @private
	 */
	onSaveSettings: function()
	{
		this.delegatesPanel.saveChanges();
	},

	/**
	 * Event handler will be called when {@link Zarafa.settings.SettingsContextModel#discardsettings} event is fired.
	 * This will relay this event to {@link Zarafa.common.delegates.ui.DelegatesPanel DelegatesPanel} so it can
	 * discard current changes and reload delegation data from server.
	 * @private
	 */
	onDiscardSettings: function()
	{
		this.delegatesPanel.discardChanges();
	}
});

Ext.reg('zarafa.settingsdelegatewidget', Zarafa.common.settings.SettingsDelegateWidget);
Ext.namespace('Zarafa.common.settings');

/**
 * @class Zarafa.common.settings.SettingsDesktopNotificationsWidget
 * @extends Zarafa.settings.ui.SettingsWidget
 * @xtype zarafa.settingsdesktopnotificationswidget
 *
 * The {@link Zarafa.settings.ui.SettingsWidget widget} for enable/disable desktop notifications
 * in the {@link Zarafa.common.settings.SettingsDesktopNotificationsCategory SettingsDesktopNotificationsCategory}.
 */
Zarafa.common.settings.SettingsDesktopNotificationsWidget = Ext.extend(Zarafa.settings.ui.SettingsWidget, {

	/**
	 * Settings model instance which will be used to get current settings
	 * @property
	 * @type Zarafa.settings.SettingsModel
	 */
	model: undefined,

	/**
	 * This property will only be true if desktop notifications plugin's settings has been applied on UI.
	 * @property
	 * @type Boolean
	 */
	pluginSettingsApplied: false,

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

		Ext.applyIf(config, {
			title: _('Desktop Notifications Settings'),
			xtype: 'zarafa.settingsdesktopnotificationswidget',
			layout: {
				// override from SettingsWidget
				type: 'fit'
			},
			items: this.createPanelItems()
		});

		Zarafa.common.settings.SettingsDesktopNotificationsWidget.superclass.constructor.call(this, config);
	},

	/**
	 * Function will return object which will be used to create items for this settings widget
	 * @return {Array} Array containing configuration objects of child items
	 */
	createPanelItems: function()
	{
		return [{
			xtype: 'form',
			border: false,
			autoHeight: true,
			items: [{
				xtype: 'displayfield',
				hideLabel: true,
				value: _('We need permissions to enable desktop notifications.'),
				hidden: true,
				ref: '../requestPermissionText'
			}, {
				xtype: 'button',
				width: 200,
				text: _('Request Permissions'),
				handler: this.requestPermission,
				hidden: true,
				scope: this,
				hideLabel: true,
				ref: '../requestPermissionBtn',
				style: 'margin-bottom: 6px;'
			}, {
				xtype: 'checkbox',
				boxLabel: _('Enable desktop notifications for new mail'),
				name: 'zarafa/v1/main/notifier/info/newmail/value',
				handler: this.onChangeNotifications,
				scope: this,
				hideLabel: true,
				ref: '../newMailNotificationsCheck'
			}, {
				xtype: 'checkbox',
				boxLabel: _('Enable desktop notifications for reminders'),
				name: 'zarafa/v1/main/notifier/info/reminder/value',
				handler: this.onChangeNotifications,
				scope: this,
				hideLabel: true,
				ref: '../reminderNotificationsCheck'
			}, {
				xtype: 'zarafa.compositefield',
				defaultMargins: '0 0 0 0',
				plugins: [ 'zarafa.splitfieldlabeler' ],
				fieldLabel: _('{A} Auto-hide desktop notification after second(s) {B}'),
				labelWidth: 300,
				combineErrors: false,
				ref: '../autoHideFieldLabel',
				items: [{
					xtype: 'checkbox',
					labelSplitter: '{A}',
					name: 'zarafa/v1/main/desktop_notification/autohide_enable',
					pluginSettingName: 'zarafa/v1/plugins/desktopnotifications/autohide_enable',
					ref: '../../autoHideBox',
					boxLabel: '',
					hideLabel: true,
					checked: true,
					listeners: {
						change: this.onFieldChange,
						scope: this
					}
				}, {
					xtype: 'zarafa.spinnerfield',
					labelSplitter: '{B}',
					vtype: 'naturalInteger',
					name: 'zarafa/v1/main/desktop_notification/autohide_time',
					pluginSettingName: 'zarafa/v1/plugins/desktopnotifications/autohide_time',
					ref: '../../autoHideTimeSpinner',
					incrementValue: 1,
					defaultValue: 1,
					minValue: 1,
					width: 55,
					allowBlank: false,
					allowDecimals: false,
					allowNegative: false,
					listeners: {
						change: this.onFieldChange,
						scope: this
					},
					plugins: ['zarafa.numberspinner']
				}]
			}, {
				xtype: 'checkbox',
				boxLabel: _('Disable sound'),
				name: 'zarafa/v1/main/desktop_notification/disable_sound',
				pluginSettingName: 'zarafa/v1/plugins/desktopnotifications/disable_sound',
				ref: '../disableSound',
				listeners: {
					change: this.onFieldChange,
					scope: this
				},
				hideLabel: true
			}]
		}];
	},

	/**
	 * Function will be used to get permission from user to show desktop notifications
	 * @FIXME move code of changing setting to some other part
	 */
	requestPermission: function()
	{
		Zarafa.common.Actions.authorize(function(perm) {
			if (perm === 'granted') {
				// update ui
				this.update(this.model);
			}
		}.createDelegate(this));
	},

	/**
	 * Function will be used to enable/disable desktop notifications for new mail and reminder functionalities.
	 */
	onChangeNotifications: function(checkbox, checked)
	{
		if (!this.model) {
			return;
		}

		var notifier = container.getSettingsModel().get(checkbox.name, false, true) || 'popup';
		if (checked) {
			notifier = 'desktopnotifier';
		}

		// change setting for new mail/reminder notification
		if (this.model.get(checkbox.name) !== notifier) {
			this.model.set(checkbox.name, notifier);
		}
	},

	/**
	 * Update the view with the new values of the settings
	 * model. Called when opening the settings widget or when a new
	 * folder is selected.
	 *
	 * @param {Zarafa.settings.SettingsModel} settingsModel The settings to display.
	 */
	update: function(settingsModel)
	{
		this.model = settingsModel;

		/**************************************
		 * Permissions button and notifications
		 *************************************/

		// Enable/disable request permission button
		var hasPermission = Zarafa.common.Actions.hasPermission();
		this.requestPermissionBtn.setVisible(!hasPermission);
		this.requestPermissionText.setVisible(!hasPermission);

		// Enable/disable mail, reminder notification
		this.newMailNotificationsCheck.setDisabled(!hasPermission);
		this.reminderNotificationsCheck.setDisabled(!hasPermission);

		// Check/uncheck checkboxes for newmail and reminder functionality
		var newMailChecked = (this.model.get(this.newMailNotificationsCheck.name) === 'desktopnotifier');
		this.newMailNotificationsCheck.setValue(newMailChecked);
		var reminderChecked = (this.model.get(this.reminderNotificationsCheck.name) === 'desktopnotifier');
		this.reminderNotificationsCheck.setValue(reminderChecked);

		/**********
		 * Autohide
		 *********/

		// Disable the items if there are no permissions granted
		this.autoHideFieldLabel.setDisabled(!hasPermission);

		// Note: Desktop plugin settings needs to be applied in webapp main settings
		// due to desktop notification plugin has been included into webapp core.
		// Fetch plugin settings if any and apply them.
		this.pluginSettingsApplied = Ext.isDefined(settingsModel.get('zarafa/v1/plugins/desktopnotifications/enable'));
		var name = this.pluginSettingsApplied ? "pluginSettingName" : "name";
		var autoHideBoxEnabled = settingsModel.get(this.autoHideBox[name]);
		var spinnerValue = settingsModel.get(this.autoHideTimeSpinner[name]);
		var	soundCheckbox = settingsModel.get(this.disableSound[name]);

		// Set values in autoSave checkbox and textfield.
		this.autoHideBox.setValue(autoHideBoxEnabled);

		if (spinnerValue === 0 || !Ext.isDefined(spinnerValue)) {
			this.autoHideTimeSpinner.setValue(5); // Default 5 seconds
		} else {
			this.autoHideTimeSpinner.setValue(spinnerValue);
		}

		/********
		 * Sound
		 ********/
		// Disable sound checkbox
		this.disableSound.setDisabled(!hasPermission);

		// Show the correct value of the checkbox
		this.disableSound.setValue(soundCheckbox);
	},

	/**
	 * Called by the {@link Zarafa.settings.ui.SettingsCategory Category} when
	 * it has been called with {@link zarafa.settings.ui.SettingsCategory#updateSettings}.
	 * This is used to update the settings from the UI into the {@link Zarafa.settings.SettingsModel settings model}.
	 * @param {Zarafa.settings.SettingsModel} settingsModel The settings to update
	 */
	updateSettings: function(settingsModel)
	{
		var spinnerValue = this.autoHideTimeSpinner.getValue();
		if (spinnerValue === 0 || !Ext.isDefined(spinnerValue)) {
			spinnerValue = 1;
		}

		settingsModel.beginEdit();
		settingsModel.set(this.autoHideTimeSpinner.name, spinnerValue);

		// Note: Need to remove unnecessary plugin's settings and save the current settings in webapp main settings
		// so that there will not be any dependency of plugin settings.
		// And this will only run once to convert plugin's settings into main settings.
		if (this.pluginSettingsApplied) {
			settingsModel.remove('zarafa/v1/plugins/desktopnotifications', {type: 'deprecated'});
			settingsModel.set(this.autoHideBox.name, this.autoHideBox.getValue());
			settingsModel.set(this.disableSound.name, this.disableSound.getValue());
		}
		settingsModel.endEdit();
	},

	/**
	 * Event handler which is called when one of the textfields has been changed.
	 * This will apply the new value to the settings.
	 * @param {Ext.form.Field} field The field which has fired the event
	 * @param {String} value The new value
	 * @private
	 */
	onFieldChange: function(field, value)
	{
		if (this.model) {
			// FIXME: The settings model should be able to detect if
			// a change was applied
			var name = this.pluginSettingsApplied && Ext.isDefined(field["pluginSettingName"]) ? "pluginSettingName" : "name";
			if (this.model.get(field[name]) !== value) {
				this.model.set(field[name], value);
			}
		}
	}
});

Ext.reg('zarafa.settingsdesktopnotificationswidget', Zarafa.common.settings.SettingsDesktopNotificationsWidget);
Ext.namespace('Zarafa.common.settings');

/**
 * @class Zarafa.common.settings.SettingsNotificationsCategory
 * @extends Zarafa.settings.ui.SettingsCategory
 * @xtype zarafa.settingsnotificationscategory
 *
 * The desktop notification settings category that will allow users to enable/disable desktop notifications
 */
Zarafa.common.settings.SettingsNotificationsCategory = Ext.extend(Zarafa.settings.ui.SettingsCategory, {
	// Insertion points for this class
	/**
	 * @insert context.settings.category.notifications
	 * Insertion point to register new {@link Zarafa.settings.ui.SettingsWidget widgets}
	 * for the {@link Zarafa.common.settings.SettingsNotificationsCategory SettingsNotificationsCategory}.
	 * @param {Zarafa.common.settings.SettingsNotificationsCategory} category The notification
	 * category to which the widgets will be added.
	 */

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

		Ext.applyIf(config, {
			title: _('Notifications'),
			categoryIndex: 9996,
			xtype: 'zarafa.settingsnotificationscategory',
			iconCls: 'zarafa-settings-category-notifications',
			hidden: Ext.isIE,
			items: [{
					xtype: 'zarafa.settingsdesktopnotificationswidget',
					settingsContext: config.settingsContext
				},
				container.populateInsertionPoint('context.settings.category.notifications', this)
			]
		});

		Zarafa.common.settings.SettingsNotificationsCategory.superclass.constructor.call(this, config);
	}
});

Ext.reg('zarafa.settingsnotificationscategory', Zarafa.common.settings.SettingsNotificationsCategory);
Ext.namespace('Zarafa.common.settings');

/**
 * @class Zarafa.common.settings.SettingsRuleCategory
 * @extends Zarafa.settings.ui.SettingsCategory
 * @xtype zarafa.settingsrulecategory
 *
 * The rule category to modify inbox rules for user.
 */
Zarafa.common.settings.SettingsRuleCategory = Ext.extend(Zarafa.settings.ui.SettingsCategory, {
	// Insertion points for this class
	/**
	 * @insert context.settings.category.rules
	 * Insertion point to register new {@link Zarafa.settings.ui.SettingsWidget widgets}
	 * for the {@link Zarafa.common.settings.SettingsRuleCategory Rule Category}.
	 * @param {Zarafa.common.settings.SettingsRuleCategory} category The rule
	 * category to which the widgets will be added.
	 */

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

		Ext.applyIf(config, {
			title: _('Rules'),
			categoryIndex: 3,
			xtype: 'zarafa.settingsrulecategory',
			iconCls: 'zarafa-settings-category-rules',
			layout: 'fit',
			autoScroll: false,
			items: [{
					xtype: 'zarafa.settingsrulewidget',
					settingsContext: config.settingsContext
				},
				container.populateInsertionPoint('context.settings.category.rules', this)
			]
		});

		Zarafa.common.settings.SettingsRuleCategory.superclass.constructor.call(this, config);
	},

	/**
	 * Called by superclass when the Category has been deselected and is hidden from the user,
	 * this will unregister the {@link #onBeforeSaveRules} event handler.
	 * @private
	 */
	onHide: function()
	{
		Zarafa.common.settings.SettingsRuleCategory.superclass.onHide.apply(this, arguments);

		// Unregister the 'beforesave' event. This could be lingering when
		// 'savesettings' was fired but it was cancelled by one of the
		// event handlers.
		var store = this.get(0).getRulesStore();
		this.mun(store, 'beforesave', this.onBeforeSaveRules, this);
	},

	/**
	 * Event handler for the
	 * {@link Zarafa.settings.SettingsContextModel ContextModel}#{@link Zarafa.settings.SettingsContextModel#beforesavesettings beforesavesettings}
	 * event. It will register the 'beforesave' event on {@link Zarafa.common.rules.data.RulesStore Rules Store}.
	 * @private
	 */
	onBeforeSaveSettingsModel: function()
	{
		var store = this.get(0).getRulesStore();
		this.mon(store, 'beforesave', this.onBeforeSaveRules, this, { single: true });
	},

	/**
	 * Event handler which is fired when the {@link Zarafa.common.rules.data.RulesStore Rules Store}
	 * fires the 'beforesave' event. This will {@link #displaySavingMask show a notification} and register the
	 * event handlers for the completion of the save.
	 * @private
	 */
	onBeforeSaveRules: function()
	{
		this.displaySavingMask();

		var store = this.get(0).getRulesStore();
		this.mon(store, 'save', this.onRulesSave, this);
		this.mon(store, 'exception', this.onRulesException, this);
	},

	/**
	 * Event handler which is fired when the {@link Zarafa.common.rules.data.RulesStore Rules Store}
	 * fires the 'save' event indicating the successful save of the rules. This will
	 * {@link #hideSavingMask hide the notification}.
	 * @private
	 */
	onRulesSave: function()
	{
		this.hideSavingMask(true);

		var store = this.get(0).getRulesStore();
		this.mun(store, 'save', this.onRulesSave, this);
		this.mun(store, 'exception', this.onRulesException, this);
	},

	/**
	 * Event handler which is fired when the {@link Zarafa.common.rules.data.RulesStore Rules Store}
	 * fires the 'exception' event indicating a failing save of the rules. This will
	 * {@link #hideSavingMask hide the notification}.
	 * @private
	 */
	onRulesException: function()
	{
		this.hideSavingMask(false);

		var store = this.get(0).getRulesStore();
		this.mun(store, 'save', this.onRulesSave, this);
		this.mun(store, 'exception', this.onRulesException, this);
	}
});

Ext.reg('zarafa.settingsrulecategory', Zarafa.common.settings.SettingsRuleCategory);
Ext.namespace('Zarafa.common.settings');

/**
 * @class Zarafa.common.settings.SettingsRuleWidget
 * @extends Zarafa.settings.ui.SettingsWidget
 * @xtype zarafa.settingsrulewidget
 *
 * The {@link Zarafa.settings.ui.SettingsWidget widget} for configuring
 * delegation options in the {@link Zarafa.common.settings.SettingsRuleCategory rule category}.
 */
Zarafa.common.settings.SettingsRuleWidget = Ext.extend(Zarafa.settings.ui.SettingsWidget, {

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

		Ext.applyIf(config, {
			title: _('Rules'),
			xtype: 'zarafa.settingsrulewidget',
			cls: 'zarafa-settings-widget k-rules-widget k-settings-nogap',
			layout: {
				// override from SettingsWidget
				type: 'fit'
			},
			items: [{
				xtype: 'zarafa.rulespanel',
				ref: 'rulesPanel'
			}]
		});

		Zarafa.common.settings.SettingsRuleWidget.superclass.constructor.call(this, config);
	},

	/**
	 * @return {Zarafa.common.rules.data.RulesStore} The store which is holds all rules
	 */
	getRulesStore: function()
	{
		return this.rulesPanel.store;
	},

	/**
	 * initialize events for the {@link Zarafa.common.settings.SettingsDelegateWidget SettingsDelegateWidget}.
	 * @private
	 */
	initEvents: function()
	{
		Zarafa.common.settings.SettingsRuleWidget.superclass.initEvents.call(this);

		// listen to savesettings and discardsettings to save/discard rules data
		var contextModel = this.settingsContext.getModel();

		this.mon(contextModel, 'savesettings', this.onSaveSettings, this);
		this.mon(contextModel, 'discardsettings', this.onDiscardSettings, this);

		this.mon(this.getRulesStore(), {
			// Don't listen to the 'add' event,
			// when the UI adds a rule, it will fire
			// the 'update' event when the rule is
			// completely filled in.
			'remove': this.doStoreRemove,
			'update': this.doStoreUpdate,
			scope: this
		});
	},

	/**
	 * Event handler for the {@link Ext.data.Store#remove} event which is fired
	 * by the {@link Ext.data.Store} inside the {@link #rulesPanel}.
	 * This will mark the {@link Zarafa.settings.SettingsContextModel} as
	 * {@link Zarafa.settings.SettingsContextModel#setDirty dirty}.
	 * @param {Ext.data.Store} store The store which fired the event
	 * @param {Ext.data.Record} record The record which was updated
	 * @private
	 */
	doStoreRemove: function(store, record)
	{
		if (record.phantom !== true) {
			this.settingsContext.getModel().setDirty();
		}
	},

	/**
	 * Event handler for the {@link Ext.data.Store#update} event which is fired
	 * by the {@link Ext.data.Store} inside the {@link #rulesPanel}.
	 * This will mark the {@link Zarafa.settings.SettingsContextModel} as
	 * {@link Zarafa.settings.SettingsContextModel#setDirty dirty}.
	 * @param {Ext.data.Store} store The store which fired the event
	 * @param {Ext.data.Record} record The record which was updated
	 * @param {String} operation The update operation being performed.
	 * @private
	 */
	doStoreUpdate: function(store, record, operation)
	{
		if (operation !== Ext.data.Record.COMMIT) {
			this.settingsContext.getModel().setDirty();
		}
	},

	/**
	 * Event handler will be called when {@link Zarafa.settings.SettingsContextModel#savesettings} event is fired.
	 * This will relay this event to {@link Zarafa.common.delegates.ui.RulesPanel RulesPanel} so it can
	 * save rules data.
	 * @private
	 */
	onSaveSettings: function()
	{
		this.rulesPanel.saveChanges();
	},

	/**
	 * Event handler will be called when {@link Zarafa.settings.SettingsContextModel#discardsettings} event is fired.
	 * This will relay this event to {@link Zarafa.common.delegates.ui.RulesPanel RulesPanel} so it can
	 * discard current changes and reload rules data from server.
	 * @private
	 */
	onDiscardSettings: function()
	{
		this.rulesPanel.discardChanges();
	}
});

Ext.reg('zarafa.settingsrulewidget', Zarafa.common.settings.SettingsRuleWidget);
Ext.namespace('Zarafa.common.settings');

/**
 * @class Zarafa.common.settings.SettingsSendAsCategory
 * @extends Zarafa.settings.ui.SettingsCategory
 * @xtype zarafa.settingssendascategory
 *
 * The sendas category for users which will allow the user to configure send as settings.
 */
Zarafa.common.settings.SettingsSendAsCategory = Ext.extend(Zarafa.settings.ui.SettingsCategory, {
	// Insertion points for this class
	/**
	 * @insert context.settings.category.sendas
	 * Insertion point to register new {@link Zarafa.settings.ui.SettingsWidget widgets}
	 * for the {@link Zarafa.common.settings.SettingsSendAsCategory SendAs Category}.
	 * @param {Zarafa.common.settings.SettingsSendAsCategory} category The sendas
	 * category to which the widgets will be added.
	 */

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

		Ext.applyIf(config, {
			title: _('From Addresses'),
			categoryIndex: 6,
			xtype: 'zarafa.settingssendascategory',
			iconCls: 'zarafa-settings-category-sendas',
			items: [{
					xtype: 'zarafa.settingssendaswidget',
					settingsContext: config.settingsContext
				},
				container.populateInsertionPoint('context.settings.category.sendas', this)
			]
		});

		Zarafa.common.settings.SettingsSendAsCategory.superclass.constructor.call(this, config);
	}
});

Ext.reg('zarafa.settingssendascategory', Zarafa.common.settings.SettingsSendAsCategory);
Ext.namespace('Zarafa.common.settings');

/**
 * @class Zarafa.common.settings.SettingsSendAsWidget
 * @extends Zarafa.settings.ui.SettingsWidget
 * @xtype zarafa.settingssendaswidget
 *
 * The {@link Zarafa.settings.ui.SettingsWidget widget} for configuring
 * send as options in the {@link Zarafa.common.settings.SettingsSendAsCategory sendas category}.
 */
Zarafa.common.settings.SettingsSendAsWidget = Ext.extend(Zarafa.settings.ui.SettingsWidget, {

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

		Ext.applyIf(config, {
			height: 400,
			title: _('From Addresses settings'),
			cls: 'zarafa-settings-widget k-settings-nogap',
			xtype: 'zarafa.settingssendaswidget',
			layout: {
				// override from SettingsWidget
				type: 'fit'
			},
			items: [{
				xtype: 'zarafa.sendaspanel',
				ref: 'sendasPanel'
			}]
		});

		Zarafa.common.settings.SettingsSendAsWidget.superclass.constructor.call(this, config);
	},

	/**
	 * Returns the {@link Zarafa.core.data.IPMRecipientStore IPMRecipientStore} associated
	 * with this panel.
	 * @return {Zarafa.core.data.IPMRecipientStore} The store
	 */
	getStore: function()
	{
		return this.sendasPanel.getStore();
	},

	/**
	 * Initialize events for the panel.
	 * @private
	 */
	initEvents: function()
	{
		Zarafa.common.settings.SettingsSendAsWidget.superclass.initEvents.call(this);

		this.mon(this.getStore(), {
			'remove': this.doStoreRemove,
			'update': this.doStoreUpdate,
			'add': this.doStoreAdd,
			scope: this
		});
	},

	/**
	 * Called by the {@link Zarafa.settings.ui.SettingsCategory Category} when
	 * it has been called with {@link zarafa.settings.ui.SettingsCategory#update}.
	 * This is used to load the latest version of the settings from the
	 * {@link Zarafa.settings.SettingsModel} into the UI of this category.
	 * @param {Zarafa.settings.SettingsModel} settingsModel The settings to load
	 */
	update: function(settingsModel)
	{
		this.model = settingsModel;

		// Convert the send as into Store data
		var addresses = settingsModel.get('zarafa/v1/contexts/mail/sendas', true);

		var addressData = {'item': []};
		Ext.each(addresses, function(address, index) {
			addressData.item.push({props: address});
		});

		// Load all addresses into the Store
		this.getStore().loadData(addressData);
	},

	/**
	 * Called by the {@link Zarafa.settings.ui.SettingsCategory Category} when
	 * it has been called with {@link zarafa.settings.ui.SettingsCategory#updateSettings}.
	 * This is used to update the settings from the UI into the {@link Zarafa.settings.SettingsModel settings model}.
	 * @param {Zarafa.settings.SettingsModel} settingsModel The settings to update
	 */
	updateSettings: function(settingsModel)
	{
		// Start reading the Grid store and convert the contents back into
		// an object which can be pushed to the settings.
		var addresses = this.getStore().getRange();
		addresses = Ext.pluck(addresses, 'data');

		// removing unnecessary props, some required props of recipient record is going to be stored into setting.
		for (var i = 0; i < addresses.length; i++) {
			var address = addresses[i];

			delete address.proposednewtime;
			delete address.proposednewtime_end;
			delete address.proposednewtime_start;
			delete address.recipient_flags;
			delete address.recipient_trackstatus;
			delete address.recipient_trackstatus_time;
		}

		settingsModel.set('zarafa/v1/contexts/mail/sendas', addresses);
	},

	/**
	 * Event handler for the {@link Ext.data.Store#remove} event which is fired
	 * by the {@link Ext.data.Store} inside the {@link #sendasPanel}.
	 * This will mark the {@link Zarafa.settings.SettingsContextModel} as
	 * {@link Zarafa.settings.SettingsContextModel#setDirty dirty}.
	 * @param {Ext.data.Store} store The store which fired the event
	 * @param {Ext.data.Record} record The record which was updated
	 * @param {Number} index index of the record in store which is removed
	 * @private
	 */
	doStoreRemove: function(store, record, index)
	{
		if(!record.phantom) {
			this.settingsContext.getModel().setDirty();
		}
	},

	/**
	 * Event handler for the {@link Ext.data.Store#add} event which is fired
	 * by the {@link Ext.data.Store} inside the {@link #sendasPanel}.
	 * This will mark the {@link Zarafa.settings.SettingsContextModel} as
	 * {@link Zarafa.settings.SettingsContextModel#setDirty dirty} while
	 * send as was added from address book only.
	 * @param {Ext.data.Store} store The store which fired the event
	 * @param {Array} records An Array of {@link Ext.data.Record record} objects which are added to store
	 * @param {Number} index index of the record in store which is removed
	 * @private
	 */
	doStoreAdd: function(store, records, index)
	{
		/*
		 * In case of externally added send as, we will configure oneoff entryid
		 * after the send as is added into store. Means entryid is configured only if
		 * record is added from address book.
		 * Additionally, Multiple send as will be added from address book only,
		 * So we just need to check for the first record.
		 */
		if (!Ext.isEmpty(records[0].get('entryid'))) {
			// mark the model as dirty as the new send as is added
			this.settingsContext.getModel().setDirty();
		}
	},

	/**
	 * Event handler for the {@link Ext.data.Store#update} event which is fired
	 * by the {@link Ext.data.Store} inside the {@link #sendasPanel}.
	 * This will mark the {@link Zarafa.settings.SettingsContextModel} as
	 * {@link Zarafa.settings.SettingsContextModel#setDirty dirty}.
	 * @param {Ext.data.Store} store The store which fired the event
	 * @param {Ext.data.Record} record The record which was updated
	 * @param {String} operation The update operation being performed.
	 * @private
	 */
	doStoreUpdate: function(store, record, operation)
	{
		if (operation !== Ext.data.Record.COMMIT) {
			this.settingsContext.getModel().setDirty();
		}
	}
});

Ext.reg('zarafa.settingssendaswidget', Zarafa.common.settings.SettingsSendAsWidget);
Ext.ns('Zarafa.common.ui');

/**
 * @class Zarafa.common.ui.DatePeriodField
 * @extends Zarafa.common.ui.DateRangeField
 * @xtype zarafa.dateperiodfield
 *
 * This class can be used to combine two {@link Ext.form.DateField datefield}
 * objects together to configure a startdate and enddate.
 */
Zarafa.common.ui.DatePeriodField = Ext.extend(Zarafa.common.ui.DateRangeField, {
	/**
	 * @cfg {String} dateFormat The format in which the date appears in the
	 * {@link Ext.form.DateField DateField}.
	 */
	// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
	dateFormat: _('d/m/Y'),

	/**
	 * @cfg {Boolean} allowBlank Specifies empty dates are accepted or not by {@link Ext.form.DateField DateField}.
	 */
	allowBlank: false,

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

		config.startFieldConfig = config.startFieldConfig || {};
		config.startFieldConfig.listeners = Zarafa.core.Util.mergeListeners(config.startFieldConfig.listeners, {
			select: this.onStartSelect,
			scope: this
		});

		config.startFieldConfig = Ext.applyIf(config.startFieldConfig, {
			xtype: 'datefield',
			fieldLabel: _('Start date'),
			labelWidth: this.labelWidth,
			emptyText: _('None'),
			format: this.dateFormat,
			allowBlank: config.allowBlank || this.allowBlank
		});

		config.endFieldConfig = config.endFieldConfig || {};
		config.endFieldConfig.listeners = Zarafa.core.Util.mergeListeners(config.endFieldConfig.listeners, {
			select: this.onEndSelect,
			scope: this
		});

		config.endFieldConfig = Ext.applyIf(config.endFieldConfig || {}, {
			xtype: 'datefield',
			fieldLabel: _('End date'),
			labelWidth: this.labelWidth,
			emptyText: _('None'),
			format: this.dateFormat,
			allowBlank: config.allowBlank || this.allowBlank
		});

		if(config.allowBlank && !Ext.isDefined(config.defaultValue)) {
			Ext.applyIf(config, {
				// use a daterange which can accept empty dates
				defaultValue: new Zarafa.core.DateRange({ allowBlank: true }),
				defaultPeriod: 30
			});
		}

		Zarafa.common.ui.DatePeriodField.superclass.constructor.call(this, config);
	},

	/**
	 * Event handler which is raised when the start date has been changed,
	 * this will update the end date to maintain the currently active period settings.
	 * @param {Ext.form.Field} field The field which has changed
	 * @param {Mixed} newValue The new value for the field
	 * @private
	 */
	onStartSelect: function(field, newValue)
	{
		this.onStartChange(field, newValue, field.startValue);
	},

	/**
	 * Event handler which is raised when the end date has been changed,
	 * this will update the start date when the end date is earlier then
	 * the start date.
	 * @param {Ext.form.Field} field The field which has changed
	 * @param {Mixed} newValue The new value for the field
	 * @private
	 */
	onEndSelect: function(field, newValue)
	{
		this.onEndChange(field, newValue, field.startValue);
	},

	/**
	 * Event handler which is raised when the start date has been changed,
	 * this will update the end date to maintain the currently active period settings.
	 * this will also honor the {#allowBlank} config and do calculation according to it.
	 *
	 * @param {Ext.form.Field} field The field which has changed
	 * @param {Mixed} newValue The new value for the field
	 * @param {Mixed} oldValue The old value for the field
	 * @private
	 */
	onStartChange: function(field, newValue, oldValue)
	{
		var range = this.defaultValue;
		var oldRange = this.defaultValue.clone();

		if(range.getStartDate() != newValue) {
			if(Ext.isEmpty(newValue)) {
				range.setStartDate(null);
			} else {
				var dueTime = range.getDueTime();

				// clear time information as that is not important for dateperiodfield
				// @FIXME we should extend daterange object to only support dates and clear time information
				// whenever its used in DatePeriodField
				newValue = newValue.clearTime(true);

				if(dueTime) {
					// due date is present so do dome validations
					var duration = range.getDuration();
					if(duration !== 0) {
						// we have fixed duration so dates should be changed with respect to duration
						var newDueValue = newValue.add(Date.MILLI, duration);
						range.set(newValue, newDueValue);
					} else {
						if(newValue.getTime() > dueTime) {
							// start date is greater then due date then change due date
							// to be same as start date
							range.set(newValue, newValue.clone());
						} else {
							// start date is less then due date so no validations required
							range.setStartDate(newValue);
						}
					}
				} else {
					// due date is not provided so set it same as start date
					range.set(newValue, newValue.clone());
				}
			}

			this.fireEvent('change', this, range.clone(), oldRange);
		}
	},

	/**
	 * Event handler which is raised when the end date has been changed,
	 * this will update the start date when the end date is earlier then
	 * the start date.
	 * this will also honor the {#allowBlank} config and do calculation according to it.
	 *
	 * @param {Ext.form.Field} field The field which has changed
	 * @param {Mixed} newValue The new value for the field
	 * @param {Mixed} oldValue The old value for the field
	 * @private
	 */
	onEndChange: function(field, newValue, oldValue)
	{
		var range = this.defaultValue;
		var oldRange = this.defaultValue.clone();

		if(range.getDueDate() != newValue) {
			if(Ext.isEmpty(newValue)) {
				// if due date is removed then we should remove start date also
				range.set(null, null);
			} else {
				var startTime = range.getStartTime();
				var duration = range.getDuration();

				// clear time information as that is not important for dateperiodfield
				// @FIXME we should extend daterange object to only support dates and clear time information
				// whenever its used in DatePeriodField
				newValue = newValue.clearTime(true);

				if(startTime && duration !== 0 && newValue.getTime() < startTime) {
					// start date is present so do dome validations
					// we have fixed duration so dates should be changed with respect to duration
					var newStartValue = newValue.add(Date.MILLI, -duration);
					range.set(newStartValue, newValue);
				} else {
					// start date is not present so simply set due date
					range.setDueDate(newValue);
				}
			}

			this.fireEvent('change', this, range.clone(), oldRange);
		}
	}
});

Ext.reg('zarafa.dateperiodfield', Zarafa.common.ui.DatePeriodField);
Ext.ns('Zarafa.common.ui');

/**
 * @class Zarafa.common.ui.DateTimeField
 * @extends Zarafa.common.ui.CompositeField
 * @xtype zarafa.datetimefield
 *
 * This class can be used to construct a {@link Ext.form.Field field}
 * which contains a {@link Ext.form.DateField DateField} and a {@link Ext.ux.form.Spinner Spinner}
 * {@link Ext.form.Field field} for the time.
 */
Zarafa.common.ui.DateTimeField = Ext.extend(Zarafa.common.ui.CompositeField, {
	/**
	 * @cfg {Boolean} enableTimeSelection Enable the time selection components
	 * to appear, otherwise the period will only exist between dates.
	 */
	enableTimeSelection: true,
	/**
	 * @cfg {Date/String} defaultValue The default value which must be applied
	 * to the date
	 */
	defaultValue: undefined,
	/**
	 * @cfg {Date/String} minValue The minimum value which can be used.
	 */
	minValue: undefined,
	/**
	 * @cfg {Date/String} maxValue The maximum value which can be used.
	 */
	maxValue: undefined,
	/**
	 * @cfg {String} dateFormat The format in which the date appears in the
	 * {@link Ext.form.DateField DateField}.
	 */
	// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
	dateFormat: _('d/m/Y'),
	/**
	 * @cfg {String} timeFormat The format in which the time appears in the
	 * time {@link Ext.ux.form.Spinner Spinner}.
	 */
	timeFormat: _('G:i'),
	/**
	 * @cfg {Number} timeIncrement The number of minutes to increase/decrease
	 * when the time {@link Ext.ux.form.Spinner Spinner} is used.
	 */
	timeIncrement: 15,
	/**
	 * @cfg {Object} dateFieldConfig The way by which one can pass the configuration options of {@link Ext.form.DateField}.
	 */
	dateFieldConfig: {},
	/**
	 * @cfg {Object} timeFieldConfig The way by which one can pass the configuration options of {@link #this.TimeField}.
	 */
	timeFieldConfig: {},
	/**
	 * @constructor
	 * @param {Object} Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.apply(this, config);

		this.timeFormat = config.timeFormat || container.settingsModel.get('zarafa/v1/main/datetime_time_format');

		if (Ext.isDefined(this.defaultValue) && !Ext.isDate(this.defaultValue)) {
			this.defaultValue = Date.parseDate(this.defaultValue, this.dateFormat + ' ' + this.timeFormat);
		}
		if (Ext.isDefined(this.minValue) && !Ext.isDate(this.minValue)) {
			this.minValue = Date.parseDate(this.minValue, this.dateFormat + ' ' + this.timeFormat);
		}
		if (Ext.isDefined(this.maxValue) && !Ext.isDate(this.maxValue)) {
			this.maxValue = Date.parseDate(this.maxValue, this.dateFormat + ' ' + this.timeFormat);
		}

		// Hide the labels, as we will draw them ourself to have more control over them
		config.hideLabels = true;

		var items = [];

		if ( !config.hideLabel && config.fieldLabel && this.ownerCt.layout!=='form' ){
			var labelConfig = config.labelConfig || {};
			labelConfig = Ext.applyIf(labelConfig, {
				xtype: 'label',
				text: config.fieldLabel + (config.labelSeparator || ':') || ''
			});
			if ( config.labelWidth ){
				labelConfig.width = config.labelWidth;
			} else {
				labelConfig.autoWidth = true;
			}

			items.push(labelConfig);
		}

		// Build accessible label prefix from parent fieldLabel if available
		var labelPrefix = config.fieldLabel ? config.fieldLabel + ' ' : '';

		items = items.concat([
			Ext.apply({
				xtype: 'datefield',
				ref: 'dateField',
				flex: 1,
				fieldLabel: labelPrefix + _('Date'),
				format: this.dateFormat,
				value: this.defaultValue,
				minValue: this.minValue,
				maxValue: this.maxValue,
				listeners: {
					change: this.onDateChange,
					select: this.onDateSelect,
					scope: this
				},
				hideLabel: true,
				hideLabels: true
			}, this.dateFieldConfig ),
			Ext.apply({
				xtype: 'zarafa.spinnerfield',
				ref: 'timeField',
				fieldLabel: labelPrefix + _('Time'),
				width: 85,
				defaultValue: this.defaultValue,
				minValue: this.minValue,
				maxValue: this.maxValue,
				incrementValue: this.timeIncrement,
				alternateIncrementValue: 1,
				plugins: [{
					ptype: 'zarafa.timespinner',
					format: this.timeFormat
				}],
				listeners: {
					change: this.onTimeChange,
					spin: this.onTimeSpin,
					scope: this
				}
			}, this.timeFieldConfig )
		]);

		config = Ext.applyIf(config, {
			isSingleValued: true,
			hideLabels: true,
			items: items
		});

		Ext.apply(this, config);

		Zarafa.common.ui.DateTimeField.superclass.constructor.call(this, config);
	},

	/**
	 * Called by {@link #onDateChange} or {@link #onDateSelect} when the {@link #dateField}
	 * has a new value. This will construct a new value for the {@link #timeField} and
	 * fire the {@link #change} event.
	 * @param {Date} newValue The new Date inside the DateField
	 * @param {Date} oldValue The old Date from the DateField
	 * @private
	 */
	doDateChange: function(newValue, oldValue)
	{
		var oldDate;
		var newDate;

		if (this.enableTimeSelection) {
			var curTime = this.timeField.getValue();
			oldDate = this.combineDateAndTime(oldValue, curTime);
			newDate = this.combineDateAndTime(newValue, curTime);
		} else {
			oldDate = oldValue;
			newDate = newValue;
		}

		// Restrict value to minimum and maximum value. Both the Time and
		// Date fields already enforce their individual restriction, but
		// it is still possible to create a value outside of the range
		// when the individual values are combined.
		if(Ext.isDefined(newDate)) {
			if (newDate > this.maxValue) {
				newDate = this.maxValue;
			} else if (newDate < this.minValue) {
				newDate = this.minValue;
			}

			// Enforce the new date to the timeField, this way the
			// timeField holds the full date value for this entire
			// component.
			this.timeField.setValue(newDate);

			this.fireEvent('change', this, newDate, oldDate);
		}
	},

	/**
	 * Called by {@link #onTimeChange} or {@link #onTimeSpin} when the {@link #timeField}
	 * has a new value. This will update the {@link #dateField} and fire the {@link #change}
	 * event.
	 * @param {Date} newValue The new Date inside the SpinnerField
	 * @param {Date} oldValue The old Date from the SpinnerField
	 * @private
	 */
	doTimeChange: function(newValue, oldValue) {
		// While spinning the date could have changed,
		// update the dateField with the new date.
		this.dateField.setValue(newValue);

		this.fireEvent('change', this, newValue, oldValue);
	},

	/**
	 * Event handler which is raised when the date has been changed (by typing),
	 * this will call {@link #doDateChange} for updating the new Field value
	 * and firing the {@link #change} event.
	 * @param {Ext.form.DateField} field The field which has fired the event
	 * @param {Date} newValue The new Date inside the DateField
	 * @param {Date} oldValue The old Date from the DateField
	 * @private
	 */
	onDateChange: function(field, newValue, oldValue)
	{
		this.doDateChange(newValue, oldValue);
	},

	/**
	 * Event handler which is raised when the date has been selected
	 * this will call {@link #doDateChange} for updating the new Field value
	 * and firing the {@link #change} event.
	 * @param {Ext.form.DateField} field The field which has fired the event
	 * @param {Date} date The new value for the field
	 * @private
	 */
	onDateSelect: function(field, date)
	{
		var oldValue = field.startValue;
		var newValue = date;

		this.doDateChange(newValue, oldValue);
	},

	/**
	 * Event handler which is raised when the timefield has been changed (by typing),
	 * this will call {@link #doTimeChange} for updating the new Field value
	 * and firing the {@link #change} event.
	 * @param {Zarafa.common.ui.SpinnerField} field The field which fired the event
	 * @param {Date} newValue The new Date inside the SpinnerField
	 * @param {Date} oldValue The old Date from the SpinnerField
	 * @private
	 */
	onTimeChange: function(field, newValue, oldValue)
	{
		this.doTimeChange(newValue, oldValue);
	},

	/**
	 * Event handler which is raised when the timeSpinner has been spinned
	 * this will call {@link #doTimeChange} for updating the new Field value
	 * and firing the {@link #change} event.
	 * @param {Zarafa.common.plugins.TimeSpinner} spinner The spinner plugin
	 * @private
	 */
	onTimeSpin: function(spinner)
	{
		var oldValue = spinner.field.startValue;
		var newValue = spinner.field.getValue();

		this.doTimeChange(newValue, oldValue);
	},

	/**
	 * Sets a data value into the field and validates it. To set the value directly
	 * without validation see {@link #setRawValue}.
	 * @param {Date} The value to set
	 */
	setValue: function(value)
	{
		this.dateField.setValue(value);
		if (this.enableTimeSelection) {
			this.timeField.setValue(value);
		}
	},

	/**
	 * Sets the underlying DOM field's value directly, bypassing validation.
	 * To set the value with validation see {@link #setValue}.
	 */
	setRawValue: function(value)
	{
		this.dateField.setRawValue(value);
		if (this.enableTimeSelection) {
			this.timeField.setRawValue(value);
		}
	},

	/**
	 * Returns the normalized data value (undefined or emptyText will be returned as '').
	 * To return the raw value see {@link #getRawValue}.
	 * @return {Date} The date object
	 */
	getValue: function()
	{
		if (this.enableTimeSelection) {
			return this.timeField.getValue();
		} else {
			return this.dateField.getValue();
		}
	},

	/**
	 * Returns the raw data value which may or may not be a valid, defined value.
	 * To return a normalized value see {@link #getValue}.
	 * @return {Date} The date object
	 */
	getRawValue: function()
	{
		if (this.enableTimeSelection) {
			return this.timeField.getRawValue();
		} else {
			return this.dateField.getRawValue();
		}
	},

	/**
	 * Combine the date value together with a time value
	 * and return the corresponding {@link Date date} object.
	 * @param {Date/String} dateObject The date object, either a string (containing
	 * a date formatted using {@link #dateFormat} or a {@link Date date} object.
	 * @param {Date/String} timeObject The object containing the time
	 * @return {Date} The date object
	 */
	combineDateAndTime: function(dateObject, timeObject)
	{
		// A bit silly, but dateField returns the Date object,
		// but to add the time information we must convert it
		// to a string, append the time, and convert it into
		// a Date object again.
		if (Ext.isDate(dateObject)) {
			dateObject = dateObject.format(this.dateFormat);
		}
		if (Ext.isDate(timeObject)) {
			timeObject = timeObject.format(this.timeFormat);
		}

		if(Ext.isDefined(dateObject) && Ext.isDefined(timeObject)) {
			return Date.parseDate(dateObject + ' ' + timeObject, this.dateFormat + ' ' + this.timeFormat);
		} else {
			return undefined;
		}
	},

	/**
	 * Toggle the enabled state of the {@link Zarafa.common.ui.SpinnerField SpinnerField}
	 * for setting the time.
	 * @param {Boolean} enabled True to enable the selection of time
	 */
	setEnabledTimeSelection: function(enabled)
	{
		this.enableTimeSelection = enabled;
		if (this.rendered) {
			this.timeField.setVisible(enabled);
			this.doLayout();
		}
	},

  /**
   * Function add css class to the {@link Zarafa.common.ui.DateTimeField dateTimeField}
   * @param {String} cls CSS class name
   */
  addClass: function (cls)
  {
    this.timeField.addClass(cls);
    this.dateField.addClass(cls);
  },

  /**
   * Function remove css class to the {@link Zarafa.common.ui.DateTimeField dateTimeField}
   * @param {String} cls CSS class name
   */
  removeClass: function (cls)
  {
    this.timeField.removeClass(cls);
    this.dateField.removeClass(cls);
  }
});

Ext.reg('zarafa.datetimefield', Zarafa.common.ui.DateTimeField);
Ext.ns('Zarafa.common.ui');

/**
 * @class Zarafa.common.ui.DateTimePeriodField
 * @extends Zarafa.common.ui.DateRangeField
 * @xtype zarafa.datetimeperiodfield
 *
 * This class can be used to combine two {@link Zarafa.common.ui.DateTimeField DateTimeField}
 * objects together to configure a period.
 */
Zarafa.common.ui.DateTimePeriodField = Ext.extend(Zarafa.common.ui.DateRangeField, {
	/**
	 * @cfg {Boolean} enableTimeSelection Enable the time selection components
	 * to appear, otherwise the period will only exist between dates.
	 */
	enableTimeSelection: true,
	/**
	 * @cfg {String} dateFormat The format in which the date appears in the
	 * {@link Ext.form.DateField DateField}.
	 */
	// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
	dateFormat: _('d/m/Y'),
	/**
	 * @cfg {String} timeFormat The format in which the time appears in the
	 * time {@link Ext.ux.form.Spinner Spinner}.
	 */
	timeFormat: _('G:i'),
	/**
	 * @cfg {Number} timeIncrement The number of minutes to increase/decrease
	 * when the time {@link Ext.ux.form.Spinner Spinner} is used.
	 */
	timeIncrement: 30,
	/**
	 * @constructor
	 * @param {Object} Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};
		config.hideLabels = true;
		Ext.apply(this, config);

		this.timeFormat = config.timeFormat || container.settingsModel.get('zarafa/v1/main/datetime_time_format');

		config.startFieldConfig = Ext.applyIf(config.startFieldConfig || {}, {
			xtype: 'zarafa.datetimefield',
			fieldLabel: _('Start date'),
			enableTimeSelection: this.enableTimeSelection,
			dateFormat: this.dateFormat,
			timeFormat: this.timeFormat,
			timeIncrement: this.timeIncrement
		});

		config.endFieldConfig = Ext.applyIf(config.endFieldConfig || {}, {
			xtype: 'zarafa.datetimefield',
			fieldLabel: _('End date'),
			enableTimeSelection: this.enableTimeSelection,
			dateFormat: this.dateFormat,
			timeFormat: this.timeFormat,
			timeIncrement: this.timeIncrement
		});

		Zarafa.common.ui.DateTimePeriodField.superclass.constructor.call(this, config);
	},

	/**
	 * Toggle the enabled state of the {@link Zarafa.common.ui.SpinnerField SpinnerField}
	 * for setting the time.
	 * @param {Boolean} enabled True to enable the selection of time
	 */
	setEnabledTimeSelection: function(enabled)
	{
		this.enableTimeSelection = enabled;
		if (this.rendered) {
			this.startField.setEnabledTimeSelection(enabled);
			this.endField.setEnabledTimeSelection(enabled);
		} else {
			this.get(0).setEnabledTimeSelection(enabled);
			this.get(1).setEnabledTimeSelection(enabled);
		}
	}
});

Ext.reg('zarafa.datetimeperiodfield', Zarafa.common.ui.DateTimePeriodField);
Ext.namespace('Zarafa.common.ui');

/**
 * @class Zarafa.common.ui.EditorTreeGrid
 * @extends Zarafa.common.ui.TreeGrid
 * @xtype zarafa.editortreegrid
 *
 * Special extension of the {@link Zarafa.common.ui.TreeGrid} to add editing
 * capabilities into the treegrid. This works similar to the {@link Ext.grid.EditorGrid}.
 */
Zarafa.common.ui.EditorTreeGrid = Ext.extend(Zarafa.common.ui.TreeGrid, {
	/**
	 * @cfg {Number} clicksToEdit
	 * The number of clicks on a cell required to display the cell's editor (defaults to 2).
	 */
	clicksToEdit: 2,

	/**
	 * The currently active {@link Ext.form.Field Form Field} which is currently
	 * displayed as editor inside the grid.
	 * @property
	 * @type Ext.grid.GridEditor
	 */
	activeEditor: undefined,

	/**
	 * True when a cell is currently being edited by the user.
	 * @property
	 * @type Boolean
	 */
	editing: false,

	/**
	 * @cfg {Boolean} autoEncode
	 * True to automatically HTML encode and decode values pre and post edit (defaults to false)
	 */
	autoEncode: false,

	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		this.addEvents(
			/**
			 * @event beforeedit
			 * Fires before cell editing is triggered.
			 * @param {Zarafa.common.ui.EditorTreeGrid} grid The grid which fired the event
			 * @param {Ext.tree.TreeNode} node The node which is going to be edited
			 * @param {Number} column The column which is going to be edited
			 */
			'beforeedit',
			/**
			 * @event afteredit
			 * Fires after a cell is edited.
			 * @param {Zarafa.common.ui.EditorTreeGrid} grid The grid which fired the event
			 * @param {Ext.tree.TreeNode} node The node which was edited
			 * @param {Number} column The column which was edited
			 * @param {Mixed} value The new value which is applied into the node
			 */
			'afteredit',
			/**
			 * @event validateedit
			 * Fires after a cell is edited, but before the value is set in the record. Return false
			 * to cancel the change.
			 * @param {Zarafa.common.ui.EditorTreeGrid} grid The grid which fired the event
			 * @param {Ext.tree.TreeNode} node The node which was edited
			 * @param {Number} column The column which was edited
			 * @param {Mixed} value The new value which should be validated
			 */
			'validateedit'
		);

		Zarafa.common.ui.EditorTreeGrid.superclass.constructor.call(this, config);
	},

	/**
	 * Called by Extjs to initialize all event handlers.
	 * This will register the {@link #onClickEdit} function on the {@link #click} or
	 * {@link #dblclick} event (depending on the {@link #clicksToEdit} configuration option.
	 * @private
	 */
	initEvents: function()
	{
		Zarafa.common.ui.EditorTreeGrid.superclass.initEvents.call(this);

		// When the columns are being resized, stop all editing
		this.mon(this.colResizer.tracker, 'dragstart', this.stopEdit, this, [true]);

		// Depending on the clicksToEdit configuration option,
		// we must listen to the single-click or double-click event
		// to instantiate the editing.
		if (this.clicksToEdit == 1) {
			this.on('click', this.onClickEdit, this);
		} else {
			this.on('dblclick', this.onClickEdit, this);
		}
	},

	/**
	 * Event handler called when the user either clicked or double-clicked on this grid
	 * (depending on {@link #clicksToEdit}. This will determine if the node can be edited
	 * (When the node is a {@link Ext.tree.TreeNode#leaf leaf}. If so, it will determine
	 * which column was clicked and call {@link #startEdit}.
	 * @param {Ext.tree.TreeNode} node The node which was clicked
	 * @param {Ext.EventObject} event The event object
	 * @private
	 */
	onClickEdit: function(node, event)
	{
		if (node.leaf) {
			var td = Ext.get(event.target);
			if (td.dom.nodeName !== 'TD') {
				td = td.parent('td');
			}
			var tr = td.parent('tr');
			var index = [].slice.call(tr.dom.childNodes).indexOf(td.dom);

			this.startEdit(node, index);
		}
	},

	/**
	 * Called to initiate the editing of a given column for the provided node.
	 * This will obain the {@link #getCellEditor editor} to be used for the column,
	 * if an editor is provided it will be rendered into the given cell to allow
	 * the user to edit the cell.
	 * @param {Ext.tree.TreeNode} node The node on which the editing will take place
	 * @param {Number} column The column index in which the editing will start
	 * @private
	 */
	startEdit: function(node, column)
	{
		this.stopEdit();

		if (this.isCellEditable(node, column)) {
			if (this.fireEvent('beforeedit', this, node, column) !== false) {
				this.editing = true;

				var editor = this.getCellEditor(node, column);
				if (!editor) {
					return;
				}

				if (!editor.rendered) {
					this.mon(editor, {
						render: this.onEditRender,
						complete: this.onEditComplete,
						canceledit: this.stopEdit.createDelegate(this, [true]),
						scope: this
					});
				}

				Ext.apply(editor, {
					node: node,
					column: column
				});

				this.activeEditor = editor;

				var value = this.preEditValue(node, column);
				editor.startEdit(this.getCell(node, column), value);
			}
		}
	},

	/**
	 * Event handler called when the {@link #activeEditor} is being rendered.
	 * This will force the focus to be applied to the editor, and make sure
	 * that the editor is correctly blurred when needed.
	 * @param {Ext.grid.GridEditor} editor The editor which is being rendered
	 * @private
	 */
	onEditRender: function(editor)
	{
		// When rendering, we need to directly place the focus on the editor
		editor.field.focus();

		// When the user clicks on the component, we must preserve the focus
		// to prevent the component being blurred and the editor to be disabled
		// again. This is especially needed for CheckBox component which requires
		// a 'click' event to change the value.
		this.mon(editor.field.el, 'mousedown', function() {
			editor.selectSameEditor = true;
			// A few milliseconds after the event, we must clear the status again.
			(function(){
				delete editor.selectSameEditor;
				// Force the focus back to the field, otherwise we will miss the
				// next blur event and are we stuck with the editor.
				editor.field.focus();
			}).defer(50);
		});
	},

	/**
	 * Called to end all editing on the current cell. This will reset the {@link #editing}
	 * and {@link #activeEditor} fields.
	 * @param {Boolean} cancel True when the editing is stopped due to a cancellation, and the
	 * changes should not be saved.
	 * @private
	 */
	stopEdit: function(cancel)
	{
		if (this.editing === true) {
			var editor = this.activeEditor;
			if (editor) {
				editor[cancel === true ? 'cancelEdit' : 'completeEdit']();
			}
			this.activeEditor = null;
		}
		this.editing = false;
	},

	/**
	 * Event handler which is called when the editing in the {@link #activeEditor} has been
	 * completed and a new value must be applied to the cell.
	 * @param {Ext.grid.GridEditor} editor The grid editor used to modify the field
	 * @param {Mixed} value The new value which must be applied to the cell
	 * @param {Mixed} oldValue The previous value which was applied to the cell
	 * @private
	 */
	onEditComplete: function(editor, value, oldValue)
	{
		var node = editor.node;
		var column = editor.column;

		this.editing = false;
		this.activeEditor = null;

		value = this.postEditValue(node, column, value, oldValue);

		if (String(value) !== String(oldValue)) {
			if (this.fireEvent('validateedit', this, node, column, value) !== false) {
				var columnObj = this.getColumn(column);

				node.attributes[columnObj.dataIndex] = value;

				// Check if the column has a templated,
				// if that is the case format the value before
				// applying it to the cell.
				if (columnObj.tpl) {
					var data = {};
					data[columnObj.dataIndex] = value;
					this.getCell(node, column).innerHTML = columnObj.tpl.apply(data);
				} else {
					this.getCell(node, column).innerHTML = value;
				}

				this.fireEvent('afteredit', this, node, column, value);
			}
		}
	},

	/**
	 * Obtain the correct value for the requested cell, and if {@link #autoEncode needed} then
	 * the value will be {@link Ext.util.Format.htmlDecode decoded}.
	 * @param {Ext.tree.TreeNode} node The node which is going to be edited
	 * @param {Number} column The column index which is going to be edited
	 * @return {Mixed} value The value which will be placed in the editor component
	 * @private
	 */
	preEditValue: function(node, column)
	{
		var value = node.attributes[this.getColumn(column).dataIndex];
		return this.autoEncode && Ext.isString(value) ? Ext.util.Format.htmlDecode(value) : value;
	},

	/**
	 * If {@link #autoEncode needed} then the value will be {@link Ext.util.Format.htmlEncode encoded}
	 * before it is saved into the cell.
	 * @param {Ext.tree.TreeNode} node The node which was edited
	 * @param {Number} column The column index which was edited
	 * @param {Mixed} value The new value which was entered into the editor
	 * @param {Mixed} oldValue The old value which was inside the editor
	 * @private
	 */
	postEditValue: function(node, column, value, oldValue)
	{
		return this.autoEncode && Ext.isString(value) ? Ext.util.Format.htmlEncode(value) : value;
	},

	/**
	 * Checks if the cell which was selected by the user is editable
	 * @param {Ext.tree.TreeNode} node The node on which we are going to edit
	 * @param {Number} column The column which is going to be edited
	 * @return {Boolean} True when the given cell is editable
	 */
	isCellEditable: function(node, column)
	{
		return this.getColumn(column).editable === true;
	},

	/**
	 * Obtain the {@link Ext.grid.Column column} belonging to the given index.
	 * @param {Number} column The index number of the column which is requested
	 * @return {Ext.grid.Column} The column which belongs to the given index
	 */
	getColumn: function(column)
	{
		return this.columns[column];
	},

	/**
	 * Obtain the HTMLElement which belongs to the cell indicated by the node and the column.
	 * @param {Ext.tree.TreeNode} node The node for which the cell is looked up
	 * @param {Number} column The index number of the column for which we are looking up the cell
	 * @return {Ext.Element} The element which represents the cell
	 */
	getCell: function(node, column)
	{
		return node.ui.elNode.childNodes[column].firstChild;
	},

	/**
	 * Obtain the {@link Ext.form.Field} which should be used for editing the cell
	 * indicated by the given node and column.
	 * @param {Ext.tree.TreeNode} node The node for which the required editor is looked up
	 * @param {Number} column The column index for which the editor is looked up
	 * @return {Ext.form.Field} The editor used to edit the given cell
	 */
	getEditor: function(node, column)
	{
		return this.columns[column].editor;
	},

	/**
	 * Obtain the {@link Ext.grid.GridEditor} for the {@link #getEditor editor} that should be
	 * used for editing the given cell.
	 * @param {Ext.tree.TreeNode} node The node for which the required editor is looked up
	 * @param {Number} column The column index for which the editor is looked up
	 * @return {Ext.grid.GridEditor} The grid editor which wraps the editor from {@link #getEditor}.
	 */
	getCellEditor: function(node, column)
	{
		var editor = this.getEditor(node, column);
		if (editor) {
			if (!editor.startEdit) {
				if (!editor.gridEditor) {
					// We remove the default x-small-editor CSS class as that makes the trigger
					// image smaller then the actual textfield.
					editor.gridEditor = new Ext.grid.GridEditor(editor, { cls: 'x-grid-editor' });
				}
				editor = editor.gridEditor;
			}
		}

		return editor;
	}
});

Ext.reg('zarafa.editortreegrid', Zarafa.common.ui.EditorTreeGrid);
/*
 * #dependsFile client/zarafa/mail/data/ActionTypes.js
 */
Ext.namespace("Zarafa.common.ui");

/**
 * @class Zarafa.common.ui.HtmlEditor
 * @extends Ext.ux.form.TinyMCETextArea
 * @xtype zarafa.htmleditor
 *
 * Our HtmlEditor is a simple override of the TinyMCE provided HtmlEditor.
 * It automatically enables all buttons which are needed within Zarafa, and adds
 * plugins to extend the HtmlEditor with additional features.
 * The toolbar is extended with the overflow option, which moves all buttons
 * which do not fit within the length of the toolbar to a new popup menu at
 * the right position of the toolbar.
 */
Zarafa.common.ui.HtmlEditor = Ext.extend(Ext.ux.form.TinyMCETextArea, {
	/**
	 * defaultFontFamily The default font family set by the user.
	 * @property
	 * @type String
	 */
	defaultFontFamily: undefined,

	/**
	 * defaultFontSize The default font size set by the user.
	 * @property
	 * @type String
	 */
	defaultFontSize: undefined,

	/**
	 * Reference to the document inside the TinyMCE iframe for event cleanup.
	 * @property
	 * @type Document
	 */
	editorDoc: null,

	/**
	 * @constructor
	 * @param config configuration object
	 * @cfg enableOverflow set {@link Ext.Toolbar.enableOverflow enableOverflow} on the toolbar.
	 */
	constructor: function(config)
	{
		config = config || {};
		// Captured here because inside the TinyMCE `setup` callback below, `this` is
		// bound by TinyMCE itself (not the HtmlEditor instance), so a plain `this`
		// reference there would not point to this component.
		var self = this;
		// Get supported font families
		var fontFamilies = Zarafa.common.ui.htmleditor.Fonts.getFontFamilies();

		var baseUrl = container.getServerConfig().getBaseUrl();
		const cacheBuster = "8.1.2.369";

		var themeIsDark = Zarafa.core.DarkMode.isDark();

		this.defaultFontFamily = container.getSettingsModel().get("zarafa/v1/main/default_font");
		this.defaultFontSize = Zarafa.common.ui.htmleditor.Fonts.getDefaultFontSize();
		config = Ext.applyIf(config, {
			xtype: "zarafa.tinymcetextarea",
			hideLabel: true,
			hideMode: "offsets",
			readOnly: false,
			tinyMCEConfig: {
				delta_height: 1,
				plugins: "autolink directionality image link emoticons media charmap anchor lists advlist quickbars searchreplace visualchars table officepaste",
				quickbars_insert_toolbar: false,
				cache_suffix: "?version=" + cacheBuster,
				link_assume_external_targets: true,
				toolbar: "undo redo | fontfamily fontsizeinput bold italic underline strikethrough | backcolor forecolor removeformat | bullist numlist outdent indent align lineheight | ltr rtl | subscript superscript | link anchor image media table | charmap emoticons | searchreplace",
				quickbars_selection_toolbar: "fontsizeinput | bold italic underline strikethrough | backcolor forecolor removeformat | quicklink blockquote quickimage quicktable",
				toolbar_mode: "sliding",
				paste_data_images: true,
				automatic_uploads: false,
				remove_trailing_brs: false,
				font_family_formats: fontFamilies,
				font_size_input_default_unit: "pt",
				browser_spellcheck: true,
				valid_elements: '*[*]',
				extended_valid_elements: 'img[src|data-mce-src|alt|width|height],p[class|style],span[class|style],a[href|style],*[*]',
				valid_children: '+body[p],+p[span|a|b|strong|i|em|u|#text]',
				paste_as_text: false,
				width: "100%",
				menubar: false,
				contextmenu: false,
				statusbar: false,
				skin: themeIsDark ? "oxide-dark" : "oxide",
				content_css: themeIsDark ? "dark" : "default",
				branding: false,
				relative_urls: false,
				remove_script_host: false,
				forced_root_block: 'P',
				forced_root_block_attrs: {
					'style': 'padding: 0; margin: 0; '
				},
				content_style: "body{ word-wrap: break-word; margin: 1rem !important;" +
					(themeIsDark ? " background-color: #1e1e1e !important; color: #e0e0e0 !important; color-scheme: dark;" : "") +
					"}" +
					(themeIsDark ? " ::-webkit-scrollbar { width: 8px; } ::-webkit-scrollbar-track { background: #2a2a2a; } ::-webkit-scrollbar-thumb { background: #555; border-radius: 4px; } ::-webkit-scrollbar-thumb:hover { background: #777; }" : ""),
				setup: function(editor) {
					editor.on("PostProcess", function (e) {
						if (e.get) {
							e.content = inlineCSS(e.content);
						}
					});
					// Add accessible title to TinyMCE iframe
					editor.on("init", function() {
						var iframe = editor.iframeElement;
						if (iframe) {
							iframe.setAttribute('title', _('Message body editor'));
						}
					});
					// When a file is dropped into the editor body, TinyMCE embeds
					// images inline but blocks all other file types with an error.
					// Instead, offer to add non-embeddable files as attachments.
					editor.on("drop", function(e) {
						var dt = e.dataTransfer;
						if (!dt) {
							return;
						}

						// Defensive: if another plugin already handled this drop and
						// prevented the default, leave it alone.
						if (e.isDefaultPrevented()) {
							return;
						}

						var files = dt.files;
						if (!files || files.length === 0) {
							return;
						}

						// Image extensions that TinyMCE can embed inline.
						var imageExts = (editor.options.get('images_file_types') || 'jpeg,jpg,jpe,jfi,jif,jfif,png,gif,bmp,webp').split(',');

						var nonEmbeddable = [];
						var hasEmbeddable = false;
						for (var i = 0; i < files.length; i++) {
							var ext = (files[i].name.split('.').pop() || '').toLowerCase();
							if (imageExts.indexOf(ext) < 0) {
								nonEmbeddable.push(files[i]);
							} else {
								hasEmbeddable = true;
							}
						}

						if (nonEmbeddable.length === 0) {
							return;
						}

						// Suppress TinyMCE's own "Dropped file type is not supported" error
						// only when there is nothing for TinyMCE to embed. When the same drop
						// also contains images, TinyMCE's paste handler (which runs after this
						// one) embeds them and calls preventDefault() itself -- which also
						// suppresses that error. Calling preventDefault() here in that case
						// would make TinyMCE's handler bail on isDefaultPrevented() and the
						// images would be silently discarded.
						if (!hasEmbeddable) {
							e.preventDefault();
						}

						// HTML-encode each name individually so that the '<br>' separators
						// used to join them remain real line breaks instead of being
						// escaped as literal text.
						var names = nonEmbeddable.map(function(f) { return '\u2022 ' + Ext.util.Format.htmlEncode(f.name); });

						// Capture the record reference before the async callback.
						var record = self.record;

						// Zarafa.core.data.IPMAttachmentStore#uploadFiles() only treats its
						// argument as "real" files (with size/type/contents) if it is a
						// genuine FileList (it does a strict `instanceof FileList` check).
						// A plain Array -- which is what our filtering above produces --
						// fails that check and would silently be handled as a list of bare
						// filenames, so the actual file contents would never be uploaded
						// and the resulting attachment would be broken (0 bytes, no type).
						// Rebuild a real FileList containing only the non-embeddable files
						// so it is uploaded/attached exactly like a drop on the Attachments
						// field would be.
						var uploadFileList = nonEmbeddable;
						if (typeof DataTransfer !== 'undefined') {
							var fileListBuilder = new DataTransfer();
							nonEmbeddable.forEach(function(f) {
								fileListBuilder.items.add(f);
							});
							uploadFileList = fileListBuilder.files;
						}

						Ext.MessageBox.confirm(
							_('Add as attachment?'),
							_('The following files cannot be embedded in the message body. Add them as attachments instead?') +
								'<br><br>' + names.join('<br>'),
							function(btn) {
								if (btn !== 'yes' || !record) {
									return;
								}

								var store = record.getSubStore('attachments');
								// Run the same validation (max attachment count/size limits)
								// that a direct drop on the Attachments field would perform.
								if (store && store.canUploadFiles(uploadFileList, { container: this.getEl() })) {
									store.uploadFiles(uploadFileList);
								}
							}.createDelegate(self)
						);
					});
				}
			}
		});

		// Set the correct language for tinymce
		var lang = container.getSettingsModel().get("zarafa/v1/main/language", true);
		var tinyLanguageCode = Zarafa.common.ui.htmleditor.LanguageMap.getTinyLanguageCode(lang);
		if (!Ext.isEmpty(tinyLanguageCode) && tinyLanguageCode !== "en_GB" && tinyLanguageCode !== "en_US") {
			config.tinyMCEConfig.language = tinyLanguageCode;
			config.tinyMCEConfig.language_url = baseUrl + "client/tinymce-languages/" + tinyLanguageCode + ".js";
		}

		// Give plugins the option to change the tinyMCE configuration
		container.populateInsertionPoint("common.htmleditor.tinymceconfig", {
			scope: this,
			config: config.tinyMCEConfig
		});

		this.addEvents(
		/**
		 * @event valuecorrection
		 * Fires after {@link #setValue} has been called, and the given value has been
		 * formatted by the browser or tinymce. This allows the user of this component
		 * to better determine what the user-made changes were.
		 * @param {Zarafa.common.ui.HtmlEditor} this The component which fired the event
		 * @param {String} correctedValue The new (by browser corrected) value
		 * @param {String} originalValue The value as set by the caller of setValue
		 */
		"valuecorrection",
		/**
		 * @event dblclick
		 * Fired when the user double clicks inside the editor content.
		 */
		"dblclick");

		Zarafa.common.ui.HtmlEditor.superclass.constructor.call(this, config);

		this.on("initialized", this.onEditorInitialized, this);
		this.on("beforedestroy", this.onBeforeDestroy, this);
	},

	/**
	 * Function is called when the tinymce editor is initialized.
	 * @param {Zarafa.common.ui.HtmlEditor} htmlEditor The editor which is initialized.
	 * @param {tinymce.Editor} tinymceEditor The tinymce editor instance
	 * @private
	 */
	onEditorInitialized: function(htmlEditor, tinymceEditor)
	{
		var server = container.getServerConfig();
		var clientTimeout = server.getClientTimeout();

		if (clientTimeout) {
			this.setIdleTimeEventListeners();
		}

		tinymceEditor.on("keydown", this.onKeyDown.createDelegate(this), this);
		tinymceEditor.on("mousedown", this.relayIframeEvent.createDelegate(this), this);

		var doc = tinymceEditor.getDoc();
		Ext.EventManager.on(doc, 'click', this.onEditorClick, this);
		Ext.EventManager.on(doc, 'dblclick', this.onEditorDocDblClick, this);
		this.editorDoc = doc;

		if (Ext.isGecko) {
			tinymceEditor.on("dblclick", this.onDBLClick.createDelegate(this));
			if (container.mainPanel) {
				var contentPanel = container.getTabPanel();
				this.mon(contentPanel, "beforetabchange", this.onBeforeTabChange, this);
			}
		}

		var listeners = {
			blur: this.onBlur,
			focus: this.onFocus,
			scope: this
		};

		Ext.EventManager.on(this.getEditor().getWin(), listeners);
		tinymceEditor.on("focus", this.onFocus.createDelegate(this), this);
		tinymceEditor.on("blur", this.onBlur.createDelegate(this), this);

		// Apply default formatting after initialization
		this.applyFontStyles();
		this.addDefaultFormatting();
	},

	isHtmlEditor: function()
	{
		return true;
	},

	/**
	 * Check whether the given action type belongs to reply/forward style compose actions.
	 * @param {String} actionType The message action type.
	 * @return {Boolean}
	 * @private
	 */
	isResponseAction: function(actionType)
	{
		return actionType === Zarafa.mail.data.ActionTypes.REPLY ||
			actionType === Zarafa.mail.data.ActionTypes.REPLYALL ||
			actionType === Zarafa.mail.data.ActionTypes.FORWARD ||
			actionType === Zarafa.mail.data.ActionTypes.FORWARD_ATTACH ||
			actionType === Zarafa.mail.data.ActionTypes.EDIT_AS_NEW;
	},

	/**
	 * Set event listeners on the iframe that will reset the
	 * {@link Zarafa#idleTime idle time} when the user performs
	 * an action in the iframe (i.e. click, mousemove, keydown)
	 * @private
	 */
	setIdleTimeEventListeners: function()
	{
		var doc = this.getEditorDocument();
		if (!doc.addEventListener) {
			/**
			 * Client timeout will still be handled by the backend though,
			 * but the message will only be shown to the user when he tries to
			 * connect to the backend after the session has timed out.
			*/
			return;
		}
		var clickHandler = (function() {
			Zarafa.idleTime = 0;
		});
		var mouseMoveHandler = (function() {
			Zarafa.idleTime = 0;
		});

		try {
			doc.addEventListener("click", clickHandler, { capture: true, passive: true });
			doc.addEventListener("mousemove", mouseMoveHandler, { capture: true, passive: true });
		} catch (ex) {
			doc.addEventListener("click", clickHandler, true);
			doc.addEventListener("mousemove", mouseMoveHandler, true);
		}

		doc.addEventListener("keydown", (function() {
			Zarafa.idleTime = 0;
		}), true);
	},

	/**
	 * Event handler for the beforedestroy event
	 * The handler will call the remove function the tinyMCE editor, so it can clean up properly.
	 */
	onBeforeDestroy: function()
	{
		var editor = this.getEditor();

		if (this.editorDoc) {
			Ext.EventManager.removeListener(this.editorDoc, 'click', this.onEditorClick, this);
			Ext.EventManager.removeListener(this.editorDoc, 'dblclick', this.onEditorDocDblClick, this);
			this.editorDoc = null;
		}

		// Check if the editor was activated
		if (editor) {
			editor.remove();
		}
	},

	/**
	 * Event handler is called when fonct family combobox gets change the font type.
	 * @param {object} event the event object
	 */
	onFontFamilyChange: function(event)
	{
		this.defaultFontFamily = event.control.settings.value;
	},

	/**
	 * Event handler is called when fonct size combobox gets change the font size.
	 * @param {object} event the event object
	 */
	onFontSizeChange: function(event)
	{
		this.defaultFontSize = event.control.settings.value;
	},

	/**
	 * Function is used to execute a tinymce command to compose
	 * default formatting as a style attribute of currently selected node.
	 * @param {tinymce.Editor} editor The tinymce editor instance
	 * @private
	 */
	composeDefaultFormatting: function(editor)
	{
		editor.execCommand("FontSize", false, this.defaultFontSize, {
			skip_focus: true
		});
		editor.execCommand("FontName", false, this.defaultFontFamily, {
			skip_focus: true
		});
		this.repositionBrTag(editor);
	},

	/**
	 * Function is used to add the BR tag to inner SPAN tag of P tag and remove BR tag from P tag.
	 * it required because BR tag is used to identify the cursor location as wall as after pressing
	 * enter or pasting content in editor default formatting gets repeated in next line , to make cursor
	 * position ideal it required that BR tag must be in SPAN tag.
	 * @param {tinymce.Editor} editor The tinymce editor instance
	 */
	repositionBrTag: function(editor)
	{
		var node = editor.selection.getNode();
		editor.dom.add(node, "br");
		var parentNode = editor.dom.getParent(node, "P");
		if (parentNode && parentNode.lastChild.nodeName === "BR") {
			parentNode.removeChild(parentNode.lastChild);
		}
	},

	/**
	 * Function will set value in the editor and will call {@link #checkValueCorrection}
	 * to update tinymce's editor value into record. for more information check code/comments
	 * of {@link #checkValueCorrection}.
	 * @param {String} value The value which was being set on the editor
	 */
	setValue: function(value)
	{
		var self = this;
		this.withEd((function() {
			var setValue = Ext.isDefined(value) ? value : '';
			var editor = self.getEditor();
			if (editor) {
				var currentValue = editor.getContent();
				if (currentValue === setValue) {
					// No changes, so we don't need to update
					return;
				}
			}
			Zarafa.common.ui.HtmlEditor.superclass.setValue.call(this, setValue);
			if (self.rendered) {
				// checkValueCorrection function will be only called when editor is created and
				// initialized. Otherwise, it will it wait for that.
				self.checkValueCorrection(this, setValue);
			}
		}));
	},

	/**
	 * Add the default formatting before composing a new mail.
	 * @private
	 */
	addDefaultFormatting: function()
	{
		var tinymceEditor = this.getEditor();
		if (tinymceEditor.getContent({
			format: "text"
		}).trim()) {
			// Only add default formatting when there is no content
			// Otherwise is has already been added
			return;
		}
		tinymceEditor.selection.setCursorLocation(tinymceEditor.getBody().firstChild, 0);
		this.composeDefaultFormatting(tinymceEditor);
		// HTML styles will be applied while selecting default values from comboboxes.
		// We need to set those styles into the record to avoid change detection in record.
		// For reply/forward records, don't push this temporary default formatting into
		// the bound record as it can race with quoted body initialization.
		var actionType = this.record && Ext.isFunction(this.record.getMessageAction) ? this.record.getMessageAction('action_type') : null;
		if (!this.isResponseAction(actionType)) {
			this.checkValueCorrection(this, "");
		}
		// Remove all the undo level from tinymce, so that user can't rollback the default HTML styles.
		tinymceEditor.undoManager.clear();
	},

	/**
	* Toggle read only mode for the underlying TinyMCE editor without
	* disabling the component.
	* @param {Boolean} readOnly True to make the editor read-only
	*/
	setReadOnly: function(readOnly)
	{
		this.readOnly = readOnly;
		this.withEd((function() {
			var ed = this.getEditor();
			if (ed && ed.mode && ed.mode.set) {
				ed.mode.set(readOnly ? 'readonly' : 'design');
			}
		}), this);
	},

	/**
	 * Enable this component by calling parent class {@link Ext.ux.form.TinyMCETextArea.enable enable} function.
	 * It also call the {@link #addDefaultFormatting} function to add the default formatting.
	 */
	enable: function()
	{
		if (this.disabled) {
			Zarafa.common.ui.HtmlEditor.superclass.enable.apply(this, arguments);
			var editor = this.getEditor();
			// Reset the remembered selection value to its default value, because the single instance of
			// tinymce editor is being used by composing multiple signatures. it just enabled/disabled
			// with new/updated content.
			editor.selection.lastFocusBookmark = null;
			// Apply default formatting for the signature editor.
			if (Ext.isEmpty(editor.getContent())) {
				this.addDefaultFormatting(editor);
			}
		}
	},

	/**
	 * This is called after {@link #setValue} and determines if the value has been adjusted by the tinymce.
	 * Normally the value is applied to the <iframe> element where it is being rendered, tinymce will
	 * however also apply extra formatting, and change the ordering of some attributes. Because we only
	 * want to detect when the user made changes rather then the browser, we will check here what
	 * {@link #getValue} returns and check if that is different then the value which was being set.
	 * If a difference was found, the {@link #valuecorrection} event will be fired to inform the
	 * component user that he might need to implement some workaround.
	 *
	 * @param {Zarafa.common.ui.htmleditor.HtmlEditor} editor The HTML editor
	 * @param {String} value The value which was being set on the <iframe>
	 * @private
	 */
	checkValueCorrection: function(editor, value)
	{
		var correctedValue = editor.getValue();
		if (value !== correctedValue) {
			editor.fireEvent("valuecorrection", editor, correctedValue, value);
		}
	},

	/**
	 * Function is called when double clicked in the editor.
	 * While body is empty and double click in editor then firefox consider current selection on P tag instead of Span
	 * and P tag doesn't have formatting that's way default formatting will remove.
	 * Handle above situation by setting up selection on Span tag then after fire nodechange event of editor.
	 */
	onDBLClick: function()
	{
		var editor = this.getEditor();
		var element = editor.selection.getStart();
		// tinymce used zero width space characters as character container in empty line,
		// So needs to verify body is empty or not.
		var isEmptyBody = editor.selection.getContent({
			format: "text"
		}) === "";
		if (element === editor.getBody().firstChild && isEmptyBody) {
			editor.selection.setCursorLocation(element.firstChild.firstChild, 0);
		}
	},

	/**
	 * Handle click events inside the editor and open hyperlinks while in read-only mode.
	 * @param {Object} event The click event fired by TinyMCE
	 */
	onEditorClick: function(event)
	{
		if (!event.target) {
			return;
		}

		var node = event.target;
		while (node && node.nodeName !== 'A') {
			node = node.parentNode;
		}

		if (node && node.nodeName === 'A') {
			var href = node.getAttribute('href');
			if (href) {
				if (event.preventDefault) {
					event.preventDefault();
				}
				window.open(href, '_blank');
			}
		}
	},

	/**
	* Relay double click events from the iframe document to the component.
	* @param {Object} event The DOM event
	*/
	onEditorDocDblClick: function(event)
	{
		this.fireEvent('dblclick', this, event);
	},

	/**
	 * Function is called when mouse is clicked in the editor.
	 * Editor mousedown event needs to be relayed for the document element of grommunio Web page,
	 * to hide the context-menu.
	 * wheel event propagated from underlying iframe needs to be relayed to grommunio Web document
	 * to perform zoom functionality in DeskApp.
	 * @param {Object} event The event object
	 */
	relayIframeEvent: function(event)
	{
		Ext.getDoc().fireEvent(event.type, event);
	},

	/**
	 * Function is called when any key is pressed in the editor.
	 * @param {Object} event The event object
	 */
	onKeyDown: function(event)
	{
		var editor = this.getEditor();
		if (!Zarafa.core.BrowserWindowMgr.isMainWindowActive()) {
			switch (event.keyCode) {
			  case 116:
				//F5 button
				event.returnValue = false;
				event.keyCode = 0;
				return false;

			  case 82:
				//R button
				if (event.ctrlKey) {
					event.returnValue = false;
					event.keyCode = 0;
					return false;
				}
			}
		}

		if (event.keyCode === Ext.EventObject.ENTER) {
			// Ctrl + ENTER (and CMD + ENTER on a Mac) is the shortcut for sending an item. Because
			// we do not want to add the ENTER to the content then, we stop processing the event.
			if (event.ctrlKey || Ext.isMac && event.metaKey) {
				event.preventDefault();
				return false;
			}

			// Check if we are in a blockquote, and if so break the blockquote,
			// so the user can add his comments between the blockquotes.
			var blockquote = this.inBlockquote(editor);
			if (blockquote) {
				// Prevent normal enter-behaviour as we will handle it
				event.preventDefault();
				this.handleEnterInBlockquote();
				return false;
			}

			// We will do this in a deferred function to make sure that tiny first adds the bogus BR element to empty paragraphs
			(function() {
				var node = editor.selection.getNode();
				// When we created an empty P tag by pressing ENTER we must apply default formatting to it.
				// When we already have an empty SPAN with formatting in the P tag tiny adds a BR node with the attribute
				// 'data-mce-bogus' because otherwise it is not possible to move the cursor there. Tiny will remove those
				// bogus nodes when we get the content value. But we have removed the padding and margin of P-tags,
				// resulting in empty P nodes not being visible to the user. That's why we will remove the attribute
				// 'data-mce-bogus' of the BR nodes, so tiny will not remove them.
				if (node.nodeName === "P") {
					if (!node.hasChildNodes() || node.firstChild.nodeName === "BR") {
						this.composeDefaultFormatting(editor);
					}
				} else if (node.nodeName === "SPAN" && node.hasChildNodes() && node.firstChild.nodeName === "BR") {
					// This one is for when we created an empty paragraph by pressing ENTER while on the cursor was on end of a line
					node.firstChild.removeAttribute("data-mce-bogus");
				}
				// This one is for when we created an empty paragraph by pressing ENTER while the cursor was on the begin of a
				// line that already contained text
				var pNode = node.nodeName === "P" ? node : editor.dom.getParent(node, "p");
				var prevPNode = editor.dom.getPrev(pNode, "p");
				if (prevPNode && prevPNode.hasChildNodes()) {
					if (prevPNode.firstChild.nodeName === "SPAN" && prevPNode.firstChild.hasChildNodes() && prevPNode.firstChild.firstChild.nodeName === "BR") {
						// When we have a paragraph on which we added our default formatting, there will be a SPAN
						prevPNode.firstChild.firstChild.removeAttribute("data-mce-bogus");
					} else if (prevPNode.firstChild.nodeName === "BR") {
						// When we have a plain paragraph without our crappy default formatting there will only be a BR
						prevPNode.firstChild.removeAttribute("data-mce-bogus");
					}
				}
			}).createDelegate(this).defer(1);
		}

		/*
		 * HACK: Prevent the tab event to stop moving focus out of the editor.
		 * Instead of that, Insert four spaces while user presses TAB key.
		 */
		if (event.keyCode === Ext.EventObject.TAB && !event.shiftKey) {
			var listElement = editor.dom.getParent(editor.selection.getStart(), "LI,DT,DD");
			// check if cursor is not pointing to any list element to allow increase/decrease
			// indent of UL/OL instead of adding four white spaces.
			if (!listElement) {
				event.preventDefault();
				editor.execCommand("mceInsertContent", false, "&nbsp;&nbsp;&nbsp;&nbsp;");
				editor.selection.collapse(false);
			}
			return;
		}
		this.fireEvent("keypress", this, Ext.EventObject);
	},

	/**
	 * Checks if the cursor is positioned inside a blockquote
	 * @param {tinymce.Editor} editor The editor we will check
	 * @return {DOMElement} The blockquote element if found or false otherwise
	 */
	inBlockquote: function(editor)
	{
		var blockquoteElement = null;
		var node = editor.selection.getNode();
		if (node.nodeName === "BLOCKQUOTE") {
			blockquoteElement = node;
		} else {
			blockquoteElement = editor.dom.getParent(node, "BLOCKQUOTE", editor.getBody());
		}
		return blockquoteElement !== null ? blockquoteElement : false;
	},

	/**
	 * Splits a blockquote DOM element at the position of the cursor
	 * @param {tinymce.Editor} editor The editor we will work in
	 * @param {DOMElement} blockquote The blockquote element that must be split.
	 */
	splitBlockquote: function(editor, blockquote)
	{
		// Save the current selection
		const selection = editor.selection;
		const range = selection.getRng(true);
		// Create a marker element to preserve the cursor's location
				const marker = editor.dom.create("span", {
			id: "split-marker"
		}, "");
		range.insertNode(marker);
		// Split the blockquote at the marker
				const newBlockquote = editor.dom.split(blockquote, marker);
		// Create a new paragraph with custom margin for the content after the split
				const newParagraph = editor.dom.create("p", {
			style: "margin: 10px 0 10px 0;"
		}, '<br data-mce-bogus="1" />');
		editor.dom.insertAfter(newParagraph, newBlockquote);
		// Move the cursor to the new paragraph (between the split)
				const newRange = editor.dom.createRng();
		newRange.setStartAfter(marker);
		newRange.setEndAfter(marker);
		// Remove the marker after setting the range
				editor.dom.remove(marker);
		// Apply the new range
				selection.setRng(newRange);
		// Focus the editor to ensure the cursor is active
				editor.focus();
	},

	/**
	 * Handles the event when the user presses ENTER when the cursor is positioned
	 * in a blockquote
	 */
	handleEnterInBlockquote: function()
	{
		var editor = this.getEditor();
		var blockquote = this.inBlockquote(editor);
		// Add an undo level and wrap the split operation in a transaction
				editor.undoManager.transact((() => {
			if (blockquote) {
				this.splitBlockquote(editor, blockquote);
			}
		}));
		editor.focus();
	},

	/**
	 * Event handler which is raised when the component looses the focus
	 * of the user. This will determine if the typed in value has changed
	 * since the moment it gained focus, and will raise the 'change' event
	 * in that case.
	 * @private
	 */
	onBlur: function()
	{
		// if container of HtmlEditor, i.e. dialog, is closed then component is
		// also destroyed or removed and we won't be able to get value/content.
		if (this.isDestroyed === true || this.getEditor().removed) {
			return;
		}
		// This event can be triggered multiple times, so we don't need to check
		// for changed when we blur without being focused. However, we do want to
		// invoke the 'blur' event since that will produce a consistent behavior
		// with other UI components from ExtJs.
				if (this.hasFocus === true) {
			var v = this.getRawValue();
			if (v.length !== this.originalValue.length || v !== this.originalValue) {
				this.fireEvent("change", this, v, this.originalValue);
			}
		}
		this.hasFocus = false;
		this.fireEvent("blur", this);
	},

	/**
	 * Event handler which is raised when the component gains the focus
	 * of the user. This will update the originalValue field so later,
	 * when the component looses focus, it can be determined if the
	 * value has been changed.
	 * @private
	 */
	onFocus: function()
	{
		// This event can be triggered multiple times,
		// even when it looses focus the onFocus will
		// be raised twice before the onBlur is called.
		if (this.hasFocus === true) {
			return;
		}
		this.originalValue = this.getRawValue();
		this.hasFocus = true;
		this.fireEvent("focus", this);
		// While body is empty and switching a focus in editor after tab change then
		// some how firefox consider current selection on P tag instead of span and
		// P tag doesn't have formatting that's way default formatting will remove.
		if (this.isTabChanged) {
			this.getEditor().selection.setCursorLocation();
			this.isTabChanged = false;
		}
	},

	/**
	 * Function is used to set the cursor within editor while mail replay/forward.
	 * It fires the 'focusin' event of editor.
	 */
	setCursorLocation: function()
	{
		var editor = this.getEditor();
		// check If editor instance is available or not
		if (editor) {
			this.withEd((function() {
				// Here we need to fire the focusin event of editor because
				// in default formatting we have two span tags, so somehow
				// when mail is reply/forward cursor set in first outer span which is not contains any
				// formatting so default formatting gets lost. to overcome this problem
				// we need to fire the focusin event of editor.
				editor.dispatch("focusin", {
					target: editor.getBody(),
					relatedTarget: null
				});
			}));
		} else {
			// if editor instance is not available yet to do auto focus than we have to reschedule the
			// auto focus mechanism un till editor instance will be available, simply register on
			// {@link #initialized} event.
			this.on("initialized", (function() {
				var focusCmp = this.ownerCt;
				focusCmp.focus.defer(1, focusCmp);
				focusCmp.fireEvent("setAutoFocusCursor", focusCmp.getEditor());
			}), this);
		}
	},

	/**
	 * Function will get the editor and fetch document element of the editor.
	 * @return {HTMLDocument} it will return document element of the editor iframe
	 */
	getEditorDocument: function()
	{
		return this.getEditor().getDoc();
	},

	/**
	 * Function will get the editor and fetch body element of the editor.
	 * @return {HTMLBodyElement} it will return body element of the editor iframe
	 */
	getEditorBody: function()
	{
		return this.getEditor().getBody();
	},

	/**
	 * bind an {@link Zarafa.core.data.IPMRecord} to this component
	 * record is necessary to properly convert img tags, for which store and entryid are needed
	 * @param {Zarafa.core.data.IPMRecord} record
	 */
	bindRecord: function(record)
	{
		this.record = record;
	},

	/**
	 * Event handler triggers when content tab panel is changed
	 */
	onBeforeTabChange: function()
	{
		this.isTabChanged = true;
		if (this.hasFocus) {
			this.getEditor().fire("blur", this);
		}
	},

	/**
	 * Function to set formatting with default font family and font size
	 */
	applyFontStyles: function()
	{
		var body = Ext.get(this.getEditorBody());
		body.setStyle("font-family", this.defaultFontFamily);
		body.setStyle("font-size", this.defaultFontSize);
	}
});

Ext.reg("zarafa.htmleditor", Zarafa.common.ui.HtmlEditor);

// Create a hidden html editor to prefetch all tiny files
Zarafa.onReady((function()
{
	var body = Ext.getBody();
	// Only prefetch for the webclient not for the welcome screen
	if (body.hasClass("zarafa-webclient")) {
		var el = body.createChild({
			id: "tiny-prefetch",
			style: {
				width: "10px",
				height: "10px",
				"z-index": -10,
				top: "-10000px",
				left: "-10000px",
				visibility: "hidden",
				position: "absolute"
			}
		});
		new Ext.Panel({
			xtype: "panel",
			applyTo: el,
			layout: "fit",
			border: false,
			flex: 1,
			autoHeight: false,
			items: [ {
				xtype: "zarafa.editorfield",
				ref: "../editorField",
				flex: 1,
				useHtml: true
			} ]
		});
	}
}));
Ext.namespace('Zarafa.common.ui');

/**
 * @class Zarafa.common.ui.ImportanceButton
 * @extends Zarafa.core.ui.menu.ConditionalItem
 * @xtype zarafa.importancebutton
 *
 * Extension of the {@link Zarafa.core.ui.menu.ConditionalItem Conditional MenuItem}.
 * This class adds support for easily setting the importance on a {@link Zarafa.core.data.IPMRecord record}.
 */
Zarafa.common.ui.ImportanceButton = Ext.extend(Zarafa.core.ui.menu.ConditionalItem, {
	/**
	 * @cfg {Number} The importance value for this button
	 */
	importanceValue: 1,
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			xtype: 'zarafa.importancebutton',
			handler: function() {
				this.applyFlag(this.getRecords());
			},
			scope: this
		});

		Zarafa.common.ui.ImportanceButton.superclass.constructor.call(this, config);
	},

	/**
	 * Apply the Flag settings as defined in this button to the
	 * {@link Zarafa.core.data.IPMRecord records} given as arguments.
	 *
	 * @param {Zarafa.core.data.IPMRecord} records The records to which the flags must be applied
	 */
	applyFlag: function(records)
	{
		if (Ext.isEmpty(records)) {
			return;
		}

		var store;

		Ext.each(records, function(record) {
			store = record.getStore();
			record.set('importance', this.importanceValue);
		}, this);

		store.save(records);
	}
});

Ext.reg('zarafa.importancebutton', Zarafa.common.ui.ImportanceButton);
Ext.namespace('Zarafa.common.ui');

/**
 * @class Zarafa.common.ui.ImportanceMenu
 * @extends Zarafa.core.ui.menu.ConditionalMenu
 * @xtype zarafa.importancemenu
 *
 * Extend {@link Zarafa.core.ui.menu.ConditionalMenu ConditionalMenu} to add the
 * {@link Zarafa.core.ui.menu.ConditionalItems ConditionalItems} for all possible
 * Importance settings.
 */
Zarafa.common.ui.ImportanceMenu = Ext.extend(Zarafa.core.ui.menu.ConditionalMenu, {
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			items: this.createContextImportanceItems()
		});

		Zarafa.common.ui.ImportanceMenu.superclass.constructor.call(this, config);
	},

	/**
	 * Create the Importance context menu items
	 * @return {Zarafa.core.ui.menu.ConditionalItem[]} The list of Option context menu items
	 * @private
	 */
	createContextImportanceItems: function()
	{
		var buttons = [];

		Ext.each(Zarafa.common.data.ImportanceFlags.flags, function(flag) {
			buttons.push({
				xtype: 'zarafa.importancebutton',
				text: flag.name,
				importanceValue: flag.value,
				iconCls: flag.iconCls
			});
		}, this);

		return buttons;
	}
});

Ext.reg('zarafa.importancemenu', Zarafa.common.ui.ImportanceMenu);
Ext.namespace('Zarafa.common.ui');

/**
 * @class Zarafa.common.ui.RadioDateTimeField
 * @extends Zarafa.common.ui.CompositeField
 * @xtype zarafa.radiodatetimefield
 *
 * This class can be used to construct a {@link Ext.form.Field field}
 * which contains a {@link Ext.form.Radio} and a {@link Zarafa.common.ui.DateTimeField}
 */
Zarafa.common.ui.RadioDateTimeField = Ext.extend(Zarafa.common.ui.CompositeField, {

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

    Ext.apply(this, config);
    config = Ext.applyIf(config, {
      items: [{
        xtype: 'radio',
        boxLabel: ' ',
        ref: 'radioField',
        name: 'radioField',
        listeners: {
          check: this.onRadioCheck,
          scope: this
        }
      }, {
        xtype: 'zarafa.datetimefield',
        ref: 'dateTimeField',
				cls:'zarafa-radiodatetime-datetimefield',
        minValue: new Date(),
        width: 200,
        listeners: {
          change: this.onDateTimeChange,
          afterrender: this.initEvent,
          scope: this
        }
      }]
    });
    Zarafa.common.ui.RadioDateTimeField.superclass.constructor.call(this, config);
  },

  /**
   * Event handler which is called when {@link Zarafa.common.ui.DateTimeField dateTimeField} has receives input value,
   * If the input value is number then call the {@link Zarafa.common.ui.DateTimeField doDateChange} function.
   * @param {Zarafa.common.ui.DateTimeField} field The field which fired the event
   */
  onKeyUp: function (field)
  {
    var keyCode = Ext.EventObject.keyCode;
    if ((keyCode >= Ext.EventObject.ZERO && keyCode <= Ext.EventObject.NINE) || (keyCode >= Ext.EventObject.NUM_ZERO && keyCode <= Ext.EventObject.NUM_NINE)) {
      var oldValue = field.startValue;

      // Only change the time when it is a valid time
	    if ( field === this.dateTimeField.dateField || (Ext.isDefined(field.isValidTimeString) && field.isValidTimeString()) ) {
	      var newValue = field.getValue();
	      this.dateTimeField.doDateChange(newValue, oldValue);
      }
    }
  },

  /**
   * Event handler which is called when the {@link Ext.form.Radio radioButton} is checked,
   * and firing the {@link #focus} event.
   * @param {Ext.form.Radio} field The field which fired the event
   * @param {Boolean} checked true if field checked
   */
  onRadioCheck: function (field, checked)
  {
    if (checked === true) {
      this.fireEvent('focus', this);
    }
  },

  /**
   * Event handler which is called when the {@link Zarafa.common.ui.DateTimeField dateTimeField} receives input focus,
   * Set true in {@link Ext.form.Radio radioButton} and
   * Firing the {@link #focus} event.
   * @param {Zarafa.common.ui.DateTimeField} field The field which fired the event
   */
  onDateTimeFocus: function (field)
  {
    this.radioField.setValue(true);
    this.fireEvent('focus', this);
  },


  /**
   * Event handler which is called when the date has been changed,
   * and firing the {@link #change} event.
   * @param {Zarafa.common.ui.DateTimeField} field The field which has fired the event
   * @param {Date} newValue The new Date inside the DateTimeField
   * @param {Date} oldValue The old Date from the DateTimeField
   * @private
   */
  onDateTimeChange: function (field, newValue, oldValue)
  {

    var dateTime = this.getDateTime();
    this.fireEvent('change', this, dateTime);
  },

  /**
   * Function is use to add listener on keyup and focus event
   * and remove listener on change event of {@link Ext.form.DateField dateField} and a {@link Ext.ux.form.Spinner spinner}
   */
  initEvent: function ()
  {
    var timeField = this.dateTimeField.timeField;
    var dateField = this.dateTimeField.dateField;
    timeField.enableKeyEvents = true;
    dateField.enableKeyEvents = true;
    timeField.initEvents();
    dateField.initEvents();

    // Add listener on keyup event
    timeField.on('keyup', this.onKeyUp, this);
    dateField.on('keyup', this.onKeyUp, this);

    // Add listener on focus event
    timeField.on('focus', this.onDateTimeFocus, this);
    dateField.on('focus', this.onDateTimeFocus, this);

    // Remove listener on change event
    timeField.un('change', this.dateTimeField.onTimeChange, this.dateTimeField);
    dateField.un('change', this.dateTimeField.onDateChange, this.dateTimeField);
  },

  /**
   * Function return the {@link Zarafa.common.ui.DateTimeField dateTimeField} value
   * @return {Date} The date object
   */
  getDateTime: function ()
  {
    return this.dateTimeField.getValue();
  },

  /**
   * Function set true in {@link Ext.form.Radio radioButton} and
   * set value in {@link Zarafa.common.ui.DateTimeField dateTimeField}
   * @param {Date} value The date object
   */
  setValue: function (value)
  {
    this.radioField.setValue(true);
    this.dateTimeField.setValue(value);
  },

  /**
   * Function add css class to the {@link Zarafa.common.ui.DateTimeField dateTimeField}
   * @param {String} cls CSS class name
   */
  addClass: function (cls)
  {
    this.dateTimeField.addClass(cls);
  },

  /**
   * Function remove css class to the {@link Zarafa.common.ui.DateTimeField dateTimeField}
   * @param {String} cls CSS class name
   */
  removeClass: function (cls)
  {
    this.dateTimeField.removeClass(cls);
  }

});

Ext.reg('zarafa.radiodatetimefield', Zarafa.common.ui.RadioDateTimeField);
Ext.namespace('Zarafa.common.ui');

/**
 * @class Zarafa.common.ui.RadioSpinnerField
 * @extends Zarafa.common.ui.CompositeField
 * @xtype zarafa.radiospinnerfield
 *
 * This class can be used to construct a {@link Ext.form.Field field}
 * which contains a {@link Zarafa.common.ui.SpinnerField},{@link Ext.form.Radio} and {@link Ext.form.DisplayField}
 */
Zarafa.common.ui.RadioSpinnerField = Ext.extend(Zarafa.common.ui.CompositeField, {
  /**
   * @cfg {String} type The type of this spinnerfield. Can be 'hours', 'days', or 'months'
   */
  type: undefined,

  /**
   * @cfg {String} value The value which is inputValue of {@link Ext.form.Radio} and value for {@link Ext.form.DisplayField}
   */
  value: undefined,

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

    Ext.apply(this, config);
    config = Ext.applyIf(config, {
      items: [{
        xtype: 'radio',
        boxLabel: _('in'),
        ref: 'radioField',
        name: 'radioField',
        inputValue: this.value,
        listeners: {
          check: this.onRadioCheck,
          scope: this
        }
      }, {
        xtype: 'zarafa.spinnerfield',
        ref: 'spinnerField',
        defaultValue: 1,
        minValue: 1,
        maxValue: 999,
        incrementValue: 1,
        enableKeyEvents: true,
        width: 45,
        plugins: ['zarafa.numberspinner'],
        listeners: {
          spin: this.onValueChange,
          keyup: this.onValueChange,
          focus: this.onFocus,
          scope: this
        }
      }, {
        xtype: 'displayfield',
        value: this.value
      }]
    });
    Zarafa.common.ui.RadioSpinnerField.superclass.constructor.call(this, config);
  },

  /**
   * Event handler which is called when the {@link Ext.form.Radio radioButton} is checked,
   * and firing the {@link #focus} event.
   * @param {Ext.form.Radio} field The field which fired the event
   * @param {Boolean} checked true if field checked
   */
  onRadioCheck: function (field, checked)
  {
    if (checked === true) {
      this.fireEvent('focus', this);
    }
  },

  /**
   * Event handler which is called when the {@link Zarafa.common.ui.SpinnerField spinnerField} receives input focus,
   * Set true in {@link Ext.form.Radio radioButton} and Firing the {@link #focus} event.
   * @param {Zarafa.common.ui.SpinnerField} field The field which fired the event
   */
  onFocus: function (field)
  {
    this.radioField.setValue(true);
    this.fireEvent('focus', this);
  },

  /**
   * Event handler which is called when the {@link Zarafa.common.ui.SpinnerField spinnerField} value change,
   * and firing the {@link #change} event.
   * @param {Zarafa.common.ui.SpinnerField} field The field which fired the event
   */
  onValueChange: function (field)
  {
    var dateTime = this.getDateTime();
    this.fireEvent('change', this, dateTime);
  },

  /**
   * Function create date and time using {@link Zarafa.common.ui.SpinnerField spinnerField} value
   * @returns {Date} Date object
   */
  getDateTime: function ()
  {
    var spinnerValue = this.spinnerField.getValue();
    var currentDate = new Date();
    switch (this.type) {
      case 'days':
        return currentDate.add(Date.DAY, spinnerValue);
      case 'months':
        return currentDate.add(Date.MONTH, spinnerValue);
      default:
        return currentDate.add(Date.HOUR, spinnerValue);
    }
  }
});

Ext.reg('zarafa.radiospinnerfield', Zarafa.common.ui.RadioSpinnerField);
Ext.namespace('Zarafa.common.ui');

/**
 * This class is especially created to hold the recipient fields.
 * Since recipient fields can resize dynamically (in height) based on contents,
 * so should this composite field.
 *
 * @class Zarafa.common.ui.ResizableCompositeField
 * @extends Zarafa.common.ui.CompositeField
 * @xtype zarafa.resizablecompositefield
 */
Zarafa.common.ui.ResizableCompositeField = Ext.extend(Zarafa.common.ui.CompositeField, {

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

		Ext.applyIf(config, {
			// Overridden from Ext.Component
			xtype: 'zarafa.resizablecompositefield'
		});
		config.cls = config.cls ? config.cls + ' zarafa-resizable-compositefield' : 'zarafa-resizable-compositefield';

		Ext.apply(this, config);

		Zarafa.common.ui.ResizableCompositeField.superclass.constructor.call(this, config);
	},

	/**
	 * This function is overridden to be able to make sure the items in the hbox layout are stretched.
	 */
  initComponent: function() {
    var labels = [],
      items = this.items,
      item;

    for (var i=0, j = items.length; i < j; i++) {
      item = items[i];

      if (!Ext.isEmpty(item.ref)){
        item.ref = '../' + item.ref;
      }

      labels.push(item.fieldLabel);

      //apply any defaults
      Ext.applyIf(item, this.defaults);

      //apply default margins to each item except the last
      if (!(i == j - 1 && this.skipLastItemMargin)) {
        Ext.applyIf(item, {margins: this.defaultMargins});
      }
    }

    this.fieldLabel = this.fieldLabel || this.buildLabel(labels);

    /**
     * @property fieldErrors
     * @type Ext.util.MixedCollection
     * MixedCollection of current errors on the Composite's subfields. This is used internally to track when
     * to show and hide error messages at the Composite level. Listeners are attached to the MixedCollection's
     * add, remove and replace events to update the error icon in the UI as errors are added or removed.
     */
    this.fieldErrors = new Ext.util.MixedCollection(true, function(item) {
      return item.field;
    });

    this.fieldErrors.on({
      scope: this,
      add : this.updateInvalidMark,
      remove: this.updateInvalidMark,
      replace: this.updateInvalidMark
    });

    Ext.form.Field.prototype.initComponent.apply(this);

    this.innerCt = new Ext.Container({
      layout: 'hbox',
      layoutConfig: {
      	pack: 'start',
      	align: 'stretch'
      },
      items : this.items,
      cls  : 'x-form-composite',
      defaultMargins: '0 3 0 0',
      ownerCt: this
    });
    delete this.innerCt.ownerCt;

    var fields = this.innerCt.findBy(function(c) {
      return c.isFormField;
    }, this);

    /**
     * @property items
     * @type Ext.util.MixedCollection
     * Internal collection of all of the subfields in this Composite
     */
    this.items = new Ext.util.MixedCollection();
    this.items.addAll(fields);

		// Ext.form.CompositeField removes references to components which are not form fields
		// but we should support adding non form fields to composite field also
    fields = this.innerCt.findBy(function(c) {
			return c.isXType('box');
    }, this);

    this.items.addAll(fields);

		// Correct the ownerCt reference of the container, so we can use findParentBy
		// on the items in the innerCt to reach the compositefield (and its parents).
		this.innerCt.ownerCt = this;

    // Set a listener on boxfield items
    Ext.each( this.items.items, function(item){
    	if ( item.isXType('zarafa.boxfield') ){
    		item.mon(item, 'resizeheight', function(){
    			this.onResize();
    			this.ownerCt.doLayout(false);
    		}, this, { delay: 2 });
    	}
    }, this);
  },

	/**
	 * Overwrite this to give the inner container the correct height when the composite field
	 * is resized.
	 *
   * @param {Number} adjWidth The box-adjusted width that was set
   * @param {Number} adjHeight The box-adjusted height that was set
   * @param {Number} rawWidth The width that was originally specified
   * @param {Number} rawHeight The height that was originally specified
	 */
  onResize: function(adjWidth, adjHeight, rawWidth, rawHeight) {
  	// Store the padding because getting it is an expensive operation
  	if ( !Ext.isDefined(this.padding) ){
  		this.padding = this.getEl().getPadding('tb');
  	}

    var innerCt = this.innerCt;

    // Now check the heights of all items because they might have been resized
    // And then we should resize this container accordingly
    var height = 0;
    var items = innerCt.items;
    if ( items.items ){
    	items = items.items;
    }
    Ext.each(items, function(item){
    	var itemHeight = 0;
    	var itemPadding = 0;
    	if ( item.getResizeEl && item.getResizeEl() ){
	    	itemHeight = item.getResizeEl().getHeight();
	    	itemPadding = item.getResizeEl().getPadding('tb');
	    }
    	if ( itemHeight + itemPadding > height ){
    		height = itemHeight + itemPadding;
    	}
    }, this);

    height += this.padding;

    if ( height !== adjHeight ){
    	adjHeight = height;
    }

    if (this.rendered && innerCt.rendered) {
      innerCt.setSize(adjWidth, adjHeight);
    }

    Zarafa.common.ui.ResizableCompositeField.superclass.onResize.apply(this, [adjWidth, adjHeight, rawWidth, rawHeight]);
  }

});

Ext.reg('zarafa.resizablecompositefield', Zarafa.common.ui.ResizableCompositeField);
Ext.ns('Zarafa.common.ui');

/**
 * @class Zarafa.common.ui.TimePeriodField
 * @extends Zarafa.common.ui.DateRangeField
 * @xtype zarafa.timeperiodfield
 *
 * This class can be used to combine two {@link Zarafa.common.ui.SpinnerField SpinnerFields}
 * with the {@link Zarafa.common.plugins.TimeSpinner TimeSpinner} plugins applied to them.
 */
Zarafa.common.ui.TimePeriodField = Ext.extend(Zarafa.common.ui.DateRangeField, {
	/**
	 * @cfg {String} timeFormat The format in which the time appears in the
	 * time {@link Zarafa.common.ui.SpinnerField Spinner}.
	 */
	timeFormat: _('G:i'),
	/**
	 * @cfg {Number} timeIncrement The number of minutes to increase/decrease
	 * when the time {@link Zarafa.common.ui.SpinnerField Spinner} is used.
	 */
	timeIncrement: 30,
	/**
	 * @cfg {Object} pluginCfg Configuration object which should be applied
	 * by default on the {@link Zarafa.common.plugins.TimeSpinner TimeSpinner} plugin.
	 */
	pluginCfg: undefined,
	/**
	 * @constructor
	 * @param {Object} Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

		this.timeFormat = config.timeFormat || container.settingsModel.get('zarafa/v1/main/datetime_time_format');

		config.startFieldConfig = config.startFieldConfig || {};
		config.startFieldConfig.plugins = Ext.value(config.startFieldConfig.plugins, []);
		config.startFieldConfig.plugins.push(Ext.applyIf(config.pluginCfg || {}, {
			ptype: 'zarafa.timespinner',
			format: config.timeFormat || this.timeFormat
		}));
		config.startFieldConfig.listeners = Ext.value(config.startFieldConfig.listeners, {});
		if (config.startFieldConfig.listeners.spin) {
			config.startFieldConfig.listeners.spin = config.startFieldConfig.listeners.spin.createInterceptor(this.onStartSpin, this);
		} else {
			config.startFieldConfig.listeners.spin = this.onStartSpin.createDelegate(this);
		}

		Ext.applyIf(config.startFieldConfig, {
			xtype: 'zarafa.spinnerfield',
			fieldLabel: _('Start time'),
			width: 190,
			minValue: config.minValue || this.minValue,
			maxValue: config.maxValue || this.maxValue,
			incrementValue: config.timeIncrement || this.timeIncrement,
			alternateIncrementValue: 1
		});

		config.endFieldConfig = config.endFieldConfig || {};
		config.endFieldConfig.plugins = Ext.value(config.endFieldConfig.plugins, []);
		config.endFieldConfig.plugins.push(Ext.applyIf(config.pluginCfg || {}, {
			ptype: 'zarafa.timespinner',
			format: config.timeFormat || this.timeFormat
		}));
		config.endFieldConfig.listeners = Ext.value(config.endFieldConfig.listeners, {});
		if (config.endFieldConfig.listeners.spin) {
			config.endFieldConfig.listeners.spin = config.endFieldConfig.listeners.spin.createInterceptor(this.onEndSpin, this);
		} else {
			config.endFieldConfig.listeners.spin = this.onEndSpin.createDelegate(this);
		}

		config.endFieldConfig = Ext.applyIf(config.endFieldConfig || {}, {
			xtype: 'zarafa.spinnerfield',
			fieldLabel: _('End time'),
			width: 190,
			minValue: config.minValue || this.minValue,
			maxValue: config.maxValue || this.maxValue,
			incrementValue: config.timeIncrement || this.timeIncrement,
			alternateIncrementValue: 1
		});

		Zarafa.common.ui.TimePeriodField.superclass.constructor.call(this, config);
	},

	/**
	 * Event handler fired when the {@link #startField} has spinned. This will call
	 * the {@link #onStartChange} function to fix the duration of the entire field
	 * appropriately.
	 * @param {Zarafa.common.plugins.TimeSpinner} spinner The spinner which fired the event
	 * @private
	 */
	onStartSpin: function(spinner)
	{
		this.onStartChange(spinner.field, spinner.field.getValue(), spinner.field.startValue);
	},

	/**
	 * Event handler fired when the {@link #endField} has spinned. This will call
	 * the {@link #onEndChange} function to fix the duration of the entire field
	 * appropriately.
	 * @param {Zarafa.common.plugins.TimeSpinner} spinner The spinner which fired the event
	 * @private
	 */
	onEndSpin: function(spinner)
	{
		this.onEndChange(spinner.field, spinner.field.getValue(), spinner.field.startValue);
	}
});

Ext.reg('zarafa.timeperiodfield', Zarafa.common.ui.TimePeriodField);
Ext.namespace('Zarafa.common.ui.grid');

/**
 * @class Zarafa.common.ui.grid.MapiMessageGrid
 * @extends Zarafa.common.ui.grid.GridPanel
 * @xtype zarafa.mapimessagegrid
 *
 * Grid for MAPI messages, like Notes, Mails, Appointments, Tasks, Contacts.
 * To handle specific action that are performed on MAPI messages grid, like keyboard controls.
 */
Zarafa.common.ui.grid.MapiMessageGrid = Ext.extend(Zarafa.common.ui.grid.GridPanel, {
	/*
	 * @TODO:
	 * This grid is created only to handle keyboard events easily for
	 * Notes, Mails, Appointments, Tasks, Contacts.
	 * This allows 'grid.mapimessage' mapid to register common key events on
	 * above grids and handle them without code duplication.
	 * Grids like addressbookgrid, attachitemgrid, rulesgrid, remindergrid doesn't
	 * have common key mapping but extends Zarafa.common.ui.grid.GridPanel.
	 */

	/**
	 * The tooltip that will be used to show the full name of truncated categories
	 * @property
	 * @type {Zarafa.common.categories.ui.Tooltip}
	 */
	categoryTooltip: null,

	/**
	 * @cfg {String} sliderCls
	 * A CSS class to add to the slider element after it has been rendered (defaults to
	 * <code>'k-slider'</code>).
	 * @type String
	 */
	sliderCls: 'k-slider',

	/**
	 * The supportLiveScroll is true if grid supports live scroll facility and
	 * default it is false.
	 *
	 * @property
	 * @type Boolean
	 */
	supportLiveScroll: false,

	/**
	 * Initialize event handlers
	 * @private
	 */
	initEvents: function()
	{
		Zarafa.common.ui.grid.MapiMessageGrid.superclass.initEvents.call(this);

		this.mon(this.getView().scroller, 'scroll', this.onScroll, this);

		this.on({
			'afterrender': this.onRenderGrid,
			'cellcontextmenu': this.onCellContextMenu,
			scope: this
		});

		this.getEl().on({
			'mouseenter': this.onMouseEnter,
			'mouseleave': this.onMouseLeave,
			scope: this
		});

		// Only add the event listeners that resize the category labels when
		// we actually have a category column
		var columnModel = this.getColumnModel();
		if ( columnModel.getIndexById('categories') >= 0 ){
			this.on({
				'viewready': this.resizeCategoryLabels,
				'resize': this.resizeCategoryLabels,
				'columnresize': this.resizeCategoryLabels,
				scope: this
			});

			this.mon(this.store, 'load', this.resizeCategoryLabels, this);
		}
	},

	/**
	 * Event handler for the afterrender event of this panel. Creates a tooltip
	 * that will be used to show the full name of truncated categories.
	 * @param (Zarafa.mail.ui.MailGrid) grid this
	 */
	onRenderGrid: function(grid)
	{
		// Create a tooltip that will be used for truncated category labels
	  this.categoryTooltip = new Zarafa.common.categories.ui.Tooltip({
	    target: grid.getView().mainBody
	  });
	},

	/**
	 * Event handler which is triggered when the user opens the context menu.
	 *
	 * There are some selection rules regarding the context menu. If no rows where
	 * selected, the row on which the context menu was requested will be marked
	 * as selected. If there have been rows selected, but the context menu was
	 * requested on a different row, then the old selection is lost, and the new
	 * row will be selected. If the row on which the context menu was selected is
	 * part of the previously selected rows, then the context menu will be applied
	 * to all selected rows.
	 *
	 * @param {Zarafa.mail.ui.MailGrid} grid The grid which was right clicked
	 * @param {Number} rowIndex The index number of the row which was right clicked
	 * @param {Number} cellIndex The index number of the column which was right clicked
	 * @param {Ext.EventObject} event The event structure
	 * @private
	 */
	onCellContextMenu: function(grid, rowIndex, cellIndex, event)
	{
		var selectionModel = this.getSelectionModel();
		var columnModel = this.getColumnModel();

		if ( !selectionModel.isSelected(rowIndex) ) {
			selectionModel.selectRow(rowIndex);
		}

		// Take into account that the function onRowBodyContextMenu passes -1 as the column index.
		var dataIndex = (cellIndex >= 0) ? columnModel.getDataIndex(cellIndex) : undefined;
		var records = selectionModel.getSelections();

		switch (dataIndex) {
			case 'importance':
				Zarafa.core.data.UIFactory.openContextMenu(Zarafa.core.data.SharedComponentType['common.contextmenu.importance'], records, { position: event.getXY() });
				break;
			default:
				// If the click was on a category, we must open the category context menu
				var targetElement = Ext.get(event.target);
				if ( targetElement.hasClass('k-category-block') ){
					Zarafa.core.data.UIFactory.openContextMenu(Zarafa.core.data.SharedComponentType['common.contextmenu.category'], records, {
						category: targetElement.dom.textContent,
						position: event.getXY()
					});
					return;
				}

				Zarafa.core.data.UIFactory.openDefaultContextMenu(records, {
					position: event.getXY(),
					context: this.context,
					actsOnTodoListFolder: this.model.getDefaultFolder().isTodoListFolder()
				});
				break;
		}
	},

	/**
	 * Event handler for the viewready, resize, and columnresize events. Will
	 * resize the category labels in the category column (for the non-compact view)
	 */
	resizeCategoryLabels: function()
	{
		var columnModel = this.getColumnModel();
		var categoriesColIndex = columnModel.findColumnIndex('categories');

		// Only do the resizing when we are in the full view and the category
		// column is not hidden
		if ( categoriesColIndex === -1 || columnModel.isHidden(categoriesColIndex) ){
			return;
		}

		// Subtract the padding of the column
		var colWidth = columnModel.getColumnWidth(categoriesColIndex) - 16;

		var rows = this.getView().getRows();
		Ext.each(rows, function(row){
			var labels = row.querySelectorAll('.k-category-block');
			if ( Ext.isEmpty(labels[0]) ){
				return;
			}

			this.resizeCategoryLabelsInRow(row, labels, colWidth);
		}, this);
	},

	/**
	 * Calculates the width of the category labels in a row and will resize them if they
	 * are too large.
	 * @param {HtmlElement} row The container element of the row in the grid
	 * @param {Array} labels Array of HtmlElements that represent the category labels of
	 * the given row
	 * @param {Number} colWidth The width of the categories column in pixels
	 * @private
	 */
	resizeCategoryLabelsInRow: function(row, labels, colWidth)
	{
		var labelCount = labels.length;

		// Instead of hardcoding the margin and padding, we will read it once from the first label we find
		this.labelMarginRight = this.labelMarginRight || parseFloat(window.getComputedStyle(labels[0]).marginRight);
		this.labelPaddingLeftRight = this.labelPaddingLeftRight || parseFloat(window.getComputedStyle(labels[0]).paddingLeft) + parseFloat(window.getComputedStyle(labels[0]).paddingRight);

		// Calculate what the label width would be if we would divide the column width by the number of labels
		// (taking into account the right-margin of a label)
		var labelWidth = (colWidth + this.labelMarginRight)/labelCount - this.labelMarginRight - this.labelPaddingLeftRight;

		// Some labels are smaller than the calculated label width, so we will divide the space that
		// is not used over the remaining labels
		var unusedSpace = 0;
		var smallLabels = [];
		Ext.each(labels, function(label, index){
			// Temporarily disable the max-width, so we can get the untruncated width of the label
			var maxWidth = label.style.maxWidth;
			label.style.maxWidth = 'none';
			var width = parseFloat(window.getComputedStyle(label).width);
			if ( width < labelWidth ){
				smallLabels.push(index);
				unusedSpace += labelWidth - width;
			}
			label.style.maxWidth = maxWidth;
		}, this);

		// Now we can recalculate the label width
		labelWidth = labelWidth + unusedSpace/(labelCount-smallLabels.length);

		// Finally apply the calculated label width to all labels that need it
		Ext.each(labels, function(label, index){
			if ( smallLabels.indexOf(index) > -1 ){
				label.style.maxWidth = '';
			} else {
				label.style.maxWidth = labelWidth + 'px';
			}
		}, this);

		// Note: There might still be labels smaller than the maxWidth, so there could be space left.
		// Fixing this iteratively would be too much of a performance decrease, so we will settle for
		// these 'pseudo' optimized label widths.
	},

	/**
	 * Event handler which triggers when scrollbar is scroll.
	 * Slider element hide after 5 second of stop scrolling.
	 */
	onScroll: function()
	{
		if (!this.isPagingEnabled()) {
			container.getNotifier().notify('pagination.livescroll', undefined, undefined,{
				parentEl: this.getEl(),
				model: this.model,
				store: this.getStore()
			});
		}
	},

	/**
	 * Function used to identify that pagination is enabled or not.
	 * @return {Boolean} true if pagination is enabled else false.
	 */
	isPagingEnabled: function ()
	{
		return !container.getSettingsModel().get('zarafa/v1/contexts/mail/enable_live_scroll') || !this.supportLiveScroll;
	},

	/**
	 * Called when the grid is being resized. It will display
	 * slider in center of the grid.
	 *
	 * @private
	 */
	onResize: function()
	{
		Zarafa.common.ui.grid.MapiMessageGrid.superclass.onResize.apply(this, arguments);

		var slider = Ext.get(this.id + '-k-slider');
		if (slider) {
			container.getNotifier().notify('pagination.livescroll', undefined, undefined,{
				parentEl: this.getEl(),
				update: true,
				slider: slider
			});
		}
	},

	/**
	 * Event handler which triggered when mouse enter/over the {@link Ext.grid.GridPanel Grid}.
	 * It will also shows the notification/slider at bottom-center of {@link Ext.grid.GridPanel Grid}.
	 * which contains the {@link Zarafa.common.ui.PagingToolbar PagingToolbar}.
	 */
	onMouseEnter: function()
	{
		if (!this.isPagingEnabled()) {
			return;
		}

		container.getNotifier().notify('pagination.paging', undefined, undefined,{
			parentEl: this.getEl(),
			model: this.model,
			store: this.getStore()
		});
	},

	/**
	 * Event handler which triggered when mouse leaves the {@link Ext.grid.GridPanel Grid}.
	 * It will destroy the slider from bottom-center of {@link Ext.grid.GridPanel Grid}.
	 */
	onMouseLeave: function()
	{
		if (this.isPagingEnabled()) {
			container.getNotifier().notify('pagination.paging', undefined, undefined,{
				destroy: true
			});
		}
	}
});

Ext.reg('zarafa.mapimessagegrid', Zarafa.common.ui.grid.MapiMessageGrid);
/**
 * #dependsFile client/zarafa/common/ui/layout/SwitchBorderLayout.js
 */
Ext.namespace('Zarafa.common.ui.layout');

/**
 * @class Zarafa.common.ui.layout.SwitchBorderLayout.Orientation
 * @extends Zarafa.core.Enum
 *
 * The different orientations which are available for the
 * {@link Zarafa.common.ui.layout.SwitchBorderLayout}
 *
 * @singleton
 */
Zarafa.common.ui.layout.SwitchBorderLayout.Orientation = Zarafa.core.Enum.create({
	/**
	 * Denotes that non-center panel is disabled
	 * @property
	 * @type String
	 */
	OFF		: 'off',
	/**
	 * Denotes that the non-center panel is placed above or below the center panel
	 * @property
	 * @type String
	 */
	VERTICAL	: 'vertical',
	/**
	 * Denotes that the non-center panel is placed left or right the center panel
	 * @property
	 * @type String
	 */
	HORIZONTAL	: 'horizontal'
});
/**
 * #dependsFile client/zarafa/common/ui/layout/SwitchBorderLayout.js
 */
Ext.namespace('Zarafa.common.ui.layout');

/**
 * @class Zarafa.common.ui.layout.SwitchBorderLayout.SwitchRegion
 * @extends Ext.layout.BorderLayout.Region
 *
 * Extension to the {@link Ext.layout.BorderLayout.Region} for usage
 * in the {@link Zarafa.common.ui.layout.SwitchBorderLayout}. This defines
 * a non-resizable area in the layout.
 */
Zarafa.common.ui.layout.SwitchBorderLayout.SwitchRegion = Ext.extend(Ext.layout.BorderLayout.Region, {
	/**
	 * True if the region is currently hidden. This is controlled
	 * by {@link #setVisible}. If not defined, then it will depend on the visibility of the
	 * {@link #panel}.
	 * @property
	 * @type Boolean
	 */
	hidden: undefined,

	/**
	 * The old {@link Ext.Component#getWidth width} or {@link Ext.Component#getHeight height}
	 * or the {@link #panel} when it was being {@link #setVisible hidden}. This value represents
	 * the height when the {@link #position} is 'north' or 'south', it will represent the
	 * width otherwise.
	 * @property
	 * @type Number
	 */
	oldSize: undefined,

	/**
	 * Enables the visibility of the region (not the {@link #panel}!).
	 * This will update the {@link #hidden} property. If the region is
	 * being hidden, then the {@link #oldSize} will be saved, when the
	 * region is shown again, the {@link #oldSize} will be restored to
	 * the {@link #panel}.
	 *
	 * @param {Boolean} visible False to mark the region as hidden
	 */
	setVisible: function(visible)
	{
		var oldHidden = this.hidden;

		this.hidden = visible === false;

		if (oldHidden !== this.hidden) {
			if (this.hidden) {
				if (this.position === 'north' || this.position === 'south') {
					this.oldSize = this.panel.getHeight();
				} else {
					this.oldSize = this.panel.getWidth();
				}
			} else {
				// Ensure the panel thinks he is in a different position
				this.panel.region = this.position;

				if (this.position === 'north' || this.position === 'south') {
					this.panel.setHeight(this.oldSize || this.panel.height);
				} else {
					this.panel.setWidth(this.oldSize || this.panel.width);
				}
			}
		}
	},

	/**
	 * Check if the region is currently {@link #hidden visible}. If the {@link #hidden} property
	 * is undefined, it will check the visibility of the {@link #panel}.
	 * @return {Boolean} True when the region is visible
	 */
	isVisible: function()
	{
		return Ext.isDefined(this.hidden) ? !this.hidden : !this.panel.hidden;
	},

	/**
	 * Obtain the desired size for the region, this will use {@link #restrictSize}
	 * to restrict the values to the allowed boundaries.
	 * @return {Object} size The size for the region
	 */
	getSize: function()
	{
		var size = Zarafa.common.ui.layout.SwitchBorderLayout.SwitchRegion.superclass.getSize.call(this);
		return this.restrictSize(size);
	},

	/**
	 * Restrict the given size object (as obtained by {@link #getSize}) to ensure that
	 * the {@link Ext.Component#minHeight}/{@ink Ext.Component#minWidth} is enforced.
	 * @param {Object} size The desired size
	 * @return {Object} The restricted size
	 * @private
	 */
	restrictSize: function(size)
	{
		var panel = this.panel;
		var center = this.layout.center.panel;
		var targetEl = this.targetEl;
		var splitEl = this.splitEl;
		var splitSize = 0;

		if (splitEl && splitEl.dom) {
			splitSize = (this.position === 'north' || this.position === 'south') ?
				splitEl.dom.offsetHeight :
				splitEl.dom.offsetWidth;
		}

		if (this.position === 'north' || this.position === 'south') {
			// Ensure enough room remains for the center panel.
			var maxHeight = targetEl.getHeight() - (center.minHeight || center.height) - splitSize;
			if (Ext.isNumber(maxHeight)) {
				size.height = Math.min(size.height, Math.max(maxHeight, 0));
			}

			// Check if the given height is larger then the target
			if (size.height >= targetEl.getHeight()) {
				if (panel.height) {
					// If the panel was initially configured with an height,
					// assume that as the default height
					size.height = panel.height;
				} else {
					// Otherwise we check how much space is available to us
					// considering the minimum width of the center
					size.height = Math.min(panel.maxHeight || 0, targetEl.getHeight() - (center.minHeight || center.height));
				}

				// Take something off for the split element
				size.height -= splitSize;
			}

			// Enforce minimum height of the panel
			if (Ext.isNumber(panel.minHeight)) {
				size.height = Math.max(size.height, panel.minHeight);
			}
		} else {
			// Ensure enough room remains for the center panel.
			var maxWidth = targetEl.getWidth() - (center.minWidth || center.width) - splitSize;
			if (Ext.isNumber(maxWidth)) {
				size.width = Math.min(size.width, Math.max(maxWidth, 0));
			}

			// Check if the given width is larger then the target
			if (size.width >= targetEl.getWidth()) {
				if (panel.width) {
					// If the panel was initially configured with an width,
					// assume that as the default width
					size.width = panel.width;
				} else {
					// Otherwise we check how much space is available to us
					// considering the minimum height of the center
					size.width = Math.min(panel.maxWidth || 0, targetEl.getWidth() - (center.minWidth || center.width));
				}

				// Take something off for the split element
				size.width -= splitSize;
			}

			// Enforce minimum width of the panel
			if (Ext.isNumber(panel.minWidth)) {
				size.width = Math.max(size.width, panel.minWidth);
			}
		}

		return size;
	}
});
Ext.namespace('Zarafa.common.ui.messagepanel');

/**
 * @class Zarafa.common.ui.messagepanel.ExtraInfoContextMenu
 * @extends Zarafa.core.ui.menu.ConditionalMenu
 * @xtype zarafa.extrainfocontextmenu
 */
Zarafa.common.ui.messagepanel.ExtraInfoContextMenu = Ext.extend(Zarafa.core.ui.menu.ConditionalMenu, {
	// Insertion points for this class
	/**
	 * @insert context.common.previewpanel.extrainfo.contextmenu.actions
	 * @param {Zarafa.common.ui.messagepanel.ExtraInfoContextMenu} contextmenu This contextmenu
	 */

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

		if (config.records) {
			if (Array.isArray(config.records)) {
				config.records = config.records[0];
			}
		}

		Ext.applyIf(config, {
			items: [{
				xtype: 'zarafa.conditionalitem',
				text: _('Download Pictures'),
				handler: this.showPictures,
				scope: this
			}, {
				xtype: 'menuseparator'
			}, {
				xtype: 'zarafa.conditionalitem',
				text: _('Add Sender to Safe Senders List'),
				handler: this.addSenderToSafeList,
				scope: this
			}, {
				xtype: 'zarafa.conditionalitem',
				text: _('Add Domain to Safe Senders List'),
				handler: this.addDomainToSafeList,
				scope: this
			}]
		});

		Zarafa.common.ui.messagepanel.ExtraInfoContextMenu.superclass.constructor.call(this, config);
	},

	/**
	 * Function will calculate block_status property value of {@link Zarafa.core.data.MessageRecord MessageRecord}
	 * and set it and will save the record to send changes to server
	 * @private
	 */
	showPictures: function()
	{
		this.setBlockStatus();
	},

	/**
	 * Function will add smtp address of sender of this mail to {@link Zarafa.settings.SettingsModel #safe_senders_list}
	 * so the mails from this user will not be checked for blocking external content.
	 * @private
	 */
	addSenderToSafeList: function()
	{
		var smtpAddress = this.records.get('sent_representing_email_address') || this.records.get('sender_email_address');
		if(Ext.isEmpty(smtpAddress)) {
			return;
		}

		var safeSenders = container.getSettingsModel().get('zarafa/v1/contexts/mail/safe_senders_list', true);

		if(!Ext.isEmpty(safeSenders)) {
			safeSenders.push(smtpAddress);
		} else {
			safeSenders = [smtpAddress];
		}

		container.getSettingsModel().set('zarafa/v1/contexts/mail/safe_senders_list', safeSenders);

		this.setBlockStatus();
	},

	/**
	 * Function will add domain address of sender of this mail to {@link Zarafa.settings.SettingsModel #safe_senders_list}
	 * so the mails from this domain will not be checked for blocking external content.
	 * @private
	 */
	addDomainToSafeList: function()
	{
		var smtpAddress = this.records.get('sent_representing_email_address') || this.records.get('sender_email_address');
		var domainName = smtpAddress.substr(smtpAddress.indexOf('@') + 1);
		if(Ext.isEmpty(domainName)) {
			return;
		}

		var safeSenders = container.getSettingsModel().get('zarafa/v1/contexts/mail/safe_senders_list', true);

		if(!Ext.isEmpty(safeSenders)) {
			safeSenders.push(domainName);
		} else {
			safeSenders = [domainName];
		}

		container.getSettingsModel().set('zarafa/v1/contexts/mail/safe_senders_list', safeSenders);

		this.setBlockStatus();
	},

	setBlockStatus: function()
	{
		var blockStatusValue = this.records.calculateBlockStatus();

		if (blockStatusValue) {
			this.records.set('block_status', blockStatusValue);
			this.records.save();
		}
	}
});

Ext.reg('zarafa.extrainfocontextmenu', Zarafa.common.ui.messagepanel.ExtraInfoContextMenu);
/*
 * #dependsFile client/zarafa/core/KeyMapMgr.js
 */
Ext.namespace('Zarafa.contact');

/**
 * @class Zarafa.contact.KeyMapping
 * @extends Object
 *
 * The map of keys used in the Contact Context.
 * @singleton
 */
Zarafa.contact.KeyMapping = Ext.extend(Object, {
	/**
	 * @constructor
	 */
	constructor: function()
	{
		var newItemKeys = [{
			key: Ext.EventObject.C,
			ctrl: true,
			alt: true,
			shift: false,
			stopEvent: true,
			handler: this.onNewContact,
			scope: this,
			settingsCfg: {
				description: _('New contact'),
				category: _('Creating an item')
			}
		},{
			key: Ext.EventObject.D,
			ctrl: true,
			alt: true,
			shift: false,
			stopEvent: true,
			handler: this.onNewDistributionList,
			scope: this,
			settingsCfg: {
				description: _('New distribution list'),
				category: _('Creating an item')
			}
		}];

		Zarafa.core.KeyMapMgr.register('global', newItemKeys);
	},

	/**
	 * Event handler for the keydown event of the {@link Zarafa.core.KeyMap KeyMap} when the user wants to
	 * create a new contact.
	 * @param {Number} key Key code
	 * @param {Ext.EventObject} event The event
	 * @param {Ext.Component} component The component on which key event is fired.
	 */
	onNewContact: function(key, event, component)
	{
		Zarafa.contact.Actions.openCreateContactContent(container.getContextByName('contact').getModel());
	},

	/**
	 * Event handler for the keydown event of the {@link Zarafa.core.KeyMap KeyMap} when the user wants to
	 * create a new distribution list.
	 * @param {Number} key Key code
	 * @param {Ext.EventObject} event The event
	 * @param {Ext.Component} component The component on which key event is fired.
	 */
	onNewDistributionList: function(key, event, component)
	{
		Zarafa.contact.Actions.openCreateDistlistContent(container.getContextByName('contact').getModel());
	}

});

Zarafa.contact.KeyMapping = new Zarafa.contact.KeyMapping();/*
 * #dependsFile client/zarafa/common/ui/grid/Renderers.js
 */
Ext.namespace('Zarafa.contact.attachitem');

/**
 * @class Zarafa.contact.attachitem.AttachContactColumnModel
 * @extends Zarafa.common.ui.grid.ColumnModel
 *
 * The {@link Zarafa.contact.attachitem.AttachContactColumnModel AttachContactColumnModel} is the column model containing
 * sets of {@link Ext.grid.Column columns} for contact folders.
 * This column model will be used with {@link Zarafa.common.attachment.dialog.AttachItemGrid AttachItemGrid}.
 */
Zarafa.contact.attachitem.AttachContactColumnModel = Ext.extend(Zarafa.common.ui.grid.ColumnModel, {
	/**
	 * @constructor
	 * @param config Configuration structure
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			columns: this.getColumns(),
			defaults: {
				sortable: true
			}
		});

		Zarafa.contact.attachitem.AttachContactColumnModel.superclass.constructor.call(this, config);
	},

	/**
	 * Create an array of {@link Ext.grid.Column columns} which will be visible when loading data from
	 * contact folder into {@link Zarafa.common.attachment.dialog.AttachItemGrid AttachItemGrid}.
	 *
	 * @return {Ext.grid.Column[]} The array of columns
	 * @private
	 */
	getColumns: function()
	{
		return [{
			dataIndex: 'icon_index',
			headerCls: 'zarafa-icon-column',
			header: '<p class=\'icon_index\'>&nbsp;</p>',
			tooltip: _('Sort by: Icon'),
			width: 24,
			fixed: true,
			renderer: Zarafa.common.ui.grid.Renderers.icon
		}, {
			header: '<p class=\'icon_paperclip\'>&nbsp;</p>',
			headerCls: 'zarafa-icon-column',
			dataIndex: 'hasattach',
			width: 24,
			fixed: true,
			renderer: Zarafa.common.ui.grid.Renderers.attachment,
			tooltip: _('Sort by: Attachment')
		}, {
			dataIndex: 'fileas',
			header: _('File As'),
			tooltip: _('Sort by: File As'),
			renderer: Ext.util.Format.htmlEncode
		}, {
			dataIndex: 'company_name',
			header: _('Company Name'),
			tooltip: _('Sort by: Company Name'),
			renderer: Ext.util.Format.htmlEncode
		}, {
			dataIndex: 'title',
			header: _('Job Title'),
			tooltip: _('Sort by: Job Title'),
			renderer: Ext.util.Format.htmlEncode
		}];
	}
});Ext.namespace('Zarafa.contact.attachitem');

/**
 * @class Zarafa.contact.attachitem.AttachContactRenderer
 * @extends Zarafa.common.attachment.dialogs.AttachItemBaseRenderer
 *
 * Renderer that can be used to get text data from {@link Zarafa.contact.ContactRecord ContactRecord}.
 */
Zarafa.contact.attachitem.AttachContactRenderer = Ext.extend(Zarafa.common.attachment.dialogs.AttachItemBaseRenderer, {
	/**
	 * Constructor will initialize default properties
	 * @param {Object} config configuration object
	 */
	constructor: function(config)
	{
		Zarafa.contact.attachitem.AttachContactRenderer.superclass.constructor.call(this, config);

		Ext.apply(this.labels, {
			'display_name': _('Full Name'),
			'last_name': _('Last Name'),
			'first_name': _('First Name'),
			'job_title': _('Job Title'),
			'department_name': _('Department Name'),
			'company_name': _('Company Name'),
			'im': _('IM Address'),
			'home_address': _('Home Address'),
			'business_address': _('Business Address'),
			'other_address': _('Other Address'),
			'office_telephone_number': _('Business Phone'),
			'business2_telephone_number': _('Business 2 Phone'),
			'assistant_telephone_number': _('Assistant Phone'),
			'company_telephone_number': _('Company Main Phone'),
			'home_telephone_number': _('Home Phone'),
			'home2_telephone_number': _('Home 2 Phone'),
			'cellular_telephone_number': _('Mobile'),
			'car_telephone_number': _('Car Phone'),
			'radio_telephone_number': _('Radio'),
			'pager_telephone_number': _('Pager'),
			'callback_telephone_number': _('Callback Phone'),
			'other_telephone_number': _('Other Phone'),
			'primary_telephone_number': _('Primary Phone'),
			'telex_telephone_number': _('Telex'),
			'ttytdd_telephone_number': _('TTY/TDD Phone'),
			'isdn_number': _('ISDN'),
			'other_fax_number': _('Other Fax'),
			'business_fax_number': _('Business Fax'),
			'home_fax_number': _('Home Fax'),
			'email_address_1': _('Email'),
			'email_address_display_name_1': _('Email Display As'),
			'email_address_2': _('Email 2'),
			'email_address_display_name_2': _('Email2 Display As'),
			'email_address_3': _('Email 3'),
			'email_address_display_name_3': _('Email3 Display As'),
			'birthday': _('Birthday'),
			'wedding_anniversary': _('Wedding Anniversary'),
			'spouse_name': _('Spouse/Partner'),
			'profession': _('Profession'),
			'assistant': _('Assistant\'s Name'),
			'webpage': _('Webpage')
		});
	},

	/**
	 * Function should be used to generate template which can be used by {@link #generateText} to add data into template and return the string that is generated.
	 * This will generate template for the html format only.
	 * @return {String} The HTML for the XTemplate to use
	 */
	generateHTMLTemplate: function()
	{
		var html = '';

		html += this.addHTMLRowGroup({
			'display_name': '{display_name:htmlEncode}',
			'last_name': '{last_name:htmlEncode}',
			'first_name': '{first_name:htmlEncode}',
			'job_title': '{job_title:htmlEncode}',
			'department_name': '{department_name:htmlEncode}',
			'company_name': '{company_name:htmlEncode}'
		});

		html += this.addHTMLRowGroup({
			'business_address': '{business_address:htmlEncode}',
			'home_address': '{home_address:htmlEncode}',
			'other_address': '{other_address:htmlEncode}',
			'im': '{im:htmlEncode}'
		});

		html += this.addHTMLRowGroup({
			'office_telephone_number': '{office_telephone_number:htmlEncode}',
			'business2_telephone_number': '{business2_telephone_number:htmlEncode}',
			'assistant_telephone_number': '{assistant_telephone_number:htmlEncode}',
			'company_telephone_number': '{company_telephone_number:htmlEncode}',
			'home_telephone_number': '{home_telephone_number:htmlEncode}',
			'home2_telephone_number': '{home2_telephone_number:htmlEncode}',
			'cellular_telephone_number': '{cellular_telephone_number:htmlEncode}',
			'car_telephone_number': '{car_telephone_number:htmlEncode}',
			'radio_telephone_number': '{radio_telephone_number:htmlEncode}',
			'pager_telephone_number': '{pager_telephone_number:htmlEncode}',
			'callback_telephone_number': '{callback_telephone_number:htmlEncode}',
			'other_telephone_number': '{other_telephone_number:htmlEncode}',
			'primary_telephone_number': '{primary_telephone_number:htmlEncode}',
			'telex_telephone_number': '{telex_telephone_number:htmlEncode}',
			'ttytdd_telephone_number': '{ttytdd_telephone_number:htmlEncode}',
			'isdn_number': '{isdn_number:htmlEncode}',
			'other_fax_number': '{other_fax_number:htmlEncode}',
			'business_fax_number': '{business_fax_number:htmlEncode}',
			'home_fax_number': '{business_fax_number:htmlEncode}'
		});

		html += this.addHTMLRowGroup({
			'email_address_1': '{email_address_1:htmlEncode}',
			'email_address_display_name_1': '{email_address_display_name_1:htmlEncode}',
			'email_address_2': '{email_address_2:htmlEncode}',
			'email_address_display_name_2': '{email_address_display_name_2:htmlEncode}',
			'email_address_3': '{email_address_3:htmlEncode}',
			'email_address_display_name_3': '{email_address_display_name_3:htmlEncode}'
		});

		html += this.addHTMLRowGroup({
			// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
			'birthday': '{birthday:formatDefaultTimeString("' + _("l jS F Y {0}") + '")}',
			// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
			'wedding_anniversary': '{wedding_anniversary:formatDefaultTimeString("' + _("l jS F Y {0}") + '")}',
			'spouse_name': '{spouse_name:htmlEncode}',
			'profession': '{profession:htmlEncode}',
			'assistant': '{assistant:htmlEncode}',
			'webpage': '{webpage:htmlEncode}'
		});

		html += this.addHTMLRowGroup({
			'sensitivity': '{sensitivity:sensitivityString}',
			'categories': '{categories:htmlEncode}',
			'attachment_names': '{attachment_names:htmlEncode}'
		});

		html += '{body}';

		return html;
	},

	/**
	 * Function should be used to generate template which can be used by {@link #generateText} to add data into template and return the string that is generated.
	 * This will generate template for the plain text format only.
	 * @return {String} The HTML for the XTemplate to use
	 */
	generatePlainTemplate: function()
	{
		var html = '';

		html += this.addPlainRowGroup({
			'display_name': '{display_name}',
			'last_name': '{last_name}',
			'first_name': '{first_name}',
			'job_title': '{job_title}',
			'department_name': '{department_name}',
			'company_name': '{company_name}'
		});

		html += this.addPlainRowGroup({
			'business_address': '{business_address}',
			'home_address': '{home_address}',
			'other_address': '{other_address}',
			'im': '{im}'
		});

		html += this.addPlainRowGroup({
			'office_telephone_number': '{office_telephone_number}',
			'business2_telephone_number': '{business2_telephone_number}',
			'assistant_telephone_number': '{assistant_telephone_number}',
			'company_telephone_number': '{company_telephone_number}',
			'home_telephone_number': '{home_telephone_number}',
			'home2_telephone_number': '{home2_telephone_number}',
			'cellular_telephone_number': '{cellular_telephone_number}',
			'car_telephone_number': '{car_telephone_number}',
			'radio_telephone_number': '{radio_telephone_number}',
			'pager_telephone_number': '{pager_telephone_number}',
			'callback_telephone_number': '{callback_telephone_number}',
			'other_telephone_number': '{other_telephone_number}',
			'primary_telephone_number': '{primary_telephone_number}',
			'telex_telephone_number': '{telex_telephone_number}',
			'ttytdd_telephone_number': '{ttytdd_telephone_number}',
			'isdn_number': '{isdn_number}',
			'other_fax_number': '{other_fax_number}',
			'business_fax_number': '{business_fax_number}',
			'home_fax_number': '{business_fax_number}'
		});

		html += this.addPlainRowGroup({
			'email_address_1': '{email_address_1}',
			'email_address_display_name_1': '{email_address_display_name_1}',
			'email_address_2': '{email_address_2}',
			'email_address_display_name_2': '{email_address_display_name_2}',
			'email_address_3': '{email_address_3}',
			'email_address_display_name_3': '{email_address_display_name_3}'
		});

		html += this.addPlainRowGroup({
			// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
			'birthday': '{birthday:formatDefaultTimeString("' + _("l jS F Y {0}") + '")}',
			// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
			'wedding_anniversary': '{wedding_anniversary:formatDefaultTimeString("' + _("l jS F Y {0}") + '")}',
			'spouse_name': '{spouse_name}',
			'profession': '{profession}',
			'assistant': '{assistant}',
			'webpage': '{webpage}'
		});

		html += this.addPlainRowGroup({
			'sensitivity': '{sensitivity:sensitivityString}',
			'categories': '{categories}',
			'attachment_names': '{attachment_names}'
		});

		html += '{body}';

		return html;
	}
});Ext.namespace('Zarafa.contact.attachitem');

/**
 * @class Zarafa.contact.attachitem.AttachDistlistRenderer
 * @extends Zarafa.common.attachment.dialogs.AttachItemBaseRenderer
 *
 * Renderer that can be used to get text data from {@link Zarafa.contact.ContactRecord ContactRecord}.
 */
Zarafa.contact.attachitem.AttachDistlistRenderer = Ext.extend(Zarafa.common.attachment.dialogs.AttachItemBaseRenderer, {
	/**
	 * Constructor will initialize default properties
	 * @param {Object} config configuration object
	 */
	constructor: function(config)
	{
		Zarafa.contact.attachitem.AttachDistlistRenderer.superclass.constructor.call(this, config);

		Ext.apply(this.labels, {
			'display_name': _('Distribution List Name'),
			'members': _('Members')
		});
	},

	/**
	 * Function should be used to generate template which can be used by {@link #generateText} to add data into template and return the string that is generated.
	 * This will generate template for the html format only.
	 * @return {String} The HTML for the XTemplate to use
	 */
	generateHTMLTemplate: function()
	{
		var html = '';

		html += this.addHTMLRowGroup({
			'display_name': '{display_name:htmlEncode}'
		});

		html += this.addHTMLRowGroup({
			'sensitivity': '{sensitivity:sensitivityString}',
			'categories': '{categories:htmlEncode}',
			'attachment_names': '{attachment_names:htmlEncode}'
		});

		html += this.addHTMLRowGroup({
			'members': '{members:htmlEncode}'
		});

		html += '{body}';

		return html;
	},

	/**
	 * Function should be used to generate template which can be used by {@link #generateText} to add data into template and return the string that is generated.
	 * This will generate template for the plain text format only.
	 * @return {String} The HTML for the XTemplate to use
	 */
	generatePlainTemplate: function()
	{
		var html = '';

		html += this.addPlainRowGroup({
			'display_name': '{display_name}'
		});

		html += this.addPlainRowGroup({
			'sensitivity': '{sensitivity:sensitivityString}',
			'categories': '{categories}',
			'attachment_names': '{attachment_names}'
		});

		html += this.addPlainRowGroup({
			'members': '{members}'
		});

		html += '{body}';

		return html;
	},

	/**
	 * Prepares data suitable for use in an XTemplate from the record.
	 * @param {Zarafa.core.data.IPMRecord} record The record to acquire data from.
	 * @return {Array} An array of data which is customized for our purpose.
	 */
	prepareData: function(record, htmlFormat)
	{
		var data = Zarafa.contact.attachitem.AttachDistlistRenderer.superclass.prepareData.apply(this, arguments);

		// generate members data
		data['members'] = record.getMemberNames();

		return data;
	}
});/*
 * #dependsFile client/zarafa/contact/data/ContactConfig.js
 */
Ext.namespace('Zarafa.contact.data');

/**
 * @class Zarafa.contact.data.ContactDetailsParser
 * @extends Object
 * This class is used to parse/combine detailed info for full name, address and phone numbers
 * this class can not parse all the formats of different regions so it assumes input data
 * in some specific format
 */
Zarafa.contact.data.ContactDetailsParser = Ext.extend(Object, {
	/**
	 * @cfg {Array} prefixOptions prefix options array in the form of [[displayText]]
	 */
	prefixOptions: undefined,

	/**
	 * @cfg {Array} suffixOptions suffix options array in the form of [[displayText]]
	 */
	suffixOptions: undefined,

	/**
	 * @cfg {HexValue} CR carriage return value in form of 8-bit hex value
	 */
	CR: Zarafa.contact.data.config.CR,

	/**
	 * @cfg {HexValue} LF line feed value in form of 8-bit hex value
	 */
	LF: Zarafa.contact.data.config.LF,

	/**
	 * @cfg {HexValue} CRLF carriage return + line feed in form of 8-bit hex value
	 */
	CRLF: Zarafa.contact.data.config.CRLF,

	/**
	 * @cfg {HexValue} SP space value in form of 8-bit hex value
	 */
	SP: Zarafa.contact.data.config.SP,

	/**
	 * @cfg {HexValue} LF non-breaking space value in form of 8-bit hex value
	 */
	NBSP: Zarafa.contact.data.config.NBSP,

	/**
	 * @constructor
	 * @param {Object} configuration object.
	 */
	constructor: function(config)
	{
		config = config || {};
		this.prefixOptions = Zarafa.contact.data.config.Prefix;
		this.suffixOptions = Zarafa.contact.data.config.Suffix;
		Ext.apply(this, config);
		Zarafa.contact.data.ContactDetailsParser.superclass.constructor.call(this, config);
	},

	/**
	 * This is a wrapper function for all parsing methods for different type of data
	 * @param {String} infoType type of data that needs to be parsed
	 * @param {String} data data string that will be parsed
	 * @return {Object} object that contains parsed data
	 */
	parseInfo: function(infoType, data)
	{
		// invalid data
		if(!Ext.isString(data)) {
			return null;
		}

		switch(infoType) {
			case 'name':
				return this.parseNameInfo(data);
			case 'phone':
				return this.parsePhoneInfo(data);
			case 'address':
				return this.parseAddressInfo(data);
			default:
				return null;
		}
	},

	/**
	 * This function will parse full name string and convert into an object that will contain
	 * prefix, given_name, middle_name, surname, generation
	 *
	 * assumptions that is used for parsing full name in this function
	 * value       | prefix | given_name | middle_name | surname | generation
	 * John        |    | John    |       |     |
	 * John Doe      |    | John    |       | Doe   |
	 * John A Doe     |    | John    | A      | Doe   |
	 * John Doe Jr.    |    | John    |       | Doe   | Jr.
	 * Mr. John      | Mr.  | John    |       |     |
	 * Mr. John Doe    | Mr.  | John    |       | Doe   |
	 * Mr. John A Doe   | Mr.  | John    | A      | Doe   |
	 * Mr. John A Doe Jr. | Mr.  | John    | A      | Doe   | Jr.
	 * Doe Jr.      |    |      |       | Doe   | Jr.
	 *
	 * @param {String} data data string that will be parsed
	 * @return {Object} object that contains parsed data
	 * @private
	 */
	parseNameInfo: function(data)
	{
		var result = {
			'display_name_prefix': '',
			'given_name': '',
			'middle_name': '',
			'surname': '',
			'generation': '',
			'incomplete_info': false
		};

		// split display name
		// ([0] => display_name_prefix, [1] => given_name, [2] => middle_name, [3] => surname, [4] => generation)
		var displayNameParts = data.split(new RegExp(this.SP + '|' + this.NBSP, 'g'));
		displayNameParts = Zarafa.core.Util.trimStringArray(displayNameParts);

		switch(displayNameParts.length) {
			case 4:
				// search for display name prefix in name
				var firstToken = displayNameParts.shift();
				result['display_name_prefix'] = Zarafa.core.Util.inArray(this.prefixOptions, firstToken, true) ? firstToken : '';

				// search for suffix in name
				var lastToken = displayNameParts.pop();
				result['generation'] = Zarafa.core.Util.inArray(this.suffixOptions, lastToken, true) ? lastToken : '';
				result['given_name'] = !Ext.isEmpty(result['display_name_prefix']) ? displayNameParts.shift() : firstToken;
				result['surname'] = !Ext.isEmpty(result['generation']) ? displayNameParts.pop() : lastToken;
				result['middle_name'] = !Ext.isEmpty(displayNameParts.length) ? displayNameParts.shift() : '';
				break;
			case 3:
				// search for display name prefix in name
				var firstToken = displayNameParts.shift();
				result['display_name_prefix'] = Zarafa.core.Util.inArray(this.prefixOptions, firstToken, true) ? firstToken : '';

				// search for suffix in name
				var lastToken = displayNameParts.pop();
				result['generation'] = Zarafa.core.Util.inArray(this.suffixOptions, lastToken, true) ? lastToken : '';

				if(Ext.isEmpty(result['display_name_prefix']) && Ext.isEmpty(result['generation'])) {
					result['given_name'] = firstToken;
					result['middle_name'] = displayNameParts.shift();
					result['surname'] = lastToken;
				} else if(!Ext.isEmpty(result['display_name_prefix']) && Ext.isEmpty(result['generation'])) {
					result['given_name'] = displayNameParts.shift();
					result['surname'] = lastToken;
				} else if(Ext.isEmpty(result['display_name_prefix']) && !Ext.isEmpty(result['generation'])) {
					result['given_name'] = firstToken;
					result['surname'] = displayNameParts.shift();
				} else if(!Ext.isEmpty(result['display_name_prefix']) && !Ext.isEmpty(result['generation'])) {
					result['surname'] = displayNameParts.shift();
				}
				break;
			case 2:
				// search for display name prefix in name
				var firstToken = displayNameParts.shift();
				result['display_name_prefix'] = Zarafa.core.Util.inArray(this.prefixOptions, firstToken, true) ? firstToken : '';

				// search for suffix in name
				var lastToken = displayNameParts.pop();
				result['generation'] = Zarafa.core.Util.inArray(this.suffixOptions, lastToken, true) ? lastToken : '';

				if(Ext.isEmpty(result['display_name_prefix']) && Ext.isEmpty(result['generation'])) {
					result['given_name'] = firstToken;
					result['surname'] = lastToken;
				} else if(Ext.isEmpty(result['display_name_prefix']) && !Ext.isEmpty(result['generation'])) {
					result['surname'] = firstToken;
					// information entered is incomplete or unclear
					result['incomplete_info'] = true;
				} else if(!Ext.isEmpty(result['display_name_prefix']) && Ext.isEmpty(result['generation'])) {
					result['surname'] = lastToken;
					// information entered is incomplete or unclear
					result['incomplete_info'] = true;
				}
				break;
			case 1:
				result['given_name'] = displayNameParts.shift();
				// information entered is incomplete or unclear
				result['incomplete_info'] = true;
				break;
			default:
				// search for display name prefix in name
				var firstToken = displayNameParts.shift();
				result['display_name_prefix'] = Zarafa.core.Util.inArray(this.prefixOptions, firstToken, true) ? firstToken : '';

				// search for suffix in name
				var lastToken = displayNameParts.pop();
				result['generation'] = Zarafa.core.Util.inArray(this.suffixOptions, lastToken, true) ? lastToken : '';

				result['given_name'] = !Ext.isEmpty(result['display_name_prefix']) ? displayNameParts.shift() : firstToken;
				result['surname'] = !Ext.isEmpty(result['display_name_prefix']) ? displayNameParts.pop() : lastToken;
				result['middle_name'] = displayNameParts.join(this.NBSP);
				break;
		}

		return result;
	},

	/**
	 * This function will parse phone number string and convert into an object that will contain
	 * countr_code, city_code, local_number, extension
	 *
	 * assumptions that is used for parsing phone numbers in this function
	 * value            | country_code | city_code | local_number | extension
	 * 1234567           |       |      | 1234567   |
	 * 1234567 x 123        |       |      | 1234567   | 123
	 * +91 1234567 x 123      | +91     |      | 1234567   | 123
	 * +91 1234567         | +91     |      | 1234567   |
	 * +91 (0265) 1234567      | +91     | 0265   | 1234567   |
	 * +91 (0265) 1234567 x 123   | +91     | 0265   | 1234567   | 123
	 * +91 (0265) 1234567/123    | +91     | 0265   | 1234567   | 123
	 * (0265) 1234567        |       | 0265   | 1234567   |
	 *
	 * @param {String} data data string that will be parsed
	 * @return {Object} object that contains parsed data
	 * @private
	 */
	parsePhoneInfo: function(data)
	{
		var result = {
			'country_code': '',
			'city_code': '',
			'local_number': '',
			'extension': ''
		};

		// Search for extensions
		var extensions = ['x', '/', '-'];
		for (var i = 0, len = extensions.length; i < len; i++) {
			var ext = extensions[i];
			var index = data.indexOf(ext);

			if (index >= 0) {
				result['extension'] = data.slice(index);
				// remove extension from phone number
				data = data.replace(result['extension'], '');

				// remove separator character from extension
				result['extension'] = result['extension'].replace(ext, '').trim();

				// Only one extension can be allowed
				break;
			}
		}

		// Valid country codes start with '00' or '+', for example: 0031 or +31
		var countryRegexp = new RegExp('(\\+|0{2})[^' + this.SP + '|' + this.NBSP + ']+');
		// valid city codes start with a single '0', for example: 020 or (0)20
		var cityRegexp = new RegExp('(^|' + this.SP + '|' + this.NBSP + ')\\(?0\\)?[^0|' + this.SP + '|' + this.NBSP + ']+');

		// Check if a section of the telephone number is grouped, this is either the
		// country or area code. When we extracted the portion, replace it with a space
		// because the part which comes before might be the country code as well.
		var group = data.match('\\((.{2,})\\)');
		if (group && group.length === 2) {
			var code = group[1].trim();

			// The group is either country code or city code, for country code
			// there are stricter rules, so we apply that countryRegexp and if it doesn't
			// matches we assume it will be the city code (even when the cityRegexp
			// won't match).
			if (code.match(countryRegexp)) {
				result['country_code'] = code;
			} else {
				result['city_code'] = code;
			}

			// remove the group from the number, be aware that the group
			// might not be at the start, or be surrounded by spaces. So
			// replace it with a space
			data = data.replace(group[0], ' ');
		} else {
			// What if a single number was surrounded by (), we must ensure
			// it has a space before it, because this kind of grouping is used
			// for adding a number to the group following it.
			data = data.replace(new RegExp('([^' + this.SP + '|' + this.NBSP + '])\\((.)\\)([^' + this.SP + '|' + this.NBSP + '])'), '$1 $2$3');
		}

		// Split whatever is left of the telephone number, we can then apply
		// simple heuristics on determining the remaining pieces.
		var phoneNumberParts = data.split(new RegExp(this.SP + '|' + this.NBSP + '|' + '-', 'g'));
		phoneNumberParts = Zarafa.core.Util.trimStringArray(phoneNumberParts);

		// If we don't have the country code yet, then check if the first section qualifies.
		if (phoneNumberParts.length > 1 && !result['country_code'] && phoneNumberParts[0].match(countryRegexp)) {
			result['country_code'] = phoneNumberParts.shift();
		}

		// If we don't have the city code yet, then check if this is the city
		if (phoneNumberParts.length > 1 && !result['city_code'] && phoneNumberParts[0].match(cityRegexp)) {
			result['city_code'] = phoneNumberParts.shift();
		}

		// The remainder is assumed to be the telephone number itself
		result['local_number'] = phoneNumberParts.join(this.NBSP);

		return result;
	},

	/**
	 * This function will parse address string and convert into an object that will contain
	 * street, country, postal_code, state, city
	 *
	 * assumptions that is used for parsing address in this function
	 * value                 | street   | city    |	state	| postal_code | country
	 * first                 |       | first   |     |       |
	 * first second              |       | first   | second  |       |
	 * first(\n)second            | first    | second   |     |       |
	 * first(\n)second third(\n)fourth    | first    | second   | third  |       | fourth
	 * first(\n)second third fourth(\n)fourth | first    | second   | third  | fourth   | fourth
	 *
	 * @param {String} data data string that will be parsed
	 * @return {Object} object that contains parsed data
	 * @private
	 */
	parseAddressInfo: function(data)
	{
		var singleLine = false;
		var result = {
			'street': '',
			'country': '',
			'postal_code': '',
			'city': '',
			'state': '',
			'incomplete_info': false
		};

		// split address based on newline characters
		var addressParts = data.split(new RegExp(this.CR + '|' + this.LF + '|' + this.CRLF, 'g'));
		addressParts = Zarafa.core.Util.trimStringArray(addressParts);

		// What has the user filled in, we can make educated guesses regarding the content.
		// 1) If provided, the country is the last line
		// 2) The city & state will most likely be on the same line
		// 3) The zipcode might be on the same line as the city & state
		// 4) The first line(s) are likely the street
		//
		// From this we can make the following breakdown based on the line count
		switch(addressParts.length) {
			default:
				// For 3 lines or more, the chance is high that
				// the last line is the country
				result['country'] = addressParts.pop();
				// furthermore the second to last line is the postal/city/state information
				singleLine = addressParts.pop();
				// The remaining information is the street, this might not be accurate,
				// but at least we parse all remaining information into a field.
				result['street'] = addressParts.join(this.CRLF);
				// A lot of information was provided, the educated guess is that
				// we have parsed the address correctly.
				result['incomplete_info'] = false;
				break;
			case 2:
				// For 2 lines, we don't expect the country to be given.
				// The first line is likely the street, and the second line
				// is probably the postal/city/state information
				result['street'] = addressParts.shift();
				singleLine = addressParts.shift();
				// Not all fields could be recognized, lets ask the user
				// if we did a good job.
				result['incomplete_info'] = true;
				break;
			case 1:
				// Only 1 line means that the user probably doesn't know
				// where the user is exactly located. He probably did nothing
				// more then type some postal/city/state information.
				singleLine = addressParts.shift();
				// We really have no clue if we did a good job. The user should
				// be more informative.
				result['incomplete_info'] = true;
				break;
			case 0:
				// No information was provided, but that is considered "complete".
				result['incomplete_info'] = false;
				break;
		}

		// We might have some extra parsing todo, we have a single line which
		// contains the city, state and postal code information which has to be recognized.
		if(singleLine !== false) {
			// split address based on spaces characters
			addressParts = singleLine.split(new RegExp(this.SP + '|' + this.NBSP, 'g'));
			addressParts = Zarafa.core.Util.trimStringArray(addressParts);

			// The line might have started or ended with the postal code, postal codes
			// are most likely numbers. The chance that a field which consists only of
			// numbers being a city or state is very small. The postal code is normally
			// at the start or at the end of the line as well. So we can simplify our
			// check
			if (Ext.isNumber(parseInt(addressParts[0]))) {
				result['postal_code'] = addressParts.shift();
			} else if (Ext.isNumber(parseInt(addressParts[addressParts.length - 1]))) {
				result['postal_code'] = addressParts.pop();
			}

			// If something is left, the city is the most likely thing that was
			// provided. If we have sufficient parts left, then the last component
			// is most likely the state.
			if (addressParts.length > 1) {
				result['state'] = addressParts.pop();
				result['city'] = addressParts.join(this.NBSP);
			} else if (addressParts.length === 1) {
				result['city'] = addressParts.shift();
			}
		}

		return result;
	},

	/**
	 * This function will work as a wrapper function for all functions that will combine
	 * data and create string representation
	 * @param {String} infoType type of data that will be combined
	 * @param {Object} data data object that will be combined
	 * @return {String} object that contains combined string data
	 */
	combineInfo: function(infoType, data)
	{
		// invalid data
		if(!Ext.isObject(data)) {
			return null;
		}

		switch(infoType) {
			case 'name':
				return this.combineNameInfo(data);
			case 'phone':
				return this.combinePhoneInfo(data);
			case 'address':
				return this.combineAddressInfo(data);
			default:
				return null;
		}
	},

	/**
	 * This function will combine full name object and convert into a string representation
	 * @param {Object} data data object that will be combined
	 * @return {String} string that contains combined data
	 * @private
	 */
	combineNameInfo: function(data)
	{
		var nameString = !Ext.isEmpty(data['display_name_prefix']) ? (data['display_name_prefix'] + this.NBSP) : '';
		nameString += !Ext.isEmpty(data['given_name']) ? (data['given_name'] + this.NBSP) : '';
		nameString += !Ext.isEmpty(data['middle_name']) ? (data['middle_name'] + this.NBSP) : '';
		nameString += !Ext.isEmpty(data['surname']) ? (data['surname'] + this.NBSP) : '';
		nameString += !Ext.isEmpty(data['generation']) ? data['generation'] : '';

		return nameString.trim();
	},

	/**
	 * This function will combine phone number object and convert into a string representation
	 * @param {Object} data data object that will be combined
	 * @return {String} string that contains combined data
	 * @private
	 */
	combinePhoneInfo: function(data)
	{
		var phoneString = '';
		// Check country code is available if yes then
		// check if it is valid country code,
		// if not then convert it to valid country code by appending '+'
		if (!Ext.isEmpty(data['country_code'])) {
			var countryRegex = new RegExp('(\\+|0{2})[^' + this.SP + '|' + this.NBSP + ']+');
			phoneString = data['country_code'] + this.NBSP;
			if (!data['country_code'].match(countryRegex)) {
				phoneString = '+' + phoneString;
			}
		}

		phoneString += !Ext.isEmpty(data['city_code']) ? ('(' + data['city_code'] + ')' + this.NBSP) : '';
		phoneString += !Ext.isEmpty(data['local_number']) ? (data['local_number'] + this.NBSP) : '';
		phoneString += !Ext.isEmpty(data['extension']) ? ('-' + this.NBSP + data['extension']) : '';

		return phoneString.trim();
	},

	/**
	 * This function will combine address object and convert into a string representation
	 * @param {Object} data data object that will be combined
	 * @return {String} string that contains combined data
	 * @private
	 */
	combineAddressInfo: function(data)
	{
		var addressString = '';

		if (!Ext.isEmpty(data['street'])) {
			addressString += data['street'];
		}
		if (addressString[addressString.length - 1] !== this.LF) {
			addressString += this.CRLF;
		}

		if (!Ext.isEmpty(data['city'])) {
			if (addressString[addressString.length - 1] !== this.LF) {
				addressString += this.NBSP;
			}
			addressString += data['city'];
		}
		if (!Ext.isEmpty(data['state'])) {
			if (addressString[addressString.length - 1] !== this.LF) {
				addressString += this.NBSP;
			}
			addressString += data['state'];
		}
		if (!Ext.isEmpty(data['postal_code'])) {
			if (addressString[addressString.length - 1] !== this.LF) {
				addressString += this.NBSP;
			}
			addressString += data['postal_code'];
		}
		if (addressString[addressString.length - 1] !== this.LF) {
			addressString += this.CRLF;
		}

		if (!Ext.isEmpty(data['country'])) {
			addressString += data['country'];
		}

		return addressString.trim();
	}
});
Ext.namespace('Zarafa.contact.data');

/**
 * @class Zarafa.contact.data.DataModes
 * @extends Zarafa.core.Enum
 *
 * Enum containing the different data modes of the contact context.
 *
 * @singleton
 */
Zarafa.contact.data.DataModes = Zarafa.core.Enum.create({
	/**
	 * Lists all contact items from the selected folder(s).
	 *
	 * @property
	 * @type Number
	 */
	ALL: 0,

	/**
	 * Lists all contact items from the selected folder(s) based on a first-character restriction.
	 *
	 * @property
	 * @type Number
	 */
	CHARACTER_RESTRICT: 1,

	/**
	 * Search for contacts in the selected folder(s).
	 *
	 * @property
	 * @type Number
	 */
	SEARCH: 2
});
Ext.namespace('Zarafa.contact.data');

/**
 * @class Zarafa.contact.data.JsonMemberWriter
 * @extends Zarafa.core.data.JsonWriter
 */
Zarafa.contact.data.JsonMemberWriter = Ext.extend(Zarafa.core.data.JsonWriter, {
	/**
	 * Similar to {@link Ext.data.JsonWriter#toHash}
	 *
	 * Convert members into a hash. Members exists as
	 * {@link Zarafa.contact.DistlistMemberRecord DistlistMemberRecord} within
	 * a {@link Zarafa.core.data.IPMRecord IPMRecord} and thus must be serialized
	 * separately into the hash object.
	 *
	 * @param {Ext.data.Record} record The record to hash
	 * @return {Object} The hashed object
	 * @override
	 * @private
	 */
	toPropHash: function(record)
	{
		var memberStore = record.getMemberStore();
		var hash = {};

		if (!Ext.isDefined(memberStore)) {
			return hash;
		}

		var memberRecords = memberStore.getRange();
		hash.members = [];

		for (var i = 0; i < memberRecords.length; i++) {
			hash.members.push(memberRecords[i].data);
		}

		return hash;
	}
});
Ext.namespace('Zarafa.contact.data');

/**
 * @class Zarafa.contact.data.ViewModes
 * @extends Zarafa.core.Enum
 *
 * Enum containing the different viewing modes of the contact context.
 *
 * @singleton
 */
Zarafa.contact.data.ViewModes = Zarafa.core.Enum.create({
	/**
	 * View all contact items from the selected folder(s) using a normal view.
	 *
	 * @property
	 * @type Number
	 */
	NORMAL: 0,

	/**
	 * View all contact items from the selected folder(s) as business cards.
	 *
	 * @property
	 * @type Number
	 */
	BUSINESS: 1,

	/**
	 * View all contact items from the selected folder(s) as address cards.
	 *
	 * @property
	 * @type Number
	 */
	ADDRESS: 2,

	/**
	 * View all contact items from the selected folder(s) as detailed cards.
	 *
	 * @property
	 * @type Number
	 */
	DETAILED: 3,

	/**
	 * View all contact items from the selected folder(s) as telephone list.
	 *
	 * @property
	 * @type Number
	 */
	PHONE_LIST: 4,

	/**
	 * View all contact items from the selected folder(s) grouped by category.
	 *
	 * @property
	 * @type Number
	 */
	GROUP_CATEGORY: 5,

	/**
	 * View all contact items from the selected folder(s) grouped by company.
	 *
	 * @property
	 * @type Number
	 */
	GROUP_COMPANY: 6,

	/**
	 * View all contact items from the selected folder(s) grouped by location.
	 *
	 * @property
	 * @type Number
	 */
	GROUP_LOCATION: 7,
	/**
	 * View all found contact items of the selected folder(s) in a list.
	 *
	 * @property
	 * @type Number
	 */
	SEARCH: 8
});
Ext.namespace('Zarafa.contact.data');

/**
 * @class Zarafa.contact.data.Views
 * @extends Zarafa.core.Enum
 *
 * Enum containing the different views of the contact context.
 *
 * @singleton
 */
Zarafa.contact.data.Views = Zarafa.core.Enum.create({
	/**
	 * View all contact items from the selected folder(s) in the 'card' view.
	 *
	 * @property
	 * @type Number
	 */
	ICON: 0,

	/**
	 * View all contact items from the selected folder(s) in the 'list' view.
	 *
	 * @property
	 * @type Number
	 */
	LIST: 1,

	/**
	 * View the found contact items from the selected folder(s) in the 'list' view.
	 *
	 * @property
	 * @type Number
	 */
	SEARCH : 2
});
/*
 * #dependsFile client/zarafa/contact/data/ContactConfig.js
 */
Ext.namespace('Zarafa.contact.ui');

/**
 * @class Zarafa.contact.dialogs.ContactGeneralTab
 * @extends Ext.form.FormPanel
 * @xtype zarafa.contactgeneraltab
 *
 * This class is used to create layout of general tab panel.
 */
Zarafa.contact.dialogs.ContactGeneralTab = Ext.extend(Ext.form.FormPanel, {

	/**
	 * If contact has photo then this is set to true, false otherwise.
	 * it is used to navigate single click, double click, and context menu(right click) events.
	 * if hasContactPhoto is false it means contact has no contact picture and
	 * it should listen single click event. else it will listen double click
	 * and context menu events.
	 * @property
	 * @type Boolean
	 */
	hasContactPhoto: false,

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

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

		// make sure it is first applied in the config before used
		Ext.applyIf(config, {
			labelWidth: 150,
			labelAlign: 'left'
		});

		Ext.applyIf(config, {
			xtype: 'zarafa.contactgeneraltab',
			cls: 'zarafa-contactgeneraltab',
			title: _('General'),
			autoScroll: true,
			border: false,
			layoutConfig: {
				columns: 2
			},
			defaults: {
				columnWidth: 0.5,
				border: false,
				xtype: 'fieldset',
				labelSeparator: ''
			},
			items: [
				this.createNameFieldset(config),
				this.createPhotoFieldset(config),
				this.createClear(),
				this.createPhoneFieldset(config),
				this.createEmailFieldset(config),
				this.createClear(),
				this.createAddressFieldset(config),
				this.createAdditionalFieldset(config),
				this.createClear(),
				this.createAttachmentFieldset(config)
			]
		});

		Zarafa.contact.dialogs.ContactGeneralTab.superclass.constructor.call(this, config);
	},

	createClear: function()
	{
		return {
			xtype: 'panel',
			cls: 'zarafa-clear',
			columnWidth: 1
		};
	},

	/**
	 * Creates the name fieldset for general tab of form panel.
	 * @param {Object} config config object of {@Zarafa.contact.dialogs.ContactGeneralTab ContactGeneralTab}.
	 * @return {Object} config object for creating {@link Ext.form.FieldSet FieldSet}.
	 * @private
	 */
	createNameFieldset: function(config)
	{
		return {
			title: _('Name'),
			defaultType: 'zarafa.compositefield',
			defaults: {
				anchor: '99%'
			},
			items: [{
				hideLabel: true,
				items: [{
					xtype: 'button',
					width: config.labelWidth-1,
					text: _('Full Name'),
					listeners: {
						scope: this,
						click: function() {
							// use wrapper function to discard arguments passed with click handler
							this.showDetailedNameContent();
						}
					}
				},{
					xtype: 'textfield',
					flex: 1,
					name: 'display_name',
					fieldLabel: _('Full Name'),
					listeners: {
						scope: this,
						change: this.onDisplayNameChange
					}
				}]
			}, {
				xtype: 'textfield',
				flex: 1,
				name: 'company_name',
				fieldLabel: _('Company'),
				listeners: {
					scope: this,
					change: this.onFieldChange
				}
			}, {
				xtype: 'textfield',
				flex: 1,
				name: 'title',
				fieldLabel: _('Job Title'),
				listeners: {
					scope: this,
					change: this.onFieldChange
				}
			}, {
				xtype: 'combo',
				name: 'fileas',
				fieldLabel: _('File as'),
				ref: '../fileAsField',
				editable: false,
				mode: 'local',
				triggerAction: 'all',
				lazyInit: false,
				store: {
					xtype: 'arraystore',
					fields: ['displayText'],
					data: []
				},
				displayField: 'displayText',
				valueField: 'displayText',
				listeners: {
					scope: this,
					change: this.onFileasChange
				}
			}]
		};
	},

	/**
	 * Creates the photo fieldset for general tab of form panel.
	 * @param {Object} config config object of {@Zarafa.contact.dialogs.ContactGeneralTab ContactGeneralTab}.
	 * @return {Object} config object for creating {@link Ext.form.FieldSet FieldSet}.
	 * @private
	 */
	createPhotoFieldset: function(config)
	{
		return {
			title: _('Photo'),
			layout: {
				type: 'hbox'
			},
			items: {
				xtype: 'box',
				cls: 'contact_photo_box default_contact_photo',
				ctCls: 'contact_photo_box_ct',
				autoEl: {
					tag: 'img',
					src: Ext.BLANK_IMAGE_URL
				},
				ref: '../contactPhotoBox',
				listeners: {
					afterrender: this.onAfterRender,
					scope: this
				}
			}
		};
	},

	/**
	 * Creates the phone fieldset for general tab of form panel.
	 * @param {Object} config config object of {@Zarafa.contact.dialogs.ContactGeneralTab ContactGeneralTab}.
	 * @return {Object} config object for creating {@link Ext.form.FieldSet FieldSet}.
	 * @private
	 */
	createPhoneFieldset: function(config)
	{
		return {
			title: _('Phone Numbers'),
			defaultType: 'zarafa.compositefield',
			defaults: {
				hideLabel: true,
				anchor: '99%'
			},
			items: [{
				items: [{
					xtype: 'splitbutton',
					width: config.labelWidth-1,
					text: _('Business'),
					handler: this.handlePhoneButtonClick,
					scope: this,
					menu: this.initPhoneButtonMenu('message_phonenumber_1', 'business_telephone_number')
				},{
					xtype: 'textfield',
					flex: 1,
					ref: "../../phone1",
					name: 'business_telephone_number',
					fieldLabel: _('Business'),
					listeners: {
						scope: this,
						change: this.onFieldChange
					}
				}]
			}, {
				items: [{
					xtype: 'splitbutton',
					width: config.labelWidth-1,
					text: _('Home'),
					handler: this.handlePhoneButtonClick,
					scope: this,
					menu: this.initPhoneButtonMenu('message_phonenumber_2', 'home_telephone_number')
				},{
					xtype: 'textfield',
					flex: 1,
					name: 'home_telephone_number',
					fieldLabel: _('Home'),
					ref: "../../phone2",
					listeners: {
						scope: this,
						change: this.onFieldChange
					}
				}]
			}, {
				items: [{
					xtype: 'splitbutton',
					width: config.labelWidth-1,
					text: _('Business Fax'),
					handler: this.handlePhoneButtonClick,
					scope: this,
					menu: this.initPhoneButtonMenu('message_phonenumber_3', 'business_fax_number')
				},{
					xtype: 'textfield',
					flex: 1,
					name: 'business_fax_number',
					fieldLabel: _('Business Fax'),
					ref: "../../phone3",
					listeners: {
						scope: this,
						change: this.onFieldChange
					}
				}]
			}, {
				items: [{
					xtype: 'splitbutton',
					width: config.labelWidth-1,
					text: _('Mobile'),
					handler: this.handlePhoneButtonClick,
					scope: this,
					menu: this.initPhoneButtonMenu('message_phonenumber_4', 'cellular_telephone_number')
				},{
					xtype: 'textfield',
					flex: 1,
					name: 'cellular_telephone_number',
					fieldLabel: _('Mobile'),
					ref: "../../phone4",
					listeners: {
						scope: this,
						change: this.onFieldChange
					}
				}]
			}]
		};
	},

	/**
	 * Creates the email fieldset for general tab of form panel.
	 * @param {Object} config config object of {@Zarafa.contact.dialogs.ContactGeneralTab ContactGeneralTab}.
	 * @return {Object} config object for creating {@link Ext.form.FieldSet FieldSet}.
	 * @private
	 */
	createEmailFieldset: function(config)
	{
		return {
			title: _('Email'),
			items: [{
				xtype: 'zarafa.compositefield',
				anchor: '99%',
				hideLabel: true,
					items: [{
						xtype: 'splitbutton',
						width: config.labelWidth-1,
						text: _('Email'),
						handler: this.openAddressBook,
						scope: this,
						menu: this.initEmailButtonMenu('message_email_address', 'email_address_1')
					},{
						xtype: 'textfield',
						flex: 1,
						ref: '../../mailAddressField',
						name: 'email_address_1',
						fieldLabel: _('Email'),
						listeners: {
							scope: this,
							change: this.onEmailAddressChange
						}
					}]
			}, {
				xtype: 'textfield',
				flex: 1,
				anchor: '99%',
				name: 'email_address_display_name_1',
				fieldLabel: _('Display name'),
				ref: '../mailDisplayNameField',
				readOnly: true
			}, {
				xtype: 'textfield',
				ref: '../webpageField',
				flex: 1,
				anchor: '99%',
				fieldLabel: _('Webpage'),
				name: 'webpage',
				listeners: {
					scope: this,
					change: this.onWebpageChange
				}
			}, {
				xtype: 'textfield',
				flex: 1,
				anchor: '99%',
				fieldLabel: _('IM Address'),
				name: 'im',
				listeners: {
					scope: this,
					change: this.onFieldChange
				}
			}]
		};
	},

	/**
	 * Creates the address fieldset for general tab of form panel.
	 * @param {Object} config config object of {@Zarafa.contact.dialogs.ContactGeneralTab ContactGeneralTab}.
	 * @return {Object} config object for creating {@link Ext.form.FieldSet FieldSet}.
	 * @private
	 */
	createAddressFieldset: function(config)
	{
		return {
			title: _('Addresses'),
			defaultType: 'zarafa.compositefield',
			defaults: {
				hideLabel: true,
				anchor: '99%'
			},
			items: [{
				items: [{
					xtype: 'splitbutton',
					width: config.labelWidth-1,
					text: _('Business'),
					handler: this.handleAddressButtonClick,
					scope: this,
					menu: this.initAddressButtonMenu('business_address', 'business_address')
					// @TODO add checkbox for email address selection
				},{
					xtype: 'textarea',
					flex: 1,
					name: 'business_address',
					fieldLabel: _('Business'),
					height: 100,
					listeners: {
						scope: this,
						change: this.onAddressChange
					}
				}]
			},{
				items: [{
					xtype: 'splitbutton',
					width: config.labelWidth-1,
					text: _('Home'),
					handler: this.handleAddressButtonClick,
					scope: this,
					menu: this.initAddressButtonMenu('home_address', 'home_address')
					// @TODO add checkbox for email address selection
				},{
					xtype: 'textarea',
					flex: 1,
					name: 'home_address',
					fieldLabel: _('Home'),
					height: 100,
					listeners: {
						scope: this,
						change: this.onAddressChange
					}
				}]
			}]
		};
	},

	/**
	 * Creates the additional fieldset for general tab of form panel.
	 * @param {Object} config config object of {@Zarafa.contact.dialogs.ContactGeneralTab ContactGeneralTab}.
	 * @return {Object} config object for creating {@link Ext.form.FieldSet FieldSet}.
	 * @private
	 */
	createAdditionalFieldset: function(config)
	{
		return {
			title: _('Additional information'),
			items: [{
				xtype: 'zarafa.editorfield',
				useHtml: true,
				readOnly: false,
				ref: '../editorField',
				anchor: '99% 99%',
				height: 236,
				listeners: {
					change: this.onBodyChange,
					scope: this
				}
			}]
		};
	},

	/**
	 * Creates the attachment fieldset for general tab of form panel.
	 * @param {Object} config config object of {@Zarafa.contact.dialogs.ContactGeneralTab ContactGeneralTab}.
	 * @return {Object} config object for creating {@link Ext.form.FieldSet FieldSet}.
	 * @private
	 */
	createAttachmentFieldset: function(config)
	{
		return {
			title: _('Attachments'),
			columnWidth: 1,
			border: false,
			defaultType: 'zarafa.resizablecompositefield',
			defaults: {
				hideLabel: true,
				anchor: '99%'
			},
			items: [{
				cls: 'zarafa-contactcreatepanel-field-attachments',
				items: [{
					xtype: 'zarafa.attachmentbutton',
					plugins: [ 'zarafa.recordcomponentupdaterplugin' ],
					width: config.labelWidth-1,
					autoHeight: true,
					text: _('Attachments')
				},{
					xtype: 'zarafa.attachmentfield',
					plugins: [ 'zarafa.recordcomponentupdaterplugin' ],
					flex: 1,
					hideLabel: true
				}]
			}]
		};
	},

	/**
	 * Function will initialize button menu config object for the telephone buttons.
	 * group config option is used to group all checkbox items into a single select radio button group
	 * and its name should be unique across all instances of menu button
	 * @param {String} textFieldName name of the corresponding textfield
	 * @param {String} property will be used to show default selection
	 * @private
	 */
	initPhoneButtonMenu: function(textFieldName, property)
	{
		return {
			xtype: 'menu',
			listeners: {
				click: this.onMenuItemSelection,
				scope: this
			},
			items: [{
				xtype: 'menucheckitem',
				group: textFieldName,
				text: _('Assistant'),
				name: 'assistant_telephone_number',
				checked: property === 'assistant_telephone_number'
			}, {
				xtype: 'menucheckitem',
				group: textFieldName,
				text: _('Business'),
				name: 'business_telephone_number',
				checked: property === 'business_telephone_number'
			}, {
				xtype: 'menucheckitem',
				group: textFieldName,
				text: _('Business 2'),
				name: 'business2_telephone_number',
				checked: property === 'business2_telephone_number'
			}, {
				xtype: 'menucheckitem',
				group: textFieldName,
				text: _('Business Fax'),
				name: 'business_fax_number',
				checked: property === 'business_fax_number'
			}, {
				xtype: 'menucheckitem',
				group: textFieldName,
				text: _('Callback'),
				name: 'callback_telephone_number',
				checked: property === 'callback_telephone_number'
			}, {
				xtype: 'menucheckitem',
				group: textFieldName,
				text: _('Car'),
				name: 'car_telephone_number',
				checked: property === 'car_telephone_number'
			}, {
				xtype: 'menucheckitem',
				group: textFieldName,
				text: _('Company'),
				name: 'company_telephone_number',
				checked: property === 'company_telephone_number'
			}, {
				xtype: 'menucheckitem',
				group: textFieldName,
				text: _('Home'),
				name: 'home_telephone_number',
				checked: property === 'home_telephone_number'
			}, {
				xtype: 'menucheckitem',
				group: textFieldName,
				text: _('Home 2'),
				name: 'home2_telephone_number',
				checked: property === 'home2_telephone_number'
			}, {
				xtype: 'menucheckitem',
				group: textFieldName,
				text: _('Home Fax'),
				name: 'home_fax_number',
				checked: property === 'home_fax_number'
			}, {
				xtype: 'menucheckitem',
				group: textFieldName,
				text: _('ISDN'),
				name: 'isdn_number',
				checked: property === 'isdn_number'
			}, {
				xtype: 'menucheckitem',
				group: textFieldName,
				text: _('Mobile'),
				name: 'cellular_telephone_number',
				checked: property === 'cellular_telephone_number'
			}, {
				xtype: 'menucheckitem',
				group: textFieldName,
				text: _('Other'),
				name: 'other_telephone_number',
				checked: property === 'other_telephone_number'
			}, {
				xtype: 'menucheckitem',
				group: textFieldName,
				text: _('Other Fax'),
				name: 'primary_fax_number',
				checked: property === 'primary_fax_number'
			}, {
				xtype: 'menucheckitem',
				group: textFieldName,
				text: _('Pager'),
				name: 'pager_telephone_number',
				checked: property === 'pager_telephone_number'
			}, {
				xtype: 'menucheckitem',
				group: textFieldName,
				text: _('Primary'),
				name: 'primary_telephone_number',
				checked: property === 'primary_telephone_number'
			}, {
				xtype: 'menucheckitem',
				group: textFieldName,
				text: _('Radio'),
				name: 'radio_telephone_number',
				checked: property === 'radio_telephone_number'
			}, {
				xtype: 'menucheckitem',
				group: textFieldName,
				text: _('Telex'),
				name: 'telex_telephone_number',
				checked: property === 'telex_telephone_number'
			}, {
				xtype: 'menucheckitem',
				group: textFieldName,
				text: _('TTY/TDD'),
				name: 'ttytdd_telephone_number',
				checked: property === 'ttytdd_telephone_number'
			}]
		};
	},

	/**
	 * Function will initialize button menu config object for the address button
	 * @param {String} textFieldName name of the corresponding textfield
	 * @param {String} property will be used to show default selection
	 * @private
	 */
	initAddressButtonMenu: function(textFieldName, property)
	{
		return {
			xtype: 'menu',
			listeners: {
				click: this.onMenuItemSelection,
				scope: this
			},
			items: [{
				xtype: 'menucheckitem',
				group: textFieldName,
				text: _('Home'),
				name: 'home_address',
				checked: property === 'home_address'
			}, {
				xtype: 'menucheckitem',
				group: textFieldName,
				text: _('Business'),
				name: 'business_address',
				checked: property === 'business_address'
			}, {
				xtype: 'menucheckitem',
				group: textFieldName,
				text: _('Other'),
				name: 'other_address',
				checked: property === 'other_address'
			}]
		};
	},

	/**
	 * Function will initialize button menu config object for the email button.
	 * group config option is used to group all checkbox items into a single select radio button group
	 * and its name should be unique across all instances of menu button
	 * @param {String} textFieldName name of the corresponding textfield
	 * @param {String} property will be used to show default selection
	 * @private
	 */
	initEmailButtonMenu: function(textFieldName, property)
	{
		return {
			xtype: 'menu',
			listeners: {
				click: {
					fn: this.onEmailMenuItemSelection,
					scope: this
				}
			},
			defaults: {
				xtype: 'menucheckitem',
				group: textFieldName
			},
			items: [{
				text: _('Email'),
				name: 'email_address_1',
				checked: property === 'email_address_1'
			}, {
				text: _('Email 2'),
				name: 'email_address_2',
				checked: property === 'email_address_2'
			}, {
				text: _('Email 3'),
				name: 'email_address_3',
				checked: property === 'email_address_3'
			}]
		};
	},

	/**
	 * Function that will be called when one of the phone buttons is clicked,
	 * this function is used as wrapper to discard arguments passed with the handler
	 * and it will call function that will open the {@link Zarafa.contact.dialogs.ContactPhoneContent ContactPhoneContent}.
	 * @param {Ext.SplitButton} buttonEl split button element which was clicked.
	 * @param {Ext.EventObject} eventObj event object for the click event.
	 */
	handlePhoneButtonClick: function(buttonEl, eventObj)
	{
		this.showDetailedPhoneContent(buttonEl.ownerCt.findByType('textfield')[0].getName());
	},

	/**
	 * Function that will be called when address button is clicked,
	 * this function is used as wrapper to discard arguments passed with the handler
	 * and it will call function that will open the {@link Zarafa.contact.dialogs.ContactAddressContent ContactAddressContent}.
	 * @param {Ext.SplitButton} buttonEl split button element which was clicked.
	 * @param {Ext.EventObject} eventObj event object for the click event.
	 */
	handleAddressButtonClick: function(buttonEl, eventObj)
	{
		this.showDetailedAddressContent(buttonEl.ownerCt.findByType('textarea')[0].getName());
	},

	/**
	 * Event handler which is fired when the user presses the 'Email' button.
	 * This will open the Address Book User Selection ContentPanel to select a user-email.
	 * @private
	 */
	openAddressBook: function()
	{
		Zarafa.common.Actions.openABUserSelectionContent({
			callback: this.abCallBack,
			scope: this
		});
	},

	/**
	 * Callback function for {@link Zarafa.addressbook.dialogs.ABUserSelectionContentPanel AddressBook}
	 * Function will set email_address_x and email_address_type_x property for the record.
	 * @param {Ext.data.Record} record user selected from AddressBook
	 * @private
	 */
	abCallBack: function(record)
	{
		var emailAddress;
		var addressType = record.get('address_type') || 'SMTP';

		if(addressType === 'EX') {
			emailAddress = record.get('smtp_address');
		} else {
			emailAddress = record.get('email_address');
		}

		this.mailAddressField.setValue(emailAddress);

		this.record.beginEdit();
		switch (this.mailAddressField.getName())
		{
			case 'email_address_1':
				this.record.set('email_address_1', emailAddress);
				// local contact must have SMTP as address type
				this.record.set('email_address_type_1', 'SMTP', true);
				this.record.set('email_address_display_name_1', record.get('display_name'));
				break;
			case 'email_address_2':
				this.record.set('email_address_2', emailAddress);
				this.record.set('email_address_type_2', 'SMTP', true);
				this.record.set('email_address_display_name_2', record.get('display_name'));
				break;
			case 'email_address_3':
				this.record.set('email_address_3', emailAddress);
				this.record.set('email_address_type_3', 'SMTP', true);
				this.record.set('email_address_display_name_3', record.get('display_name'));
				break;
		}
		this.record.endEdit();
	},

	/**
	 * Function is used to update values of form fields when ever
	 * an updated {@link Zarafa.core.data.IPMRecord record} is received.
	 * @param {Zarafa.core.data.IPMRecord} 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)
	{
		if(Ext.isEmpty(record)) {
			return;
		}

		this.record = record;

		this.editorField.setAllowEdit(true);
		this.editorField.setReadOnly(false);

		this.getForm().loadRecord(record);

		if (record.isOpened() && contentReset) {
			this.updateContactPhoto();
		}

		if (contentReset) {
			// when loading first time generate combobox items and select item that is stored in the record
			this.generateFileAsItems();
			this.fileAsField.selectByValue(record.get('fileas'), true);
		}

		if (record.isModifiedSinceLastUpdate('fileas')) {
			// if fileas property is changed in record then simply select that entry in combobox
			this.fileAsField.selectByValue(record.get('fileas'), true);
		}

		if (contentReset && record.isOpened()) {
			this.editorField.setValue(record.getBody(this.editorField.isHtmlEditor()));
		}

		if (record.isModifiedSinceLastUpdate('display_name') || record.isModifiedSinceLastUpdate('company_name')) {
			// display_name or company_name has been changed, regenerate options and select according to previous selection
			this.generateFileAsItems();
			this.updateFileas();
		}
	},

	/**
	 * Function is used to update contact photo in the dialog
	 * according to updated {@link Zarafa.core.data.IPMRecord record} data.
	 */
	updateContactPhoto: function()
	{
		var attachmentStore = this.record.getAttachmentStore();
		var imageField = this.contactPhotoBox.getEl();

		// Set an event handler for the load event of the image
		var imgEl = new Ext.Element(Ext.getDoc().dom.createElement('img'));
		imgEl.on('load', this.onLoadContactPhoto, this, {single: true});

		// update the new contact photo in contact picture field.
		if(imageField && attachmentStore.getCount() > 0) {
			attachmentStore.each(function(attach) {
				if(attach.isContactPhoto()) {
					var inlineAttachmentUrl = attach.getInlineImageUrl();
					imgEl.set({'src': inlineAttachmentUrl});
					imageField.setStyle({'background-image': 'url(' + encodeURI(inlineAttachmentUrl) + ')'});
					this.hasContactPhoto = true;
					this.record.set('has_picture', true);
					this.contactPhotoBox.removeClass('default_contact_photo');

					return false;
				}
			}, this);
		}
	},

	/**
	 * Event handler for the load event of the contact photo. It resizes the image
	 * if necessary and places it on the right position.
	 * @param {Ext.EventObject} event The load event
	 * @param {HtmlElement} The img element that fired the load event
	 */
	onLoadContactPhoto: function(event, img)
	{
		var imageField = this.contactPhotoBox.getEl();
		var imgEl = Ext.get(img);
		var ct = imageField.up('.contact_photo_box_ct');
		var ctPadding = ct.getPadding('lr');
		var maxWidth = ct.getWidth() - ctPadding - 2; // Subtract two for border
		var imageWidth = imgEl.dom.naturalWidth;
		var imageHeight = imgEl.dom.naturalHeight;
		var width, height;
		if ( imageWidth < maxWidth && imageHeight < maxWidth ) {
			// The image is smaller than the area we reserved for it
			// so we don't resize it.
			width = imageWidth;
			height = imageHeight;
		} else {
			// The image is bigger than the area we reserved for it,
			// so we will have resize it
			if ( imageWidth > imageHeight ){
				width = maxWidth;
				height = maxWidth*imageHeight/imageWidth;
			} else {
				width = maxWidth*imageWidth/imageHeight;
				height = maxWidth;
			}
		}

		imageField.setWidth(width);
		imageField.setHeight(height);
		imageField.setStyle({
			'background-size': width + 'px ' + height + 'px',
			left: ((maxWidth - width) / 2) + 'px',
			top: ((maxWidth - height) / 2) + 'px'
		});
	},

	/**
	 * Event handler which is fired after the {@link Ext.form.FormPanel FormPanel}
	 * has been {@link Ext.Component#afterrender rendered}. Here contact photo box has
	 * listen {@link Ext.Element#click single click}, {@link Ext.Element#dblclick double click} and
	 * {@link Ext.Element#contextmenu context menu} evetns.
	 * @param {Ext.Component} contactPhotoBox which show the contact picture.
	 * @private
	 */
	onAfterRender: function(contactPhotoBox)
	{
		var imageFieldCt = contactPhotoBox.getEl().up('div');
		this.mon(imageFieldCt, {
			'click': this.onSingleClick,
			'dblclick': this.onDoubleClick,
			'contextmenu': this.onContextMenuClick,
			'scope': this
		});
	},

	/**
	 * Event handler which is triggered when contact has contact picture and right click
	 * perform on contact photo box. it should used to open context menu for changing or
	 * removing contact picture.
	 *
	 * @param {Ext.EventObject} eventObj eventObj object of the event
	 * @param {Element} target Event target
	 * @param {Object} object Configuration object
	 */
	onContextMenuClick: function(eventObj, target, object)
	{
		if(this.hasContactPhoto) {
			var attachmentRecord = this.getAttachedContactPhoto();
			Zarafa.core.data.UIFactory.openDefaultContextMenu(attachmentRecord, { position: eventObj.getXY(), parent: this});
		}
	},

	/**
	 * Function will get attached contact picture record
	 * from {@link Zarafa.core.data.IPMAttachmentStore IPMAttachmentStore}.
	 * @return {Zarafa.core.data.IPMAttachmentRecord} returns the contact picture attachment record.
	 */
	getAttachedContactPhoto: function()
	{
		var attachmentStore = this.record.getAttachmentStore();
		var attachmentRecord;
		attachmentStore.each(function(attach) {
			if(attach.isContactPhoto()) {
				attachmentRecord = attach;
			}
		}, this);
		return attachmentRecord;
	},

	/**
	 * Event handler which is fired when the contact picture field is being clicked
	 * and contact has default image or contact is new contact. this will call the
	 * {@link #uploadContactPhoto} function to open upload attachment dialog.
	 *
	 * @param {Ext.EventObject} eventObj eventObj object of the event
	 * @param {Element} target Event target
	 * @param {Object} object Configuration object
	 */
	onSingleClick: function(eventObj, target, object)
	{
		if(!this.hasContactPhoto) {
			this.uploadContactPhoto();
		}
	},

	/**
	 * Event handler which is fired when contact picture field is being double-clicked and
	 * contact has contact picture. this will call the {@link #uploadContactPhoto}
	 * function to open upload attachment dialog.
	 *
	 * @param {Ext.EventObject} eventObj eventObj object of the event
	 * @param {Element} target Event target
	 * @param {Object} object Configuration object
	 */
	onDoubleClick: function(eventObj, target, object)
	{
		if(this.hasContactPhoto) {
			this.uploadContactPhoto();
		}
	},

	/**
	 * Event handler which is triggered when contact photo gets deleted from
	 * {@link Zarafa.core.data.IPMAttachmentStore IPMAttachmentStore}. Also it will
	 * set the src to {@link Ext.BLANK_IMAGE_URL BlankImage} because empty src attribute of img tag
	 * show page break icon in contact photo box, also set hasContactPhoto to false so
	 * contact picture field listen the single click event to open the browser's file selection dialog
	 * and set the has_picture property false to hide the contact picture from contact.
	 *
	 * @param {Zarafa.core.data.IPMAttachmentStore} attachmentStore The Attachmentstore.
	 * @param {Zarafa.core.data.IPMAttachmentRecord} attachmentRecord Attachment record.
	 */
	clearContactPhoto: function(store, attachmentRecord)
	{
		store.remove(attachmentRecord);
		var imageField = this.contactPhotoBox.getEl();
		this.hasContactPhoto = false;
		this.record.set('has_picture', false);
		this.record.set('contact_photo_attach_num', -1);
		var ct = this.contactPhotoBox.el.up('.contact_photo_box_ct');
		var maxWidth = ct.getWidth() - ct.getPadding('lr') - 2; //subtracting 2px for the border
		imageField.set({src: Ext.BLANK_IMAGE_URL});
		imageField.setStyle({
			width: maxWidth + 'px', // The image is a square, so we can use the width also for the height
			height: maxWidth + 'px',
			left: 0,
			top: 0,
			'background-image': '',
			'background-size': 'auto'
		});
		this.contactPhotoBox.addClass('default_contact_photo');
	},

	/**
	 * Event handler for opening the Browser's file selection dialog.
	 * See {@link #onFileInputChange} for the handling of the selected files.
	 * @private
	 */
	uploadContactPhoto: function()
	{
		var attachmentStore = this.record.getAttachmentStore();
		this.mon(attachmentStore, 'update', this.updateContactPhoto, this);

		var attachComponent = new Zarafa.common.attachment.ui.UploadAttachmentComponent({
			callback: this.uploadContactPhotoCallback,
			accept: 'image/*',
			scope: this
		});

		attachComponent.openAttachmentDialog();
	},

	/**
	 * Callback function for {@link Zarafa.common.attachment.ui.UploadAttachmentComponent}.
	 * which is going to add the contact picture(Attachment record) in {@link Zarafa.core.data.IPMAttachmentStore store}.
	 *
	 * @param {Object/Array} files The files is contains file information.
	 * @param {Object} form the form is contains {@link Ext.form.BasicForm bacisform} info.
	 */
	uploadContactPhotoCallback: function(files, form)
	{
		var store = this.record.getAttachmentStore();
		this.beforeUploadContactPhoto(store);
		var isHidden = true;
		var param = {sourcetype: 'contactphoto'};
		store.uploadFiles(files, form, isHidden, param);
	},

	/**
	 * Function is check that contact has already contact picture, if it is than remove
	 * contact picture from attachment store before uploading new contact picture.
	 * @param {Zarafa.core.data.IPMAttachmentStore} attachmentStore The Attachmentstore.
	 */
	beforeUploadContactPhoto: function(store)
	{
		var attachmentRecord = this.getAttachedContactPhoto();
		if(Ext.isDefined(attachmentRecord)) {
			store.remove(attachmentRecord);
		}
	},

	/**
	 * Function will be called whenever selection of address or telephone number
	 * will be changed, this function will change text of button and also change value
	 * of the corresponding textfield.
	 * @param {Ext.menu.Menu} Menu button manu
	 * @param {Ext.menu.CheckItem} CheckItem menu item that is selected
	 * @param {Ext.EventObject} EventObjectt event object
	 * @private
	 */
	onMenuItemSelection: function(menu, menuItem, eventObj)
	{
		if(!Ext.isEmpty(menuItem)) {
			var compositeField = menu.findParentByType('zarafa.compositefield');
			var buttonField = compositeField.findByType('splitbutton')[0];
			var textField = compositeField.findByType('textfield')[0];

			if(!Ext.isEmpty(buttonField) && !Ext.isEmpty(textField)) {
				// update text of button
				buttonField.setText(menuItem.initialConfig.text);

				// update corresponding textfield with new value
				textField.name = menuItem.name;
				textField.el.dom.name = menuItem.name;
				textField.setValue(this.record.get(menuItem.name));
			}
		}
	},

	/**
	 * Function will be called whenever selection of email address
	 * will be changed, this function will change text of button, also change value
	 * of the corresponding textfield and display_email field
	 * @param {Ext.menu.Menu} Menu button manu
	 * @param {Ext.menu.CheckItem} CheckItem menu item that is selected
	 * @param {Ext.EventObject} EventObjectt event object
	 * @private
	 */
	onEmailMenuItemSelection: function(menu, menuItem, eventObj)
	{
		/*
		 * This will update text of splitbutton for Email, and
		 * also change value of the corresponding mailDisplayField
		 */
		this.onMenuItemSelection(menu, menuItem, eventObj);

		// This will set corresponding email-address displayname in mailDisplayNameField field.
		if(!Ext.isEmpty(this.mailDisplayNameField)) {
			var displayFieldProperty = menuItem.name.replace('email_address','email_address_display_name');
			this.mailDisplayNameField.name = displayFieldProperty;
			this.mailDisplayNameField.el.dom.name = displayFieldProperty;
			this.mailDisplayNameField.setValue(this.record.get(displayFieldProperty));
		}
	},

	/**
	 * Function will re-generate combobox items for fileas field whenever display_name or company_name
	 * property in {@link Zarafa.core.data.IPMRecord IPMRecord} is changed. This function is also called when
	 * loading contents in the dialog first time.
	 */
	generateFileAsItems: function()
	{
		var record = this.record;

		var fileasOptions = [];
		var displayName = record.get('display_name');
		var displayNamePrefix = record.get('display_name_prefix');
		var displayNameSuffix = record.get('generation');
		var companyName = record.get('company_name');
		var comboStore = this.fileAsField.getStore();

		if(!Ext.isEmpty(displayName)) {
			// Remove prefix and suffix from display name
			displayName = displayName.replace(displayNamePrefix, '').replace(displayNameSuffix, '').replace(',', '');
			var displayNameArray = displayName.trim().split(new RegExp(Zarafa.contact.data.config.SP + '|' + Zarafa.contact.data.config.NBSP, 'g'));

			// remove whitespaces from array
			displayNameArray = Zarafa.core.Util.trimStringArray(displayNameArray);

			// two options based on name fields only
			// surname, givenname middlename and givenname middlename surname
			var contactName = '';
			if(displayNameArray.length > 0) {
				if(displayNameArray.length === 1) {
					// givenname
					contactName = displayNameArray[0];
					fileasOptions.push([contactName]);
				} else {
					// surname, givenname middlename
					var surname = displayNameArray[displayNameArray.length - 1] + ',' + Zarafa.contact.data.config.NBSP;
					var remainder = displayNameArray.slice(0, displayNameArray.length - 1);

					contactName = surname + remainder.join(Zarafa.contact.data.config.NBSP);
					fileasOptions.push([contactName]);

					// givenname middlename surname
					fileasOptions.push([displayNameArray.join(Zarafa.contact.data.config.NBSP)]);
				}
			}

			// two options based on company name and name fields
			if(!Ext.isEmpty(companyName)) {
				// companyname
				fileasOptions.push([companyName]);

				if(!Ext.isEmpty(contactName)) {
					// givenname | givenname middlename surname | surname, givenname middlename (companyname)
					fileasOptions.push([contactName + Zarafa.contact.data.config.NBSP + '(' + companyName + ')']);
					// companyname (givenname | givenname middlename surname | surname, givenname middlename)
					fileasOptions.push([companyName + Zarafa.contact.data.config.NBSP + '(' + contactName + ')']);
				}
			}
		} else if(!Ext.isEmpty(companyName)) {
			// only companyname is set
			fileasOptions.push([companyName]);
 		}

		comboStore.loadData(fileasOptions);
	},

	/**
	 * Function will update fileas field in {@link Zarafa.core.data.IPMRecord IPMRecord} based on
	 * previous selected entry in fileas combobox or else it will select first entry in the combobox store.
	 * @private
	 */
	updateFileas: function()
	{
		var comboStore = this.fileAsField.getStore();
		var oldSelectedIndex = this.fileAsField.selectedIndex;

		if(comboStore.getCount() <= 0) {
			// if no data is populated in combobox then clear the fileas value
			this.record.set('fileas', '');
		} else {
			if(oldSelectedIndex === -1 || oldSelectedIndex > comboStore.getCount()) {
				// if no selection is present then select first item by default
				this.record.set('fileas', comboStore.getAt(0).get(this.fileAsField.displayField));
			} else {
				this.record.set('fileas', comboStore.getAt(oldSelectedIndex).get(this.fileAsField.displayField));
			}
		}
	},

	/**
	 * Update the {@link Zarafa.core.data.IPMRecord IPMRecord} with the data from the {@link Ext.Panel Panel}.
	 * @param {Zarafa.core.data.IPMRecord} record The record which has to be updated
	 */
	updateRecord: function(record)
	{
		record.beginEdit();

		var newWebpage = this.webpageField.getValue();
		var oldWebpage = record.get(this.webpageField.getName());
		if (newWebpage !== oldWebpage) {
			this.onWebpageChange(this.webpageField, newWebpage, oldWebpage);
		}

		var newFileas = this.fileAsField.getValue();
		var oldFileas = record.get(this.fileAsField.getName());
		if (newFileas !== oldFileas) {
			this.onFileasChange(this.fileAsField, newFileas, oldFileas);
		}

		var newEmail = this.mailAddressField.getValue();
		var oldEmail = record.get(this.mailAddressField.getName());
		if (newEmail !== oldEmail) {
			this.onEmailAddressChange(this.mailAddressField, newEmail, oldEmail);
		}

		this.getForm().updateRecord(record);

		// We only need to generate the subject and fileas,
		// when the properties have been changed.
		if (record.isModified('display_name') ||
		  record.isModified('display_name_prefix') ||
		  record.isModified('generation') ||
		  record.isModified('fileas')) {
			record.updateSubject();
		}

		this.onBodyChange(this.editorField.getEditor(), this.editorField.getValue());

		record.updateAddressbookProps();

		record.endEdit();
	},

	/**
	 * Event handler which is triggered when one of the Input fields
	 * has been changed by the user. It will validate the new value,
	 * and if correct, will apply it to the {@link Zarafa.core.data.IPMRecord record}.
	 * @param {Ext.form.Field} field The {@link Ext.form.Field field} which was changed.
	 * @param {Mixed} newValue The new value
	 * @param {Mixed} oldValue The old value
	 * @private
	 */
	onFieldChange: function(field, newValue, oldValue)
	{
		if (field.validateValue(field.processValue(newValue))) {
			this.record.set(field.getName(), newValue);
		}
	},

	/**
	 * Event handler which is triggered when one of the Input fields
	 * has been changed by the user. It will validate the new value,
	 * and if correct, will apply it to the {@link Zarafa.core.data.IPMRecord record}.
	 * @param {Ext.form.Field} field The {@link Ext.form.Field field} which was changed.
	 * @param {Mixed} newValue The new value
	 * @param {Mixed} oldValue The old value
	 * @private
	 */
	onBodyChange: function(field, newValue, oldValue)
	{
		var record = this.record;
		var isHtmlEditor = field.isXType && field.isXType('zarafa.htmleditor');

		record.beginEdit();
		record.setBody(newValue, isHtmlEditor);
		record.endEdit();
	},

	/**
	 * Event handler which is triggered when one of the Email Address fields was
	 * changed. This will update some additional properties as well.
	 *
	 * @param {Ext.form.Field} field The {@link Ext.form.Field field} which was changed.
	 * @param {Mixed} newValue The new value
	 * @param {Mixed} oldValue The old value
	 * @private
	 */
	onEmailAddressChange: function(field, newValue, oldValue)
	{
		if (field.validateValue(field.processValue(newValue))) {
			this.record.beginEdit();
			this.record.set(field.getName(), newValue);

			switch(field.getName()) {
				case 'email_address_1':
					this.record.set('email_address_type_1', 'SMTP', true);
					break;
				case 'email_address_2':
					this.record.set('email_address_type_2', 'SMTP', true);
					break;
				case 'email_address_3':
					this.record.set('email_address_type_3', 'SMTP', true);
					break;
			}
			this.record.updateAddressbookProps();

			this.record.endEdit();
		}
	},

	/**
	 * Event handler which is fired when the 'fileas' property has been changed. This
	 * will also update the 'fileas_selection' property.
	 *
	 * @param {Ext.form.Field} field The {@link Ext.form.Field field} which was changed.
	 * @param {Mixed} newValue The new value
	 * @param {Mixed} oldValue The old value
	 * @private
	 */
	onFileasChange: function(field, newValue, oldValue)
	{
		if (field.validateValue(field.processValue(newValue))) {
			this.record.beginEdit();
			this.record.set(field.getName(), newValue);
			/*
			 * if fileas field has been changed then we have to also reset value of fileas_selection
			 * so outlook will re-generate fileas_selection value, because webapp is not generating that value
			 */
			this.record.set('fileas_selection', -1);
			this.record.endEdit();
		}
	},

	/**
	 * Event handler which is fired when the 'webpage' property has been changed. This
	 * will also update the 'business_home_page' property.
	 *
	 * @param {Ext.form.Field} field The {@link Ext.form.Field field} which was changed.
	 * @param {Mixed} newValue The new value
	 * @param {Mixed} oldValue The old value
	 * @private
	 */
	onWebpageChange: function(field, newValue, oldValue)
	{
		if (field.validateValue(field.processValue(newValue))) {
			this.record.beginEdit();
			this.record.set(field.getName(), newValue);
			this.record.set('business_home_page', newValue);
			this.record.endEdit();
		}
	},

	/**
	 * Event handler which is fired when the 'display_name' property has been changed.
	 * This will parse the display name and show a dialog if the display name could not
	 * fully be parsed.
	 *
	 * @param {Ext.form.Field} field The {@link Ext.form.Field field} which was changed.
	 * @param {Mixed} newValue The new value
	 * @param {Mixed} oldValue The old value
	 * @private
	 */
	onDisplayNameChange: function(field, newValue, oldValue)
	{
		if (field.validateValue(field.processValue(newValue))) {
			this.record.set(field.getName(), newValue);

			/*
			 * we want the updated record when we need open detailed dialogs,
			 * but the updation of record is done after by the update method of ui components
			 * so we will give sometime for contact general tab to update contact record and
			 * and with the latest updated contact record we can open the detailed dialogs
			 */
			/*eslint no-unused-vars: ["error", { "varsIgnorePattern": "showDetailedContent", "args": "none"}]*/
			var showDetailedContent = function() {
				var parsedData = this.getContactParser().parseInfo('name', newValue);

				// sync properties
				this.record.beginEdit();
				this.record.set('display_name_prefix', parsedData['display_name_prefix']);
				this.record.set('given_name', parsedData['given_name']);
				this.record.set('middle_name', parsedData['middle_name']);
				this.record.set('surname', parsedData['surname']);
				this.record.set('generation', parsedData['generation']);
				this.record.endEdit();

				var settingValue = container.getSettingsModel().get('zarafa/v1/contexts/contact/show_name_dialog');
				// check for incomplete data and show detailed name dialog
				if(settingValue === true && !Ext.isEmpty(newValue)) {
					if(parsedData['incomplete_info'] === true) {
						// show detailed dialog for full name
						this.showDetailedNameContent(parsedData);
					}
				}
			}.defer(1, this);
		}
	},

	/**
	 * Event handler which is fired when one of the address properties has been changed.
	 * This will parse the address and show a dialog if the address could not
	 * fully be parsed.
	 *
	 * @param {Ext.form.Field} field The {@link Ext.form.Field field} which was changed.
	 * @param {Mixed} newValue The new value
	 * @param {Mixed} oldValue The old value
	 * @private
	 */
	onAddressChange: function(field, newValue, oldValue)
	{
		if (field.validateValue(field.processValue(newValue))) {
			this.record.set(field.getName(), newValue);

			/*
			 * we want the updated record when we need open detailed dialogs,
			 * but the updation of record is done after by the update method of ui components
			 * so we will give sometime for contact general tab to update contact record and
			 * and with the latest updated contact record we can open the detailed dialogs
			 */
			/*eslint no-unused-vars: ["error", { "varsIgnorePattern": "showDetailedContent", "args": "none"}]*/
			var showDetailedContent = function() {
				// check for incomplete data and show detailed name dialog
				var parsedData = this.getContactParser().parseInfo('address', newValue);
				var propertyPrefix = field.getName();

				// sync properties
				this.record.beginEdit();
				this.record.set(propertyPrefix + '_street', parsedData['street']);
				this.record.set(propertyPrefix + '_city', parsedData['city']);
				this.record.set(propertyPrefix + '_state', parsedData['state']);
				this.record.set(propertyPrefix + '_postal_code', parsedData['postal_code']);
				this.record.set(propertyPrefix + '_country', parsedData['country']);
				this.record.endEdit();

				var settingValue = container.getSettingsModel().get('zarafa/v1/contexts/contact/show_address_dialog');
				if (settingValue === true && !Ext.isEmpty(newValue)) {
					if (parsedData['incomplete_info'] === true) {
						// show detailed dialog for address
						this.showDetailedAddressContent(propertyPrefix, parsedData);
					}
				}
			}.defer(1, this);
		}
	},

	/**
	 * Function will open detailed name dialog to enter incomplete information
	 * @param {Object} parsedData if string is already parsed into object then we can pass
	 * that object here in componentConfig so parsing will not be done twice
	 * @private
	 */
	showDetailedNameContent: function(parsedData)
	{
		Zarafa.contact.Actions.openDetailedNameContent(this.record, { parser: this.getContactParser(), parsedData: parsedData });
	},

	/**
	 * Function will open detailed address dialog to enter incomplete information
	 * @param {String} property property that will be modified
	 * @param {Object} parsedData if string is already parsed into object then we can pass
	 * that object here in componentConfig so parsing will not be done twice
	 * @private
	 */
	showDetailedAddressContent: function(property, parsedData)
	{
		Zarafa.contact.Actions.openDetailedAddressContent(this.record, { parser: this.getContactParser(), parsedData: parsedData, property: property });
	},

	/**
	 * Function will open detailed phone dialog to enter incomplete information
	 * @param {String} property property that will be modified
	 * @private
	 */
	showDetailedPhoneContent: function(property)
	{
		Zarafa.contact.Actions.openDetailedPhoneContent(this.record, { parser: this.getContactParser(), property: property });
	},

	/**
	 * @return {Zarafa.contact.data.ContactDetailsParser} contact details parser
	 */
	getContactParser: function()
	{
		return this.dialog.contactParser;
	},


  /**
   * Initialize all {@link Zarafa.core.data.MAPIRecord record} related events
   * for this {@link Zarafa.contact.dialogs.ContactGeneralTab ContactGeneralTabPanel}.
   * @private
   */
  initEvents: function ()
  {
	  this.mon(this.dialog, {
		  'beforesaverecord': this.onBeforeSaveRecord,
		  'scope': this
	  });
  },

  /**
   * Event handler which is fired when the the {@link Ext.data.Store store} for the {@link #record}
   * fires the {@link Ext.data.Store#beforesave} event.
   * This will check all phone number fields if any of this has "x" as a extension separator then
   * replace it with "-".
   * @private
   */
  onBeforeSaveRecord: function()
  {
	  for (var i = 1; i <= 4; i++) {
		  var phone = this["phone" + i];
		  if (phone.value.indexOf("x") > 0) {
			  this.record.set(phone.name, phone.value.replace("x", "-"), true);
			  if(phone.name === 'business_fax_number') {
				  this.record.updateAddressbookProps();
			  }
		  }
	  }
  }
});

Ext.reg('zarafa.contactgeneraltab', Zarafa.contact.dialogs.ContactGeneralTab);
Ext.namespace('Zarafa.contact.dialogs');

/**
 * @class Zarafa.contact.dialogs.DistlistExternalMemberContentPanel
 * @extends Zarafa.core.ui.ContentPanel
 *
 * This class will be used to create a content panel for adding new member(external contact) in distlist,
 *
 * @xtype zarafa.distlistexternalmembercontentpanel
 */
Zarafa.contact.dialogs.DistlistExternalMemberContentPanel = Ext.extend(Zarafa.core.ui.ContentPanel, {
	/**
	 * @cfg {Zarafa.core.data.IPMRecord} record The distlist record which
	 * contains memberStore for distribution list members
	 */
	parentRecord: undefined,

	/**
	 * @cfg {Zarafa.contact.DistlistMemberRecord} memberRecord The distlist member record
	 * which is being edited by this panel.
	 */
	record: undefined,

	/**
	 * The form panel which is loaded inside this panel.
	 * @property
	 * @type Ext.form.FormPanel
	 */
	formPanel: undefined,

	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		// Remove old state settings.
		if (Ext.isDefined(container.getSettingsModel().get("zarafa/v1/state/dialogs/distlistexternalmembercontentpanel"))) {
			container.getSettingsModel().remove("zarafa/v1/state/dialogs/distlistexternalmembercontentpanel");
		}

		config = config || {};

		Ext.applyIf(config, {
			xtype: 'zarafa.distlistexternalmembercontentpanel',
			title: _('New email address'),
			layout: 'form',
			width: 350,
			height: 150,
			stateful: false,
			items: this.createFormItems()
		});

		Zarafa.contact.dialogs.DistlistExternalMemberContentPanel.superclass.constructor.call(this, config);

		this.on('afterlayout', this.onAfterFirstLayout, this, { single: true });
	},

	/**
	 * Create the form in which the new email address info can be fetch
	 * @return {Object} Configuration object for the form
	 */
	createFormItems: function()
	{
		return [{
			xtype: 'form',
			layout: 'form',
			border: false,
			ref: 'formPanel',
			width: 350,
			height: 150,
			items: [{
				xtype: 'textfield',
				fieldLabel: _('Name'),
				name: 'display_name',
				allowBlank: false,
				blankText: _('This field is required'),
				anchor: '100%'
			},{
				xtype: 'textfield',
				fieldLabel: _('Email address'),
				name: 'email_address',
				anchor: '100%',
				allowBlank: false,
				blankText: _('This field is required'),
				vtype: 'email',
				vtypeText: _('Please enter a valid email address')
			}],
			buttons: [{
				text: _('Ok'),
				handler: this.onOk,
				scope: this
			},{
				text: _('Cancel'),
				handler: this.onCancel,
				scope: this
			}]
		}];
	},

	/**
	 * Event handler which is fired when {@link #afterlayout} has been called for the first time.
	 * This will load the {@link #record} into {@link #formPanel}.
	 * @private
	 */
	onAfterFirstLayout: function()
	{
		if(this.record) {
			this.formPanel.getForm().loadRecord(this.record);
		}
	},

	/**
	 * Event handler which is raised when the user clicks the "Ok" {@link Ext.Button button}
	 * Function will update or create new member record to update in distlist in memberStore.
	 * @private
	 */
	onOk: function()
	{
		var form = this.formPanel.getForm();

		if(!form.isValid()) {
			return false;
		}

		if(this.record) {
			form.updateRecord(this.record);
		} else {
			// If there isn't record then we are adding new member,
			// So create a new member record and fill it with form data.
			var newRecord = Zarafa.core.data.RecordFactory.createRecordObjectByCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_DISTLIST_MEMBER);
			form.updateRecord(newRecord);
			newRecord.set("smtp_address", newRecord.get("email_address"));

			// Add newly created record into the store.
			this.parentRecord.getMemberStore().add(newRecord);
		}

		this.close();
	},

	/**
	 * Event handler which is raised when the user clicks the "Cancel" {@link Ext.Button button}
	 * @private
	 */
	onCancel: function()
	{
		this.close();
	}
});

// register panel
Ext.reg('zarafa.distlistexternalmembercontentpanel', Zarafa.contact.dialogs.DistlistExternalMemberContentPanel);
/*
 * #dependsFile client/zarafa/common/ui/grid/Renderers.js
 */
Ext.namespace('Zarafa.contact.dialogs');

/**
 * @class Zarafa.contact.dialogs.DistlistMemberGridColumnModel
 * @extends Ext.grid.ColumnModel
 *
 * The {@link Zarafa.contact.dialogs.DistlistMemberGridColumnModel DistlistMemberGridColumnModel}
 * is the default {@link Ext.grid.ColumnModel ColumnModel} for the Distlist Member grid
 */
Zarafa.contact.dialogs.DistlistMemberGridColumnModel = Ext.extend(Ext.grid.ColumnModel, {
	/**
	 * @constructor
	 * @param config Configuration structure
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			columns: this.createColumns(),
			defaults: {
				sortable: true
			}
		});

		Zarafa.contact.dialogs.DistlistMemberGridColumnModel.superclass.constructor.call(this, config);
	},

	/**
	 * Create an array of {@link Ext.grid.Column columns} which must be visible within
	 * the default view of this {@link Ext.grid.ColumnModel ColumnModel}.
	 *
	 * @return {Ext.grid.Column[]} The array of columns
	 * @private
	 */
	createColumns: function()
	{
		return [{
					dataIndex: 'icon_index',
					headerCls: 'zarafa-icon-column',
					header: '<p class="icon_index">&nbsp;<span class="title">Icon</span></p>',
					tooltip: _('Sort by: Icon'),
					width: 24,
					renderer: Zarafa.common.ui.grid.Renderers.icon
				},{
					dataIndex: 'display_name',
					id: 'display_name',
					header: _('Name'),
					tooltip: _('Sort by: Name'),
					renderer: Zarafa.common.ui.grid.Renderers.text
				},{
					dataIndex: 'smtp_address',
					header: _('Email Address'),
					tooltip: _('Sort by: Email Address'),
					width: 350,
					renderer: Zarafa.common.ui.grid.Renderers.text
				}];
	}
});
Ext.namespace('Zarafa.contact.ui');

/**
 * @class Zarafa.contact.ui.ContactCardView
 * @extends Zarafa.common.ui.DraggableDataView
 * @xtype zarafa.contactcardview
 *
 * this class creates layout of the cards view
 * dataview doesn't automatically manage the scrollbars itself
 * so we are adding a div as a container for card layout and giving it scrollbars manually
 */
Zarafa.contact.ui.ContactCardView = Ext.extend(Zarafa.common.ui.DraggableDataView, {
	/**
	 * @cfg {Zarafa.contact.ContactContext} context The context to which this view belongs
	 */
	context: undefined,

	/**
	 * The {@link Zarafa.contact.ContactContextModel} which is obtained from the {@link #context}.
	 * @property
	 * @type Zarafa.contact.ContactContextModel
	 */
	model: undefined,

	/**
	 * The {@link Zarafa.contact.ContactStore} which is obtained from the {@link #model}.
	 * @property
	 * @type Zarafa.contact.ContactStore
	 */
	store: undefined,

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

		if (!Ext.isDefined(config.model) && Ext.isDefined(config.context)) {
			config.model = config.context.getModel();
		}
		if (!Ext.isDefined(config.store) && Ext.isDefined(config.model)) {
			config.store = config.model.getStore();
		}

		config.store = Ext.StoreMgr.lookup(config.store);

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

		Ext.apply(this, config, {
			xtype: 'zarafa.contactcardview',
			border: false,
			loadingText: _('Loading contacts') + '...',
			deferEmptyText: false,
			emptyText: '<div class="emptytext-context">' +_('There are no items to show in this list. Update the filter for more results.') +'</div>',
			tpl: this.initTemplate(config.store),

			/*
			 * this is a required property
			 * itemSelector is used as an element selector query and it guides dataview to start re-rendering
			 * all elements that are child of this element, so without this property dataview will not know
			 * from which element it has to re-render elements so it will give error on refresh or store update
			 */
			itemSelector: 'div.zarafa-contact-cardview-card',
			multiSelect: true,
			selectedClass: 'zarafa-contact-cardview-card-selected',
			enableDrag: true,
			ddGroup: 'dd.mapiitem'
		});

		Zarafa.contact.ui.ContactCardView.superclass.constructor.call(this, config);

		this.initEvents();
	},

	/**
	 * initialize events for the card view
	 * @private
	 */
	initEvents: function()
	{
		this.on({
			'dblclick': this.onNodeDblClick,
			'contextmenu': this.onNodeContextMenu,
			'selectionchange': this.onSelectionChange,
			'afterrender': this.onAfterRender,
			scope: this
		});
	},

	/**
	 * Event handler called when contact card view rendered properly.
	 */
	onAfterRender: function()
	{
		var cardViewEl = Ext.get('contact-cardview');
		cardViewEl.on({
			'mouseenter': this.onMouseEnter.createDelegate(this, [cardViewEl], true),
			'mouseleave': this.onMouseLeave,
			scope: this
		});
	},

	/**
	 * Event handler which is triggered when user opens context menu
	 * @param {Zarafa.contact.ContactCardView} dataView data view object
	 * @param {Number} index index of node
	 * @param {HTMLElement} node node object
	 * @param {Ext.EventObject} event event object
	 * @private
	 */
	onNodeContextMenu: function(dataView, index, node, event)
	{
		// check row is already selected or not, if its not selected then select it first
		if (!dataView.isSelected(node)) {
			dataView.select(node);
		}

		Zarafa.core.data.UIFactory.openDefaultContextMenu(dataView.getSelectedRecords(), { position: event.getXY() });
	},

	/**
	 * Event handler function will be called when any node is double clicked.
	 * @param {Zarafa.contact.ContactCardView} dataView data view object
	 * @param {Number} index index of node
	 * @param {HTMLElement} node node object
	 * @param {Ext.EventObject} event event object
	 * @private
	 */
	onNodeDblClick: function(dataView, index, node, event)
	{
		Zarafa.contact.Actions.openDialog(dataView.getSelectedRecords());
	},

	/**
	 * Event handler which is triggered when the {@link Zarafa.contact.ui.ContactCardView ContactCardView}
	 * {@link Zarafa.core.data.IPMRecord record} selection is changed. This will inform
	 * the {@link Zarafa.contact.ContactContextModel contextmodel} about the change.
	 *
	 * @param {Zarafa.contact.ui.ContactCardView} dataView The view object.
	 * @param {HTMLElement[]} selection Array of selected nodes.
	 * @private
	 */
	onSelectionChange: function(dataView, selections)
	{
		this.model.setSelectedRecords(dataView.getSelectedRecords());
	},

	/**
	 * Function will initialize {@link Ext.XTemplate XTemplate} and create layout for data view
	 *
	 * @param {Zarafa.contact.ContactStore} store The {@link Zarafa.contact.ContactStore} which contains all contacts.
	 * @return {Ext.XTemplate} xtemplate for layout
	 * @private
	 */
	initTemplate: function(store)
	{
		var templateStrArr = [
			'<div class="zarafa-contact-cardview">',
				'<tpl for=".">',
					'<div class="zarafa-contact-cardview-card">',
						'<div class="x-panel">',
							'<div class="x-panel-tl">',
								'<div class="x-panel-tr">',
									'<div class="x-panel-tc">',
										'<div class="x-panel-header">',
											'<span class="x-panel-header-text">',
												'<tpl if="values.message_class==\'IPM.DistList\'">' + _('[Group]') + " " +'</tpl>{fileas:htmlEncodeUndef}',
											'</span>',
										'</div>',
									'</div>',
								'</div>',
							'</div>',
							'<div class="x-panel-bwrap">',
								'<div class="x-panel-body zarafa-card-body">',
									// content
									'<p class="zarafa-contact-cardview-card-title">{display_name:htmlEncode}</p>',
									'<p>{company_name:htmlEncodeUndef}</p>',
									'<table>',
										'<tpl if="!Ext.isEmpty(values.title)"><td><p>{title:htmlEncodeUndef}</p></td></tpl>',
										'<tpl if="!Ext.isEmpty(values.department_name) && Ext.isEmpty(values.title)">',
											'<td><p>{department_name:htmlEncodeUndef}</p></td>',
										'</tpl>',
										'<tpl if="!Ext.isEmpty(values.business_telephone_number)">',
											'<tr><td class="contact-card-label">' + _('Work:') + ' {business_telephone_number:htmlEncode}</td></tr>',
										'</tpl>',
										'<tpl if="!Ext.isEmpty(values.business2_telephone_number) && Ext.isEmpty(values.business_telephone_number)">',
											'<tr><td class="contact-card-label">' + _('Work:') + ' {business2_telephone_number:htmlEncode}</td></tr>',
										'</tpl>',
										'<tpl if="!Ext.isEmpty(values.primary_telephone_number) && Ext.isEmpty(values.business_telephone_number)',
											'&& Ext.isEmpty(values.business2_telephone_number)">',
											'<tr><td class="contact-card-label">' + _('Primary:') + ' {primary_telephone_number:htmlEncode}</td></tr>',
										'</tpl>',
										'<tpl if="!Ext.isEmpty(values.cellular_telephone_number)">',
											'<tr><td class="contact-card-label">' + _('Mobile:') + ' {cellular_telephone_number:htmlEncode}</td></tr>',
										'</tpl>',
										'<tpl if="!Ext.isEmpty(values.email_address_1)"><tr><td>{email_address_1:htmlEncode}</td></tr></tpl>',
										'<tpl if="!Ext.isEmpty(values.email_address_2) && Ext.isEmpty(values.email_address_1)">',
											'<tr><td>{email_address_2:htmlEncode}</td></tr>',
										'</tpl>',
										'<tpl if="!Ext.isEmpty(values.email_address_3) && Ext.isEmpty(values.email_address_2) && Ext.isEmpty(values.email_address_1)">',
											'<tr><td>{email_address_3:htmlEncode}</td></tr>',
										'</tpl>',
										'<tr><td>{[this.getCategories(values)]}</td></tr>',
									'</table>',
								'</div>',
								'{[this.getInitials(values)]}',
							'</div>',
						'</div>',
					'</div>',
				'</tpl>',
			'</div>'
		];

		return new Ext.XTemplate(templateStrArr.join(''), {
			compiled: true, // compile immediately

			store: store,

			getInitials: function(values)
			{
				var record = this.store.getById(values.entryid);
				if (record.isOpened() && record.get('has_picture')){
					var attachmentStore = record.getAttachmentStore();
					var index = attachmentStore.findBy(function(item) {
						return item.get('attachment_contactphoto') === true && item.get('hidden') === true;
					});
					var contactPhoto = attachmentStore.getAt(index);

					return `<div class="k-contact_cardview_photo">
						<img src=${contactPhoto.getInlineImageUrl()} alt="${Ext.util.Format.htmlEncode(values.display_name || '')}">
					</div>`;
				} else if (values.contact_photo_attach_num !== -1){
					var url = container.getBaseURL();
					url = Ext.urlAppend(url, 'store=' + values.store_entryid);
					url = Ext.urlAppend(url, 'entryid=' + values.entryid);
					url = Ext.urlAppend(url, 'load=download_attachment');
					url = Ext.urlAppend(url, 'attachNum[]=' + encodeURIComponent(values.contact_photo_attach_num));
					url = Ext.urlAppend(url, 'contentDispositionType=inline');

					return `<div class="k-contact_cardview_photo">
						<img src=${url} alt="${Ext.util.Format.htmlEncode(values.display_name || '')}">
					</div>`;
				} else {
					// Contacts can have empty display names. Fall back to question mark.
					var contactInitials = "?";
					var displayName = `${values.given_name} ${values.surname}`;
					if (!Ext.isEmpty(displayName)) {
						 displayName = displayName.replace(/\(.*?\)/g, '').trim().split(' ');
						contactInitials = displayName.length > 1 ? displayName.shift().charAt(0) + displayName.pop().charAt(0) : displayName.shift().charAt(0);
					}
					return `<div class="k-contact-cardview-initials"> ${contactInitials.toUpperCase()} </div>`;
				}
			},

			getCategories: function(values)
			{
				var record = this.store.getById(values.entryid);
				var categories = Zarafa.common.categories.Util.getCategories(record);
				return Zarafa.common.categories.Util.getCategoriesHtml(categories);
			}
		});
	}
});

Ext.reg('zarafa.contactcardview', Zarafa.contact.ui.ContactCardView);

Ext.namespace('Zarafa.contact.ui');

/**
 * @class Zarafa.contact.ui.ContactContextMenu
 * @extends Zarafa.core.ui.menu.ConditionalMenu
 * @xtype zarafa.contactcontextmenu
 */
Zarafa.contact.ui.ContactContextMenu = Ext.extend(Zarafa.core.ui.menu.ConditionalMenu, {
	// Insertion points for this class
	/**
	 * @insert context.contact.contextmenu.actions
	 * Insertion point for adding actions menu items into the context menu
	 * @param {Zarafa.contact.ui.ContactContextMenu} contextmenu This contextmenu
	 */
	/**
	 * @insert context.contact.contextmenu.options
	 * Insertion point for adding options menu items into the context menu
	 * @param {Zarafa.contact.ui.ContactContextMenu} contextmenu This contextmenu
	 */

	/**
	 * @cfg {Zarafa.core.data.IPMRecord[]} The records on which this context menu acts
	 */
	records: undefined,

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

		if (Ext.isDefined(config.records) && !Array.isArray(config.records)) {
			config.records = [ config.records ];
		}

		Ext.applyIf(config, {
			items: [
				this.createContextActionItems(config.records),
				{ xtype: 'menuseparator' },
				container.populateInsertionPoint('context.contact.contextmenu.actions', this),
				this.createContextExportItems(config),
				{ xtype: 'menuseparator' },
				container.populateInsertionPoint('context.contact.contextmenu.options', this),
				{ xtype: 'menuseparator' },
				this.createOptionsMenuItem()
			],
			defaults: {
				xtype: 'zarafa.conditionalitem',
				hideOnDisabled: false
			}
		});

		Zarafa.contact.ui.ContactContextMenu.superclass.constructor.call(this, config);
	},

	/**
	 * Create the Action context menu items
	 * @param {Zarafa.core.data.IPMRecord{}} The records on which this menu acts
	 * @return {Zarafa.core.ui.menu.ConditionalItem[]} The list of Action context menu items
	 * @private
	 */
	createContextActionItems: function(records)
	{
		return [{
			text: _('Open'),
			iconCls: 'icon_open',
			scope: this,
			handler: this.onContextItemOpen,
			singleSelectOnly: true
		}, {
			text: _('Copy/Move'),
			iconCls: 'icon_copy',
			scope: this,
			handler: this.onCopyMove
		}, {
			text: _('Print'),
			iconCls: 'icon_print',
			scope: this,
			handler: this.onContextItemPrint,
			singleSelectOnly: true
		}, {
			xtype: 'menuseparator'
		}, {
			text: _('Categories'),
			cls: 'k-unclickable',
			iconCls: 'icon_categories',
			hideOnClick: false,
			menu: {
				xtype: 'zarafa.categoriescontextmenu',
				records: records
			}
		}, {
			xtype: 'menuseparator'
		}, {
			text: _('Delete'),
			iconCls: 'icon_delete',
			scope: this,
			handler: this.onContextItemDelete
		}];
	},

	/**
	 * Create the export context menu item.
	 * @param {Object} config Configuration object
	 * @return {Object} The Export context menu item
	 */
	createContextExportItems: function(config)
	{
		return [{
			text: _('Export as'),
			cls: 'k-unclickable',
			iconCls: 'icon_export',
			hideOnClick: false,
			menu: {
				xtype: 'zarafa.exportcontactcontextmenu',
				records: config.records
			}
		}];
	},

	/**
	 * Open the {@link Zarafa.common.dialogs.CopyMoveContentPanel CopyMoveContentPanel} for copying
	 * or moving the currently selected contacts.
	 * @private
	 */
	onCopyMove: function()
	{
		Zarafa.common.Actions.openCopyMoveContent(this.records);
	},

	/**
	 * Event handler which is called when the user selects the 'Open'
	 * item in the context menu. This will open the item in a new dialog.
	 * @private
	 */
	onContextItemOpen: function()
	{
		Zarafa.contact.Actions.openDialog(this.records);
	},

	/**
	 * Event handler which is called when the user selects the 'Print'
	 * item in the context menu. This will open the print dialog.
	 * @private
	 */
	onContextItemPrint: function()
	{
		Zarafa.common.Actions.openPrintDialog(this.records);
	},

	/**
	 * Event handler which is called when the user selects the 'Categories'
	 * item in the context menu. This will open {@link Zarafa.common.categories.dialogs.CategoriesContentPanel CategoriesContentPanel}.
	 * @private
	 */
	onContextItemCategories: function()
	{
		Zarafa.common.Actions.openCategoriesContent(this.records);
	},

	/**
	 * Create the Options menu entry.
	 * @return {Object} Menu item configuration
	 * @private
	*/
	createOptionsMenuItem: function()
	{
		return {
			text: _('Options'),
			iconCls: 'icon_cogwheel',
			scope: this,
			singleSelectOnly: true,
			handler: this.onContextItemOptions
		};
	},
	
		/**
		 * Open the options dialog for the selected record.
		 * @private
		 */
		onContextItemOptions: function()
		{
			Zarafa.contact.Actions.openOptionsContent(this.records, {
				autoSave: true
			});
		},

		/**
	 * Event handler which is called when the user selects the 'Delete'
	 * item in the context menu. This will delete selected contacts from view.
	 * @private
	 */
	onContextItemDelete: function()
	{
		Zarafa.common.Actions.deleteRecords(this.records);
	}
});

Ext.reg('zarafa.contactcontextmenu', Zarafa.contact.ui.ContactContextMenu);
/*
 * #dependsFile client/zarafa/common/ui/grid/Renderers.js
 */
Ext.namespace('Zarafa.contact.ui');

/**
 * @class Zarafa.contact.ui.ContactGridColumnModel
 * @extends Zarafa.common.ui.grid.ColumnModel
 *
 * The {@link Zarafa.contact.ui.ContactGridColumnModel ContactGridColumnModel}
 * is the default {@link Ext.grid.ColumnModel ColumnModel} for the
 * {@link Zarafa.contact.ui.ContactGrid ContactGrid}.
 */
Zarafa.contact.ui.ContactGridColumnModel = Ext.extend(Zarafa.common.ui.grid.ColumnModel, {
	/**
	 * @constructor
	 * @param config Configuration structure
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			columns: this.createColumns(),
			defaults: {
				sortable: true,
				width: 150
			}
		});

		Zarafa.contact.ui.ContactGridColumnModel.superclass.constructor.call(this, config);
	},

	/**
	 * Create an array of {@link Ext.grid.Column columns} which must be visible within
	 * the default view of this {@link Ext.grid.ColumnModel ColumnModel}.
	 *
	 * @return {Ext.grid.Column[]} The array of columns
	 * @private
	 */
	createColumns: function()
	{
		return [{
			dataIndex: 'icon_index',
			headerCls: 'zarafa-icon-column icon',
			header: _('Icon'),
			tooltip: _('Sort by: Icon'),
			width: 25,
			fixed: true,
			renderer: Zarafa.common.ui.grid.Renderers.icon
		},{ // @TODO add attachment column
			dataIndex: 'fileas',
			id: 'fileas',			// gridPanel.autoExpandColumn config will reference to this id
			header: _('File As'),
			tooltip: _('Sort by: File As'),
			renderer: Ext.util.Format.htmlEncode
		},{
			dataIndex: 'display_name',
			header: _('Full Name'),
			tooltip: _('Sort by: Full Name'),
			renderer: Ext.util.Format.htmlEncode
		},{
			dataIndex: 'email_address_1',
			header: _('Email'),
			tooltip: _('Sort by: Email Address'),
			renderer: Ext.util.Format.htmlEncode
		},{
			dataIndex: 'email_address_2',
			header: _('Email 2'),
			hidden: true,
			tooltip: _('Sort by: Email Address 2'),
			renderer: Ext.util.Format.htmlEncode
		},{
			dataIndex: 'email_address_3',
			header: _('Email 3'),
			hidden: true,
			tooltip: _('Sort by: Email Address 3'),
			renderer: Ext.util.Format.htmlEncode
		},{
			dataIndex: 'home_telephone_number',
			header: _('Home Phone'),
			tooltip: _('Sort by: Home Phone'),
			renderer: Ext.util.Format.htmlEncode
		},{
			dataIndex: 'cellular_telephone_number',
			header: _('Mobile Phone'),
			tooltip: _('Sort by: Mobile Phone'),
			renderer: Ext.util.Format.htmlEncode
		},{
			dataIndex: 'categories',
			header: _('Categories'),
			tooltip: _('Sort by: Categories'),
			renderer: Zarafa.common.ui.grid.Renderers.categories
		},{
			dataIndex: 'business_telephone_number',
			header: _('Business Phone'),
			tooltip: _('Sort by: Business Phone'),
			renderer: Ext.util.Format.htmlEncode
		},{
			dataIndex: 'business_fax_number',
			header: _('Business Fax'),
			tooltip: _('Sort by: Business Fax'),
			renderer: Ext.util.Format.htmlEncode
		},{
			dataIndex: 'company_name',
			header: _('Company Name'),
			tooltip: _('Sort by: Company Name'),
			hidden: true,
			renderer: Ext.util.Format.htmlEncode
		},{
			dataIndex: 'title',
			header: _('Job Title'),
			tooltip: _('Sort by: Job Title'),
			hidden: true,
			renderer: Ext.util.Format.htmlEncode
		},{
			dataIndex: 'department_name',
			header: _('Department Name'),
			hidden: true,
			tooltip: _('Sort by: Department Name'),
			renderer: Ext.util.Format.htmlEncode
		},{
			dataIndex: 'office_location',
			header: _('Office Location'),
			hidden: true,
			tooltip: _('Sort by: Office Location'),
			renderer: Ext.util.Format.htmlEncode
		},{
			dataIndex: 'profession',
			header: _('Profession'),
			hidden: true,
			tooltip: _('Sort by: Profession'),
			renderer: Ext.util.Format.htmlEncode
		},{
			dataIndex: 'manager_name',
			header: _('Manager Name'),
			hidden: true,
			tooltip: _('Sort by: Manager Name'),
			renderer: Ext.util.Format.htmlEncode
		},{
			dataIndex: 'assistant',
			header: _("Assistant's Name"),
			hidden: true,
			tooltip: _("Sort by: Assistant's Name"),
			renderer: Ext.util.Format.htmlEncode
		},{
			dataIndex: 'nickname',
			header: _('Nickname'),
			hidden: true,
			tooltip: _('Sort by: Nickname'),
			renderer: Ext.util.Format.htmlEncode
		},{
			dataIndex: 'spouse_name',
			header: _('Spouse/Partner'),
			hidden: true,
			tooltip: _('Sort by: Spouse/Partner Name'),
			renderer: Ext.util.Format.htmlEncode
		},{
			dataIndex: 'birthday',
			header: _('Birthday'),
			hidden: true,
			tooltip: _('Sort by: Birthday'),
			renderer: Zarafa.common.ui.grid.Renderers.date
		},{
			dataIndex: 'wedding_anniversary',
			header: _('Anniversary'),
			hidden: true,
			tooltip: _('Sort by: Anniversary'),
			renderer: Zarafa.common.ui.grid.Renderers.date
		},{
			dataIndex: 'business_address',
			header: _('Business Address'),
			hidden: true,
			tooltip: _('Sort by: Business Address'),
			renderer: Ext.util.Format.htmlEncode
		},{
			dataIndex: 'home_address',
			header: _('Home Address'),
			hidden: true,
			tooltip: _('Sort by: Home Address'),
			renderer: Ext.util.Format.htmlEncode
		},{
			dataIndex: 'other_address',
			header: _('Other Address'),
			hidden: true,
			tooltip: _('Sort by: Office Address'),
			renderer: Ext.util.Format.htmlEncode
		},{
			dataIndex: 'mailing_address',
			header: _('Mailing Address'),
			hidden: true,
			tooltip: _('Sort by: Mailing Address'),
			renderer: Ext.util.Format.htmlEncode
		},{
			dataIndex: 'im',
			header: _('IM Address'),
			hidden: true,
			tooltip: _('Sort by: IM Address'),
			renderer: Ext.util.Format.htmlEncode
		},{
			dataIndex: 'webpage',
			header: _('Webpage'),
			hidden: true,
			tooltip: _('Sort by: Webpage'),
			renderer: Ext.util.Format.htmlEncode
		},{
			dataIndex: 'assistant_telephone_number',
			header: _("Assistant's Phone"),
			hidden: true,
			tooltip: _("Sort by: Assistant's Phone"),
			renderer: Ext.util.Format.htmlEncode
		},{
			dataIndex: 'business2_telephone_number',
			header: _('Business Phone 2'),
			hidden: true,
			tooltip: _('Sort by: Business Phone 2'),
			renderer: Ext.util.Format.htmlEncode
		},{
			dataIndex: 'callback_telephone_number',
			header: _('Callback Phone'),
			hidden: true,
			tooltip: _('Sort by: Callback'),
			renderer: Ext.util.Format.htmlEncode
		},{
			dataIndex: 'car_telephone_number',
			header: _('Car Phone'),
			hidden: true,
			tooltip: _('Sort by: Car Phone'),
			renderer: Ext.util.Format.htmlEncode
		},{
			dataIndex: 'company_telephone_number',
			header: _('Company Phone'),
			hidden: true,
			tooltip: _('Sort by: Company Main Phone'),
			renderer: Ext.util.Format.htmlEncode
		},{
			dataIndex: 'home2_telephone_number',
			header: _('Home Phone 2'),
			hidden: true,
			tooltip: _('Sort by: Home Phone 2'),
			renderer: Ext.util.Format.htmlEncode
		},{
			dataIndex: 'home_fax_number',
			header: _('Home Fax'),
			hidden: true,
			tooltip: _('Sort by: Home Fax'),
			renderer: Ext.util.Format.htmlEncode
		},{
			dataIndex: 'isdn_number',
			header: _('ISDN'),
			hidden: true,
			tooltip: _('Sort by: ISDN'),
			renderer: Ext.util.Format.htmlEncode
		},{
			dataIndex: 'other_telephone_number',
			header: _('Other Phone'),
			hidden: true,
			tooltip: _('Sort by: Other Phone'),
			renderer: Ext.util.Format.htmlEncode
		},{
			dataIndex: 'radio_telephone_number',
			header: _('Radio Phone'),
			hidden: true,
			tooltip: _('Sort by: Radio Phone'),
			renderer: Ext.util.Format.htmlEncode
		},{
			dataIndex: 'ttytdd_telephone_number',
			header: _('TTY/TDD Phone'),
			hidden: true,
			tooltip: _('Sort by: TTY/TDD Phone'),
			renderer: Ext.util.Format.htmlEncode
		},{
			dataIndex: 'telex_telephone_number',
			header: _('Telex'),
			hidden: true,
			tooltip: _('Sort by: Telex Phone'),
			renderer: Ext.util.Format.htmlEncode
		},{
			dataIndex: 'pager_telephone_number',
			header: _('Pager'),
			hidden: true,
			tooltip: _('Sort by: Pager'),
			renderer: Ext.util.Format.htmlEncode
		}];
	}
});
/*
 * #dependsFile client/zarafa/contact/data/SearchFields.js
 */
Ext.namespace('Zarafa.contact.ui');

/**
 * @class Zarafa.contact.ui.ContactMainPanel
 * @extends Zarafa.common.ui.ContextMainPanel
 * @xtype zarafa.contactmainpanel
 *
 * this class will be containing all the views that will be created for contacts folder.
 */
Zarafa.contact.ui.ContactMainPanel = Ext.extend(Zarafa.common.ui.ContextMainPanel, {
	// Insertion points for this class
	/**
	 * @insert context.contact.toolbar.item
	 * Insertion point for populating contact context's main toolbar.
	 * This item is only visible when this context is active.
	 * @param {Zarafa.contact.ui.ContactMainPanel} panel This panel
	 */
	/**
	 * @insert context.contact.toolbar.paging
	 *
	 * Insertion point for populating contact context's toolbar with extra
	 * pagination buttons. This can be used to replace the default {@link Ext.PagingToolbar}
	 * with an alternative. Note that by default all paging toolbars will be visible, and
	 * hiding a particular toolbar is the responsibility of the new pagers.
	 * @param {Zarafa.contact.ui.ContactMainPanel} panel This panel
	 */
	/**
	 * @insert context.contact.views
	 * Insertion point for adding views within the main panel of contact context.
	 * This insertion point should be used in combination with 'main.maintoolbar.view.contact'
	 * insertion point, and also view should set its store in the config object, the reference of
	 * {@link Zarafa.contact.ContactContextModel ContactContextModel} is passed as parameter of this
	 * insertion point.
	 * @param {Zarafa.contact.ui.ContactMainPanel} mainpanel This mainpanel
	 * @param {Zarafa.contact.ContactContext} context The context for this panel
	 */

	/**
	 * The main panel in which the various views are located.
	 * @property
	 * @type Zarafa.core.ui.SwitchViewContentContainer
	 */
	viewPanel: undefined,

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

		Ext.applyIf(config, {
			xtype: 'zarafa.contactmainpanel',
			layout: 'fit',
			items: [{
				xtype: 'zarafa.switchviewcontentcontainer',
				ref: 'viewPanel',
				layout: 'card',
				lazyItems: this.initViews(config.context)
			}],
			tbar: {
				xtype: 'zarafa.contextmainpaneltoolbar',
				defaultTitle: _('Contacts'),
				paging: container.populateInsertionPoint('context.contact.toolbar.paging', this),
				items: container.populateInsertionPoint('context.contact.toolbar.item', this),
				context: config.context
			}
		});

		Zarafa.contact.ui.ContactMainPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Function will initialize all views associated with contact context
	 * it will also get views added through 3rd party plugins and add it here
	 * @param {Zarafa.contact.ContactContextModel} model data part of contact context
	 * @return {Array} array of config objects of different views
	 * @private
	 */
	initViews: function(context)
	{
		// add the standard available views
		var allViews = [{
			xtype: 'zarafa.contactgrid',
			id  : 'contact-gridview',
			context: context
		}, {
			xtype: 'zarafa.contactcardpanel',
			id  : 'contact-cardview',
			context: context
		}];

		var additionalViewItems = container.populateInsertionPoint('context.contact.views', this, context);
		allViews = allViews.concat(additionalViewItems);

		return allViews;
	},

	/**
	 * Function called by Extjs when the panel has been {@link #render rendered}.
	 * At this time all events can be registered.
	 * @private
	 */
	initEvents: function()
	{
		if (Ext.isDefined(this.context)) {
			this.mon(this.context, 'viewchange', this.onViewChange, this);
			this.onViewChange(this.context, this.context.getCurrentView());
		}
	},

	/**
	 * Event handler which is fired when the currently active view inside the {@link #context}
	 * has been updated. This will update the call
	 * {@link #viewPanel}#{@link Zarafa.core.ui.SwitchViewContentContainer#switchView}
	 * to make the requested view active.
	 *
	 * @param {Zarafa.contact.ContactContext} context The context which fired the event
	 * @param {Zarafa.contact.data.Views} newView The ID of the selected view
	 * @param {Zarafa.contact.data.Views} oldView The ID of the previously selected view
	 * @private
	 */
	onViewChange: function(context, newView, oldView)
	{
		switch(newView) {
			case Zarafa.contact.data.Views.ICON:
				this.viewPanel.switchView('contact-cardview');
				break;
			case Zarafa.contact.data.Views.LIST:
			case Zarafa.contact.data.Views.SEARCH:
				this.viewPanel.switchView('contact-gridview');
				break;
		}
	}
});

Ext.reg('zarafa.contactmainpanel', Zarafa.contact.ui.ContactMainPanel);
/*
 * #dependsFile client/zarafa/core/KeyMapMgr.js
 */
Ext.namespace('Zarafa.hierarchy');

/**
 * @class Zarafa.hierarchy.KeyMapping
 * @extends Object
 *
 * The map of keys used in the Hierarchy Context.
 * @singleton
 */
Zarafa.hierarchy.KeyMapping = Ext.extend(Object, {
	/**
	 * @constructor
	 */
	constructor: function()
	{
		var newItemKeys = [{
			key: Ext.EventObject.F,
			ctrl: true,
			alt: true,
			shift: false,
			stopEvent: true,
			handler: this.onNewFolder,
			scope: this,
			settingsCfg: {
				description: _('New folder'),
				category: _('Creating an item')
			}
		},{
			key: Ext.EventObject.S,
			ctrl: false,
			alt: true,
			shift: false,
			stopEvent: true,
			handler: this.onOpenSharedFolder,
			scope: this,
			settingsCfg: {
				description: _('Open shared folder/store'),
				category: _('Basic navigation')
			}
		},{
			key: Ext.EventObject.F2,
			ctrl: false,
			alt: false,
			shift: false,
			stopEvent: true,
			handler: this.onRenameFolder,
			scope: this,
			settingsCfg: {
				description: _('Rename folder'),
				category: _('All views')
			},
			basic: true
		}];

		Zarafa.core.KeyMapMgr.register('global', newItemKeys);
	},

	/**
	 * Event handler for the keydown event of the {@link Zarafa.core.KeyMap KeyMap} when the user wants to
	 * rename the folder.
	 */
	onRenameFolder : function()
	{
		var navigationBar = container.getNavigationBar();
		var centerPanel = navigationBar.centerPanel;
		var currentContextName = navigationBar.activeContext.getName();

		var hierarchyTree;
		if (navigationBar.showFolderList || currentContextName === 'today') {
			hierarchyTree = centerPanel.allFoldersPanel.allFoldersHierarchyTree;
		} else if (currentContextName === "calendar") {
			hierarchyTree = centerPanel.multiSelectHierarchyTree;
		} else {
			var activeItem = centerPanel.layout.activeItem;
			hierarchyTree = activeItem.hierarchytree;
		}

		var selectedNode = hierarchyTree.getSelectionModel().getSelectedNode();
		if (this.forbiddenToRenameFolder(selectedNode.getFolder())) {
			return;
		}
		hierarchyTree.startEditingNode(selectedNode);
	},

	/**
	 * Helper function used to identify that folder is not special/default folder which we can't rename folder.
	 * 
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder The folder which is currently selected in hieararchy.
	 * @returns {Boolean} return true if folder is not allow to change the name else false.
	 */
	forbiddenToRenameFolder : function(folder)
	{
		return false;
	},

	/**
	 * Event handler for the keydown event of the {@link Zarafa.core.KeyMap KeyMap} when the user wants to
	 * create a new folder.
	 * @param {Number} key Key code
	 * @param {Ext.EventObject} event The event
	 * @param {Ext.Component} component The component on which key event is fired.
	 */
	onNewFolder: function(key, event, component)
	{
		Zarafa.hierarchy.Actions.openCreateFolderContent();
	},

	/**
	 * Event handler for the keydown event of the {@link Zarafa.core.KeyMap KeyMap} when the user wants to
	 * create a new folder.
	 * @param {Number} key Key code
	 * @param {Ext.EventObject} event The event
	 * @param {Ext.Component} component The component on which key event is fired.
	 */
	onOpenSharedFolder: function(key, event, component)
	{
		var currentContextName = container.getCurrentContext().getName();
		var defaultSelectedFolderType;
		switch(currentContextName) {
			case 'calendar':
				defaultSelectedFolderType = Zarafa.hierarchy.data.SharedFolderTypes['APPOINTMENT'];
				break;
			case 'contact':
				defaultSelectedFolderType = Zarafa.hierarchy.data.SharedFolderTypes['CONTACT'];
				break;
			case 'note':
				defaultSelectedFolderType = Zarafa.hierarchy.data.SharedFolderTypes['NOTE'];
				break;
			case 'task':
				defaultSelectedFolderType = Zarafa.hierarchy.data.SharedFolderTypes['TASK'];
				break;
			case 'inbox':
			/* falls through */
			default:
				defaultSelectedFolderType = Zarafa.hierarchy.data.SharedFolderTypes['MAIL'];
		}
		Zarafa.hierarchy.Actions.openSharedFolderContent(defaultSelectedFolderType);
	}
});

Zarafa.hierarchy.KeyMapping = new Zarafa.hierarchy.KeyMapping();
Ext.namespace('Zarafa.hierarchy.data');

/**
 * @class Zarafa.hierarchy.data.CounterTypes
 * @extends Zarafa.core.Enum
 * 
 * An enum that contains all possible counter types which
 * can be used to show counter in {@link Zarafa.hierarchy.ui.FolderNode FolderNode}.
 * 
 * @singleton
 */
Zarafa.hierarchy.data.CounterTypes = Zarafa.core.Enum.create({
	/**
	 * Unread counter will be shown in {@link Zarafa.hierarchy.ui.FolderNode FolderNode}.
	 * @property
	 * @type String
	 */
	NONE		: 0,

	/**
	 * Total counter will be shown in {@link Zarafa.hierarchy.ui.FolderNode FolderNode}.
	 * @property
	 * @type String
	 */
	TOTAL		: 1,

	/**
	 * Unread counter will be shown in {@link Zarafa.hierarchy.ui.FolderNode FolderNode}.
	 * @property
	 * @type String
	 */
	UNREAD		: 2
});
Ext.namespace('Zarafa.hierarchy.data');

/**
 * @class Zarafa.hierarchy.data.HierarchyProxy
 * @extends Zarafa.core.data.MAPIProxy
 */
Zarafa.hierarchy.data.HierarchyProxy = Ext.extend(Zarafa.core.data.MAPIProxy, {
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

		var module = Zarafa.core.ModuleNames.getModule('hierarchy');

		if (module) {
			Ext.applyIf(config, {
				listModuleName: module.list,
				itemModuleName: module.item
			});
		}

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

	/**
	 * This will create a {@link Zarafa.core.data.ProxyResponseHandler ProxyResponseHandler} object
	 * which will be used by the {@link Zarafa.core.data.ResponseRouter ResponseRouter} when the
	 * response for the given request has returned.
	 *
	 * @param {String} modulename The modulename which is being accessed with this request
	 * @param {Zarafa.core.Actions} serverAction The action to perform on the server.
	 * @param {Ext.data.Api.action} action name of the action to perform.
	 * @param {Ext.data.Record[]} records list of records to operate on.
	 * @param {Object} parameters object containing user parameters such as range (pagination) information, sorting information, etc.
	 * @param {Ext.data.DataReader} reader data reader. Converts raw JavaScript objects (in our case) to instances of {@link Ext.data.Record}
	 * @param {Function} callback call back function to call when the request has finished successfully.
	 * @param {Object} scope scope for the call back function.
	 * @param {Object} args arguments object. This will be passed to the call back function on successful read.
	 * @return {Object} An instance of the {@link Zarafa.core.data.ProxyResponseHandler ProxyResponseHandler}
	 * which should be used for this request.
	 * @private
	 */
	getResponseHandlerForRequest: function(modulename, serverAction, action, records, parameters, reader, callback, scope, args)
	{
		return new Zarafa.hierarchy.data.HierarchyResponseHandler({
			proxy: this,
			action: action,
			reader: reader,
			sendRecords: records,
			options: args,
			callback: callback,
			scope: scope
		});
	}
});
Ext.namespace('Zarafa.hierarchy.data');

/**
 * @class Zarafa.hierarchy.data.HierarchyState
 * @extends Zarafa.core.data.StatefulObservable
 *
 * A special State class which can be used to obtain the current
 * state for the {@link Zarafa.hierarchy.data.HierarchyStore Hierarchy}
 * or its {@link Zarafa.hierarchy.data.MAPIFolderRecord folders}.
 *
 * This must be used in conjunction with {@link Zarafa.common.ui.grid.GridPanel}
 * which is writing directly into the namespace using the {@link Zarafa.core.data.SettingsStateProvider}.
 */
Zarafa.hierarchy.data.HierarchyState = Ext.extend(Zarafa.core.data.StatefulObservable, {

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

		Ext.applyIf(config, {
			stateful: true,
			statefulName: 'folders'
		});

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

	/**
	 * Obtain the path in which the {@link #getState state} must be saved.
	 * This option is only used when the {@link Zarafa.core.data.SettingsStateProvider SettingsStateProvider} is
	 * used in the {@link Ext.state.Manager}.
	 *
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder The folder for which the statename is requested
	 * @param {String} type The category for the folder for which the statename is requested (e.g. 'list' or 'tree').
	 * @return {String} The unique name for this component by which the {@link #getState state} must be saved.
	 */
	getStateNameForFolder: function(folder, type)
	{
		if (folder) {
			return 'folders/' + folder.get('entryid') + '/' + type;
		} else {
			return 'folders/none/' + type;
		}
	},

	/**
	 * Apply the given state to this object activating the properties which were previously
	 * saved in {@link Ext.state.Manager}.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder The folder for which the state is provided
	 * @param {String} type The category for the folder for which the state is provided (e.g. 'list' or 'tree').
	 * @param {Object} state The state object
	 */
	applyStateForFolder: function(folder, type, state)
	{
		this.statefulName = this.getStateNameForFolder(folder, type);

		var id = this.getStateId();
		if (id) {
			Ext.state.Manager.set(id, state);
		}
	},

	/**
	 * When {@link #stateful} the State object which should be saved into the
	 * {@link Ext.state.Manager}.
	 * This calls {@link Zarafa.hierarchy.data.HierarchyState#getStateForFolder getStateForFolder} on the {@link #state}.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder The folder for which the state is requested
	 * @param {String} type The category for the folder for which the state is requested (e.g. 'list' or 'tree').
	 * @return {Object} The state object
	 */
	getStateForFolder: function(folder, type)
	{
		this.statefulName = this.getStateNameForFolder(folder, type);

		var id = this.getStateId();
		if (id) {
			return Ext.apply({}, Ext.state.Manager.get(id));
		}
	}
});
Ext.namespace('Zarafa.hierarchy.data');

/**
 * @class Zarafa.hierarchy.data.JsonPermissionsWriter
 * @extends Zarafa.core.data.JsonWriter
 */
Zarafa.hierarchy.data.JsonPermissionsWriter = Ext.extend(Zarafa.core.data.JsonWriter, {
	/**
	 * Similar to {@link Ext.data.JsonWriter#toHash}
	 *
	 * Convert permissions into a hash. Permissions exists as
	 * {@link Zarafa.hierarchy.data.UserPermissionsRecord UserPermissionsRecord} within
	 * a {@link Zarafa.hierarchy.data.MAPIFolderRecord MAPIFolderRecord} and thus must be serialized
	 * separately into the hash object.
	 *
	 * @param {Ext.data.Record} record The record to hash
	 * @return {Object} The hashed object
	 * @override
	 * @private
	 */
	toPropHash: function(record)
	{
		var permissionsStore = record.getSubStore('permissions');
		var hash = {};

		if (!Ext.isDefined(permissionsStore)) {
			return hash;
		}

		// Get list of modified (modified and newly added) records
		var modifiedRecords = permissionsStore.getModifiedRecords();
		// Get list of removed records
		var deletedRecords = permissionsStore.getRemovedRecords();

		if (modifiedRecords.length > 0 || deletedRecords.length > 0) {
			hash.permissions = {};

			// Adding the modified records to the add or modified part of the permissions bit
			for (var i = 0; i < modifiedRecords.length; i++) {
				var permission = modifiedRecords[i];
				// FIXME: serialize?
				var data = permission.data;

				if (permission.phantom) {
					if(!Ext.isDefined(hash.permissions.add)) {
						hash.permissions.add = [];
					}

					hash.permissions.add.push(data);
				} else {
					// Recipient already exists and needs to be updated
					if(!Ext.isDefined(hash.permissions.modify)) {
						hash.permissions.modify = [];
					}

					hash.permissions.modify.push(data);
				}
			}

			// Adding the removed records to the remove part of the permissions bit
			for (var i = 0; i < deletedRecords.length; i++) {
				var permission = deletedRecords[i];
				var data = permission.data;

				if (!Ext.isDefined(hash.permissions.remove)) {
					hash.permissions.remove = [];
				}

				hash.permissions.remove.push(data);
			}
		}

		return hash;
	}
});
Ext.namespace('Zarafa.hierarchy.dialogs');

/**
 * @class Zarafa.hierarchy.dialogs.BrokenFilesContentPanel
 * @extends Zarafa.core.ui.ContentPanel
 * @xtype zarafa.brokenfilescontentpanel
 *
 * This content panel will be used to show error when user tries to import attachments
 * directly from drive.
 */
Zarafa.hierarchy.dialogs.BrokenFilesContentPanel = Ext.extend(Zarafa.core.ui.ContentPanel, {
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			xtype: 'zarafa.brokenfilescontentpanel',
			layout: 'fit',
			border: false,
			title: _('Import error'),
			width: 400,
			height: 250,
			items: [{
				xtype: 'zarafa.brokenfilespanel',
				records: config.record,
				buttons: [{
					text: _('Close'),
					handler: this.close,
					scope: this
				}]
			}]
		});

		Zarafa.hierarchy.dialogs.BrokenFilesContentPanel.superclass.constructor.call(this, config);
	}
});

Ext.reg('zarafa.brokenfilescontentpanel', Zarafa.hierarchy.dialogs.BrokenFilesContentPanel);Ext.namespace('Zarafa.hierarchy.dialogs');

/**
 * @class Zarafa.hierarchy.dialogs.CreateFolderContentPanel
 * @extends Zarafa.core.ui.ContentPanel
 * @xtype zarafa.createfoldercontentpanel
 */
Zarafa.hierarchy.dialogs.CreateFolderContentPanel = Ext.extend(Zarafa.core.ui.RecordContentPanel, {
	/**
	 * @cfg {Zarafa.hierarchy.data.MAPIFolderRecord} parentFolder (optional) The parent folder
	 * underneath the new folder will be created.
	 */
	parentFolder: undefined,
	/**
	 * @cfg {String} preferredContainerClass (optional) The preferred container
	 * class for the newly created Folder.
	 */
	preferredContainerClass: undefined,
	/**
	 * @constructor
	 * @param config Configuration structure
	 */
	constructor: function(config)
	{
		config = config || {};

		config = Ext.applyIf(config, {
			// Override from Ext.Component
			xtype: 'zarafa.createfoldercontentpanel',
			layout: 'fit',
			title: _('Create New Folder'),
			recordComponentPluginConfig: Ext.applyIf(config.recordComponentPluginConfig || {}, {
				allowWrite: true,
				ignoreUpdates: false,
				useShadowStore: true
			}),
			width: 330,
			height: 380,
			items: [{
				xtype: 'zarafa.createfolderpanel',
				parentFolder: config.parentFolder,
				preferredContainerClass: config.preferredContainerClass
			}]
		});

		Zarafa.hierarchy.dialogs.CreateFolderContentPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Fired when the {@link #updaterecord} event has been fired. This will close the panel if the record
	 * is being {@link Ext.data.Record#COMMIT committed}.
	 *
	 * @param {Zarafa.core.ui.RecordContentPanel} contentpanel The record which fired the event
	 * @param {String} action write Action that occurred. Can be one of
	 * {@link Ext.data.Record.EDIT EDIT}, {@link Ext.data.Record.REJECT REJECT} or
	 * {@link Ext.data.Record.COMMIT COMMIT}
	 * @param {Zarafa.core.data.IPMRecord} record The record which was updated
	 * @private
	 * @overridden
	 */
	onUpdateRecord: function(contentpanel, action, record)
	{
		Zarafa.hierarchy.dialogs.CreateFolderContentPanel.superclass.onUpdateRecord.apply(this, arguments);

		if (action == Ext.data.Record.COMMIT) {
			this.close();
			return false;
		}
	}
});

Ext.reg('zarafa.createfoldercontentpanel', Zarafa.hierarchy.dialogs.CreateFolderContentPanel);
Ext.namespace('Zarafa.hierarchy.dialogs');

/**
 * @class Zarafa.hierarchy.dialogs.FolderSelectionContentPanel
 * @extends Zarafa.core.ui.ContentPanel
 * @xtype zarafa.folderselectioncontentpanel
 *
 * Content Panel to allow the user to select a folder
 */
Zarafa.hierarchy.dialogs.FolderSelectionContentPanel = Ext.extend(Zarafa.core.ui.ContentPanel, {
	/**
	 * @cfg {Boolean} hideTodoList True to hide the To-do list.
	 */
	hideTodoList: false,

	/**
	 * @cfg {Zarafa.hierarchy.data.MAPIFolderRecord} folder The folder which is selected by
	 * default in the hierarchy tree.
	 */
	folder: undefined,

	/**
	 * @cfg {Function} callback The callback function which will be called when the
	 * user presses the 'Ok' button. This will pass the selected
	 * {@link Zarafa.hierarchy.data.MAPIFolderRecord #folder} as first argument.
	 */
	callback: Ext.emptyFn,

	/**
	 * @cfg {Object} scope The scope by which the {@link #callback} will be called.
	 */
	scope: undefined,

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

		config = Ext.applyIf(config, {
			// Override from Ext.Component
			layout: 'fit',
			title: _('Select Folder'),
			width: 330,
			height: 380,
			items: [{
				xtype: 'zarafa.folderselectionpanel',
				folder: config.folder,
				hideTodoList: !!config.hideTodoList,
				IPMSubTreeFilter: config.IPMSubTreeFilter,
				IPMFilter: config.IPMFilter ? config.IPMFilter : undefined,
				permissionFilter: config.permissionFilter ? config.permissionFilter : undefined,
				buttonAlign: 'left',
				buttons: [{
					text: _('New folder'),
					handler: this.onNewFolder,
					cls: 'zarafa-normal',
					scope: this
				},'->',{
					text: _('Ok'),
					handler: this.onOk,
					scope: this
				},{
					text: _('Cancel'),
					handler: this.onCancel,
					scope: this
				}]
			}]
		});

		Zarafa.hierarchy.dialogs.FolderSelectionContentPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Event handler which is fired when the user pressed the
	 * 'New folder' button. This will open dialog for creating new folder.
	 */
	onNewFolder: function() {
		var parentFolder = this.get(0).getFolder();
		Zarafa.hierarchy.Actions.openCreateFolderContent(parentFolder);
		this.mon(this.get(0).hierarchyTree, 'append', this.onTreeAppend, this, {delay: 10});
	},

	/**
	 * Event handler which is triggered when a new folder is appended to the tree and selects the newly created folder
	 * @param {Zarafa.hierarchy.ui.Tree} tree the folder tree
	 * @param {Ext.data.Node} parent the parent of the newly created node
	 * @param {Ext.data.Node} node the appended node
	 * @private
	 */
	onTreeAppend: function(tree, parent, node)
	{
		// Sometimes the 'append' is fired but the node is not rendered yet,so add a delay of 10 ms.
		if (!node.parentNode) {
			// The node is probably removed and appended again, so let's find the
			// correct node in the tree again
			node = tree.getNodeById(node.id);
		}
		tree.selectPath(node.getPath());
		this.mun(this.get(0).hierarchyTree, 'append', this.onTreeAppend, this);
	},

	/**
	 * Event handler which is fired when the user pressed the
	 * 'Ok' button. This will call {@link #callback} and {@link #close}
	 * the dialog.
	 * @private
	 */
	onOk: function()
	{
		if (Ext.isFunction(this.callback)) {
			this.callback.call(this.scope || this, this.get(0).getFolder());
		}
		this.close();
	},

	/**
	 * Event handler which is fired when the user pressed the
	 * 'Cancel' button. This will discard all changes and {@link #close}
	 * the dialog.
	 * @private
	 */
	onCancel: function()
	{
		this.close();
	}
});

Ext.reg('zarafa.folderselectioncontentpanel', Zarafa.hierarchy.dialogs.FolderSelectionContentPanel);
 Ext.namespace('Zarafa.hierarchy.ui');

/**
 * @class Zarafa.hierarchy.ui.ContextMenu
 * @extends Zarafa.core.ui.menu.ConditionalMenu
 * @xtype zarafa.hierarchycontextmenu
 */
Zarafa.hierarchy.ui.ContextMenu = Ext.extend(Zarafa.core.ui.menu.ConditionalMenu, {

	/**
	 * @cfg contextNode Holds {@link Zarafa.hierarchy.ui.FolderNode foldernode} on which 'contextmenu' event has occurred
	 */
	contextNode: undefined,

	/**
	 * The tree to which the {@link #contextNode} belongs to. On this tree the actual contextmenu was requested.
	 * @property
	 * @type Zarafa.hierarchy.ui.Tree
	 */
	contextTree: undefined,

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

		if (config.contextNode) {
			config.contextTree = config.contextNode.getOwnerTree();
		}

		Ext.applyIf(config, {
			items: [
				this.createContextMenuItems(config)
			],
			defaults: {
				xtype: 'zarafa.conditionalitem',
				scope: this
			}
		});

		Zarafa.hierarchy.ui.ContextMenu.superclass.constructor.call(this, config);
	},

	/**
	 * Create the Action context menu items.
	 * @param {Object} config Configuration object for the {@link Zarafa.hierarchy.ui.ContextMenu ContextMenu}
	 * @return {Zarafa.core.ui.menu.ConditionalItem[]} The list of Action context menu items
	 * @private
	 *
	 * Note: All handlers are called within the scope of {@link Zarafa.hierarchy.ui.ContextMenu HierarchyContextMenu}
	 */
	createContextMenuItems: function(config)
	{
		var folderName = _('Empty folder');
		var record = config.records;
		if (record.isSpecialFolder('junk')) {
			folderName = _('Empty Junk Email');
		} else if (record.isSpecialFolder('wastebasket')) {
			folderName = _('Empty Deleted Items');
		}

		return [{
			text: _('Open'),
			iconCls: 'icon_open',
			handler: this.onContextItemOpen,
			beforeShow: function(item, record) {
				var access = record.get('access') & Zarafa.core.mapi.Access.ACCESS_READ;
				if (!access || (record.isIPMSubTree() && !record.getMAPIStore().isDefaultStore())) {
					item.setDisabled(true);
				} else {
					item.setDisabled(false);
				}
			}
		}, {
			xtype: 'menuseparator'
		}, {
			text: _('Copy/Move Folder'),
			iconCls: 'icon_copy',
			handler: this.onContextCopyMoveFolder,
			beforeShow: function(item, record) {
				var access = record.get('access') & Zarafa.core.mapi.Access.ACCESS_READ;
				if (
          !access ||
          record.isIPMSubTree() ||
          record.isTodoListFolder() ||
          record.isRSSFolder() ||
          record.isDefaultFolder()
        ) {
					item.setDisabled(true);
				} else {
					item.setDisabled(false);
				}
			}
		}, {
			text: _('Rename Folder'),
			iconCls: 'icon_folder_rename',
			handler: this.onContextItemRenameFolder,
			beforeShow: function(item, record) {
				var access = record.get('access') & Zarafa.core.mapi.Access.ACCESS_MODIFY;
				if (
          !access ||
          !this.contextTree ||
          !this.contextNode
        ) {
					item.setDisabled(true);
				} else {
					item.setDisabled(false);
				}
			}
		}, {
			text: _('New Folder'),
			iconCls: 'icon_folder_create',
			handler: this.onContextItemNewFolder,
			beforeShow: function(item, record) {
				var access = record.get('access') & Zarafa.core.mapi.Access.ACCESS_CREATE_HIERARCHY;
				if (!access || record.getMAPIStore().isArchiveStore() || record.isTodoListFolder()) {
					item.setDisabled(true);
				} else {
					item.setDisabled(false);
				}
			}
		}, {
			xtype: 'menuseparator'
		}, {
			text: _('Mark All Messages Read'),
			iconCls: 'icon_mail_read',
			handler: this.onContextItemReadFlags,
			beforeShow: function(item, record) {
				// We're not modifying the folder, but the contents. Hence we request the READ access
				var access = record.get('access') & Zarafa.core.mapi.Access.ACCESS_READ;
				if (!access || record.isIPMSubTree() ||record.isTodoListFolder()) {
					item.setDisabled(true);
				} else {
					item.setDisabled(false);
				}
			}
		}, {
			xtype: 'menuseparator'
		}, {
			text: _('Delete Folder'),
			iconCls: 'icon_folder_delete',
			handler: this.onContextItemDeleteFolder,
			beforeShow: function(item, record) {
				var access = record.get('access') & Zarafa.core.mapi.Access.ACCESS_DELETE;
				if (
          !access ||
          record.isIPMSubTree() ||
          record.isDefaultFolder() ||
          record.isTodoListFolder() ||
          record.isRSSFolder() ||
          record.getMAPIStore().isArchiveStore()) {
					item.setDisabled(true);
				} else {
					item.setDisabled(false);
				}
			}
		}, {
			text: folderName,
			iconCls: 'icon_folder_delete',
			handler: this.onContextItemEmptyFolder,
			beforeShow: function(item, record) {
				// We're not modifying the folder, but the contents. Hence we request the READ access
				var access = record.get('access') & Zarafa.core.mapi.Access.ACCESS_READ;
				if (access && !record.isIPMSubTree() && !record.isTodoListFolder()) {
					item.setDisabled(false);
				} else {
					item.setDisabled(true);
				}
			}
		}, {
			xtype: 'menuseparator'
		}, {
			text: _('Close store'),
			iconCls: 'icon_store_close',
			handler: this.onContextItemCloseStore,
			scope: this,
			beforeShow: function(item, record) {
				if (record.isIPMSubTree() && record.getMAPIStore().isSharedStore()) {
					item.setDisabled(false);
				} else {
					item.setDisabled(true);
				}
			}
		}, {
			text: _('Close folder'),
			iconCls: 'icon_folder_close',
			handler: this.onContextItemCloseFolder,
			scope: this,
			beforeShow: function(item, record) {
				if (!record.isIPMSubTree() && record.isSharedFolder()) {
					item.setDisabled(false);
				} else {
					item.setDisabled(true);
				}
			}
		}, {
			xtype: 'menuseparator'
		}, {
			text: _('Reload'),
			iconCls: 'icon_refresh',
			handler: this.onContextItemReload,
			scope: this,
			beforeShow: function(item, record) {
				if (record.isOwnRoot()) {
					item.setDisabled(false);
				} else {
					item.setDisabled(true);
				}
			}
		},
		{
			text: _('Restore items'),
			handler: this.onContextItemRestore,
			iconCls: 'icon_restore',
			beforeShow: function(item, record) {
				if (!record.get('access') || record.isTodoListFolder()) {
					item.setDisabled(true);
				} else {
					item.setDisabled(false);
				}
			}
		},
		{
			text: _('Select color'),
			iconCls: 'icon-select-color',
			beforeShow: function(item, record) {
				var folderEntryId = this.contextNode.id;
				var contextModel = this.contextTree.model;

				// Allow user to choose calendar color from every calendar node whether it belongs to
				// MultiSelectHierarchyTree or simple HierarchyTree
				if (record.isCalendarFolder()) {
					item.setDisabled(false);

					// Store the color of the chosen color scheme, so we can use it later
					// to build a icon (more precisely just a div with a background color)
					// in the context menu
					var colorScheme = contextModel.getColorScheme(folderEntryId);
					item.iconBG = colorScheme.base;
				} else {
					item.setDisabled(true);
				}
			},
			menu: this.createSelectColorSubmenu(config)
		},{
			text: _('Add to Favorites'),
			iconCls: 'icon_favorites',
			hidden: true,
			beforeShow: function(item, record) {
				// Hide the button is favorites is hidden
				if (container.getSettingsModel().get('zarafa/v1/contexts/hierarchy/hide_favorites')) {
					return item.setVisible(false);
				}
				if(!record.isInDeletedItems()) {
					var isVisible = record.existsInFavorites();
					if(!isVisible && record.isIPMSubTree()) {
						isVisible = !record.isOwnRoot();
					}
					item.setDisabled(isVisible || record.isTodoListFolder());
				}
			},
			handler: this.onContextItemFavorites
		},{
			text: _('Remove From Favorites'),
			hidden: true,
			iconCls: 'icon_remove_favorites',
			beforeShow: function(item, record) {
				// Hide the button if favorites is hidden
				if (container.getSettingsModel().get('zarafa/v1/contexts/hierarchy/hide_favorites')) {
					return item.setVisible(false);
				}
				if(record.existsInFavorites()) {
					item.setDisabled(false);
				}
			},
			handler: this.onContextItemFavoritesRemove
		},{
			text: _('Share folder…'),
			iconCls: 'icon_share',
			name: 'shareFolder',
			beforeShow: this.onBeforeContextItem,
			handler: this.onContextItemSharedFolderAndProperties
		},{
			xtype: 'menuseparator'
		},{
			text: _('Import emails'),
			handler: this.onContextImportItem,
			iconCls: 'icon_import_attachment',
			beforeShow: this.onBeforeShowImportItem
		},{
			text: _('Import appointments'),
			iconCls: 'icon_import_attachment',
			name: 'importAppointments',
			beforeShow: this.onBeforeShowImportItem,
			handler: this.onContextImportItem
		},{
			text: _('Import contacts'),
			handler: this.onContextImportItem,
			name: 'importContacts',
			iconCls: 'icon_import_attachment',
			beforeShow: this.onBeforeShowImportItem
		},{
			xtype: 'menuseparator'
		},{
			text: _('Properties'),
			handler: this.onContextItemSharedFolderAndProperties,
			iconCls: 'icon_cogwheel',
			beforeShow: this.onBeforeContextItem
		}];
	},

	/**
	 * Gets all recursive subfolders of {@link Zarafa.hierarchy.data.MAPIFolderRecord folder}
	 * in a flat list, not a tree
	 *
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder The folder which children are returned
	 * @param {Boolean} isRoot If true, the root folder is excluded in the result
	 * @private
	 */
	getRecusiveSubfolderList: function(folder, isRoot)
	{
		const folderChildren = folder.getChildren();
		if(!folderChildren || !folderChildren.length) {
				return folder;
		}
		const newChildren = [];
		for(let i = 0; i < folderChildren.length; i++) {
			const subTree = this.getRecusiveSubfolderList(folderChildren[i]);
			if(Ext.isArray(subTree)) {
				subTree.forEach(folder => newChildren.push(folder))
			} else {
				newChildren.push(subTree);
			}
		}
		return isRoot ? newChildren : [folder, ...newChildren];
	},

	/**
	 * Fires on selecting 'Share folder…' or 'Properties' menu option from
	 * {@link Zarafa.hierarchy.ui.ContextMenu ContextMenu} Opens
	 * {@link Zarafa.hierarchy.dialogs.FolderPropertiesContent FolderPropertiesContent}
	 *
	 * @param {Ext.Button} btn The context menu item which is clicked.
	 * @private
	 */
	onContextItemSharedFolderAndProperties: function(btn)
	{
		var folder = this.records;
		var mapiStore;
		if (folder.isFavoritesFolder()) {
			mapiStore = folder.getOriginalRecordFromFavoritesRecord().getMAPIStore();
		} else {
			mapiStore = folder.getMAPIStore();
		}

		var storeName = mapiStore.get('display_name');
		var folderName = folder.get('display_name');
		var formattedTitle = '';
		var emptyText = '';
		var isIPMSubTree = folder.isIPMSubTree();
		var isAppointmentDialog = folder.isContainerClass('IPF.Appointment', true);

		if (mapiStore.isDefaultStore() || isIPMSubTree) {
			var text = isIPMSubTree ? storeName : folderName;
			formattedTitle = String.format(_('Properties of {0}'), text);
			emptyText = String.format(_('Share {0} with…'), text);
		} else {
			formattedTitle = String.format(_('Properties of {0} - {1}'), folderName, storeName);
			emptyText = String.format(_('Share {0} with…'), folderName);
		}

		var config = {
			modal: true,
			showModalWithoutParent: true,
			title: formattedTitle,
			emptyText: emptyText,
			isAppointmentDialog
		};

		if (btn.name == 'shareFolder') {
			config.activeTab = 1;
		}
		Zarafa.hierarchy.Actions.openFolderPropertiesContent(folder, config);
	},

	/**
	 * Event handler triggered before the "Share folder…" or "Properties"
	 * context menu item show.
	 *
	 * @param {Ext.Button} item The item which is going to show.
	 * @param {Zarafa.core.data.IPFRecord} record The folder record on which
	 * this context menu item shows.
	 */
	onBeforeContextItem: function(item, record)
	{
		if (!record.get('access') || record.isTodoListFolder()) {
			item.setDisabled(true);
		} else {
			item.setDisabled(false);
		}
	},

	/**
	 * Event handler triggered before the 'Import emails', 'Import contacts' and 'Import appointment'
	 * context menu item show.
	 *
	 * @param {Ext.Button} item The item which is going to show.
	 * @param {Zarafa.core.data.IPFRecord} record The folder record on which
	 * this context menu item shows.
	 */
	onBeforeShowImportItem: function(item, record)
	{
		var access = record.get('access') & Zarafa.core.mapi.Access.ACCESS_CREATE_CONTENTS;

		var hasImportSupport;
		switch (item.name) {
			case "importAppointments":
				hasImportSupport = record.isCalendarFolder();
			break;
			case "importContacts":
				hasImportSupport = record.isContactFolder();
			break;
			default:
				hasImportSupport = Zarafa.core.ContainerClass.isClass(record.get('container_class'), 'IPF.Note', true);
		}

		if (
			!access ||
			!hasImportSupport ||
			record.isIPMSubTree() ||
			record.isTodoListFolder() ||
			record.isRSSFolder()
		) {
			item.setDisabled(true);
		} else {
			item.setDisabled(false);
		}
	},

	/**
	 * Creates the submenu for the color select menu item
	 * Because this function is called before the parent constructor
	 * is called, the configuration properties are not yet set and
	 * we must get them from the passed configuration object
	 *
	 * @param {Object} config Configuration object for the {@link Zarafa.hierarchy.ui.ContextMenu ContextMenu}
	 * @return {Object[]} An array with configuration objects for
	 * {@link Zarafa.core.ui.menu.ConditionalItem menu items}
	 * @private
	 */
	createSelectColorSubmenu: function(config)
	{
		var items = [];
		var contextModel = config.contextTree.model;
		if ( !contextModel ){
			return items;
		}
		var colorSchemes = contextModel.colorScheme;
		var folderNodeColorTheme = config.contextNode.getFolder().colorTheme;
		Ext.each(colorSchemes, function(colorScheme){
			items.push({
				xtype: 'zarafa.conditionalitem',
				text: colorScheme.displayName,
				ctCls: folderNodeColorTheme===colorScheme.name ? 'x-menu-item-selected' :'',
				iconCls: 'icon-select-color color-'+colorScheme.name,
				colorSchemeName: colorScheme.name,
				handler: this.onContextItemSelectColor.createDelegate(this),
				iconBG: colorScheme.header
			});
		}, this);

		return items;
	},

	/**
	 * Fires on selecting 'Open' menu option from {@link Zarafa.hierarchy.ui.ContextMenu ContextMenu}
	 * @private
	 */
	onContextItemOpen: function()
	{
		// Select node within tree
		if (this.contextTree) {
			this.contextTree.selectFolderInTree(this.records);
		}
		Zarafa.hierarchy.Actions.openFolder(this.records);
	},

	/**
	 * Fires on selecting 'Copy/Move Folder' menu option from {@link Zarafa.hierarchy.ui.ContextMenu ContextMenu}
	 * Opens {@link Zarafa.common.dialogs.CopyMoveContent CopyMoveContent} which copies or moves the folder in the Hierarchy
	 * @private
	 */
	onContextCopyMoveFolder: function()
	{
		Zarafa.common.Actions.openCopyMoveContent(this.records);
	},

	/**
	 * Fires on selecting 'Rename' menu option from {@link Zarafa.hierarchy.ui.ContextMenu ContextMenu}
	 * Opens editor in tree to rename node.
	 * @private
	 */
	onContextItemRenameFolder: function()
	{
		this.contextTree.startEditingNode(this.contextNode);
	},

	/**
	 * Fires on selecting 'New Folder' menu option from {@link Zarafa.hierarchy.ui.ContextMenu ContextMenu}
	 * Opens {@link Zarafa.common.dialogs.CreateFolderContent CreateFolderContent} which adds new folder to Hierarchy
	 * @private
	 */
	onContextItemNewFolder: function()
	{
		Zarafa.hierarchy.Actions.openCreateFolderContent(this.records);
	},

	/**
	 * Fires on selecting 'Delete Folder' menu option from {@link Zarafa.hierarchy.ui.ContextMenu ContextMenu}
	 * Asks user to confirm deletion of folder and if true then calls {@link Zarafa.hierarchy.data.HierarchyStore HierarchyStore}
	 * to delete folder.
	 * @private
	 */
	onContextItemDeleteFolder: function()
	{
		var isFolderDeleted = this.records.isInDeletedItems() || this.records.getMAPIStore().isPublicStore();
		var msg;

		if (isFolderDeleted) {
			msg = String.format(_('Are you sure you want to permanently delete all the items and subfolders in the "{0}" folder?'), Ext.util.Format.htmlEncode(this.records.getDisplayName()));
		} else {
			msg = String.format(_('Are you sure you want to delete the folder "{0}" and move all of its contents into the Deleted Items folder?'), Ext.util.Format.htmlEncode(this.records.getDisplayName()));
		}

		Ext.MessageBox.show({
			title: _('Delete folder'),
			msg: msg,
			buttons: Ext.MessageBox.YESNO,
			fn: function (buttonClicked) {
				if (buttonClicked === 'yes') {
					var record = this.records;

					/**
					 * If folder is exist in favorites list then remove favorites folder from favorites store.
					 */
					if (record.existsInFavorites()) {
						var favoriteRecord = record.getFavoritesFolder();
						favoriteRecord.getStore().remove(favoriteRecord);
					}

					var store = record.getStore();

					if (isFolderDeleted) {
						store.remove(record);
					}
					else {
						if (container.getSettingsModel().get('zarafa/v1/contexts/mail/delegate_wastebasket_style') === 4) {
							record.moveTo(record.getMAPIStore().getDefaultFolder('wastebasket'));
						}
						else {
							record.moveTo(container.getHierarchyStore().getDefaultFolder('wastebasket'));
						}
						// Set selection to the appropriate folder after the record has been moved.
						this.setFolderSelection(record);

						// To close the deleted calendar folder from calendar tab we need to remove folder from
						// context model. which further update the state settings etc.
						this.contextTree.model.removeFolder(record);
					}

					store.save(record);
				}
			},
			scope: this
		});
	},

	/**
	 * The function checks if the folder to be deleted has any siblings.
	 * If the folder has a sibling folder, the selection is set on that folder,
	 * else the parent folder is selected.
	 */
	setFolderSelection: function(record)
	{
		var folderNode = this.contextNode;
		var folderToSelect;

		// No explicit selection is to be done if a folder is not selected
		// or if the folder is a Calendar item and 'Show all folders' is not checked, as it is already handled.
		if (!folderNode.isSelected() || (!this.contextTree.showAllFoldersCheckbox.checked && record.isCalendarFolder())) {
			return;
		}

		var previousSiblingNode = folderNode.previousSibling;
		var nextSiblingNode = folderNode.nextSibling;

		if (previousSiblingNode) {
			folderToSelect = previousSiblingNode.getFolder();
		} else if (nextSiblingNode) {
			folderToSelect = nextSiblingNode.getFolder();
		} else {
			folderToSelect = record.getParentFolder();
		}

		container.selectFolder(folderToSelect);
	},

	/**
	 * Fires on selecting 'Emtpy Folder' menu option from {@link Zarafa.hierarchy.ui.ContextMenu ContextMenu}
	 * Asks user to confirm deletion of all contents of selected folder and if true then calls {@link Zarafa.hierarchy.data.HierarchyStore HierarchyStore}
	 * to empty that folder.
	 * @private
	 */
	onContextItemEmptyFolder: function()
	{
		Ext.MessageBox.show({
			title: _('Empty folder'),
			msg: String.format(_('Are you sure you want to empty {0}?'), Ext.util.Format.htmlEncode(this.records.getDisplayName())),
			buttons: Ext.MessageBox.YESNO,
			fn: function(buttonClicked) {
				if (buttonClicked == 'yes') {
					if (this.records.get('content_count') >= 500) {
						Zarafa.hierarchy.Actions.emptyFolderBatched(this.records);
					} else {
						this.records.emptyFolder();
						this.records.save();
					}
				}
			},
			scope: this
		});
	},

	/**
	 * Fires on selecting 'Mark All msgs as read' menu option from {@link Zarafa.hierarchy.ui.ContextMenu ContextMenu}
	 * Asks {@link Zarafa.hierarchy.data.HierarchyStore HierarchyStore} to mark all messages on the selected folder as read.
	 * @private
	 */
	onContextItemReadFlags: function()
	{
		this.records.seadReadFlags();
		this.records.save();
	},

	/**
	 * Fires when a color is selected from the {@link Zarafa.hierarchy.ui.ContextMenu ContextMenu}
	 * Saves the new color to the settings and calls doLayout on the context to re-render.
	 *
	 * @param {Zarafa.core.ui.menu.ConditionalItem} item The menu item that was clicked on
	 */
	onContextItemSelectColor: function(item)
	{
		var contextModel = this.contextTree.model;
		var folder = item.getRecords();
		var store = contextModel.getStore();

		// Find the color scheme of the chosen color
		var colorSchemeName = item.colorSchemeName;
		var colorScheme = Zarafa.core.ColorSchemes.getColorScheme(colorSchemeName);

		// And set the color scheme for this folder
		contextModel.setColorScheme(folder.get('entryid'), colorScheme);

		// Update the colors of svg icon in hierarchy tree
		var treeNodeui = this.contextNode.getUI();
		var svgIcon = treeNodeui.getEl().querySelector('svg');
		if ( svgIcon ) {
			Ext.get(svgIcon).setStyle('color', colorScheme.base);
		}

		// Now let's repaint the calendar view by sending a 'fake' load event for the store.
		if (store && store.lastOptions && !store.isExecuting('list')) {
			store.fireEvent('load', store, store.getRange(), store.lastOptions);
		}

		// Update the color of svg-icon of same folder belongs to another available HierarchyTrees
		// and favorites folder if exist
		this.updateOtherHierarchyTreeNodes(folder.get('entryid'), colorScheme.base);
	},

	/**
	 * Fires on selecting "Close Store" menu option from the contextmenu. This
	 * will remove the shared store from the settings and closes the store on the server.
	 * @private
	 */
	onContextItemCloseStore: function()
	{
		var store = container.getHierarchyStore();
		var mapistore = this.records.getMAPIStore();

		store.remove(mapistore);
		store.save(mapistore);
	},

	/**
	 * Fires on selecting "Close Folder" menu option from the contextmenu. This
	 * will remove the shared folder from the settings and closes the folder on the server.
	 * @private
	 */
	onContextItemCloseFolder: function()
	{
		var mapistore = this.records.getMAPIStore();
		var folderstore = mapistore.getFolderStore();

		folderstore.remove(this.records);
		folderstore.save(this.records);
	},

	/**
	 * Fires on selecting 'Reload' menu option from {@link Zarafa.hierarchy.ui.ContextMenu ContextMenu}
	 * Reloads {@link Zarafa.hierarchy.data.HierarchyStore HierarchyStore} and populates new data in {@link Zarafa.hierarchy.ui.Tree Tree}.
	 * @private
	 */
	onContextItemReload: function()
	{
		container.getHierarchyStore().reload();
	},

	/**
	 * Open Restore dialog for restoring previously deleted items.
	 * @private
	 */
	onContextItemRestore: function()
	{
		Zarafa.common.Actions.openRestoreContent(this.records);
	},

	/**
	 * Open upload files dialog to import into folder.
	 *
	 * @param {Zarafa.core.ui.menu.ConditionalItem} button The menu item that was clicked on
	 * @private
	 */
	onContextImportItem: function(button)
	{
		var accept = '.eml';
		switch (button.name) {
			case 'importContacts':
				accept = '.vcf';
				break;
			case 'importAppointments':
				accept = '.ics,.vcs';
				break;
		}

		var config = Ext.apply({}, {
			accept: accept,
			callback: Zarafa.common.Actions.importItemCallback.createDelegate(Zarafa.common.Actions, [ button.getRecords() ], 1)
		});
		Zarafa.common.Actions.openImportContent(this.records, config);
	},

	/**
	 * Event handler triggers when 'Add to Favorites' menu option from {@link Zarafa.hierarchy.ui.ContextMenu ContextMenu}
	 * Add to Favorites mark as Favorites to selected {@link Zarafa.hierarchy.data.MAPIFolderRecord folder}.
	 */
	onContextItemFavorites: function()
	{
		// Fixme: Rather to create copy of record, create phantom link message and use it.
		var copyRecord = this.records.copy();
		var shadowStore = container.getShadowStore();
		copyRecord.phantom = true;
		shadowStore.add(copyRecord);
		copyRecord.addToFavorites();
		shadowStore.save(copyRecord);
		copyRecord.phantom = false;
		shadowStore.remove(copyRecord, true);
	},

	/**
	 * Event handler triggers when 'Remove From Favorites' menu option from {@link Zarafa.hierarchy.ui.ContextMenu ContextMenu}
	 * Remove From Favorites menu option unmark selected {@link Zarafa.hierarchy.data.MAPIFolderRecord folder} from Favorite list .
	 */
	onContextItemFavoritesRemove: function()
	{
		var record = this.records;
		if(record.existsInFavorites()) {
			record = record.getFavoritesFolder();
			var store = record.getStore();
			store.remove(record);
			record.removeFromFavorites();
			store.save(record);
		}
	},

	/**
	 * Helper function to get all the available {@link Zarafa.hierarchy.ui.Tree tree} and update
	 * icon of respective folder with newly selected color.
	 * @param {String} folderEntryid The entryid of the selected folder
	 * @param {String} colorSchemeBase The chosen color
	 * @private
	 */
	updateOtherHierarchyTreeNodes: function(folderEntryid, colorSchemeBase)
	{
		var navigationBar = container.getNavigationBar();
		var centerPanel = navigationBar.centerPanel;
		var allFoldersPanel = centerPanel.allFoldersPanel;
		var multiSelectTree = centerPanel.multiSelectHierarchyTree;
		var allFoldersTree = allFoldersPanel.allFoldersHierarchyTree;

		this.setSvgIconColor(this.contextTree, "favorites-" + folderEntryid, colorSchemeBase);
		this.setSvgIconColor(allFoldersTree, folderEntryid, colorSchemeBase);
		this.setSvgIconColor(allFoldersTree, "favorites-" + folderEntryid, colorSchemeBase);
		this.setSvgIconColor(multiSelectTree, folderEntryid, colorSchemeBase);
	},

	/**
	 * Helper function to find node based on supplied entryid from {@link Zarafa.hierarchy.ui.Tree tree}
	 * and set the style of particular node with supplied color.
	 * @param {Zarafa.hierarchy.ui.Tree} hierarchyTree The tree in which node needs to be searched
	 * @param {String} folderEntryid The entryid of the selected folder
	 * @param {String} colorSchemeBase The chosen color
	 * @private
	 */
	setSvgIconColor: function(hierarchyTree, folderEntryid, colorSchemeBase)
	{
		var respectiveNode = hierarchyTree.getNodeById(folderEntryid);
		if (respectiveNode) {
			var respectiveNodeSvg = respectiveNode.getUI().iconNode;
			Ext.get(respectiveNodeSvg).setStyle('color', colorSchemeBase);
		}
	}
});

Ext.reg('zarafa.hierarchycontextmenu', Zarafa.hierarchy.ui.ContextMenu);
Ext.namespace('Zarafa.hierarchy.ui');

/**
 * @class Zarafa.hierarchy.ui.HierarchyFolderDropZone
 * @extends Zarafa.hierarchy.ui.HierarchyTreeDropZone
 *
 * Specialized {@link Ext.tree.TreeDropZone Drop Zone} which
 * is used for dragging and dropping {@link Zarafa.hierarchy.data.MAPIFolderRecord folders}
 * in the {@link Zarafa.hierarchy.ui.HierarchyTreePanel Hierarchy Tree}.
 */
Zarafa.hierarchy.ui.HierarchyFolderDropZone = Ext.extend(Zarafa.hierarchy.ui.HierarchyTreeDropZone, {

	/**
	 * @constructor
	 * @param {String/HTMLElement/Element} tree The {@link Ext.tree.TreePanel} for which to enable dropping
	 * @param {Object} config
	 */
	constructor: function(tree, config)
	{
		Zarafa.hierarchy.ui.HierarchyFolderDropZone.superclass.constructor.call(this, tree, config);

		if (this.tree) {
			this.tree.on('beforenodedrop', this.onBeforeNodeDrop, this);
		}
	},

	/**
	 * Event handler for the {@link Ext.tree.TreePanel#beforenodedrop 'beforenodedrop'}
	 * event which is fired just before a node is being dropped. This will check if we
	 * are dropping the folder on the same parent as it is currently in already. When
	 * this happens we cancel the drop. This ensures that the UI will complete the drop
	 * just as if everything worked as expected.
	 *
	 * @param {Object} dropEvent The event object which describes what node is being
	 * dropped where.
	 * @private
	 */
	onBeforeNodeDrop: function(dropEvent)
	{
		var dropNode = dropEvent.dropNode;

		if (Ext.isDefined(dropNode)) {
			var targetNode = dropEvent.target;

			switch (dropEvent.point) {
				case 'above':
				case 'below':
					targetNode = targetNode.parentNode;
					break;
				case 'append':
				/* falls through */
				default:
					break;
			}

			// Is dropped on same parent, cancel drop event
			if (dropNode.parentNode === targetNode) {
				return false;
			}
		}
	},

	/**
	 * Obtain the DropPoint string ('above', 'append' and 'below') which
	 * indicate at which position we are currently dragging the object
	 * over the current node.
	 *
	 * This code is copied from the superclass, with the only exception
	 * that it will allow the 'append' string even though the folder
	 * over which we are hovering is a leaf node.
	 *
	 * @param {Ext.EventObj} e The Event Object used for dragging the item
	 * @param {Object} n The object which describes the node over which
	 * the item is being hovered
	 * @parmam {Ext.tree.TreeDragZone} dd The DragZone which is used
	 * for dragging the current item
	 * @private
	 */
	getDropPoint: function(e, n, dd)
	{
		var tn = n.node;

		if (tn.isRoot) {
			return tn.allowChildren !== false ? 'append' : false; // always append for root
		}
		var dragEl = n.ddel;
		var t = Ext.lib.Dom.getY(dragEl), b = t + dragEl.offsetHeight;
		var y = Ext.lib.Event.getPageY(e);
		var noAppend = tn.allowChildren === false;
		if (this.appendOnly || tn.parentNode.allowChildren === false) {
			return noAppend ? false : 'append';
		}
		var noBelow = false;
		if (!this.allowParentInsert) {
			noBelow = tn.hasChildNodes() && tn.isExpanded();
		}
		var q = (b - t) / (noAppend ? 2: 3);
		if (y >= t && y < (t + q)) {
			return 'above';
		} else if (!noBelow && (noAppend || y >= b-q && y <= b)) {
			return 'below';
		} else {
			return 'append';
		}
	},

	/**
	 * Called by {@link #processDrop} to perform the final step of of dropping the item.
	 * This will call the {@link #folderdrop} event.
	 *
	 * @param {Object} de The dropEvent as generated by {@link #processDrop}.
	 * @private
	 */
	completeDrop: function(de)
	{
		de.target.ui.endDrop();
		this.tree.fireEvent("folderdrop", de);
	},

	/**
	 * Function which return false when the dragged record is hovering over
	 * the {@link Zarafa.hierarchy.data.MAPIFolderRecord#isDropTargetForFolders special} folders.
	 *
	 * @param {Object} n The object which describes the node over which
	 * the item is being hovered
	 * @param {String} pt The DropPoint (see {@link #getDropPoint}).
	 * @param {Ext.tree.TreeDragZone} dd The DragZone which is used
	 * for dragging the current item
	 * @param {Ext.EventObject} e The Event Object used for dropping the item
	 * @param {Object} data The data object describing the {@link Zarafa.hierarchy.ui.FolderNode folder} node Item which
	 * are being dragged
	 * @private
	 */
	isValidDropPoint: function(n, pt, dd, e, data)
	{
		var folder = n.node.getFolder();
		if (folder.isDropTargetForFolders() === false) {
			return false;
		}
		return Zarafa.hierarchy.ui.HierarchyFolderDropZone.superclass.isValidDropPoint.apply(this, arguments);
	}
});
Ext.namespace('Zarafa.hierarchy.ui');

/**
 * @class Zarafa.hierarchy.ui.HierarchyItemDropZone
 * @extends Zarafa.hierarchy.ui.HierarchyTreeDropZone
 *
 * Specialized {@link Ext.tree.TreeDropZone Drop Zone} which
 * is used for dragging and dropping {@link Zarafa.core.data.MAPIRecord messages}
 * in the {@link Zarafa.hierarchy.ui.HierarchyTreePanel Hierarchy Tree}.
 */
Zarafa.hierarchy.ui.HierarchyItemDropZone = Ext.extend(Zarafa.hierarchy.ui.HierarchyTreeDropZone, {

	/**
	 * Called when the currently dragged item is being dropped on a particular node.
	 * This will call {@link #isValidDropPoint} to check if the item can actually
	 * be dropped here, and if so it will {@link #processDrop drop the item}.
	 *
	 * @param {Object} n The object which describes the node over which
	 * the item is being hovered
	 * @param {Ext.grid.GridDragZone} dd The DragZone which is used
	 * for dragging the current item
	 * @param {Ext.EventObject} e The Event Object used for dropping the item
	 * @param {Object} data The data object describing the Grid Items which
	 * are being dragged
	 * @private
	 */
	onNodeDrop: function(n, dd, e, data)
	{
		var point = this.getDropPoint(e, n, dd);
		var targetNode = n.node;
		targetNode.ui.startDrop();
		if (!this.isValidDropPoint(n, point, dd, e, data)) {
			targetNode.ui.endDrop();
			return false;
		}
		// first try to find the drop items
		var dropItem = data.selections;
		return this.processDrop(targetNode, data, point, dd, e, dropItem);
	},

	/**
	 * Called when the currently dragged item is being dropped on the container.
	 * THis will call {@link #isValidDropPoint} to check if the item can actually
	 * be dropped here, and if so it will {@link #processDrop drop the item}.
	 *
	 * @param {Ext.grid.GridDragZone} dd The DragZone which is used
	 * for dragging the current item
	 * @param {Ext.EventObject} e The Event Object used for dropping the item
	 * @param {Object} data The data object describing the Grid Items which
	 * are being dragged
	 * @private
	 */
	onContainerDrop: function(dd, e, data)
	{
		if (this.allowContainerDrop && this.isValidDropPoint({ ddel: this.tree.getRootNode().ui.elNode, node: this.tree.getRootNode() }, "append", dd, e, data)) {
			var targetNode = this.tree.getRootNode();
			targetNode.ui.startDrop();
			var dropItem = data.selections;
			return this.processDrop(targetNode, data, 'append', dd, e, dropItem);
		}
		return false;
	},

	/**
	 * Called when the item is currently being dropped by the user. This will
	 * fire the {@link #beforeitemdrop} event and if the item can be dropped,
	 * then will call {@link #completeDrop} to perform the final step of dropping
	 * the item.
	 *
	 * @param {Ext.tree.TreeNode} target The node on which the item is being dropped
	 * @param {Object} data The data object describing the Grid Items which
	 * are being dragged
	 * @param {String} point The DropPoint (see {@link #getDropPoint}).
	 * @param {Ext.grid.GridDragZone} dd The DragZone which is used
	 * for dragging the current item
	 * @param {Ext.EventObject} e The Event Object used for dropping the item
	 * @param {Ext.data.Record|Array} dropItem The items which are being dropped
	 * @private
	 */
	processDrop: function(target, data, point, dd, e, dropItem)
	{
		var dropEvent = {
			tree: this.tree,
			target: target,
			data: data,
			point: point,
			source: dd,
			rawEvent: e,
			dropItem: dropItem,
			cancel: Ext.isEmpty(dropItem),
			dropStatus: false
		};
		var retval = this.tree.fireEvent("beforeitemdrop", dropEvent);
		if(retval === false || dropEvent.cancel === true || Ext.isEmpty(dropEvent.dropItem)){
			target.ui.endDrop();
			return dropEvent.dropStatus;
		}

		this.completeDrop(dropEvent);
		return true;
	},

	/**
	 * Called by {@link #processDrop} to perform the final step of of dropping the item.
	 * This will call the {@link #itemdrop} event.
	 *
	 * @param {Object} de The dropEvent as generated by {@link #processDrop}.
	 * @private
	 */
	completeDrop: function(de){
		de.target.ui.endDrop();
		this.tree.fireEvent("itemdrop", de);
	},

	/**
	 * Check if the current item which is being dragged can be dropped
	 * over the current node over which the item is hovering.
	 *
	 * This will return false when the dragged records are dragged from
	 * the folder over which we are currently hovering or when the folder
	 * doesn't support messages from the given message class.
	 *
	 * @param {Object} n The object which describes the node over which
	 * the item is being hovered
	 * @param {String} pt The DropPoint (see {@link #getDropPoint}).
	 * @param {Ext.grid.GridDragZone} dd The DragZone which is used
	 * for dragging the current item
	 * @param {Ext.EventObject} e The Event Object used for dropping the item
	 * @param {Object} data The data object describing the Grid Items which
	 * are being dragged
	 * @private
	 */
	isValidDropPoint: function(n, pt, dd, e, data)
	{
		var ret = false;
		if (
			n.node &&
			!Ext.isEmpty(data.selections) &&
			n.node.getFolder().isDropTargetForItems()
		) {
			var folder = n.node.getFolder();
			var record = data.selections[0];

			if (!folder.isIPMSubTree()) {
				var folderEntryId = folder.get('entryid');
				var itemEntryId = record.get('parent_entryid');

				// Check if the new parent equals the old parent
				ret = !Zarafa.core.EntryId.compareEntryIds(folderEntryId, itemEntryId);
				if (ret === true) {
					// If we are hovering over the wastebasket, the item
					// can be dropped
					ret = folder.isSpecialFolder('wastebasket');
					if (ret !== true) {
						var containerClass = folder.get('container_class');
						var messageClass = record.get('message_class');

						// Check if the message class is compatible with the container class.
						ret = Zarafa.core.MessageClass.isContainerClassCompatible(messageClass, containerClass);
					}
				}
			}
		}

		return ret;
	},

	/**
	 * Obtain the DropPoint string ('above', 'append' and 'below') which
	 * indicate at which position we are currently dragging the object
	 * over the current node.
	 *
	 * Items can only be dropped inside a folder, in other words,
	 * it can only append the item to the folder. Hence this function
	 * will always return 'append'.
	 *
	 * @param {Ext.EventObj} e The Event Object used for dragging the item
	 * @param {Object} n The object which describes the node over which
	 * the item is being hovered
	 * @parmam {Ext.tree.TreeDragZone} dd The DragZone which is used
	 * for dragging the current item
	 * @private
	 */
	getDropPoint: function(e, n, dd)
	{
		return 'append';
	}
});
Ext.namespace('Zarafa.hierarchy.ui');

/**
 * @class Zarafa.hierarchy.ui.HierarchyTreeFilter
 * @extends Zarafa.common.ui.TreeFilter
 *
 * Filter class for the Tree, this enables filtering of the tree nodes,
 * in which tree nodes are filtered according to the filter query.
 */
Zarafa.hierarchy.ui.HierarchyTreeFilter = Ext.extend(Zarafa.common.ui.TreeFilter, {

    /**
     * Filter the data by a specific attribute.
     * @param {RegExp} value Regex which needs to test with the attribute value.
     * should start with a RegExp to test against the attribute.
     * @param {String} attr (optional) The attribute passed in your node's attributes collection. Defaults to "text".
     * @param {TreeNode} startNode (optional) The node to start the filter at.
     */
    filter : function(value, attr, startNode)
    {   
        attr = attr || "text";
        var fn;
        
        // regex expression.
        if(value.exec) {
            fn = function(node) {
                var nodeValue;
                var folder = node.attributes.folder;
                
                if (!Ext.isDefined(folder) || (!folder.isFavoritesFolder() && node.attributes.nodeType !== 'rootfolder') || folder.isIPMSubTree() || folder.isFavoritesRootFolder()) {
                    nodeValue = node.attributes[attr];
                } else {
                    // Favorite folders and folders in contexts like Calendar, Contacts, Tasks, Notes are displayed with the owner name.
                    // So, include the owner name in the final value.
                    nodeValue = node.attributes[attr] + node.ui.folderOwnerNode.textContent;
                }
                return value.test(nodeValue);
            };
        } else {
            throw 'Illegal filter type, must be regex';
        }
        this.filterBy(fn, null, startNode);
	}
});Ext.namespace('Zarafa.hierarchy.ui');

/**
 * @class Zarafa.hierarchy.ui.HierarchyTreePanel
 * @extends Zarafa.hierarchy.ui.Tree
 * @xtype zarafa.hierarchytreepanel
 *
 * HierarchyTreePanel for hierarchy list in the main window.
 */
Zarafa.hierarchy.ui.HierarchyTreePanel = Ext.extend(Zarafa.hierarchy.ui.Tree, {
	/**
	 * @cfg {Boolean} enableItemDrop true to enable just drag for {@link Zarafa.core.data.MAPIRecord items}
	 * from a {@Link Ext.grid.GridPanel grid}.
	 */
	enableItemDrop: false,

	/**
	 * The dropZone used by this tree if drop is enabled (see {@link #enableItemDrop})
	 * @property
	 * @type Ext.tree.TreeDropZone
	 */
	itemDropZone: undefined,

	/**
	 * @cfg {Object} itemDropConfig Custom config to pass to the {@link Ext.tree.TreeDropZone} instance
	 */
	itemDropConfig: undefined,

	/**
	 * @cfg {Object} bbarConfig Custom config to pass to the {@link Zarafa.hierarchy.ui.HierarchyTreeBottomBar}.
	 * By default the xtype in this object is set to zarafa.hierarchytreebottombar.
	 */
	bbarConfig: undefined,

	/**
	 *@cfg {String} filterSearchBoxValue will contain the current value of {@link Ext.form.textfield filterSearchTextBox}.
	 * This will be used in {@link #setSearchFilter} to check whether the value is changed.
	 */
	 filterSearchBoxValue: undefined,

	/**
	 * @cfg {Boolean} showAllFoldersDefaultValue True to render the 'Show all folders'
	 * {@link #showAllFoldersCheckbox checkbox} as {@link Ext.form.Checkbox#checked checked}.
	 */
	showAllFoldersDefaultValue: false,

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

		var checked = Ext.isDefined(config.showAllFoldersDefaultValue) ?
			config.showAllFoldersDefaultValue: this.showAllFoldersDefaultValue;

		Ext.applyIf(config, {
			xtype: 'zarafa.hierarchytreepanel',
			baseCls: 'zarafa-hierarchy-treepanel',
			flex: 1,
			minHeight: 100,
			stateful: true,
			statefulName: 'hierarchytree',
			ref: '../hierarchytree',
			tbar: [{
				xtype: 'checkbox',
				cls: 'zarafa-hierarchy-treepanel-showallfolders',
				ref: '../showAllFoldersCheckbox',
				boxLabel: _('Show All'),
				checked: checked,
				listeners: {
					beforerender: this.reviseCheckboxDisablity,
					check: this.onCheckShowAllFoldersCheckbox,
					scope: this
				}
			},
			{
				xtype: 'textfield',
				cls: 'k-searchfolder-field',
				fieldLabel: _('Filter folders'),
				doLayout: this.onLayoutSearchField.createDelegate(this),
				enableKeyEvents: true,
				ref: '../../../filterSearchTextBox',
				listeners: {
					scope: this,
					keyup: {
						fn: this.onSearchTextFiledKeyUp,
						buffer: 250
					}
				}
			}],
			loadMask: true,
			treeSorter: true,
			treeFilter: true,
			trackMouseOver: true,
			containerScroll: true,
			// Default values for the Drag&Drop objects.
			// By default is Drag&Drop disabled...
			dragConfig: {
				ddGroup: 'dd.mapifolder'
			},
			dropConfig: {
				ddGroup: 'dd.mapifolder',
				expandDelay: 250,
				allowParentInsert: true
			},
			enableItemDrop: true,
			itemDropConfig: {
				ddGroup: 'dd.mapiitem'
			}
		});

		if(!Ext.isDefined(config.bbar)){
			config.bbarConfig = config.bbarConfig || {};
			config.bbar = Ext.applyIf(config.bbarConfig, {
				xtype: 'zarafa.hierarchytreebottombar'
			});
		}

		this.addEvents(
			/**
			 * @event beforeitemdrop
			 * Fires when a DD object is dropped on a node in this tree for preprocessing. Return false to cancel the drop. The dropEvent
			 * passed to handlers has the following properties:<br />
			 * <ul style="padding:5px;padding-left:16px;">
			 * <li>tree - The TreePanel</li>
			 * <li>target - The node being targeted for the drop</li>
			 * <li>data - The drag data from the drag source</li>
			 * <li>point - The point of the drop - append, above or below</li>
			 * <li>source - The drag source</li>
			 * <li>rawEvent - Raw mouse event</li>
			 * <li>dropNode - Drop node(s) provided by the source <b>OR</b> you can supply node(s)
			 * to be inserted by setting them on this object.</li>
			 * <li>cancel - Set this to true to cancel the drop.</li>
			 * <li>dropStatus - If the default drop action is cancelled but the drop is valid, setting this to true
			 * will prevent the animated 'repair' from appearing.</li>
			 * </ul>
			 * @param {Object} dropEvent
			 */
			'beforeitemdrop',
			/**
			 * @event itemdrop
			 * Fires after a DD object is dropped on a node in this tree. The dropEvent
			 * passed to handlers has the following properties:<br />
			 * <ul style="padding:5px;padding-left:16px;">
			 * <li>tree - The TreePanel</li>
			 * <li>target - The node being targeted for the drop</li>
			 * <li>data - The drag data from the drag source</li>
			 * <li>point - The point of the drop - append, above or below</li>
			 * <li>source - The drag source</li>
			 * <li>rawEvent - Raw mouse event</li>
			 * <li>dropNode - Dropped node(s).</li>
			 * </ul>
			 * @param {Object} dropEvent
			 */
			'itemdrop',
			/**
			 * @event folderdrop
			 * Fires after a DD object is dropped on a folder node in this tree. The dropEvent
			 * passed to handlers has the following properties:<br />
			 * <ul style="padding:5px;padding-left:16px;">
			 * <li>tree - The TreePanel</li>
			 * <li>target - The node being targeted for the drop</li>
			 * <li>data - The drag data from the drag source</li>
			 * <li>point - The point of the drop - append, above or below</li>
			 * <li>source - The drag source</li>
			 * <li>rawEvent - Raw mouse event</li>
			 * <li>dropNode - Dropped node(s).</li>
			 * </ul>
			 * @param {Object} dropEvent
			 */
			'folderdrop'
		);

		Zarafa.hierarchy.ui.HierarchyTreePanel.superclass.constructor.call(this, config);

		// We cannot wait for render time to set these listeners (as in initEvents()) because
		// then the listeners would not be set when 'Show all folders' is checked.
		this.mon(this.store, 'remove', this.onStoreRemove, this);
		this.mon(this.store, 'removeFolder', this.onFolderRemove, this);
	},

	/**
	 * Called after the tree has been {@link #render rendered} This will initialize
	 * all event handlers and when {@link #enableDD Drag & Drop} has been enabled,
	 * it will initialize the {@link #dropZone} with a special
	 * {@link Zarafa.hierarchy.ui.HierarchyFolderDropZone Folder DropZone} object.
	 * When {@link #enableItemDrop} has been enabled, it will also initialize the
	 * {@link #itemDropZone} using the {@link Zarafa.hierarchy.ui.HierarchyItemDropZone Item DropZone}.
	 * @private
	 */
	initEvents: function()
	{
		// Capture events that can change the height of the hierarchy tree, or of the containing panel,
		// so we can add a class and the css can handle the position of the bottombar
		this.on('afterrender', function(){
			this.on('expandnode', this.checkTreeHeight, this);
			this.on('collapsenode', this.checkTreeHeight, this);
			this.on('afterlayout', this.checkTreeHeight, this);
			this.on('append', this.checkTreeHeight, this);
			this.on('remove', this.checkTreeHeight, this);
			this.on('resize', this.checkTreeHeight, this);
		}, this, {single: true});

		// Add listeners to Zarafa.hierarchy.ui.Tree events
		this.on('contextmenu', this.onTreeNodeContextMenu, this);
		this.on('click', this.onFolderClicked, this);
		this.mon(container, 'folderselect', this.onFolderSelect, this);

		this.mon(container, 'contextswitch', this.reviseCheckboxDisablity, this);

		// TODO This needs to be fixed by lazy loading the stuff in the mainPanel, then we can do container.getNavigationBar()
		// But at the moment it is instantiated as getMainPanel is run and so we cannot yet get the navigationBar that way
		var navigationPanel = this.findParentByType('zarafa.navigationpanel');
		if(navigationPanel){
			this.mon(navigationPanel, 'toggleshowallfolders', this.onToggleShowAllFolders, this);
		}

		// Add listener for the 'load' event so we can select
		// the currently active folder (when a folder was activated
		// before the hierarchy is shown).
		this.mon(this.loader, 'load', this.onHierarchyLoaderLoad, this);

		if (this.stateful === true) {
			this.on('expandnode', this.saveFolderState, this, { buffer: 5 });
			this.on('collapsenode', this.saveFolderState, this, { buffer: 5 });
		}

		if (this.enableDD || this.enableDrop) {
			// Initialize a special DropZone which has better support for detecting where
			// mapifolders can be dropped inside the hierarchy.
			if (!this.dropZone) {
				this.dropZone = new Zarafa.hierarchy.ui.HierarchyFolderDropZone(this, this.dropConfig || {
					ddGroup: this.ddGroup || 'TreeDD', appendOnly: this.ddAppendOnly === true
				});
			}

			this.on('folderdrop', this.onFolderDrop, this);
		}

		if (this.enableItemDrop) {
			// Initialize a special DropZone which has support for dragging MAPIRecord objects
			// from a grid into the hierarchy.
			if (!this.itemDropZone) {
				this.itemDropZone = new Zarafa.hierarchy.ui.HierarchyItemDropZone(this, this.itemDropConfig || {
					ddGroup: this.ddGroup || 'TreeDD'
				});
			}

			this.on('itemdrop', this.onItemDrop, this);
		}

		Zarafa.hierarchy.ui.HierarchyTreePanel.superclass.initEvents.call(this);
	},

	/**
	 * Event handler which is triggered when
	 * a key is pressed in the filterSearchTextBox.
	 *
	 * @param {Ext.form.TextField} field
	 * @param {Ext.EventObject} eventObj
	 * @private
	 */
	onSearchTextFiledKeyUp: function (field, eventObj)
	{
		var value = field.getRawValue();
		this.setSearchFilter(value);
	},

	/**
	 * Function will apply or clear the search filter based on {@link Ext.form.textfield filterSearchTextBox value}.
	 * @param {String} value which needs to be search in the tree.
	 */
	setSearchFilter: function(value)
	{
		// Check if search text value is changed.
		if  (value !== this.filterSearchBoxValue) {
			this.filterSearchBoxValue = value;
			if (Ext.isEmpty(value) && !Ext.isEmpty(this.treeFilter)) {
				this.treeFilter.clear();
				this.checkTreeHeight();
				return;
			}
			var regEx = new RegExp('' + value + '', 'i');
			this.treeFilter.filter(regEx);
			this.checkTreeHeight();
		}
	},

	/**
	 * Function is used to resize the {@link Ext.form.textfield filterSearchTextBox}
	 * and set the search filter.
	 */
	onLayoutSearchField: function()
	{
		var filterSearchTextBox = this.getTopToolbar().findByType('textfield')[0];

		if (filterSearchTextBox.isVisible()) {
			var containerWidth = this.el.getStyleSize().width;
			var showFolderCheckFieldWidth = this.showAllFoldersCheckbox.getWidth();
			// Fill remaining space: container - checkbox - left padding
			var adjWidth = containerWidth - showFolderCheckFieldWidth - 8;

			filterSearchTextBox.setWidth(adjWidth);

			this.setSearchFilter(filterSearchTextBox.getValue());
		}
	},

	/**
	 * Register the {@link #stateEvents state events} to the {@link #saveState} callback function.
	 * @protected
	 */
	initStateEvents: function(){
		Zarafa.hierarchy.ui.HierarchyTreePanel.superclass.initStateEvents.call(this);
		this.mon(this.showAllFoldersCheckbox, 'check', this.saveState, this, {delay: 100});
	},

	/**
	 * Checks the height of the hierarchy tree. When it is smaller then the height of the containing box
	 * it will add a class so the the css can handle the position of the bottombar.
	 */
	checkTreeHeight: function(){
		if(!this.ownerCt) {
			return;
		}

		var treeHeight = this.body.down('ul').getHeight();
		var panelHeight = this.ownerCt.getHeight();
		var topBarHeight = this.getTopToolbar().getHeight();
		var bottomBarHeight = this.getBottomToolbar().getHeight();
		if ( panelHeight < treeHeight + bottomBarHeight + topBarHeight ){
			this.ownerCt.getEl().addClass('fixed-bottombar');
		} else {
			this.ownerCt.getEl().removeClass('fixed-bottombar');
		}
	},

	/**
	 * Fires on {@link Zarafa.core.Container#contextswitch}, or {@link Ext.form.Checkbox#beforerender}.
	 * Make (@link #showAllFoldersCheckbox) disable if current context is Settings or Zarafa, enable otherwise.
	 * @param {Object | Ext.form.Checkbox} parameters contains folder details or checkbox instance
	 * @param {Context} oldContext (optional) previously selected context
	 * @param {Context} newContext (optional) selected context
	 *
	 * @private
	 */
	reviseCheckboxDisablity: function (parameters, oldContext, newContext)
	{
		var context = newContext || container.getCurrentContext();
		var settingsOrToday = (context == container.getContextByName('settings') || context == container.getContextByName('today'));
		this.showAllFoldersCheckbox.setDisabled(settingsOrToday);

		var model = context.getModel();
		if (Ext.isDefined(model)) {
			this.mon(model, 'beforefolderchange', this.onBeforeFolderChange, this);
		}
	},

	/**
	 * Event handler which is triggered after drop is completed on {@link Zarafa.hierarchy.ui.Tree Tree}.
	 * @param {Object} dropEvent The object describing the drop information
	 * @private
	 */
	onFolderDrop: function(dropEvent)
	{
		var dropNode = dropEvent.dropNode;
		if (Ext.isDefined(dropNode)) {
			var targetNode = dropEvent.target;

			switch (dropEvent.point) {
				case 'above':
				case 'below':
					targetNode = dropEvent.target.parentNode;
					break;
				case 'append':
				/* falls through */
				default:
					break;
			}

			var sourceFolder = dropNode.getFolder();
			var targetFolder = targetNode.getFolder();

			var hasAccess = targetFolder.get('access') & Zarafa.core.mapi.Access.ACCESS_CREATE_HIERARCHY;
			var hasCtrlKeyPressed = dropEvent.rawEvent.ctrlKey;

			// Guard: dropping a folder onto the tier it already lives in must be a no-op.
			// Compare by MAPI entryid (not tree-node identity) so this also holds for
			// shared/favorites stores where nodes may be distinct objects. Only guard the
			// move path; a Ctrl-copy into the same parent is a legitimate duplicate.
			if (!hasCtrlKeyPressed &&
				Zarafa.core.EntryId.compareEntryIds(sourceFolder.get('parent_entryid'), targetFolder.get('entryid')) &&
				Zarafa.core.EntryId.compareEntryIds(sourceFolder.get('store_entryid'), targetFolder.get('store_entryid'))) {
				return false;
			}

			var msg;
			if (!hasAccess) {
				msg = hasCtrlKeyPressed ? _("You have insufficient privileges to copy this folder. Ask the folder owner to grant you permissions or contact your system administrator.") :
					_("You have insufficient privileges to move this folder. Ask the folder owner to grant you permissions or contact your system administrator.");
				container.getNotifier().notify('error', _("Insufficient privileges"), msg);
				return false;
			}

			if (hasCtrlKeyPressed) {
				sourceFolder.copyTo(targetFolder);
			} else {
				// Can not perform move operation, so suggest user a copy operation.
				if (!sourceFolder.hasDeleteOwnRights()) {
					msg = _("You have insufficient privileges to move this folder. Would you like to copy instead?");
					Zarafa.common.Actions.showMessageBox(sourceFolder, targetFolder, sourceFolder.getMAPIFolderStore(), msg, this);
					return false;
				}
				sourceFolder.moveTo(targetFolder);
			}

			sourceFolder.save();
		}
	},

	/**
	 * Event handler which is triggered after drop of item is completed on {@link Zarafa.hierarchy.ui.Tree Tree}.
	 * @param {Object} dropEvent The object describing the drop information
	 * @private
	 */
	onItemDrop: function(dropEvent)
	{
		if (!Ext.isEmpty(dropEvent.dropItem)) {
			var targetNode = dropEvent.target;

			var sourceNodes = Array.isArray(dropEvent.dropItem) ? dropEvent.dropItem : [ dropEvent.dropItem ];
			var targetFolder = targetNode.getFolder();
			var store = sourceNodes[0].getStore();

			var cloneSourceNodes = sourceNodes.clone();
			cloneSourceNodes = Zarafa.common.Actions.resolveRecords(cloneSourceNodes, store);

			if (Ext.isEmpty(cloneSourceNodes)) {
				return false;
			}

			var isCtrlKeyPress = dropEvent.rawEvent.ctrlKey;
			var noAccessRecord = [];

			// Check folder has create item rights.
			if (!targetFolder.hasCreateRights()) {
				var message = _("You have insufficient privileges to move and copy this item. Ask the folder owner to grant you permissions or contact your system administrator.");
				if (isCtrlKeyPress) {
					message = _("You have insufficient privileges to copy this item. Ask the folder owner to grant you permissions or contact your system administrator.");
				}
				container.getNotifier().notify('error', _("Insufficient privileges"), message);
				return false;
			}

			var sourceNode = this.getNodeById(cloneSourceNodes[0].get('parent_entryid'));
			var sourceFolder = sourceNode.getFolder();
			var requireDeleteCheck = cloneSourceNodes[0].get('object_type') !== Zarafa.core.mapi.ObjectType.MAPI_MESSAGE && !sourceFolder.hasDeleteOwnRights();

			// If targetFolder has create item rights and source folder does not have delete item rights,
			// in that case move operation is not possible, therefore show message box which indicate that
			// move operation is not possible and ask user to copy the item.
			if (requireDeleteCheck && targetFolder.hasCreateRights() && !sourceFolder.hasDeleteOwnRights() && !isCtrlKeyPress) {
				Zarafa.common.Actions.showMessageBox(cloneSourceNodes, targetFolder, store, undefined, this);
				return false;
			}

			cloneSourceNodes.forEach(function (sourceNode, index) {
				if (isCtrlKeyPress) {
					sourceNode.copyTo(targetFolder);
				} else {
					// Check record access. If record has no delete access (record not belongs to user)
					// user can't move this item.
					if (requireDeleteCheck && !sourceNode.hasDeleteAccess()) {
						noAccessRecord.push({
							record: sourceNode,
							index:index
						});
					} else {
						sourceNode.moveTo(targetFolder);
					}
				}
			}, this);

			// Show detailed warning message when record have no access to delete
			// ask user to copy that records.
			if (requireDeleteCheck && !Ext.isEmpty(noAccessRecord)) {
				var msg = undefined;
				if (noAccessRecord.length > 1) {
					msg = _("You have insufficient privileges to move following items.");
					msg += "<br/><br/>";
					noAccessRecord.forEach(function (item) {
						cloneSourceNodes.splice(item.index, 1);
						var subject = item.record.get('subject');
						subject = !Ext.isEmpty(subject) ? subject : _("None");
						msg += "<b>" +_("Subject:") + "</b> " + subject ;
						msg += "<br/>";
					}, this);
					msg += "<br/>" + _("Would you like to copy instead?");
				}
				var records = Ext.pluck(noAccessRecord, "record");
				Zarafa.common.Actions.showMessageBox(records, targetFolder, store, msg, this);
			}

			// Don't call store.save if cloneSourceNodes is empty array.
		if(!Ext.isEmpty(cloneSourceNodes)) {
			if (!isCtrlKeyPress) {
				Zarafa.common.Actions.ensureStoreReloadOnEmpty(store);
			}
			store.save(cloneSourceNodes);
		}
		}
	},

	/**
	 * Event handler which is fired when the {@link #store} fires the
	 * {@link Zarafa.hierarchy.data.HierarchyStore#remove} event handler. This will check
	 * if any of the folders inside the store is currently opened, and
	 * will deselect those folders.
	 *
	 * @param {Zarafa.hierarchy.data.HierarchyStore} store The store which fired the event
	 * @param {Zarafa.hierarchy.data.MAPIStoreRecord} storeRecord The store which was deleted
	 * @private
	 */
	onStoreRemove: function(store, storeRecord)
	{
		var subFolders = storeRecord.getSubStore('folders');

		if (this.model) {
			subFolders.each(function(folder) {
				this.model.removeFolder(folder);
			}, this);
		}
	},

	/**
	 * Event handler which is fired when the {@link #store} fires the
	 * {@link Zarafa.hierarchy.data.HierarchyStore#removeFolder} event handlerr. This will check
	 * if the folder is currently opened, and will deselect that folder.
	 *
	 * @param {Zarafa.hierarchy.data.HierarchyStore} store The store which fired the event
	 * @param {Zarafa.hierarchy.data.MAPIStoreRecord} storeRecord The store from where the folder is
	 * removed
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder The folder which was removed from the store
	 * @private
	 */
	onFolderRemove: function(store, storeRecord, folder)
	{
		if (this.model) {
			this.model.removeFolder(folder);
		}
	},

	/**
	 * Fires when the {@Link Zarafa.core.ContextModel} fires the
	 * {@Link Zarafa.core.ContextModel#beforefolderchange} event.
	 * This will add the folder to be selected in the array
	 * of selected folders. If 'Show all folders' is checked and the folder
	 * to be selected does not belong to the current context,
	 * then the context is switched to the context of the folder to be selected, else a sibling folder of the same
	 * context will be selected. If the webapp is reloaded, the default folder of the current context is selected.
	 * @param {Array} folders Selected folders as an array of {@link Zarafa.hierarchy.data.MAPIFolderRecord Folder} objects.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder The folder which was removed from the store
	 */
	onBeforeFolderChange: function (folders, folder)
	{
		var folderNode, folderToSelect;

		var isSharedFolder = folder.getMAPIStore().isSharedStore();
		var showAllCheckBox = this.showAllFoldersCheckbox;
		var isAllFolderHierarchy = showAllCheckBox.checked && this.showAllFoldersDefaultValue;
		var isContextHierarchy = !this.showAllFoldersDefaultValue && !showAllCheckBox.checked;

		// Return if a folder belongs to a shared store, or the folder is a Calendar item
		// and the 'Show all folders' checkbox is unchecked, or the container class of folder is
		// not of the current context hierarchy.
		if (isSharedFolder || (!isAllFolderHierarchy && !isContextHierarchy)
			|| (isContextHierarchy && (folder.isCalendarFolder() || folder.get('container_class') !== this.IPMFilter))) {
			return;
		}
		folderNode = this.getNodeById(folder.get('entryid'));

		// No need to change the current selection if folder is not selected.
		if (!folderNode.isSelected()) {
			return;
		}

		var previousSiblingFolder = folderNode.previousSibling;
		var nextSiblingFolder = folderNode.nextSibling;

		if (previousSiblingFolder) {
			folderToSelect = previousSiblingFolder.getFolder();
		} else if (nextSiblingFolder) {
			folderToSelect = nextSiblingFolder.getFolder();
		} else {
			folderToSelect = folder.getParentFolder();
		}

		var context = container.getContextByFolder(folderToSelect);

		// Check if the context is different, if the context of the folder
		// to be selected is different, then switch context.
		if (container.getCurrentContext().getName() !== context.getName()) {
			container.switchContext(context, folderToSelect);
			return false;
		}

		folders.push(folderToSelect);
	},

	/**
	 * Fires when the {@link Zarafa.core.Container} fires the
	 * {@link Zarafa.core.Container#folderselect} event. This
	 * will search for the corresponding node in the tree,
	 * and will mark the given folder as {@link #selectFolderInTree selected}.
	 *
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord|Array} folder The folder which
	 * is currently selected.
	 * @private
	 */
	onFolderSelect: function(folder)
	{
		if (Array.isArray(folder)) {

			// If we have multi selected folder then select previously selected node in tree.
			if (folder.length > 1 && this.model) {
				folder = this.model.getDefaultFolder();
			} else {
				folder = folder[0];
			}
		}

		// Select the node of selected folder.
		// Guard against non-MAPI folder objects (e.g. from the files plugin)
		if (folder && Ext.isFunction(folder.getMAPIStore)) {
			this.selectFolderInTree(folder);
		}
	},

	/**
	 * Fires when the {@link #loader} fires the {@link Zarafa.hierarchy.data.HierarchyTreeLoader#load}
	 * event to indicate that all nodes have been rendered into the tree.
	 * This will {@link #selectFolderInTree select} {@link Zarafa.core.ContextModel#getFolders all folders}
	 * @param {Object} loader TreeLoader object.
	 * @param {Object} node The {@link Ext.tree.TreeNode} object being loaded.
	 * @param {Object} response The response object containing the data from the server.
	 * @private
	 */
	onHierarchyLoaderLoad: function(loader, node, response)
	{
		// Use respective model considering the case the current model doesn't belongs to current context
		var currentContextModel = container.getCurrentContext().getModel();
		if (currentContextModel) {
			var folders = currentContextModel.getFolders();
			for (var i = 0, len = folders.length; i < len; i++) {
				this.selectFolderInTree(folders[i], folders[i].id === node.id);
			}

			// If we have multi selected folder then select previously selected node in tree.
			if (folders.length > 1 && currentContextModel) {
				this.selectFolderInTree(currentContextModel.getDefaultFolder());
			}
		}
	},

	/**
	 * Fired on contextmenu event on {@link Zarafa.hierarchy.ui.FolderNode}
	 * @param {Zarafa.hierarchy.ui.FolderNode} treeNode The node on which the contextmenu
	 * was requested.
	 * @param {Ext.EventObject} eventObj The event object with event information
	 * @private
	 */
	onTreeNodeContextMenu: function(treeNode, eventObj)
	{
		// If folder is favorites root folder then disable the right click
		// as it doesn't support any context menu items.
		if(treeNode.getFolder().isFavoritesRootFolder()) {
			return;
		}

		var positionEventObj = eventObj.getXY();

		// Handle a specific situation for Edge where somehow eventObj replaced with 'blur' event which doesn't have the position.
		// Check if the position is available or not, get the position of treeNode
		// and use that position to render context menu if not available.
		if (positionEventObj[0] === 0 && positionEventObj[1] === 0) {
			var treeNodeAnchor = treeNode.ui.anchor;
			var nodePosition = treeNodeAnchor.getBoundingClientRect();
			positionEventObj = [nodePosition.left, nodePosition.top];
		}
		var folder = treeNode.getFolder();
		if(folder.isFavoritesFolder() && !folder.isSearchFolder()) {
			folder = folder.getOriginalRecordFromFavoritesRecord();
		}
		Zarafa.core.data.UIFactory.openDefaultContextMenu(folder, { position: positionEventObj, contextNode: treeNode });
	},

	/**
	 * Fired when a node is clicked in {@link Zarafa.hierarchy.ui.Tree}.
	 * It calls container to change folder.
	 * @param {Ext.tree.TreeNode} node which is clicked
	 */
	onFolderClicked: function(treeNode)
	{
		var folder = treeNode.getFolder();
		Zarafa.hierarchy.Actions.openFolder(folder);
	},

	/**
	 * @return {Zarafa.hierarchy.ui.TreeEditor} The tree editor which can be used
	 * @private
	 */
	getTreeEditor: function()
	{
		if (!this.treeEditor) {
			this.treeEditor = new Zarafa.hierarchy.ui.TreeEditor(this);
		}
		return this.treeEditor;
	},

	/**
	 * Triggers node editing
	 * @param {Zarafa.hierarchy.ui.FolderNode} treeNode node to be edited
	 */
	startEditingNode: function(treeNode)
	{
		this.getTreeEditor().startEditingNode(treeNode);
	},

	/**
	 * When {@link #stateful} is enabled, this will test if the given
	 * folder has the 'is_open' state enabled.
	 * If the folder is not found in the settings, the rule is that
	 * {@link Zarafa.hierarchy.data.MAPIFolderRecord#isIPMSubTree subtrees}
	 * will be expanded by default, all other folders are collapsed.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder The folder to check
	 * @return {Boolean} True if the folder should be expanded by default
	 * @private
	 */
	isFolderOpened: function(folder)
	{
		var opened;
		if (this.stateful === true) {
			var state = container.getHierarchyStore().getState(folder, 'tree');
			if (state) {
				opened = state.is_open;
			}
		}
		if (!Ext.isDefined(opened)) {
			opened = Zarafa.hierarchy.ui.HierarchyTreePanel.superclass.isFolderOpened.call(this, folder);
		}
		return opened;
	},

	/**
	 * Event handler for the {@link #expandnode} and {@link #collapsenode} events. When
	 * {@link #stateful} is enabled, then this function will save the current
	 * state of the given node.
	 *
	 * @param {Zarafa.hierarchy.ui.FolderNode} node The node which will be saved into the settings
	 * @private
	 */
	saveFolderState: function(node)
	{
		if (this.stateful === true && !node.isRoot) {
			var folder = node.getFolder();
			var state = container.getHierarchyStore().getState(folder, 'tree');

			if (state.is_open !== node.expanded) {
				container.getHierarchyStore().applyState(folder, 'tree', { is_open: node.expanded });
			}
		}
	},

	/**
	 * Called when the {@link #showAllFoldersCheckbox} checkbox in the top toolbar is checked or
	 * unchecked. It will set the toggle showAllFolders option in the
	 * {@link Zarafa.core.ui.NavigationPanel}.
	 * @param {Ext.Form.Checkbox} button The pressed button
	 * @param {Boolean} checkState True when checkbox is checked, false when not
	 */
	onCheckShowAllFoldersCheckbox: function(checkbox, checkState)
	{
		container.getNavigationBar().setShowFolderList(checkState);
	},

	/**
	 * Called when the {@link Zarafa.core.ui.NavigationPanel} fires the
	 * {@link Zarafa.core.ui.NavigationPanel#toggleshowallfolders toggleshowallfolders} event. Then
	 * we change the {@link #showAllFoldersCheckbox} button in the top toolbar accordingly.
	 * @param {Boolean} show Value of the showAllFolders state
	 */
	onToggleShowAllFolders: function(show)
	{
		// Suspend events to stop the check-event that will fire from triggering all kinds of
		// other updates externally.
		this.suspendEvents(false);

		this.showAllFoldersCheckbox.setValue(show);

		this.resumeEvents();
	},

	/**
	 * Called before the panel is being destroyed.
	 */
	beforeDestroy: function()
	{
		if (this.rendered) {
			Ext.destroy(this.itemDropZone);
		}

		Zarafa.hierarchy.ui.HierarchyTreePanel.superclass.beforeDestroy.apply(this, arguments);
	},

	/**
	 * When {@link #stateful} the State object which should be saved into the
	 * {@link Ext.state.Manager}.
	 * @return {Object} The state object
	 * @protected
	 */
	getState: function()
	{
		var state = Zarafa.hierarchy.ui.HierarchyTreePanel.superclass.getState.call(this) || {};
		var checkboxValue = this.showAllFoldersCheckbox.getValue();
		return Ext.apply(state, {
			showallcheckbox: checkboxValue
		});
	},

	/**
	 * Obtain the path in which the {@link #getState state} must be saved.
	 * This option is only used when the {@link Zarafa.core.data.SettingsStateProvider SettingsStateProvider} is
	 * used in the {@link Ext.state.Manager}. This returns {@link #statefulName} if provided, or else generates
	 * a custom name.
	 * @return {String} The unique name for this component by which the {@link #getState state} must be saved.
	 */
	getStateName: function()
	{
		return 'sidebars/' + Zarafa.core.ui.MainViewSidebar.superclass.getStateName.call(this);
	}
});
Ext.reg('zarafa.hierarchytreepanel', Zarafa.hierarchy.ui.HierarchyTreePanel);
Ext.namespace('Zarafa.hierarchy.ui');

/**
 * @class Zarafa.hierarchy.ui.RootFolderNode
 * @extends Zarafa.hierarchy.ui.FolderNode
 *
 * This will register itself as 'rootfolder' nodetype in the {@link Ext.tree.TreePanel#nodeTypes} object.
 */
Zarafa.hierarchy.ui.RootFolderNode = Ext.extend(Zarafa.hierarchy.ui.FolderNode, {
	/*
	 * @constructor
	 * @param {Object} config configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

		var containerCls = 'zarafa-tree-root-container';
		var nodeCls = 'zarafa-tree-root-node';

		// Format IPM subtree container differently
		if (config.folder) {
			if (config.folder.isIPMSubTree()) {
				containerCls += ' zarafa-tree-ipm-subtree-container';
			} else if (config.folder.isFavoritesRootFolder()) {
				containerCls += ' zarafa-tree-ipm-subtree-favorites-container';
			}
			nodeCls += ' zarafa-tree-ipm-subtree-node';
		}

		Ext.applyIf(config, {
			containerCls: containerCls,
			cls: nodeCls,
			allowDrag: false,
			draggable: false
		});

		Zarafa.hierarchy.ui.RootFolderNode.superclass.constructor.call(this, config);

		this.on('beforeclick', this.onBeforeClick, this);
	},

	/**
	 * Event handler which is fired when the user has clicked the foldernode, this event will
	 * be called just before the event handlers will run to allow the default action to be cancelled.
	 *
	 * When the folder is the {@link Zarafa.hierarchy.data.MAPIFolderRecord#isIPMSubtree subtree} of
	 * a non-{@link Zarafa.hierarchy.data.MAPIStoreRecord#isDefaultStore default} store or
	 * {@link Zarafa.hierarchy.data.MAPIFolderRecord#isFavoritesRootFolder FavoritesRootFolder} then click
	 * action will be cancelled. The reason is that by default there are no actions possible on
	 * a IPM_SUBTREE or Favorites(IPM_COMMON_VIEWS) folder.
	 *
	 * Another case for which we will allow the click action is if the folder has the 'is_unavailable'
	 * property set. This indicates that the folder is fake, and by allowing the user to interact
	 * with it, we can show an error to the user.
	 *
	 * @param {Zarafa.hierarchy.ui.RootFolderNode} node The node which is being clicked
	 * @private
	 */
	onBeforeClick: function(node)
	{
		var folder = node.getFolder();
		if (folder.isFavoritesRootFolder()) {
			return false;
		} else if (folder && folder.isIPMSubTree()) {
			return folder.getMAPIStore().isDefaultStore() || folder.get('is_unavailable') === true;
		}
	}
});

Ext.tree.TreePanel.nodeTypes.rootfolder = Zarafa.hierarchy.ui.RootFolderNode;
/*
 * #dependsFile client/zarafa/core/KeyMapMgr.js
 */
Ext.namespace('Zarafa.mail');

/**
 * @class Zarafa.mail.KeyMapping
 * @extends Object
 *
 * The map of keys used in the Mail Context.
 * @singleton
 */
Zarafa.mail.KeyMapping = Ext.extend(Object, {
	/**
	 * @constructor
	 */
	constructor: function()
	{
		var newMailKeys = [{
			key: Ext.EventObject.X,
			ctrl: true,
			alt: true,
			shift: false,
			stopEvent: true,
			handler: this.onNewMail,
			scope: this,
			settingsCfg: {
				description: _('New mail'),
				category: _('Creating an item')
			}
		}];

		var itemActionKeys = [{
			key: Ext.EventObject.R,
			ctrl: true,
			alt: false,
			shift: false,
			// Overwrites the browser behavior to reload the browser
			stopEvent: true,
			handler: this.onReplyMail,
			scope: this,
			settingsCfg: {
				description: _('Reply'),
				category: _('Mail')
			}
		},{
			key: Ext.EventObject.R,
			ctrl: true,
			alt: true,
			shift: false,
			// Overwrites the browser behavior to reload the browser
			stopEvent: true,
			handler: this.onReplyAllMail,
			scope: this,
			settingsCfg: {
				description: _('Reply all'),
				category: _('Mail')
			}
		},{
			key: Ext.EventObject.F,
			ctrl: true,
			alt: false,
			shift: false,
			stopEvent: true,
			handler: this.onForwardMail,
			scope: this,
			settingsCfg: {
				description: _('Forward'),
				category: _('Mail')
			}
		},{
			key: Ext.EventObject.E,
			ctrl: true,
			alt: false,
			shift: false,
			stopEvent: true,
			handler: this.onEditAsNewMail,
			scope: this,
			settingsCfg: {
				description: _('Edit as New'),
				category: _('Mail')
			}
		}];

		var mailGridKeys = [{
			key: Ext.EventObject.U,
			ctrl: true,
			alt: true,
			shift: false,
			stopEvent: true,
			handler: this.onReadUnreadToggle,
			scope: this,
			settingsCfg: {
				description: _('Toggle read/unread'),
				category: _('Mail')
			}
		},{
			key: Ext.EventObject.G,
			ctrl: true,
			alt: true,
			shift: false,
			stopEvent: true,
			handler: this.onFlagToggle,
			scope: this,
			settingsCfg: {
				description: _('Toggle red/complete flag'),
				category: _('Mail')
			}
		},{
			key: Ext.EventObject.CONTEXT_MENU,
			ctrl: false,
			alt: false,
			shift: false,
			stopEvent: true,
			handler: this.onContextMenuKey,
			scope: this,
			basic: true
		}];

		Zarafa.core.KeyMapMgr.register('global', newMailKeys);

		// These keys might need to be in mail-global handler
		Zarafa.core.KeyMapMgr.register('grid.mapimessage.mail', mailGridKeys);

		// Reply/Reply All/Forward/Edit as New Key handlers
		Zarafa.core.KeyMapMgr.register('grid.mapimessage.mail', itemActionKeys);
		Zarafa.core.KeyMapMgr.register('previewpanel.mail', itemActionKeys);
		Zarafa.core.KeyMapMgr.register('contentpanel.record.message.showmail', itemActionKeys);
	},

	/**
	 * Event handler for the keydown event of the {@link Zarafa.core.KeyMap KeyMap} when the user wants to
	 * create a new item.
	 * @param {Number} key Key code
	 * @param {Ext.EventObject} event The event
	 * @param {Ext.Component} component The component on which key event is fired.
	 */
	onNewMail: function(key, event, component)
	{
		Zarafa.mail.Actions.openCreateMailContent(container.getContextByName('mail').getModel());
	},

	onContextMenuKey: function(keyCode, key, component)
	{
		var records = component.getSelectionModel().getSelections();
		Zarafa.core.data.UIFactory.openDefaultContextMenu(records, {
			position: component.getEl().getXY(),
			context: this.context,
			actsOnTodoListFolder: component.model.getDefaultFolder().isTodoListFolder()
		});
	},

	/**
	 * Helper function to get selected record from component.
	 * @param {Ext.Component} component The component on which key event is fired.
	 * @return {Ext.data.Record} The record which is selected int the view.
	 * @private
	 */
	getSelectedRecord: function (component)
	{
		if(Ext.isDefined(component.record)) {
			return component.record;
		} else if(Ext.isFunction(component.getSelectionModel)) {
			return component.getSelectionModel().getSelected();
		}

		return false;
	},

	/**
	 * Helper function to get model of component.
	 * @param {Ext.Component} component The component on which key event is fired.
	 * @return {Zarafa.core.ContextModel} The model of component..
	 * @private
	 */
	getModel: function (component)
	{
		if(Ext.isFunction(component.getContextModel)) {
			return component.getContextModel();
		} else if(Ext.isDefined(component.model)) {
			return component.model;
		}

		return false;
	},

	/**
	 * Event handler for the keydown event of the {@link Zarafa.core.KeyMap KeyMap} when the user wants to
	 * reply to an item.
	 * @param {Number} key Key code
	 * @param {Ext.EventObject} event The event
	 * @param {Ext.Component} component The component on which key event is fired.
	 */
	onReplyMail: function(key, event, component)
	{
		var record = this.getSelectedRecord(component);
		var model = this.getModel(component);
		if(record){
			Zarafa.mail.Actions.openCreateMailResponseContent(record, model, Zarafa.mail.data.ActionTypes.REPLY);
		}
	},

	/**
	 * Event handler for the keydown event of the {@link Zarafa.core.KeyMap KeyMap} when the user wants to
	 * reply to all recipients of an item
	 * @param {Number} key Key code
	 * @param {Ext.EventObject} event The event
	 * @param {Ext.Component} component The component on which key event is fired.
	 */
	onReplyAllMail: function(key, event, component)
	{
		var record = this.getSelectedRecord(component);
		var model = this.getModel(component);
		if(record){
			Zarafa.mail.Actions.openCreateMailResponseContent(record, model, Zarafa.mail.data.ActionTypes.REPLYALL);
		}
	},

	/**
	 * Event handler for the keydown event of the {@link Zarafa.core.KeyMap KeyMap} when the user wants to
	 * forward an item.
	 * @param {Number} key Key code
	 * @param {Ext.EventObject} event The event
	 * @param {Ext.Component} component The component on which key event is fired.
	 */
	onForwardMail: function(key, event, component)
	{
		var record = this.getSelectedRecord(component);
		var model = this.getModel(component);
		if(record){
			Zarafa.mail.Actions.openCreateMailResponseContent(record, model, Zarafa.mail.data.ActionTypes.FORWARD);
		}
	},

	/**
	 * Event handler for the keydown event of the {@link Zarafa.core.KeyMap KeyMap} when the user wants to
	 * edit a sent item as a new item.
	 * @param {Number} key Key code
	 * @param {Ext.EventObject} event The event
	 * @param {Ext.Component} component The component on which key event is fired.
	 */
	onEditAsNewMail: function(key, event, component)
	{
		var record = this.getSelectedRecord(component);
		var model = this.getModel(component);
		if(record){
			Zarafa.mail.Actions.openCreateMailResponseContent(record, model, Zarafa.mail.data.ActionTypes.EDIT_AS_NEW);
		}
	},

	/**
	 * Event handler for the keydown event of the {@link Zarafa.core.KeyMap KeyMap} when the user wants to
	 * mark an item as unread/read.
	 * @param {Number} key Key code
	 * @param {Ext.EventObject} event The event
	 * @param {Ext.Component} component The component on which key event is fired.
	 */
	onReadUnreadToggle: function(key, event, component)
	{
		var records = component.model.getSelectedRecords();
		if(!Ext.isEmpty(records)) {
			if (records[0].isRead()){
				Zarafa.common.Actions.markAsRead(records, false);
			} else {
				Zarafa.common.Actions.markAsRead(records, true);
			}
		}
	},

	/**
	 * Event handler for the keydown event of the {@link Zarafa.core.KeyMap KeyMap} when the user wants to
	 * flag an item as complete.
	 * @param {Number} key Key code
	 * @param {Ext.EventObject} event The event
	 * @param {Ext.Component} component The component on which key event is fired.
	 */
	onFlagToggle: function(key, event, component)
	{
		var records = component.model.getSelectedRecords();
		if(!Ext.isEmpty(records)) {
			var currentFlagStatus = records[0].get('flag_status');
			// Event is fired on grid for selected records so assuming that all records
			// are from same store, as they are in same grid.
			var store = records[0].getStore();

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

				// If a record isn't an email, ignore setting the flag
				if(record.isFaultyMessage() || !record.isMessageClass(['IPM.Note', 'IPM.Schedule.Meeting', 'REPORT.IPM', 'REPORT.IPM.Note'], true)) {
					// can not set flag for faulty record
					continue;
				}

				if (currentFlagStatus == Zarafa.core.mapi.FlagStatus.cleared) {
					record.set('flag_status', Zarafa.core.mapi.FlagStatus.flagged);
					record.set('flag_icon', Zarafa.core.mapi.FlagIcon.red);
				} else if (currentFlagStatus == Zarafa.core.mapi.FlagStatus.completed) {
					record.set('flag_status', Zarafa.core.mapi.FlagStatus.flagged);
					record.set('flag_icon', Zarafa.core.mapi.FlagIcon.red);
				} else {
					record.set('flag_status', Zarafa.core.mapi.FlagStatus.completed);
					record.set('flag_icon', Zarafa.core.mapi.FlagIcon.clear);
				}
			}

			// Save all modified records.
			if(store) {
				store.save(records);
			}
		}
	}

});

Zarafa.mail.KeyMapping = new Zarafa.mail.KeyMapping();
/*
 * #dependsFile client/zarafa/core/URLActionMgr.js
 */
Ext.namespace('Zarafa.mail');

/**
 * @class Zarafa.mail.URLAction
 * @extends Object
 *
 * The URL Actions for Mail Context. Currently only mailto action is handled.
 * @singleton
 */
Zarafa.mail.URLAction = Ext.extend(Object, {
	/**
	 * @constructor
	 */
	constructor: function()
	{
		var handler = {
			handler: this.handleMailTo,
			scope: this
		};

		Zarafa.core.URLActionMgr.register('mailto', handler);
	},

	/**
	 * Function reads data passed as href from {@link Zarafa.core.URLActionMgr URLActionMgr} and
	 * using that data it creates new mail record, and then opens that record into contentpanel.
	 * @param {String} action url action that is executed
	 * @param {href} href The href data using it we can handle the mailto action
	 * @param {Array|Function} handler handler config that was passed when registering this action
	 */
	handleMailTo: function(action, href, handler)
	{
		href = href.replace(/^mailto:/ig, 'to=');
		href = href.replace(/\?/i, '&');
		var urlData = Ext.urlDecode(href);
		urlData = Zarafa.core.Util.objectKeysToLowerCase(urlData);

		// get instance of mail context model by using bidding process
		var folder = container.getHierarchyStore().getDefaultFolder('drafts');
		var context = container.getContextByFolder(folder);
		var model = context.getModel();

		// create new mail record
		var record = model.createRecord(folder);

		// Set data in the mail record
		if(!Ext.isEmpty(urlData.subject)) {
			record.set('subject', urlData.subject);
		}

		if(!Ext.isEmpty(urlData.body)) {
			record.set('body', urlData.body);
			record.set('isHTML', false);
		}

		// Set recipients in the mail record
		var recipientRecords = [];

		var recipientTypes = {
			to: Zarafa.core.mapi.RecipientType.MAPI_TO,
			cc: Zarafa.core.mapi.RecipientType.MAPI_CC,
			bcc: Zarafa.core.mapi.RecipientType.MAPI_BCC
		};

		for (var key in recipientTypes) {
			if(!Ext.isEmpty(urlData[key])){
				var smtpAddresses = urlData[key].split(/[;,]/);
				for (var i=0; i<smtpAddresses.length; i++)
				{
					recipientRecords.push(Zarafa.core.data.RecordFactory.createRecordObjectByCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_RECIPIENT, {
						display_name: smtpAddresses[i],
						smtp_address: smtpAddresses[i],
						address_type: 'SMTP',
						recipient_type: recipientTypes[key]
					}));
				}
			}
		}

		var recipientStore = record.getSubStore('recipients');
		recipientStore.add(recipientRecords);

		// Open record in contentpanel
		Zarafa.common.Actions.openMessageContent(record);
	}
});

Zarafa.mail.URLAction = new Zarafa.mail.URLAction();
/*
 * #dependsFile client/zarafa/common/ui/grid/Renderers.js
 */
Ext.namespace('Zarafa.mail.attachitem');

/**
 * @class Zarafa.mail.attachitem.AttachMailColumnModel
 * @extends Zarafa.common.ui.grid.ColumnModel
 *
 * The {@link Zarafa.mail.attachitem.AttachMailColumnModel AttachMailColumnModel} is the column model containing
 * sets of {@link Ext.grid.Column columns} for mail folders.
 * This column model will be used with {@link Zarafa.common.attachment.dialog.AttachItemGrid AttachItemGrid}.
 */
Zarafa.mail.attachitem.AttachMailColumnModel = Ext.extend(Zarafa.common.ui.grid.ColumnModel, {
	/**
	 * @constructor
	 * @param config Configuration structure
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			columns: this.getColumns(),
			defaults: {
				sortable: true
			}
		});

		Zarafa.mail.attachitem.AttachMailColumnModel.superclass.constructor.call(this, config);
	},

	/**
	 * Create an array of {@link Ext.grid.Column columns} which will be visible when loading data from
	 * mail folder into {@link Zarafa.common.attachment.dialog.AttachItemGrid AttachItemGrid}.
	 *
	 * @return {Ext.grid.Column[]} The array of columns
	 * @private
	 */
	getColumns: function()
	{
		return [{
			header: '<p class=\'icon_importance\'>&nbsp;</p>',
			headerCls: 'zarafa-icon-column',
			dataIndex: 'importance',
			width: 24,
			fixed: true,
			renderer: Zarafa.common.ui.grid.Renderers.importance,
			tooltip: _('Sort by: Importance')
		}, {
			header: '<p class=\'icon_index\'>&nbsp;</p>',
			headerCls: 'zarafa-icon-column',
			dataIndex: 'icon_index',
			width: 24,
			fixed: true,
			renderer: Zarafa.common.ui.grid.Renderers.icon,
			tooltip: _('Sort by: Icon')
		}, {
			header: '<p class=\'icon_paperclip\'>&nbsp;</p>',
			headerCls: 'zarafa-icon-column',
			dataIndex: 'hasattach',
			width: 24,
			fixed: true,
			renderer: Zarafa.common.ui.grid.Renderers.attachment,
			tooltip: _('Sort by: Attachment')
		}, {
			header: _('From'),
			dataIndex: 'sent_representing_name',
			width: 100,
			renderer: Zarafa.common.ui.grid.Renderers.sender,
			tooltip: _('Sort by: From')
		}, {
			header: _('To'),
			dataIndex: 'display_to',
			width: 100,
			renderer: Zarafa.common.ui.grid.Renderers.to,
			tooltip: _('Sort by: To')
		}, {
			header: _('Subject'),
			dataIndex: 'subject',
			renderer: Zarafa.common.ui.grid.Renderers.subject,
			tooltip: _('Sort by: Subject')
		}, {
			header: _('Received'),
			dataIndex: 'message_delivery_time',
			width: 180,
			renderer: Zarafa.common.ui.grid.Renderers.datetime,
			tooltip: _('Sort by: Received')
		}, {
			header: _('Categories'),
			dataIndex: 'categories',
			width: 160,
			renderer: Zarafa.common.ui.grid.Renderers.text,
			tooltip: _('Sort by: Categories')
		}];
	}
});
Ext.namespace('Zarafa.mail.attachitem');

/**
 * @class Zarafa.mail.attachitem.AttachMailRenderer
 * @extends Zarafa.common.attachment.dialogs.AttachItemBaseRenderer
 *
 * Renderer that can be used to get text data from {@link Zarafa.mail.MailRecord MailRecord}.
 */
Zarafa.mail.attachitem.AttachMailRenderer = Ext.extend(Zarafa.common.attachment.dialogs.AttachItemBaseRenderer, {
	/**
	 * Constructor will initialize default properties
	 * @param {Object} config configuration object
	 */
	constructor: function(config)
	{
		Zarafa.mail.attachitem.AttachMailRenderer.superclass.constructor.call(this, config);

		Ext.apply(this.labels, {
			'message_delivery_time': _('Sent')
		});
	},

	/**
	 * Function should be used to generate template which can be used by {@link #generateText} to add data into template and return the string that is generated.
	 * This will generate template for the html format only.
	 * @return {String} The HTML for the XTemplate to use
	 */
	generateHTMLTemplate: function()
	{
		var html = '';

		html += this.addHTMLRowGroup({
			'from': '{from:htmlEncode}',
			// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
			'message_delivery_time': '{message_delivery_time:formatDefaultTimeString("' + _("l jS F Y {0}") + '")}',
			'display_to': '{display_to:htmlEncode}',
			'display_cc': '{display_cc:htmlEncode}',
			'display_bcc': '{display_bcc:htmlEncode}',
			'subject': '{subject:htmlEncode}',
			'attachment_names': '{attachment_names:htmlEncode}'
		});

		html += this.addHTMLRowGroup({
			'sensitivity': '{sensitivity:sensitivityString}',
			'importance': '{importance:importanceString}'
		});

		html += this.addHTMLRow('categories', '{categories:htmlEncode}');
		html += '{body}';

		return html;
	},

	/**
	 * Function should be used to generate template which can be used by {@link #generateText} to add data into template and return the string that is generated.
	 * This will generate template for the plain text format only.
	 * @return {String} The HTML for the XTemplate to use
	 */
	generatePlainTemplate: function()
	{
		var html = '';

		html += this.addPlainRowGroup({
			'from': '{from}',
			// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
			'message_delivery_time': '{message_delivery_time:formatDefaultTimeString("' + _("l jS F Y {0}") + '")}',
			'display_to': '{display_to}',
			'display_cc': '{display_cc}',
			'display_bcc': '{display_bcc}',
			'subject': '{subject}',
			'attachment_names': '{attachment_names}'
		});

		html += this.addPlainRowGroup({
			'sensitivity': '{sensitivity:sensitivityString}',
			'importance': '{importance:importanceString}'
		});

		html += this.addPlainRow('categories', '{categories}');
		html += '{body}';

		return html;
	}
});
Ext.namespace('Zarafa.mail.data');

/**
 * @class Zarafa.mail.data.ConversationItemsResponseHandler
 * @extends Zarafa.core.data.AbstractResponseHandler
 *
 * Handles the response of the 'conversationitems' action of the maillistmodule,
 * which supplies the Sent Items messages that belong to a conversation. The
 * received items are handed to the {@link Zarafa.mail.MailStore store} that
 * requested them.
 */
Zarafa.mail.data.ConversationItemsResponseHandler = Ext.extend(Zarafa.core.data.AbstractResponseHandler, {
	/**
	 * @cfg {Zarafa.mail.MailStore} store The store that requested the conversation items
	 * and into which the received items must be merged.
	 */
	store: undefined,

	/**
	 * @cfg {Function} callback Alternative to {@link #store}: called with the
	 * 'conversationitems' response.
	 */
	callback: undefined,

	/**
	 * Handles the 'conversationitems' response.
	 * @param {Object} response The response object belonging to the given command.
	 */
	doConversationitems: function(response)
	{
		if (Ext.isFunction(this.callback)) {
			this.callback(response);
		} else if (this.store) {
			this.store.onConversationItemsResponse(response);
		}
	},

	/**
	 * @cfg {String[]} queriedIds The conversation ids that were queried with
	 * the 'conversationcounts' request this handler belongs to.
	 */
	queriedIds: undefined,

	/**
	 * Handles the 'conversationcounts' response.
	 * @param {Object} response The response object belonging to the given command.
	 */
	doConversationcounts: function(response)
	{
		if (this.store) {
			this.store.onConversationCountsResponse(response, this.queriedIds);
		}
	}
});
Ext.namespace('Zarafa.mail.data');

/**
 * @class Zarafa.mail.data.DataModes
 * @extends Zarafa.core.Enum
 *
 * Enum containing the different data modes of the mail context.
 *
 * @singleton
 */
Zarafa.mail.data.DataModes = Zarafa.core.Enum.create({
	/**
	 * View all mail items from the selected folder(s).
	 *
	 * @property
	 * @type Number
	 */
	ALL: 0,
	/**
	 * View all found mail items in the selected folder(s).
	 *
	 * @property
	 * @type Number
	 */
	SEARCH: 1,
	/**
	 * View all unread filtered mail items in the selected folder(s).
	 *
	 * @property
	 * @type Number
	 */
	UNREAD: 2
});
Ext.namespace('Zarafa.mail.data');

/**
 * @class Zarafa.mail.data.ViewModes
 * @extends Zarafa.core.Enum
 *
 * Enum containing the different viewing modes of the mail context.
 *
 * @singleton
 */
Zarafa.mail.data.ViewModes = Zarafa.core.Enum.create({
	/**
	 * Don't show the preview panel
	 * @property
	 * @type Number
	 */
	NO_PREVIEW: 0,

	/**
	 * Show the preview panel to the right
	 * @property
	 * @type Number
	 */
	RIGHT_PREVIEW: 1,

	/**
	 * Show the preview panel in the bottom
	 * @property
	 * @type Number
	 */
	BOTTOM_PREVIEW: 2,

	/**
	 * Show the search results
	 * @property
	 * @type Number
	 */
	SEARCH: 3,

	/**
	 * Show the live scroll results
	 * @property
	 * @type Number
	 */
	LIVESCROLL: 4,

	/**
	 * Function was used to check that current view mode
	 * was one of the view mode from {@link #NO_PREVIEW},
	 * {@link #RIGHT_PREVIEW} and {@link #BOTTOM_PREVIEW}.
	 *
	 * @param {Zarafa.mail.data.ViewModes} viewMode the viewMode which currently selected.
	 * @return return true if current view mode is {@link #NO_PREVIEW}, {@link #RIGHT_PREVIEW}
	 * and {@link #BOTTOM_PREVIEW} else return false.
	 */
	isMainViewMode: function(viewMode)
	{
		return viewMode === this.NO_PREVIEW || viewMode === this.RIGHT_PREVIEW || viewMode === this.BOTTOM_PREVIEW;
	}
});
Ext.namespace('Zarafa.mail.data');

/**
 * @class Zarafa.mail.data.Views
 * @extends Zarafa.core.Enum
 *
 * Enum containing the different views of the mail context.
 *
 * @singleton
 */
Zarafa.mail.data.Views = Zarafa.core.Enum.create({
	/**
	 * View all mail items from the selected folder(s) in the 'list' view.
	 *
	 * @property
	 * @type Number
	 */
	LIST: 0,

	/**
	 * View all found mail items from the selected folder(s) in the 'list' view.
	 *
	 * @property
	 * @type Number
	 */
	SEARCH: 1,

	/**
	 * View all updated batch of mail items from the selected folder(s) in the 'list' view.
	 *
	 * @property
	 * @type Number
	 */
	LIVESCROLL: 2
});
Ext.namespace('Zarafa.mail.dialogs');

/**
 * @class Zarafa.mail.dialogs.DelayedDeliveryContentPanel
 * @extends Zarafa.core.ui.ContentPanel
 * @xtype zarafa.delayeddeliverycontentpanel
 *
 * The content panel which use to get Date and Time
 */
Zarafa.mail.dialogs.DelayedDeliveryContentPanel = Ext.extend(Zarafa.core.ui.ContentPanel, {

    /**
     * @cfg {Zarafa.core.data.IPMRecord} record The mail which
     * is being update by this panel.
     */
    record: null,

    /**
     * @cfg {Date} deferredSendTime which is date object of selected date and time for mail
     */
    deferredSendTime: undefined,

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

        config = Ext.apply(config, {
            xtype: 'zarafa.delayeddeliverycontentpanel',
            width: 350,
            height: 260,
            title: _('Schedule mail to be sent out') + '...',
            layout: 'fit',
            items: [{
                xtype: 'zarafa.delayeddeliverypanel',
                ref: 'delayedDeliveryPanel',
                buttons: [{
                    text: _('Send'),
                    cls: 'zarafa-action',
                    iconCls: 'icon_send_later_white',
                    ref: '../sendButton',
                    disabled: true,
                    handler: this.onSend,
                    scope: this
                },{
                    text: _('Cancel'),
                    handler: this.onCancel,
                    scope: this
                }]
            }]
        });

        Zarafa.mail.dialogs.DelayedDeliveryContentPanel.superclass.constructor.call(this, config);
    },

    /**
     * Function is use to set disabled value of "send" {@link Ext.Button button}.
     * @param {Boolean} value is true/false
     */
    setDisabled: function (value)
    {
        this.delayedDeliveryPanel.sendButton.setDisabled(value);
    },

    /**
     * Event handler which is called when the user clicks the "Cancel" {@link Ext.Button button}
     * @private
     */
    onCancel: function ()
    {
        this.close();
    },

    /**
     * Event handler which is called when the user clicks the "send" {@link Ext.Button button}
     * Add Event listeners on saverecord and completequeue events.
     * This will {@link Zarafa.core.ui.MessageContentPanel#sendRecord send} the given record.
     */
    onSend: function ()
    {
        this.deferredSendTime = this.delayedDeliveryPanel.getMailDateTime();

        //Add listener on saverecord event for set deferred_send_time in record
        this.mailPanel.un('saverecord', this.onSaveRecord, this);
        this.mailPanel.on('saverecord', this.onSaveRecord, this);

        //Add listener on completequeue event
        this.mailPanel.sendValidationQueue.un('completequeue', this.onCompleteQueue.createDelegate(this, [this.mailPanel, this.record]), this);
        this.mailPanel.sendValidationQueue.on('completequeue', this.onCompleteQueue.createDelegate(this, [this.mailPanel, this.record]), this);
        this.close();

        //send record
        this.mailPanel.sendRecord();
    },

    /**
     * Event handler remove event listener on saverecord if record contain validation error
     * @param {Zarafa.core.ui.MessageContentPanel} dialog which contains the mail record.
     * @param {Zarafa.core.data.IPMRecord} record The record which is going to be send
     */
    onCompleteQueue: function (dialog, record)
    {
        if (Ext.isEmpty(record.get('deferred_send_time'))) {
            dialog.un('saverecord', this.onSaveRecord, this);
            dialog.showInfoMask = true;
        }
        dialog.sendValidationQueue.un('completequeue', this.onCompleteQueue, this);
    },

    /**
     * Event handler set deferred_send_time in Zarafa.core.data.IPMRecord} record and
     * display notification.
     * @param {Zarafa.core.ui.MessageContentPanel} dialog which contains the mail record.
     * @param {Zarafa.core.data.IPMRecord} record The record which is going to be send
     */
    onSaveRecord: function (dialog, record)
    {
        if (dialog.isSending === true && dialog.showInfoMask === true) {
            dialog.record.set('deferred_send_time', this.deferredSendTime);
            dialog.showInfoMask = false;
            container.getNotifier().notify('info.saved', _('Scheduled Items'), this.sendLaterMessage());
            dialog.un('saverecord', this.onSaveRecord, this);
        }
    },

    /**
     * Function is create message that will display in {@link Zarafa.core.ui.notifier.Notifier notification}
     * Message content deferred send time of mail and close button.
     * @returns {String} message that will display in notification
     */
    sendLaterMessage: function ()
    {
        var messageDateTime = this.deferredSendTime.format('d/m/y H:i');
        var upperMessage = _('Your message will be sent at ');
        var lowerMessage = _('Go to your Outbox to edit your email.');

        // return html string which show message and close button
        return String.format("{0}<b>{1}.</b> <br>{2}", upperMessage, messageDateTime, lowerMessage);
    }
});

Ext.reg('zarafa.delayeddeliverycontentpanel', Zarafa.mail.dialogs.DelayedDeliveryContentPanel);
Ext.namespace('Zarafa.mail.settings');

/**
 * @class Zarafa.mail.settings.SettingsComposeWidget
 * @extends Zarafa.settings.ui.SettingsWidget
 * @xtype zarafa.settingscomposewidget
 *
 * The {@link Zarafa.settings.ui.SettingsWidget widget} for configuring
 * the composing mail options in the {@link Zarafa.mail.settings.SettingsMailCategory mail category}.
 */
Zarafa.mail.settings.SettingsComposeWidget = Ext.extend(Zarafa.settings.ui.SettingsWidget, {

	/**
	 * The editor which is currently active in the interface
	 * @property
	 * @type String
	 * @private
	 */
	selectedEditor: '',

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

		var composerStore = {
			xtype: 'jsonstore',
			autoDestroy: true,
			fields: ['name', 'value'],
			data: [{
				'name': _('HTML'),
				'value': 'html'
			},{
				'name': _('Plain Text'),
				'value': 'plain'
			}]
		};

		var fontFamilies = Zarafa.common.ui.htmleditor.Fonts.getFontFamilies();
		var fontData = fontFamilies.split(";").map(function(font) {
			return {
				'name': font.split("=")[0],
				'value': font.split("=")[1].toLowerCase()
			};
		});

		var fontStore = {
			xtype: 'jsonstore',
			autoDestroy: true,
			fields: ['name', 'value'],
			data: fontData
		};

		var fontSizes = Zarafa.common.ui.htmleditor.Fonts.getFonts();
		// To understand the sorting of the keys, please read the
		// comment in Zarafa.common.ui.htmleditor.Fonts.getFonts
		var fontSizesKeys = Object.keys(fontSizes).sort();
		var fontSizeData = [];
		Ext.each(fontSizesKeys, function(key){
			fontSizeData.push({'name': fontSizes[key] + 'pt', 'value': key });
		});

		var fontSizeStore = {
			xtype: 'jsonstore',
			autoDestroy: true,
			fields: ['name', 'value'],
			data: fontSizeData
		};

		var delegateSentItemsStore = {
			xtype: 'jsonstore',
			autoDestroy: true,
			fields: ['name', 'value'],
			data: [{
				'name': _('In both representee\'s and delegate\'s Sent Items'),
				'value': 'both'
			},{
				'name': _('Only in delegate\'s Sent Items'),
				'value': 'delegate'
			},{
				'name': _('Only in representee\'s Sent Items'),
				'value': 'representee'
			}]
		};

		var editorStore = this.createEditorStore();

		Ext.applyIf(config, {
			title: _('Compose mail settings'),
			layout: 'form',
			items: [{
				xtype: 'combo',
				name: 'zarafa/v1/contexts/mail/dialogs/mailcreate/use_html_editor',
				ref: 'composerCombo',
				fieldLabel: _('Compose mail in this format'),
				width: 400,
				store: composerStore,
				mode: 'local',
				triggerAction: 'all',
				displayField: 'name',
				valueField: 'value',
				lazyInit: false,
				forceSelection: true,
				editable: false,
				autoSelect: true,
				listeners: {
					select: this.onComposerSelect,
					scope: this
				}
			},{
				xtype: 'combo',
				name: 'zarafa/v1/contexts/mail/html_editor',
				fieldLabel: _('Editor'),
				ref: 'editorCombo',
				width: 400,
				store: editorStore,
				mode: 'local',
				allowBlank: false,
				triggerAction: 'all',
				displayField: 'name',
				valueField: 'value',
				lazyInit: false,
				forceSelection: true,
				editable: false,
				autoSelect: true,
				hidden: editorStore.getCount() <= 1,
				listeners: {
					select: this.onSelectComboItem,
					scope: this
				}
			},{
				xtype: 'combo',
				name: 'zarafa/v1/main/default_font',
				cls:'x-font-select',
				fieldLabel: _('Default font'),
				width: 400,
				ref: 'fontCombo',
				store: fontStore,
				triggerAction: 'all',
				mode: 'local',
				displayField: 'name',
				valueField: 'value',
				editable: false,
				autoSelect: true,
				forceSelection: true,
				lazyInit: false,
				listeners: {
					select: this.onSelectComboItem,
					scope: this
				}
			},{
				xtype: 'combo',
				name: 'zarafa/v1/main/default_font_size',
				cls:'x-font-select',
				fieldLabel: _('Default font size'),
				width: 400,
				ref: 'fontSizeCombo',
				store: fontSizeStore,
				triggerAction: 'all',
				mode: 'local',
				displayField: 'name',
				valueField: 'value',
				editable: false,
				autoSelect: true,
				forceSelection: true,
				lazyInit: false,
				listeners: {
					select: this.onSelectComboItem,
					scope: this
				}
			},{
				xtype: 'combo',
				name: 'zarafa/v1/contexts/mail/delegate_sent_items_style',
				cls:'x-font-select',
				fieldLabel: _('Save emails sent by delegate'),
				width: 400,
				ref: 'delegateSentItemsStyleCombo',
				store: delegateSentItemsStore,
				triggerAction: 'all',
				mode: 'local',
				displayField: 'name',
				valueField: 'value',
				editable: false,
				autoSelect: true,
				forceSelection: true,
				lazyInit: false,
				listeners: {
					select: this.onSelectComboItem,
					scope: this
				}
			},{
				xtype: 'checkbox',
				name: 'zarafa/v1/contexts/mail/always_request_readreceipt',
				ref: 'readBox',
				boxLabel: _('Always request a read receipt'),
				hideLabel: true,
				listeners: {
					check: this.onFieldChange,
					scope: this
				}
			},{
				xtype: 'checkbox',
				name: 'zarafa/v1/contexts/mail/attachment_reminder_enable',
				ref: 'attachmentReminderBox',
				boxLabel: _('Activate attachment reminder'),
				hideLabel: true,
				listeners: {
					check: this.onFieldChange,
					scope: this
				}
			},{
				xtype: 'zarafa.compositefield',
				defaultMargins: '0 0 0 0',
				plugins: [ 'zarafa.splitfieldlabeler' ],
				// # TRANSLATORS: The {A} _must_ always be at the start of the translation
				// # The '{B}' represents the number of minutes which the user will type in.
				fieldLabel: _('{A}AutoSave unsent mail every {B} minute(s)'),
				labelWidth: 250,
				combineErrors: false,
				items: [{
					xtype: 'checkbox',
					labelSplitter: '{A}',
					name: 'zarafa/v1/contexts/mail/autosave_enable',
					ref: '../autoSaveBox',
					boxLabel: '',
					hideLabel: true,
					checked: true,
					listeners: {
						check: this.onAutoSaveCheckBoxChange,
						change: this.onFieldChange,
						scope: this
					}
				},{
					xtype: 'zarafa.spinnerfield',
					labelSplitter: '{B}',
					cls: 'k-settings-spinner-narrow',
					vtype: 'naturalInteger',
					name: 'zarafa/v1/contexts/mail/autosave_time',
					ref: '../autoSaveTimeSpinner',
					width: 60,
					incrementValue: 1,
					defaultValue: 1,
					minValue: 1,
					allowBlank: false,
					allowDecimals: false,
					allowNegative: false,
					listeners: {
						change: this.onFieldChange,
						scope: this
					},
					plugins: ['zarafa.numberspinner']
				}]
			}]
		});

		Zarafa.mail.settings.SettingsComposeWidget.superclass.constructor.call(this, config);
	},

	/**
	 * Create {@link Ext.data.JsonStore JsonStore} which contains the
	 * editor plugins info.
	 *
	 * @returns {Ext.data.JsonStore} JsonStore which contains editor plugins info.
	 */
	createEditorStore: function()
	{
		return new Ext.data.JsonStore({
			autoDestroy: true,
			fields: ['name', 'value'],
			data: [{
				name: 'TinyMCE Editor',
				value: 'full_tinymce'
			}].concat(this.getHTMLEditorPlugins().map(function(e) {
				return {
					name: e.getDisplayName(),
					value: e.getName()
				};
			}))
		});
	},

	/**
	 * Function which is used to get the editor plugins which are
	 * enabled in webapp.
	 *
	 * @return {Array} array of editor plugins.
	 */
	getHTMLEditorPlugins: function()
	{
		return container.getPlugins().filter(function (htmlEditorPlguin)  {
			return htmlEditorPlguin instanceof Zarafa.core.HtmlEditorPlugin;
		});
	},

	/**
	 * Called by the {@link Zarafa.settings.ui.SettingsCategory Category} when
	 * it has been called with {@link zarafa.settings.ui.SettingsCategory#update}.
	 * This is used to load the latest version of the settings from the
	 * {@link Zarafa.settings.SettingsModel} into the UI of this category.
	 * @param {Zarafa.settings.SettingsModel} settingsModel The settings to load
	 */
	update: function(settingsModel)
	{
		this.model = settingsModel;

		// If not defined, we have a fallback setting
		var useHtml = settingsModel.get(this.composerCombo.name);

		this.composerCombo.setValue(useHtml ? 'html' : 'plain');

		var editorName = settingsModel.get(this.editorCombo.name);
		var isExistEditor = this.getHTMLEditorPlugins().some(function (editor){
			return editor.getName() === editorName;
		});
		this.selectedEditor = isExistEditor ? editorName : 'full_tinymce';
		this.editorCombo.setValue(this.selectedEditor);
		this.editorCombo.setDisabled(useHtml !== true);

		this.fontCombo.setValue(settingsModel.get(this.fontCombo.name));
		this.fontSizeCombo.setValue(settingsModel.get(this.fontSizeCombo.name));

		// disable font and font size combos for plain text compose option
		this.fontCombo.setDisabled(useHtml !== true);
		this.fontSizeCombo.setDisabled(useHtml !== true);

		this.delegateSentItemsStyleCombo.setValue(settingsModel.get(this.delegateSentItemsStyleCombo.name));

		this.readBox.setValue(settingsModel.get(this.readBox.name));
		this.attachmentReminderBox.setValue(settingsModel.get(this.attachmentReminderBox.name));

		// Set values in autoSave checkbox and textfield.
		var enabled = settingsModel.get(this.autoSaveBox.name);

		this.autoSaveBox.setValue(enabled);
		this.autoSaveTimeSpinner.setValue(settingsModel.get(this.autoSaveTimeSpinner.name) / 60);

	},

	/**
	 * Called by the {@link Zarafa.settings.ui.SettingsCategory Category} when
	 * it has been called with {@link zarafa.settings.ui.SettingsCategory#updateSettings}.
	 * This is used to update the settings from the UI into the {@link Zarafa.settings.SettingsModel settings model}.
	 * @param {Zarafa.settings.SettingsModel} settingsModel The settings to update
	 */
	updateSettings: function(settingsModel)
	{
		var spinnerValue = this.autoSaveTimeSpinner.getValue();

		if(spinnerValue === 0 || !Ext.isDefined(spinnerValue))  {
			spinnerValue = settingsModel.get('zarafa/v1/contexts/mail/autosave_time', false, true);
		}
		else {
			spinnerValue = spinnerValue * 60;
		}
		settingsModel.beginEdit();
		settingsModel.set(this.composerCombo.name, this.composerCombo.getValue() === 'html');
		settingsModel.set(this.editorCombo.name, this.editorCombo.getValue());
		settingsModel.set(this.readBox.name, this.readBox.getValue());
		settingsModel.set(this.autoSaveTimeSpinner.name, spinnerValue);
		settingsModel.endEdit();

	},

	/**
	 * Event handler which is called when a selection has been made in the
	 * Composer type {@link Ext.form.ComboBox combobox}.
	 * @param {Ext.form.ComboBox} field The field which fired the event
	 * @param {Ext.data.Record} record The selected record
	 */
	onComposerSelect: function(field, record)
	{
		if (this.model) {
			var set = record.get(field.valueField);

			// FIXME: The settings model should be able to detect if
			// a change was applied
			if (this.model.get(field.name) !== set) {
				this.model.set(field.name, set === 'html');
			}

			// disable font and font size combos for plain text compose option
			this.fontCombo.setDisabled(set !== 'html');
			this.fontSizeCombo.setDisabled(set !== 'html');
			this.editorCombo.setDisabled(set !== 'html');
		}
	},

	/**
	 * Event handler which is called when one of the textfields has been changed.
	 * This will apply the new value to the settings.
	 * @param {Ext.form.Field} field The field which has fired the event
	 * @param {String} value The new value
	 * @private
	 */
	onFieldChange: function(field, value)
	{
		if (this.model) {
			// FIXME: The settings model should be able to detect if
			// a change was applied
			if (this.model.get(field.name) !== value) {
				this.model.set(field.name, value);
			}
		}
	},

	/**
	 * Event handler which is fired when the {@link Ext.form.Checkbox Checkbox} has been clicked.
	 * @param {Ext.form.Checkbox} checkbox The checkbox which fired the event
	 * @param {Boolean} check True if the checkbox is currently checked
	 * @private
	 */
	onAutoSaveCheckBoxChange: function(checkbox, check)
	{
		// Checkbox's check event doesn't fire change event on element in
		// chrome and IE browser, so calling onFieldChange event manually.
		this.onFieldChange(checkbox, check);
		this.autoSaveTimeSpinner.setDisabled(!check);
	},

	/**
	 * Event handler which is called when a selection has been made in the
	 * Font or editor selection {@link Ext.form.ComboBox combobox}.
	 * @param {Ext.form.ComboBox} field The field which fired the event
	 * @param {Ext.data.Record} record The selected record
	 */
	onSelectComboItem: function(field, record)
	{
		if (this.model) {
			var value = record.get(field.valueField);
			// FIXME: The settings model should be able to detect if
			// a change was applied
			if(this.model.get(field.name) !== value) {
				this.model.set(field.name, value);
			}
		}
	}
});

Ext.reg('zarafa.settingscomposewidget', Zarafa.mail.settings.SettingsComposeWidget);
Ext.namespace('Zarafa.mail.settings');

/**
 * @class Zarafa.mail.settings.SettingsConversationWidget
 * @extends Zarafa.settings.ui.SettingsWidget
 * @xtype zarafa.settingsconversationwidget
 *
 * The {@link Zarafa.settings.ui.SettingsWidget widget} for configuring
 * the general mail options in the {@link Zarafa.mail.settings.SettingsMailCategory mail category}.
 */
Zarafa.mail.settings.SettingsConversationWidget = Ext.extend(Zarafa.settings.ui.SettingsWidget, {

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

		Ext.applyIf(config, {
			title: _('Conversation view settings'),
			layout: 'form',
			items: [{
				xtype: 'checkbox',
				name: 'zarafa/v1/contexts/mail/enable_conversation_view',
				ref: 'enableConversations',
				boxLabel: _('Enable conversation view'),
				hideLabel: true,
				lazyInit: false,
				handler: this.onClickEnableConversationsHandler,
				scope: this,
				listeners: {
					check: this.onCheck,
					scope: this
				}
			},{
				xtype: 'checkbox',
				name: 'zarafa/v1/contexts/mail/expand_single_conversation',
				boxLabel: _('Collapse conversation when selecting a different email'),
				hideLabel: true,
				ref: 'singleExpand',
				lazyInit: false,
				listeners: {
					check: this.onCheck,
					scope: this
				}
			},{
				xtype: 'checkbox',
				name: 'zarafa/v1/contexts/mail/enable_conversation_preview',
				boxLabel: _('Show the entire conversation in the reading pane'),
				hideLabel: true,
				ref: 'conversationPreview',
				lazyInit: false,
				listeners: {
					check: this.onCheck,
					scope: this
				}
			}]
		});

		Zarafa.mail.settings.SettingsConversationWidget.superclass.constructor.call(this, config);
	},

	/**
	 * Called by the {@link Zarafa.settings.ui.SettingsCategory Category} when
	 * it has been called with {@link zarafa.settings.ui.SettingsCategory#update}.
	 * This is used to load the latest version of the settings from the
	 * {@link Zarafa.settings.SettingsModel} into the UI of this category.
	 * @param {Zarafa.settings.SettingsModel} settingsModel The settings to load
	 */
	update: function(settingsModel)
	{
		this.model = settingsModel;

		var enableConversations = settingsModel.get(this.enableConversations.name);
		this.enableConversations.setValue(enableConversations);
		this.singleExpand.setValue(settingsModel.get(this.singleExpand.name));
		this.singleExpand.setDisabled(!enableConversations);
		this.conversationPreview.setValue(settingsModel.get(this.conversationPreview.name, true) !== false);
		this.conversationPreview.setDisabled(!enableConversations);
	},

	/**
	 * Called by the {@link Zarafa.settings.ui.SettingsCategory Category} when
	 * it has been called with {@link zarafa.settings.ui.SettingsCategory#updateSettings}.
	 * This is used to update the settings from the UI into the {@link Zarafa.settings.SettingsModel settings model}.
	 * @param {Zarafa.settings.SettingsModel} settingsModel The settings to update
	 */
	updateSettings: function(settingsModel)
	{
		settingsModel.set(this.enableConversations.name, this.enableConversations.getValue());
		settingsModel.set(this.singleExpand.name, this.singleExpand.getValue());
		settingsModel.set(this.conversationPreview.name, this.conversationPreview.getValue());
	},

	/**
	 * Event handler called when checkbox has been modified
	 *
	 * @param {Ext.form.CheckBox} checkbox Checkbox element from which the event originated
	 * @param {Boolean} checked State of the checkbox
	 * @private
	 */
	onCheck: function(checkbox, checked)
	{
		if(this.model) {
			// FIXME: The settings model should be able to detect if
			// a change was applied
			if (this.model.get(checkbox.name) !== checked) {
				this.model.set(checkbox.name, checked);
				// The mail grid and store wire up the conversation view when they
				// are created, so switching those settings requires a reload. The
				// reading pane setting is evaluated on the fly.
				if (checkbox !== this.conversationPreview) {
					this.model.requiresReload = true;
				}
			}
		}
	},

	/**
	 * Handler can uncheck and disable the {@link #singleExpand Expand single conversation} checkbox if
	 * {@link #enableConversations Enable conversation view} checkbox is unchecked and if it is checked
	 * then enable the {@link #singleExpand Expand single conversation} checkbox and check/uncheck the
	 * checkbox based on user settings.
	 *
	 * @param {Ext.form.CheckBox} checkbox The Enable conversation view checkbox element from which the event originated
	 * @param {Boolean} checked State of the checkbox
	 */
	onClickEnableConversationsHandler: function(checkbox, checked)
	{
	 	this.singleExpand.setDisabled(!checked);
	}
});

Ext.reg('zarafa.settingsconversationwidget', Zarafa.mail.settings.SettingsConversationWidget);
Ext.namespace('Zarafa.mail.settings');

/**
 * @class Zarafa.mail.settings.SettingsIncomingMailWidget
 * @extends Zarafa.settings.ui.SettingsWidget
 * @xtype zarafa.settingsincomingmailwidget
 *
 * The {@link Zarafa.settings.ui.SettingsWidget widget} for configuring
 * the incoming mail options in the {@link Zarafa.mail.settings.SettingsMailCategory mail category}.
 */
Zarafa.mail.settings.SettingsIncomingMailWidget = Ext.extend(Zarafa.settings.ui.SettingsWidget, {

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

		var incomingStore = {
			xtype: 'jsonstore',
			autoDestroy: true,
			fields: ['name', 'value'],
			data: [{
				'name': _('HTML'),
				'value': 'html'
			},{
				'name': _('Plain Text'),
				'value': 'plain'
			}]
		};

		Ext.applyIf(config, {
			title: _('Incoming mail'),
			layout: 'form',
			items: [{
				xtype: 'displayfield',
				hideLabel: true,
				value: _('How to respond to requests for read receipts')
			},{
				xtype: 'radiogroup',
				name: 'zarafa/v1/contexts/mail/readreceipt_handling',
				ref: 'readReceiptGroup',
				columns: 1,
				hideLabel: true,
				items: [{
					xtype: 'radio',
					name: 'receiptHandling',
					inputValue: 'always',
					boxLabel: _('Always send a response')
				},{
					xtype: 'radio',
					name: 'receiptHandling',
					inputValue: 'never',
					boxLabel: _('Never send a response')
				},{
					xtype: 'radio',
					name: 'receiptHandling',
					inputValue: 'ask',
					boxLabel: _('Ask me before sending a response')
				}],
				listeners: {
					change: this.onRadioChange,
					scope: this
				}
			},{
				xtype: 'zarafa.compositefield',
				defaultMargins: '0 0 0 0',
				plugins: [ 'zarafa.splitfieldlabeler' ],
				// # TRANSLATORS: The {A} _must_ always be at the start of the translation
				// # The '{B}' represents the number of seconds which the user will type in.
				fieldLabel: _('{A}Automatically mark mail as read after {B} second(s)'),
				labelWidth: 250,
				items: [{
					xtype: 'checkbox',
					labelSplitter: '{A}',
					name: 'zarafa/v1/contexts/mail/readflag_time_enable',
					ref: '../readFlagTimeCheckbox',
					boxLabel: '',
					hideLabel: true,
					listeners: {
						check: this.onCheckboxChange,
						scope: this
					}
				},{
					xtype: 'zarafa.spinnerfield',
					labelSplitter: '{B}',
					name: 'zarafa/v1/contexts/mail/readflag_time',
					ref: '../readFlagTimeSpinner',
					width: 60,
					incrementValue: 1,
					defaultValue: 0,
					minValue: 0,
					listeners: {
						'change': this.onFieldChange,
						scope: this
					},
					plugins: ['zarafa.numberspinner']
				}]
			}, {
				xtype: 'combo',
				name: 'zarafa/v1/contexts/mail/use_html_email_preview',
				ref: 'incomingCombo',
				fieldLabel: _('View mail in this format'),
				labelWidth: 400,
				store: incomingStore,
				mode: 'local',
				triggerAction: 'all',
				displayField: 'name',
				valueField: 'value',
				lazyInit: false,
				forceSelection: true,
				editable: false,
				autoSelect: true,
			}
		]
		});

		Zarafa.mail.settings.SettingsIncomingMailWidget.superclass.constructor.call(this, config);
	},

	/**
	 * Called by the {@link Zarafa.settings.ui.SettingsCategory Category} when
	 * it has been called with {@link zarafa.settings.ui.SettingsCategory#update}.
	 * This is used to load the latest version of the settings from the
	 * {@link Zarafa.settings.SettingsModel} into the UI of this category.
	 * @param {Zarafa.settings.SettingsModel} settingsModel The settings to load
	 */
	update: function(settingsModel)
	{
		this.model = settingsModel;

		var enabled = settingsModel.get(this.readFlagTimeCheckbox.name);

		this.readFlagTimeCheckbox.setValue(enabled);
		this.readFlagTimeSpinner.setValue(settingsModel.get(this.readFlagTimeSpinner.name));
		this.readReceiptGroup.setValue(settingsModel.get(this.readReceiptGroup.name));

		this.readFlagTimeSpinner.setDisabled(!enabled);

		var useHtml = settingsModel.get(this.incomingCombo.name);
		this.incomingCombo.setValue(useHtml ? 'html' : 'plain');
	},

	/**
	 * Called by the {@link Zarafa.settings.ui.SettingsCategory Category} when
	 * it has been called with {@link zarafa.settings.ui.SettingsCategory#updateSettings}.
	 * This is used to update the settings from the UI into the {@link Zarafa.settings.SettingsModel settings model}.
	 * @param {Zarafa.settings.SettingsModel} settingsModel The settings to update
	 */
	updateSettings: function(settingsModel)
	{
		settingsModel.beginEdit();
		settingsModel.set(this.readReceiptGroup.name, this.readReceiptGroup.getValue().inputValue);
		settingsModel.set(this.readFlagTimeCheckbox.name, this.readFlagTimeCheckbox.getValue());
		settingsModel.set(this.readFlagTimeSpinner.name, this.readFlagTimeSpinner.getValue());
		settingsModel.set(this.incomingCombo.name, this.incomingCombo.getValue() === 'html');
		settingsModel.endEdit();
	},

	/**
	 * Event handler which is fired when a {@link Ext.form.Radio} in the
	 * {@link Ext.form.RadioGroup} has been changed. This will toggle the
	 * visibility of the other fields.
	 * @param {Ext.form.RadioGroup} group The radio group which fired the event
	 * @param {Ext.form.Radio} radio The radio which was enabled
	 * @private
	 */
	onRadioChange: function(group, radio)
	{
		if (this.model) {
			// FIXME: The settings model should be able to detect if
			// a change was applied
			if (this.model.get(group.name) !== radio.inputValue) {
				this.model.set(group.name, radio.inputValue);
			}
		}
	},

	/**
	 * Event handler which is fired when {@link Ext.form.Field field} has been changed.
	 * @param {Ext.form.Field} field The Field which fired the event.
	 * @param {Mixed} value The updated value.
	 * @private
	 */
	onFieldChange: function(field, value) {
		if (this.model) {
			// FIXME: The settings model should be able to detect if
			// a change was applied
			if (this.model.get(field.name) !== value) {
				this.model.set(field.name, value);
			}
		}
	},

	/**
	 * Event handler which is fired when the {@link Ext.form.Checkbox Checkbox} has been clicked.
	 * @param {Ext.form.Checkbox} checkbox The checkbox which fired the event
	 * @param {Boolean} check True if the checkbox is currently checked
	 * @private
	 */
	onCheckboxChange: function(checkbox, check) {
		this.onFieldChange(checkbox, check);
		this.readFlagTimeSpinner.setDisabled(!check);
	}
});

Ext.reg('zarafa.settingsincomingmailwidget', Zarafa.mail.settings.SettingsIncomingMailWidget);
Ext.namespace('Zarafa.mail.settings');

/**
 * @class Zarafa.mail.settings.SettingsMailCategory
 * @extends Zarafa.settings.ui.SettingsCategory
 * @xtype zarafa.settingsmailcategory
 *
 * The mail category for users which will
 * allow the user to configure Mail related settings
 */
Zarafa.mail.settings.SettingsMailCategory = Ext.extend(Zarafa.settings.ui.SettingsCategory, {
	// Insertion points for this class
	/**
	 * @insert context.settings.category.mail
	 * Insertion point to register new {@link Zarafa.settings.ui.SettingsWidget widgets}
	 * for the {@link Zarafa.mail.settings.SettingsMailCategory Mail Category}.
	 * @param {Zarafa.mail.settings.SettingsMailCategory} category The mail
	 * category to which the widgets will be added.
	 */

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

		var items = [{
				xtype: 'zarafa.settingsmailwidget'
			},{
				xtype: 'zarafa.settingscomposewidget',
				settingsContext: config.settingsContext
			},{
				xtype: 'zarafa.settingswidget',
				title: _('Cc recipients'),
				cls: 'zarafa-settings-widget k-settings-nogap',
				height: 400,
				layout: {
					type: 'fit'
				},
				items: [{
					xtype: 'zarafa.manageccpanel',
					settingsContext: config.settingsContext
				}],
				getCcPanel: function() {
					return this.findByType('zarafa.manageccpanel')[0];
				},
				update: function(settingsModel) {
					var panel = this.getCcPanel();
					if (panel) {
						panel.update(settingsModel);
					}
				},
				updateSettings: function(settingsModel) {
					var panel = this.getCcPanel();
					if (panel) {
						panel.updateSettings(settingsModel);
					}
				}
			},
			container.populateInsertionPoint('context.settings.category.mail.aftercomposesettings', this),
			{
				xtype: 'zarafa.settingsincomingmailwidget'
			},{
				xtype: 'zarafa.settingsconversationwidget'
			},{
				xtype: 'zarafa.settingssignatureswidget',
				settingsMailCategory: this
			},
			container.populateInsertionPoint('context.settings.category.mail', this)
		];

		Ext.applyIf(config, {
			title: _('Mail'),
			categoryIndex: 1,
			iconCls: 'zarafa-settings-category-mail',
			items: items
		});

		Zarafa.mail.settings.SettingsMailCategory.superclass.constructor.call(this, config);
	}
});

Ext.reg('zarafa.settingsmailcategory', Zarafa.mail.settings.SettingsMailCategory);
Ext.namespace('Zarafa.mail.settings');

/**
 * @class Zarafa.mail.settings.SettingsMailWidget
 * @extends Zarafa.settings.ui.SettingsWidget
 * @xtype zarafa.settingsmailwidget
 *
 * The {@link Zarafa.settings.ui.SettingsWidget widget} for configuring
 * the general mail options in the {@link Zarafa.mail.settings.SettingsMailCategory mail category}.
 */
Zarafa.mail.settings.SettingsMailWidget = Ext.extend(Zarafa.settings.ui.SettingsWidget, {

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

		var previewStore = {
			xtype: 'jsonstore',
			autoDestroy: true,
			fields: ['name', 'value'],
			data: [{
				'name': _('No preview'),
				'value': Zarafa.mail.data.ViewModes.NO_PREVIEW
			},{
				'name': _('Right'),
				'value': Zarafa.mail.data.ViewModes.RIGHT_PREVIEW
			},{
				'name': _('Bottom'),
				'value': Zarafa.mail.data.ViewModes.BOTTOM_PREVIEW
			}]
		};

		var delegateWastebasketStore = {
			xtype: 'jsonstore',
			autoDestroy: true,
			fields: ['name', 'value'],
			data: [{
				'name': _('Into mailbox owner\'s wastebasket'),
				'value': 4 // 4 = Stores deleted items in the mailbox owner's folder.
			},{
				'name': _('Into your wastebasket'),
				'value': 8 // 8 = Stores deleted items in your folder.
			}]
		};

		var items = [{
			xtype: 'combo',
			name: 'zarafa/v1/state/contexts/mail/current_view_mode',
			ref: 'previewCombo',
			fieldLabel: _('Location of preview pane'),
			width: 400,
			store: previewStore,
			mode: 'local',
			triggerAction: 'all',
			displayField: 'name',
			valueField: 'value',
			lazyInit: false,
			forceSelection: true,
			editable: false,
			autoSelect: true,
			listeners: {
				select: this.onComboSelect,
				scope: this
			}
		},{
			xtype: 'checkbox',
			name: 'zarafa/v1/contexts/mail/use_english_abbreviations',
			ref: 'englishAbb',
			/* Re is actually Latin, not English. */
			boxLabel: _('Use subject prefixes in accordance with RFC 5256/5322'),
			hideLabel: true,
			lazyInit: false,
			hidden: Zarafa.core.Util.inArray(['en_GB', 'en_GB.UTF-8', 'en_US.UTF-8'], container.getSettingsModel().get('zarafa/v1/main/language'), false, false),
			listeners: {
				check: this.onCheck,
				scope: this
			}
		},{
			xtype: 'checkbox',
			name: 'zarafa/v1/contexts/mail/close_on_respond',
			ref: 'closeCheck',
			boxLabel: _('Close original message on reply or forward'),
			hideLabel: true,
			lazyInit: false,
			listeners: {
				check: this.onCheck,
				scope: this
			}
		},{
			xtype: 'combo',
			name: 'zarafa/v1/contexts/mail/delegate_wastebasket_style',
			cls:'x-font-select',
			fieldLabel: _("Move items deleted in delegate's store"),
			width: 400,
			ref: 'delegateWastebasketStyleCombo',
			store: delegateWastebasketStore,
			triggerAction: 'all',
			mode: 'local',
			displayField: 'name',
			valueField: 'value',
			editable: false,
			autoSelect: true,
			forceSelection: true,
			lazyInit: false,
			listeners: {
				select: this.onComboSelect,
				scope: this
			}
		}];

		Ext.applyIf(config, {
			title: _('General mail settings'),
			layout: 'form',
			items: items
		});

		// Display the popout settings only if supported.
		if (Zarafa.supportsPopOut()) {
			config.items.splice(1, 0, {
				xtype: 'displayfield',
				hideLabel: true,
				value: _('Open or compose a mail item in a') + ':'
			},{
				xtype: 'radiogroup',
				name: 'zarafa/v1/main/base_content_layer',
				ref: 'openingMailField',
				columns: 1,
				hideLabel: true,
				items: [{
					xtype: 'radio',
					name: 'openingMail',
					inputValue: 'tabs',
					boxLabel: _('grommunio Web tab')
				},{
					xtype: 'radio',
					name: 'openingMail',
					inputValue: 'separateWindows',
					boxLabel: _('Browser window')
				}],
				listeners: {
					change: this.onRadioChange,
					scope: this
				}
			});
		}

		Zarafa.mail.settings.SettingsMailWidget.superclass.constructor.call(this, config);
	},

	/**
	 * Called by the {@link Zarafa.settings.ui.SettingsCategory Category} when
	 * it has been called with {@link zarafa.settings.ui.SettingsCategory#update}.
	 * This is used to load the latest version of the settings from the
	 * {@link Zarafa.settings.SettingsModel} into the UI of this category.
	 * @param {Zarafa.settings.SettingsModel} settingsModel The settings to load
	 */
	update: function(settingsModel)
	{
		this.model = settingsModel;

		var previewLocation = settingsModel.get(this.previewCombo.name);
		if(!Ext.isDefined(previewLocation)) {
			previewLocation = Zarafa.mail.data.ViewModes.RIGHT_PREVIEW;
		}
		this.previewCombo.setValue(previewLocation);
		this.closeCheck.setValue(settingsModel.get(this.closeCheck.name));
		this.englishAbb.setValue(settingsModel.get(this.englishAbb.name));

		if (Zarafa.supportsPopOut()) {
			this.openingMailField.setValue(settingsModel.get(this.openingMailField.name));
		}

		this.delegateWastebasketStyleCombo.setValue(settingsModel.get(this.delegateWastebasketStyleCombo.name));
	},

	/**
	 * Called by the {@link Zarafa.settings.ui.SettingsCategory Category} when
	 * it has been called with {@link zarafa.settings.ui.SettingsCategory#updateSettings}.
	 * This is used to update the settings from the UI into the {@link Zarafa.settings.SettingsModel settings model}.
	 * @param {Zarafa.settings.SettingsModel} settingsModel The settings to update
	 */
	updateSettings: function(settingsModel)
	{
		settingsModel.set(this.previewCombo.name, this.previewCombo.getValue());
		settingsModel.set(this.closeCheck.name, this.closeCheck.getValue());

		if (Zarafa.supportsPopOut()) {
			settingsModel.set(this.openingMailField.name, this.openingMailField.getValue().inputValue);
		}
	},

	/**
	 * Event handler which is called when a selection has been made in the
	 * Preview Panel type {@link Ext.form.ComboBox combobox}.
	 * @param {Ext.form.ComboBox} field The field which fired the event
	 * @param {Ext.data.Record} record The selected record
	 */
	onComboSelect: function(field, record)
	{
		if (this.model) {
			var set = record.get(field.valueField);

			// FIXME: The settings model should be able to detect if
			// a change was applied
			if (this.model.get(field.name) !== set) {
				this.model.set(field.name, set);
			}
		}
	},

	/**
	 * Event handler called when checkbox has been modified
	 *
	 * @param {Ext.form.CheckBox} checkbox Checkbox element from which the event originated
	 * @param {Boolean} checked State of the checkbox
	 * @private
	 */
	onCheck: function(checkbox, checked)
	{
		if(this.model) {
			// FIXME: The settings model should be able to detect if
			// a change was applied
			if (this.model.get(checkbox.name) !== checked) {
				this.model.set(checkbox.name, checked);
			}
		}
	},

	/**
	 * Event handler which is fired when a {@link Ext.form.Radio} in the
	 * {@link Ext.form.RadioGroup} has been changed. This will set the value
	 * selected by user in settingsModel.
	 * @param {Ext.form.RadioGroup} group The radio group which fired the event
	 * @param {Ext.form.Radio} radio The radio which was enabled
	 * @private
	 */
	onRadioChange: function(group, radio)
	{
		if (this.model && (this.model.get(group.name) !== radio.inputValue)) {
			this.model.set(group.name, radio.inputValue);
		}
	}
});

Ext.reg('zarafa.settingsmailwidget', Zarafa.mail.settings.SettingsMailWidget);
Ext.namespace('Zarafa.mail.settings');

/**
 * @class Zarafa.mail.settings.SettingsOofWidget
 * @extends Zarafa.settings.ui.SettingsWidget
 * @xtype zarafa.settingsoofwidget
 *
 * The {@link Zarafa.settings.ui.SettingsWidget widget} for configuring
 * the Out of Office settings in the {@link Zarafa.mail.settings.SettingsMailCategory mail category}.
 */
Zarafa.mail.settings.SettingsOofWidget = Ext.extend(Zarafa.settings.ui.SettingsWidget, {
	/**
	 * @cfg {Zarafa.common.outofoffice.data.OofStore} store store to use for loading out of office settings
	 */
	store: undefined,

	/**
	 * @cfg {Zarafa.common.outofoffice.data.OofRecord} record selected record to populate data into view by {@link #updateView}
	 * and save data.
	 */
	record: undefined,

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

		if(Ext.isEmpty(config.store)) {
			config.store = new Zarafa.common.outofoffice.data.OofStore();
		}

		Ext.applyIf(config, {
			xtype: 'zarafa.settingsoofwidget',
			title: _('Out of Office'),
			ref: '../outOfOfficeWidget',
			cls: 'zarafa-settings-widget',
			layout: 'form',
			labelWidth: 225,
			items: this.getNewConfig()
		});

		Zarafa.mail.settings.SettingsOofWidget.superclass.constructor.call(this, config);

		// Register 'selectnow' event for both the DateTimeFields
		this.outOfOfficeDateTimeField.dateField.on('selectnow', this.onOutOfOfficeSelectNow, this);
		this.backDateTimeField.dateField.on('selectnow', this.onBackSelectNow, this);
	},

	/**
	 * initialize events for the {@link Zarafa.mail.settings.SettingsOofWidget SettingsOofWidget}.
	 * @private
	 */
	initEvents: function()
	{
		Zarafa.mail.settings.SettingsOofWidget.superclass.initEvents.call(this);

		var contextModel = this.settingsContext.getModel();

		// Set handler for update to set the settings model dirty.
		this.mon(this.getOofStore(),'update', this.doStoreUpdate, this);
		// listen to savesettings and discardsettings to save/discard oof settings data
		this.mon(contextModel, 'savesettings', this.onSaveSettings, this);
		this.mon(contextModel, 'discardsettings', this.onDiscardSettings, this);
	},

	/**
	 * @return {Zarafa.common.outofoffice.data.OofStore} The store which holds record of users' out of office settings.
	 */
	getOofStore: function()
	{
		return this.store;
	},

	/**
	 * The configuration object for the new oof that allows the user to enter
	 * a time span for when he is out.
	 * Also adds user selection combo which holds user names who has given 'owner' permission.
	 * @return {Object} Configuration object
	 */
	getNewConfig: function()
	{
		var backDate = new Date().getNextWorkWeekDay() || new Date().add(Date.DAY, 1);
		backDate = backDate.clearTime().add(Date.MINUTE, container.getSettingsModel().get('zarafa/v1/main/start_working_hour'));
		var comboStore = this.createComboboxStore();
		var items = [];

		// Show user combo box only when shared stores with 'owner' permission available.
		if (comboStore.getCount() > 1) {
			items.push({
				xtype: 'container',
				cls: 'k-store-picker',
				border: false,
				layout: 'form',
				labelWidth: 225,
				items: {
					xtype: 'combo',
					mode: 'local',
					ref: '../userCombo',
					store: comboStore,
					fieldLabel: _('Update Out of Office settings for'),
					triggerAction: 'all',
					displayField: 'name',
					valueField: 'value',
					lazyInit: false,
					forceSelection: true,
					value: comboStore.getAt(0).get('value'),
					editable: false,
					listeners: {
						beforeselect: this.onBeforeUserSelect,
						select: this.updateView,
						scope: this
					}
				}
			});
		}

		items.push({
			xtype: 'radio',
			name: 'set',
			// ExtJs demands inputValue to be a string
			inputValue: 'false',
			boxLabel: _('I am currently in the office'),
			hideLabel: true,
			ref: 'inOfficeField',
			listeners: {
				check: this.onRadioChecked,
				scope: this
			}
		},{
			xtype: 'container',
			layout: {
				type: 'table',
				columns: 3
			},
			border: false,
			style: {
				paddingBottom: '25px'
			},
			defaults:{
				xtype: 'container',
				layout: 'form',
				cellCls: 'zarafa-settings-oof-table-cellpadding'
			},
			items: [{
				items:[{
					xtype: 'radio',
					name: 'set',
					hideLabel: true,
					boxLabel: _('I am out of the office from') + ':',
					ref: '../../outOfOfficeRadio',
					// ExtJs demands inputValue to be a string
					inputValue: 'true',
					listeners: {
						check: this.onRadioChecked,
						scope: this
					}
				}]
			},{
				items:[{
					xtype: 'zarafa.datetimefield',
					ref: '../../outOfOfficeDateTimeField',
					name: 'from',
					dateFieldConfig: {
						showNow: true,
						invalidClass: 'zarafa-settings-oof-invalid'
					},
					fieldLabel: _('Out of office from'),
					width: 300,
					hideLabel: true,
					dateFormat: _('d/m/Y'),
					minValue: new Date(),
					defaultValue: new Date(),
					timeIncrement: container.getSettingsModel().get('zarafa/v1/contexts/calendar/default_zoom_level'),
					listeners: {
						change: this.onOutOfOfficeChange,
						scope: this
					}
				}]
			},{
				xtype: 'displayfield'
			},{
				items:[{
					xtype: 'checkbox',
					disabled: true,
					name: 'zarafa/v1/contexts/mail/autosave_enable',
					ref: '../../willBeBackCheckBox',
					boxLabel: _('I will be back on') + ':',
					hideLabel: true,
					listeners: {
						render: this.onWillBeBackCheckRender,
						check: this.onWillBeBackCheck,
						scope: this
					}
				}]
			},{
				xtype: 'zarafa.datetimefield',
				fieldLabel: _('Back on'),
				ref: '../backDateTimeField',
				name: 'until',
				dateFieldConfig: {
					showNow: true,
					invalidClass: 'zarafa-settings-oof-invalid'
				},
				timeFieldConfig: {
					invalidClass: 'zarafa-settings-oof-invalid'
				},
				width: 300,
				disabled: true,
				hideLabel: true,
				dateFormat: _('d/m/Y'),
				minValue: new Date(),
				defaultValue: backDate,
				timeIncrement: container.getSettingsModel().get('zarafa/v1/contexts/calendar/default_zoom_level'),
				listeners: {
					change: this.backDateTimeChange,
					scope: this
				}
			},{
				xtype: 'displayfield',
				width: '100%',
				cellCls: 'zarafa-settings-oof-table-cellpadding',
				ref: '../oofWarning'
			}]
		},{
			xtype: 'displayfield',
			anchor: '100%',
			cls: 'zarafa-settings-oof-autoreplay',
			hideLabel: true,
			value: _('Automatically reply with the following message:') + ' ' + _("(The frequency of replies is dependent on server configuration.)")
		},{
			xtype: 'tabpanel',
			ref: 'replyMessages',
			activeTab: 0,
			items: [{
				title: _('Inside My Organization'),
				itemId: 'internal',
				height: '100%',
				width: '100%',
				items: [
				{
					xtype: 'container',
					cls: 'zarafa-settings-oof-subject',
					anchor: '100%',
					layout: 'hbox',
					items: [{
						xtype: 'displayfield',
						cls: 'label-subject',
						value: _('Subject') + ':',
						autoWidth: true
					},{
						xtype: 'textfield',
						name: 'internal_subject',
						ref: '../../../intSubjectField',
						emptyText: _('Out of Office'),
						defaultValue: _('Out of Office'),
						flex: 1,
						listeners: {
							change: this.onFieldChange,
							scope: this
						}
					}]
				},{
					xtype: 'zarafa.editorfield',
					cls: 'k-signature-editor',
					name: 'internal_reply',
					ref: '../../intBodyField',
					defaultValue: _('User is currently out of office.'),
					anchor: '100%',
					height: 450,
					useHtml: true,
					disableEditor: false,
					listeners: {
						change: this.onFieldChange,
						scope: this
					}
				}],
			}, {
				title: _('Outside My Organization'),
				itemId: 'external',
				height: '100%',
				width: '100%',
				items: [{
					xtype: 'container',
					style: {
						paddingBottom: '5px'
					},
					anchor: '100%',
					layout: 'hbox',
					items: [{
						xtype: 'checkbox',
						name: 'allow_external',
						ref: '../../../externalCheckBox',
						boxLabel: _('Auto-reply to people outside my organization'),
						hideLabel: true,
						listeners: {
							render: this.onCheckBoxRender,
							check: this.onExternalChecked,
							scope: this
						}
					}]
				},{
					xtype: 'container',
					style: {
						paddingLeft: '25px',
						paddingBottom: '5px'
					},
					anchor: '100%',
					layout: 'hbox',
					items: [{
						xtype: 'radio',
						name: 'external_audience',
						// ExtJs demands inputValue to be a string
						inputValue: 'true',
						boxLabel: _('My contacts only') + '&nbsp;',
						hideLabel: true,
						ref: '../../../contactsOnlyField',
						listeners: {
							check: this.onAudienceChecked,
							scope: this
						}
					}, {
						xtype: 'radio',
						name: 'external_audience',
						// ExtJs demands inputValue to be a string
						inputValue: 'false',
						boxLabel: _('Anyone outside my organization'),
						hideLabel: true,
						ref: '../../../anyoneField',
						listeners: {
							check: this.onAudienceChecked,
							scope: this
						}
					}]
				},{
					xtype: 'container',
					cls: 'zarafa-settings-oof-subject',
					anchor: '100%',
					layout: 'hbox',
					items: [{
						xtype: 'displayfield',
						cls: 'label-subject',
						value: _('Subject') + ':',
						autoWidth: true
					},{
						xtype: 'textfield',
						name: 'external_subject',
						ref: '../../../extSubjectField',
						emptyText: _('Out of Office'),
						defaultValue: _('Out of Office'),
						flex: 1,
						listeners: {
							change: this.onFieldChange,
							scope: this
						}
					}]
				},{
					xtype: 'zarafa.editorfield',
					cls: 'k-signature-editor',
					name: 'external_reply',
					ref: '../../extBodyField',
					defaultValue: _('User is currently out of office.'),
					anchor: '100%',
					height: 450,
					useHtml: true,
					disableEditor: false,
					listeners: {
						change: this.onFieldChange,
						scope: this
					}
				}]
			}],
			listeners: {
				tabchange: this.onTabChange,
				scope: this
			}
		});

		return items;
	},

	/**
	 * Creates an jsonstore for the combobox which contains the users of which store is fully opened
	 * and the Inbox 'folder permissions' are set.
	 * @return {Object} array which contains the jsonstore.
	 * @private
	 */
	createComboboxStore: function()
	{
		var hierarchyStore = container.getHierarchyStore();
		var myStoreEntryId = hierarchyStore.getDefaultStore().get('store_entryid');
		var data = [{name: _('myself'), value: myStoreEntryId }];
		var processedStores = [];

		hierarchyStore.getStores().forEach(function(store) {
			var subtree = store.getSubtreeFolder();
			var hasSufficientPermission = (subtree.get('rights') & Zarafa.core.mapi.Rights.RIGHTS_SECRETARY) === Zarafa.core.mapi.Rights.RIGHTS_SECRETARY;
			var storeEntryId = store.get('store_entryid');
			if (storeEntryId != myStoreEntryId && hasSufficientPermission) {
				data = data.concat({ name: store.get('mailbox_owner_name'), value: storeEntryId });
				processedStores.push(Zarafa.core.Util.bin2hex(store.get('user_name')));
			}
		});

		var sharedStores = container.getSettingsModel().get('zarafa/v1/contexts/hierarchy/shared_stores', true);
		for (var user in sharedStores) {
			// Skip not fully opened or already included stores
			if (!sharedStores[user].hasOwnProperty("all") || processedStores.includes(user)) {
				continue;
			}

			hierarchyStore.getStores().forEach(function(store) {
				if (store.get('user_name') === user) {
					// Saving out of office only works with equal or more than secretary permissions on the full store.
					// Note: The grommunio Web backend will not check the rights and allows saving out of office settings
					// when the user has folder rights, because that is what Gromox needs).
					var subtree = store.getSubtreeFolder();
					var hasSufficientPermission = (subtree.get('rights') & Zarafa.core.mapi.Rights.RIGHTS_SECRETARY) === Zarafa.core.mapi.Rights.RIGHTS_SECRETARY;

					if (hasSufficientPermission) {
						data = data.concat({name: store.get('mailbox_owner_name'), value: store.get('store_entryid') });
					}
				}
			});

		}

		return new Ext.data.JsonStore({
			fields: ['name', 'value'],
			data: data
		});
	},

	/**
	 * Event handler for the beforeselect event, when the user selects a different
	 * user to update the out of office settings.
	 *
	 * Checks if the model is dirty and shows the user a dialog if the user wants to
	 * save any changes.
	 *
	 * @param {Ext.form.ComboBox} field The combobox which was selected
	 * @param {Ext.Record} nextRecord The record that was selected
	 * @param {Number} index The index of the selected record
	 *
	 * @return {Mixed} False if there are pending changes. The selecting will then be
	 * handled by {@link #applyChanges}. Undefined otherwise.
	 */
	onBeforeUserSelect: function(field, nextRecord, index)
	{
		var model = this.settingsContext.getModel();
		if (model.hasChanges()) {
			Ext.MessageBox.show({
				title: _('Apply changes'),
				msg: _('Do you wish to apply the changes?'),
				fn: this.applyChanges.createDelegate(this, [ model, field, nextRecord ], 1),
				buttons: Ext.MessageBox.YESNOCANCEL
			});

			return false;
		}
	},

	/**
	 * Handler for the event fired after the {@link Ext.form.Checkbox checkbox} markup is rendered.
	 * We are aligning the check box to the right of its cell.
	 * @param {Ext.form.Checkbox} checkbox Will be back checkbox instance.
	 */
	onWillBeBackCheckRender: function(checkbox)
	{
		checkbox.wrap.addClass('zarafa-settings-oof-table-cellalign');
	},

	/**
	 * Handler for the event fired after the {@link Ext.form.Checkbox checkbox} markup is rendered.
	 * We are aligning the check box to the right of its cell.
	 * @param {Ext.form.Checkbox} checkbox Will be back checkbox instance.
	 */
	onCheckBoxRender : function(checkbox)
	{
		checkbox.wrap.addClass('zarafa-settings-oof-table-cellalign');
	},


	/**
	 * Called by the {@link Zarafa.settings.ui.SettingsCategory Category} when
	 * it has been called with {@link zarafa.settings.ui.SettingsCategory#update}.
	 * This is used to get the latest version of the settings from the
	 * {@link Zarafa.settings.SettingsModel}.
	 * @param {Zarafa.settings.SettingsModel} settingsModel latest settings
	 */
	update: function(settingsModel)
	{
		this.model = settingsModel;

		this.updateView();
	},

	/**
	 * Called by the {@link #update} or when first time load of {@link Zarafa.mail.settings.SettingsOofWidget}
	 * and when user from drop down has been changed.
	 *
	 * This is used to load the latest version of the settings from the
	 * {@config record} into the UI of this widget.
	 */
	updateView: function()
	{
		this.loading = true;

		if(this.userCombo) {
			var defaultUser = this.userCombo.getValue();
			this.record = this.getOofStore().getById(defaultUser);
			this.filterUserFromDropDown();
		} else {
			this.record = this.getOofStore().getAt(0);
		}
		var record = this.record;
		var isOofSet = record.get('set');
		this.inOfficeField.setValue(!isOofSet);
		this.outOfOfficeRadio.setValue(!!isOofSet);

		if (isOofSet) {
			var configuredFromSetting = record.get('from');
			var configuredUntilDateSetting = record.get('until');

			// Check if the values of 'from' out of office is proper to be set.
			if(configuredFromSetting !== 0) {
				this.outOfOfficeDateTimeField.setValue(new Date(configuredFromSetting*1000));
			}

			// Check if the values of 'until' out of office is proper to be set.
			// Otherwise set default back date value and disable the back date field.
			if(configuredUntilDateSetting !== 0) {
				this.backDateTimeField.setValue(new Date(configuredUntilDateSetting*1000));
				this.willBeBackCheckBox.setValue(true);
			} else {
				this.backDateTimeField.setValue(this.backDateTimeField.defaultValue);
				this.willBeBackCheckBox.setValue(false);
			}

		} else {
			// Reset Date fields
			this.outOfOfficeDateTimeField.setValue(this.outOfOfficeDateTimeField.defaultValue);
			this.backDateTimeField.setValue(this.backDateTimeField.defaultValue);
		}

		var intSubject = record.get(this.intSubjectField.name) || this.intSubjectField.defaultValue;
		this.intSubjectField.setValue(intSubject);

		var extSubject = record.get(this.extSubjectField.name) || this.extSubjectField.defaultValue;
		this.extSubjectField.setValue(extSubject);

		var intBody = record.get(this.intBodyField.name) || this.intBodyField.defaultValue;
		this.intBodyField.setValue(intBody);

		if (this.extBodyLoaded) {
			var extBody = record.get(this.extBodyField.name) || this.extBodyField.defaultValue;
			this.extBodyField.setValue(extBody);
		}

		this.loading = false;
	},

	/**
	 * Called by the {@link #updateView} to filter unnecessary Users from comboBox.
	 * When shared user store's permission gets changed we need to filter those user.
	 *
	 * This function will remove users from comboBox which are not in {@config store} to sync with
	 * the latest shared users. And also hide comboBox when there's no other Users available.
	 */
	filterUserFromDropDown: function()
	{
		var comboStore = this.userCombo.getStore();
		var oofStore = this.getOofStore();

		comboStore.each(function(user){
			var comboUserEntryId = user.get('value');
			var userIndexOofStore = oofStore.findExact('store_entryid', comboUserEntryId);
			if (userIndexOofStore < 0) {
				comboStore.remove(user);
			}
		});

		this.userCombo.setVisible(comboStore.getCount() > 1);
	},

	/**
	 * Called by the {@link Zarafa.settings.ui.SettingsCategory Category} when
	 * it has been called with {@link zarafa.settings.ui.SettingsCategory#updateSettings}.
	 * This is used to update the settings from the UI into the {@config record}.
	 */
	updateSettings: function()
	{
		// We must either set the requested subject, or the default subject
		var intsubject = this.intSubjectField.getValue() || this.intSubjectField.emptyText;
		var extsubject = this.extSubjectField.getValue() || this.extSubjectField.emptyText;

		// We must either set the requested body, or the default body
		var intbody = this.intBodyField.getValue() || this.intBodyField.emptyText;
		if (this.extBodyLoaded) {
			var extbody = this.extBodyField.getValue() || this.extBodyField.emptyText;
		}

		this.record.beginEdit();
		this.record.set(this.intSubjectField.name, intsubject);
		this.record.set(this.extSubjectField.name, extsubject);
		this.record.set(this.intBodyField.name, intbody);
		if (this.extBodyLoaded) {
			this.record.set(this.extBodyField.name, extbody);
		}

		this.record.set(this.outOfOfficeRadio.name, this.outOfOfficeRadio.getValue());

		if(this.outOfOfficeRadio.getValue()){
			this.record.set(this.outOfOfficeDateTimeField.name, this.outOfOfficeDateTimeField.getValue().getTime()/1000);

			if(this.willBeBackCheckBox.getValue() === true){
				this.record.set(this.backDateTimeField.name, this.backDateTimeField.getValue().getTime()/1000);
			} else {
				this.record.set(this.backDateTimeField.name, 0);
			}
		}

		if (true == this.extBodyLoaded) {
			this.record.set(this.externalCheckBox.name, this.externalCheckBox.checked);
			this.record.set(this.contactsOnlyField.name, this.contactsOnlyField.checked);
		}

		this.record.endEdit();
	},

	onTabChange: function(tabPanel, newCard)
	{
		if (newCard.itemId === "external") {
			if (!this.extBodyLoaded) {
				var enableExternal = this.record.get(this.externalCheckBox.name);
				this.anyoneField.setDisabled(!enableExternal);
				this.contactsOnlyField.setDisabled(!enableExternal);
				this.extSubjectField.setDisabled(!enableExternal);
				this.externalCheckBox.setValue(enableExternal);
				var extbody = this.record.get(this.extBodyField.name) || this.extSubjectField.emptyText;
				this.extBodyField.setValue(extbody);
				if (enableExternal) {
					this.extBodyField.enable();
					var external_audience = this.record.get('external_audience');
					this.contactsOnlyField.setValue(external_audience ? 'false' : 'true');
					this.anyoneField.setValue(external_audience ? 'true' : 'false');
				} else {
					this.extBodyField.disable();
				}
				this.extBodyLoaded = true;
			}
		}
	},

	/**
	 * Event handler which is called when 'selectnow' event of {@link Ext.form.DateField} is fired.
	 * This will set current time to the respective sibling {@link Zarafa.common.ui.DateTimeField#timeField time field}.
	 * @param {Ext.form.DateField} dateField Which fired the event
	 * @param {Date} date The date that was selected
	 * @private
	 */
	onOutOfOfficeSelectNow: function(dateField, date)
	{
		this.outOfOfficeDateTimeField.timeField.setValue(new Date());
	},

	/**
	 * Event handler which is called when 'selectnow' event of {@link Ext.form.DateField} is fired.
	 * This will set current time to the respective sibling {@link Zarafa.common.ui.DateTimeField#timeField time field}.
	 * @param {Ext.form.DateField} dateField Which fired the event
	 * @param {Date} date The date that was selected
	 * @private
	 */
	onBackSelectNow: function(dateField, date)
	{
		this.backDateTimeField.timeField.setValue(new Date());
	},

	/**
	 * Event handler which is fired when a {@link Ext.form.Radio} in the
	 * {@link Ext.form.RadioGroup} has been changed. This will toggle the
	 * visibility of the other fields.
	 * @param {Ext.form.RadioGroup} group The radio group which fired the event
	 * @param {Ext.form.Radio} radio The radio which was enabled
	 * @private
	 */
	onRadioChange: function(group, radio)
	{
		var set = radio.inputValue === 'true';

		if (this.record.get(group.name) !== set) {
			this.record.set(group.name, set);
		}
	},

	/**
	 * Event handler which is fired when a {@link Ext.form.Radio} in the
	 * {@link Ext.form.RadioGroup} has been changed. This will toggle the
	 * visibility of the other fields.
	 * @param {Ext.form.Radio} radio The radio which was enabled
	 * @param {Boolean} checked True if the radio is currently checked, false otherwise
	 * @private
	 */
	onRadioChecked: function(radio, checked)
	{
		if (checked === true) {
			var set = radio.inputValue === 'true';

			if (this.record.get(radio.name) !== set) {
				this.record.set(radio.name, set);
			}

			this.outOfOfficeDateTimeField.setDisabled(!set);
			this.willBeBackCheckBox.setDisabled(!set);

			if(this.willBeBackCheckBox.getValue() === true) {
				this.willBeBackCheckBox.setValue(false);
			}

			this.reviseWarningStatus();
		}
	},

	onExternalChecked: function(checkbox, checked)
	{
		this.anyoneField.setDisabled(!checked);
		this.contactsOnlyField.setDisabled(!checked);
		this.extSubjectField.setDisabled(!checked);
		if (checked) {
			this.extBodyField.enable();
		} else {
			this.extBodyField.disable();
		}
		if (!!this.record) {
			this.record.set(checkbox.name, checked);
		}
	},

	onAudienceChecked: function(radio, checked)
	{
		if (checked === true) {
			var set = radio.inputValue === 'true';
			if (!!this.record) {
				this.record.set(radio.name, set);
			}
		}
	},

	/**
	 * Event handler which is fired when the "Enable reminder" checkbox has been clicked.
	 * @param {Ext.form.Checkbox} field The field which fired the event
	 * @param {Boolean} check True if the checkbox is currently checked
	 * @private
	 */
	onWillBeBackCheck: function(field, check)
	{
		if(!check) {
			this.record.set('until',0);
		} else {
			var backDateTimeValue = this.backDateTimeField.getValue().getTime()/1000;
			this.record.set('until', backDateTimeValue);
		}

		this.backDateTimeField.setDisabled(!check);
		this.reviseWarningStatus();
	},

	/**
	 * Event handler which is called when one of the textfields has been changed.
	 * This will apply the new value to the {@link #record} (or if the text is empty,
	 * the {@link Ext.form.Field#emptyText} will be saved).
	 * @param {Ext.form.Field} field The field which has fired the event
	 * @param {String} value The new value
	 * @private
	 */
	onFieldChange: function(field, value)
	{
		this.record.set(field.name, value || field.emptyText);
	},

	/**
	 * Event handler which is triggered when {@link #outOfOfficeDateTimeField} field
	 * has been changed by the user. It configures minimum value for {@link #backDateTimeField} accordingly.
	 * It calls {@link #reviseWarningStatus} to show/hide warning according to the latest date related changes.
	 * @param {Ext.form.Field} dateField The {@link Ext.form.Field field} which was changed.
	 * @param {Mixed} newValue The new value
	 * @param {Mixed} oldValue The old value
	 * @private
	 */
	onOutOfOfficeChange: function(dateField, newValue, oldValue)
	{
		this.backDateTimeField.dateField.setMinValue(newValue);
		this.reviseWarningStatus();
		this.record.set(dateField.name, newValue.getTime()/1000 || dateField.defaultValue);
	},

	/**
	 * Event handler which is triggered when {@link #backDateTimeField} field
	 * has been changed by the user. It calls {@link #reviseWarningStatus} to
	 * show/hide warning according to the latest date related changes.
	 *
	 * @param {Ext.form.Field} field The {@link Ext.form.Field field} which was changed.
	 * @param {Mixed} newValue The new value
	 * @param {Mixed} oldValue The old value
	 * @private
	 */
	backDateTimeChange: function(dateField, newValue, oldValue)
	{
		this.reviseWarningStatus();
		this.record.set(dateField.name, newValue.getTime()/1000 || dateField.defaultValue);
	},

	/**
	 * Helper function to show or hide warning according to the latest date related changes by
	 * calling {@link Ext.form.DisplayField#set set} or {@link Ext.form.DisplayField#reset reset} methods respectively.
	 * @private
	 */
	reviseWarningStatus: function()
	{
		if(this.outOfOfficeDateTimeField.getValue() >= this.backDateTimeField.getValue() && this.willBeBackCheckBox.getValue() === true) {
			this.oofWarning.setValue(_('The time you entered is before the out of office date. Please reschedule.'));
			this.backDateTimeField.dateField.markInvalid();
			this.backDateTimeField.timeField.markInvalid();
		} else {
			this.oofWarning.reset();
			this.backDateTimeField.dateField.clearInvalid();
			this.backDateTimeField.timeField.clearInvalid();
		}
	},

	/**
	 * Applies changes according to the answer from the user. And then switches
	 * the user in combobox to the user provided selection.
	 *
	 * @param {Ext.button} btn The messagebox button
	 * @param {Zarafa.settings.SettingsContextModel} model the settings model
	 * @param {Ext.form.ComboBox} field the user selection combobox.
	 * @param {Ext.Record} record The user's record which was selected from user combobox.
	 */
	applyChanges: function(btn, model, field, record)
	{
		// The user cancels the switch to a different category
		if (btn === 'cancel') {
			return;
		}

		// Check if the user wishes to save all changes
		if (btn === 'yes') {
			model.applyChanges();
		} else {
			// Check if the user wishes to discard all changes
			model.discardChanges();
		}

		// Select the user in the dropdown
		field.setValue(record.get('value'));

		this.updateView();
	},

	/**
	 * Event handler for the {@link Ext.data.Store#update} event which is fired
	 * by the {@link Zarafa.common.outofoffice.data.OofStore} inside the {@link Zarafa.mail.settings.SettingsOofWidget}.
	 * This will mark the {@link Zarafa.settings.SettingsContextModel} as
	 * {@link Zarafa.settings.SettingsContextModel#setDirty dirty}.
	 * @param {Ext.data.Store} store The store which fired the event
	 * @param {Ext.data.Record} record The record which was updated
	 * @param {String} operation The update operation being performed.
	 * @private
	 */
	doStoreUpdate: function(store, record, operation)
	{
		if (operation !== Ext.data.Record.COMMIT && !this.loading) {
			var contextModel = this.settingsContext.getModel();
			contextModel.setDirty();
		}
	},

	/**
	 * Function will be used to reload data in the {@link Zarafa.common.outofoffice.data.OofStore OofStore}.
	 */
	onDiscardSettings: function()
	{
		this.record.reject(true);
		this.updateView();
	},

	/**
	 * Event handler will be called when {@link Zarafa.settings.SettingsContextModel#savesettings} event is fired.
	 * This will save out of office store data.
	 * @private
	 */
	onSaveSettings: function()
	{
		this.getOofStore().save();
	}
});

Ext.reg('zarafa.settingsoofwidget', Zarafa.mail.settings.SettingsOofWidget);
Ext.namespace('Zarafa.mail.settings');

/**
 * @class Zarafa.mail.settings.SettingsOutOfOfficeCategory
 * @extends Zarafa.settings.ui.SettingsCategory
 * @xtype zarafa.settingsoutofofficecategory
 *
 * The category for users which willallow the user to configure Out-of-Office related settings
 */
Zarafa.mail.settings.SettingsOutOfOfficeCategory = Ext.extend(Zarafa.settings.ui.SettingsCategory, {
	// Insertion points for this class
	/**
	 * @insert context.settings.category.outofoffice
	 * Insertion point to register new {@link Zarafa.settings.ui.SettingsWidget widgets}
	 * for the {@link Zarafa.mail.settings.SettingsOutOfOfficeCategory Out of Office Category}.
	 * @param {Zarafa.mail.settings.SettingsOutOfOfficeCategory} category The Out of Office
	 * category to which the widgets will be added.
	 */

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

		Ext.applyIf(config, {
			title: _('Out of Office'),
			categoryIndex: 2,
			iconCls: 'zarafa-settings-category-oof',
			items: [{
				xtype: 'zarafa.settingsoofwidget',
				settingsContext: config.settingsContext
			},
			container.populateInsertionPoint('context.settings.category.outofoffice', this)
			]
		});

		Zarafa.mail.settings.SettingsOutOfOfficeCategory.superclass.constructor.call(this, config);
	},

	/**
	 * Event handler for the
	 * {@link Zarafa.settings.SettingsContextModel ContextModel}#{@link Zarafa.settings.SettingsContextModel#beforesavesettings beforesavesettings}
	 * event. It will register the 'beforesave' event on {@link Zarafa.mail.settings.OofStore  outofoffice Store}.
	 * @private
	 */
	onBeforeSaveSettingsModel: function()
	{
		var store = this.get(0).getOofStore();
		this.mon(store, 'beforesave', this.onBeforeSaveOofSettings, this, { single: true });
	},

	/**
	 * Event handler which is fired when the {@link Zarafa.mail.settings.OofStore outofoffice Store}
	 * fires the 'beforesave' event. This will {@link #displaySavingMask show a notification} and register the
	 * event handlers for the completion of the save.
	 * @private
	 */
	onBeforeSaveOofSettings: function()
	{
		this.displaySavingMask();

		var store = this.get(0).getOofStore();
		this.mon(store, 'save', this.onOofSettingsSave, this);
		this.mon(store, 'exception', this.onOofSettingsException, this);
	},

	/**
	 * Event handler which is fired when the {@link Zarafa.mail.settings.OofStore outofoffice Store}
	 * fires the 'save' event indicating the successful save of the out of office settings. This will
	 * {@link #hideSavingMask hide the notification}.
	 * @private
	 */
	onOofSettingsSave: function()
	{
		this.hideSavingMask(true);

		var store = this.get(0).getOofStore();
		this.mun(store, 'save', this.onOofSettingsSave, this);
		this.mun(store, 'exception', this.onOofSettingsException, this);
	},

	/**
	 * Event handler which is fired when the {@link Zarafa.mail.settings.OofStore outofoffice Store}
	 * fires the 'exception' event indicating a failing save of the out of office settings. This will
	 * {@link #hideSavingMask hide the notification}.
	 * @private
	 */
	onOofSettingsException: function()
	{
		this.hideSavingMask(false);

		var store = this.get(0).getOofStore();
		this.mun(store, 'save', this.onOofSettingsSave, this);
		this.mun(store, 'exception', this.onOofSettingsException, this);
	}

});

Ext.reg('zarafa.settingsoutofofficecategory', Zarafa.mail.settings.SettingsOutOfOfficeCategory);
Ext.namespace('Zarafa.mail.settings');

/**
 * @class Zarafa.settings.ui.SettingsSafeSenderCategory
 * @extends Zarafa.settings.ui.SettingsCategory
 * @xtype zarafa.settingssafesendercategory
 *
 * The category for users which will allow the user to update safe senders list.
 */
Zarafa.mail.settings.SettingsSafeSenderCategory = Ext.extend(Zarafa.settings.ui.SettingsCategory, {

	/**
	 * @insert context.settings.category.safesender
	 * Insertion point to register new {@link Zarafa.settings.ui.SettingsWidget widgets}
	 * for the {@link Zarafa.mail.settings.SettingsSafeSenderCategory Safe Senders Category}.
	 * @param {Zarafa.mail.settings.SettingsSafeSenderCategory} category The Safe Senders
	 * category to which the widgets will be added.
	 */

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

		Ext.applyIf(config, {
			title: _('Safe Senders'),
			categoryIndex: 8,
			iconCls: 'zarafa-settings-category-safesenders',
			items: [{
				xtype: 'zarafa.settingssafesenderswidget',
				settingsContext: config.settingsContext
			},
				container.populateInsertionPoint('context.settings.category.safesender', this)
			]
		});

		Zarafa.mail.settings.SettingsSafeSenderCategory.superclass.constructor.call(this, config);
	}
});

Ext.reg('zarafa.settingssafesendercategory', Zarafa.mail.settings.SettingsSafeSenderCategory);
Ext.namespace('Zarafa.mail.settings');

/**
 * @class Zarafa.mail.settings.SettingsSafeSendersWidget
 * @extends Zarafa.settings.ui.SettingsWidget
 * @xtype zarafa.settingssafesenderswidget
 *
 * The {@link Zarafa.settings.ui.SettingsWidget widget} for configuring
 * safesenders in the {@link Zarafa.mail.settings.SettingsMailCategory mail category}
 */
Zarafa.mail.settings.SettingsSafeSendersWidget = Ext.extend(Zarafa.settings.ui.SettingsWidget, {
	/**
	 * @property {Zarafa.settings.SettingsModel} model
	 */
	model: undefined,

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

		Ext.applyIf(config, {
			title: _('Safe Senders'),
			cls: 'zarafa-settings-widget k-settings-nogap',
			xtype: 'zarafa.settingssafesenderswidget',
			name: 'zarafa/v1/contexts/mail/safe_senders_list',
			height: 400,
			layout: {
				type: 'vbox',
				align: 'stretch',
				pack: 'start'
			},
			items: [{
				xtype: 'displayfield',
				value: _('To protect your privacy email addresses or domains can only be added via the "Add to safe sender list" functionality in an opened email.'),
				fieldClass: 'x-form-display-field zarafa-settings-widget-extrainfo'
			},{
				xtype: 'container',
				layout: {
					type: 'hbox',
					pack: 'start'
				},
				items: [{
					xtype: 'zarafa.safesendergrid',
					ref: '../safeSendersGrid',
					flex:1
				},{
					xtype: 'container',
					height: 400,
					width: 160,
					layout: {
						type: 'vbox',
						align: 'center',
						pack: 'start'
					},
					items: [{
						xtype: 'button',
						handler: this.onDeleteSafeSender,
						text: _('Delete'),
						width: 130,
						ref: '../../deleteButton',
						disabled: true,
						scope: this
					},{
						xtype: 'spacer',
						height: 10
					},{
						xtype: 'button',
						text: _('Delete all'),
						width: 130,
						handler: this.onDeleteAll,
						ref: '../../deleteAllButton',
						disabled: true,
						scope: this
					}]
				}]
			}]
		});

		Zarafa.mail.settings.SettingsSafeSendersWidget.superclass.constructor.call(this, config);
	},

	/**
	 * Initialize events for the widget.
	 * @private
	 */
	initEvents: function()
	{
		Zarafa.mail.settings.SettingsSafeSendersWidget.superclass.initEvents.call(this);

		// register event to enable/disable buttons
		this.mon(this.safeSendersGrid.getSelectionModel(), 'selectionchange', this.onGridSelectionChange, this);
		this.mon(this.safeSendersGrid.getView(), 'refresh', this.onRefreshView, this);

		// register event to set settingsContextModel dirty.
		this.mon(this.getStore(), {
			'remove': this.doStoreRemove,
			'clear': this.doStoreRemove,
			scope: this
		});
	},

	/**
	 * Event handler will be called when grid is refreshed in {@link Zarafa.mail.settings.SafeSenderGrid GridView}
	 * This handler will toggle 'Delete All' button and select first row of grid on every refresh.
	 * @param {Ext.grid.View} gridView that fired the event
	 */
	onRefreshView: function(gridView)
	{
		var safeSendersGrid = this.safeSendersGrid;
		if (!Ext.isEmpty(safeSendersGrid.getSafeSenders())) {
			safeSendersGrid.getSelectionModel().selectFirstRow();
		}

		var hasRows = gridView.hasRows();
		this.deleteAllButton.setDisabled(!hasRows);
	},

	/**
	 * Returns the {@link Ext.data.JsonStore store} associated
	 * with grid in this widget.
	 * @return {Ext.data.JsonStore} The store
	 */
	getStore: function()
	{
		return this.safeSendersGrid.getStore();
	},

	/**
	 * Event handler will be called when selection in {@link Zarafa.mail.settings.SafeSenderGrid safesenderGrid}
	 * has been changed.
	 * @param {Ext.grid.RowSelectionModel} selectionModel selection model that fired the event
	 */
	onGridSelectionChange: function(selectionModel)
	{
		var hasSelection = selectionModel.hasSelection();
		var hasRows = this.safeSendersGrid.getView().hasRows();

		// Disable the 'Delete' button if there is no selected rows
		// and also disable 'Delete All' button if there is no row left in grid.
		this.deleteButton.setDisabled(!hasSelection);
		this.deleteAllButton.setDisabled(!hasRows);
	},

	/**
	 * Called by the {@link Zarafa.settings.ui.SettingsCategory Category} when
	 * it has been called with {@link zarafa.settings.ui.SettingsCategory#update}.
	 * This is used to load the latest version of the settings from the
	 * {@link Zarafa.settings.SettingsModel} into the UI grid of this widget.
	 * @param {Zarafa.settings.SettingsModel} settingsModel The settings to load
	 */
	update: function(settingsModel)
	{
		this.model = settingsModel;
		var safeSenders = settingsModel.get(this.name, true);

		// Load all safesenders into the GridPanel
		var safeSendersNameList = {'item': []};
		Ext.each(safeSenders, function(item, index) {
			safeSendersNameList.item.push({name: item, id: index});
		});

		var store = this.getStore();
		store.loadData(safeSendersNameList);
	},

	/**
	 * Called by the {@link Zarafa.settings.ui.SettingsCategory Category} when
	 * it has been called with {@link zarafa.settings.ui.SettingsCategory#updateSettings}.
	 * This is used to update the settings from the UI into the {@link Zarafa.settings.SettingsModel settings model}.
	 * @param {Zarafa.settings.SettingsModel} settingsModel The settings to update
	 */
	updateSettings: function(settingsModel)
	{
		settingsModel.beginEdit();
		settingsModel.set(this.safeSendersGrid.name, this.safeSendersGrid.getSafeSenders());
		settingsModel.endEdit();
	},

	/**
	 * Event handler for the {@link Ext.data.JsonStore#remove} event which is fired
	 * by the {@link Ext.data.JsonStore JsonStore} associated with the {@link #safeSendersGrid}.
	 * This will mark the {@link Zarafa.settings.SettingsContextModel} as
	 * {@link Zarafa.settings.SettingsContextModel#setDirty dirty}.
	 * @param {Ext.data.Store} store The store which fired the event
	 * @param {Ext.data.Record} record The record which is removed
	 * @param {Number} index index of the record in store which is removed
	 * @private
	 */
	doStoreRemove: function(store, record, index)
	{
		this.settingsContext.getModel().setDirty();
	},

	/**
	 * Handler function which is called when the 'Delete' button has been pressed. This
	 * will call {@link Zarafa.mail.settings.SafeSenderGrid#deleteSafeSender} to remove selected row in
	 * {@link Zarafa.mail.settings.SafeSenderGrid grid}
	 * @private
	 */
	onDeleteSafeSender: function()
	{
		this.safeSendersGrid.deleteSafeSender();
	},

	/**
	 * Handler function which is called when the 'Delete all' button has been pressed. This
	 * will call {@link Zarafa.mail.settings.SafeSenderGrid#deleteAllSafeSender} to remove all rows in
	 * {@link Zarafa.mail.settings.SafeSenderGrid grid}
	 * @private
	 */
	onDeleteAll: function()
	{
		this.safeSendersGrid.deleteAllSafeSender();
	}

});

Ext.reg('zarafa.settingssafesenderswidget', Zarafa.mail.settings.SettingsSafeSendersWidget);
/*
 * #dependsFile client/zarafa/common/ui/htmleditor/Fonts.js
 */
Ext.namespace('Zarafa.mail.settings');

/**
 * @class Zarafa.mail.settings.SettingsSignaturesWidget
 * @extends Zarafa.settings.ui.SettingsWidget
 * @xtype zarafa.settingssignatureswidget
 *
 *
 * The {@link Zarafa.settings.ui.SettingsWidget widget} for configuring
 * the Signatures settings in the {@link Zarafa.mail.settings.SettingsMailCategory mail category}.
 */
Zarafa.mail.settings.SettingsSignaturesWidget = Ext.extend(Zarafa.settings.ui.SettingsWidget, {
	/**
	 * True when the currently {@link #selectedSignature edited signature}
	 * has been modified. This will trigger requests to the user if he wishes
	 * to save the signature. The dirty state should always be checked through
	 * {@link #hasDirtySignature} as that will check if any of the form fields
	 * have unsaved modifications.
	 * @property
	 * @type Boolean
	 * @private
	 */
	dirtySelectedSignature: false,

	/**
	 * The currently selected record which the user is editing. If {@link #dirtySelectedSignature}
	 * changes were made to the textfields and the user should press 'Save' to store all changes
	 * into the record.
	 * @property
	 * @type Ext.data.Record
	 * @private
	 */
	selectedSignature: undefined,

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

		var store = {
			xtype: 'jsonstore',
			fields: [
				{ name: 'id', type: 'int' },
				{ name: 'name' },
				{ name: 'content' },
				{ name: 'isHTML', type: 'boolean' }
			],
			sortInfo: {
				field: 'id',
				direction: 'ASC'
			},
			autoDestroy: true
		};

		Ext.applyIf(config, {
			title: _('Signatures'),
			cls: 'k-settings-signatures-widget',
			items: [{
				xtype: 'container',
				layout: 'column',
				items: [{
					xtype: 'grid',
					name: 'zarafa/v1/contexts/mail/signatures/all',
					ref: '../signaturesGrid',
					cls:'zarafa-settings-signatures',
					columnWidth: 0.5,
					height: 200,
					store: store,
					hideHeaders: true,
					viewConfig: {
						forceFit: true,
						deferEmptyText: false,
						emptyText: '<div class="emptytext">' + _('No signatures configured') + '</div>'
					},
					sm: new Ext.grid.RowSelectionModel({
						singleSelect: true,
						listeners: {
							beforerowselect: this.onSignatureBeforeRowSelect,
							selectionchange: this.onSignatureSelectionChange,
							scope: this
						}
					}),
					columns: [{
						dataIndex: 'name',
						header: '&#160;',
						renderer: Ext.util.Format.htmlEncode
					}],
					buttons: [{
						text: _('New'),
						ref: '../../../newSignatureBtn',
						handler: this.onAddSignature,
						scope: this
					},{
						text: _('Delete'),
						ref: '../../../delSignatureBtn',
						handler: this.onDeleteSignature,
						scope: this
					}]
				},{
					xtype: 'container',
					layout: 'form',
					columnWidth: 0.5,
					labelWidth: 150,
					items: [{
						xtype: 'combo',
						ref: '../../newMessageCombo',
						name: 'zarafa/v1/contexts/mail/signatures/new_message',
						fieldLabel: _('New messages'),
						anchor: '100%',
						store: store,
						mode: 'local',
						triggerAction: 'all',
						displayField: 'name',
						valueField: 'id',
						lazyInit: false,
						autoSelect: true,
						forceSelection: true,
						editable: false,
						listeners: {
							select: this.onSignatureComboSelect,
							scope: this
						}
					},{
						xtype: 'combo',
						ref: '../../replyMessageCombo',
						name: 'zarafa/v1/contexts/mail/signatures/replyforward_message',
						fieldLabel: _('Replies/forwards'),
						anchor: '100%',
						store: store,
						mode: 'local',
						triggerAction: 'all',
						displayField: 'name',
						valueField: 'id',
						lazyInit: false,
						autoSelect: true,
						forceSelection: true,
						editable: false,
						listeners: {
							select: this.onSignatureComboSelect,
							scope: this
						}
					}]
				}]
			},{
				xtype: 'textfield',
				name: 'name',
				cls: 'k-signature-name',
				fieldLabel: _('Signature name'),
				hideLabel: true,
				ref: 'nameField',
				anchor: '100%',
				emptyText: _('Signature name:'),
				listeners: {
					change: this.onNameFieldChange,
					scope: this
				}
			},{
				xtype: 'zarafa.editorfield',
				cls: 'k-signature-editor',
				name: 'content',
				htmlName: 'content',
				plaintextName: 'content',
				ref: 'contentField',
				anchor: '100%',
				height: 300,
				useHtml: container.getSettingsModel().get('zarafa/v1/contexts/mail/dialogs/mailcreate/use_html_editor'),
				disableEditor: true,
				listeners: {
					change: this.onEditorFieldChange,
					valuecorrection: this.onValueCorrection,
					scope: this
				}
			}],
			buttons: [{
				text: _('Save Signature'),
				ref: '../saveSignatureBtn',
				handler: this.onSaveSignature,
				scope: this
			}],
			listeners:{
				afterrender:this.onAfterRender,
				delay:1,
				scope:this
			}
		});

		Zarafa.mail.settings.SettingsSignaturesWidget.superclass.constructor.call(this, config);
	},

	/**
	 * Called during initialization.
	 *
	 * This will bind the {@link Ext.data.Store store} in the {@link Ext.grid.GridPanel} with those
	 * in the 2 {@link Ext.form.ComboBox comboboxes}. This will ensure that any change made in the
	 * editor will be reflected in the comboboxes as well. See {@link #onGridAddSignature},
	 * {@link #onGridDelSignature} and {@link #onGridUpdateSignature}.
	 * @private
	 */
	initEvents: function()
	{
		// Connect the store from the grid to the stores inside the comboboxes
		var source = this.signaturesGrid.getStore();

		this.mon(source, 'add', this.onGridAddSignature, this);
		this.mon(source, 'remove', this.onGridDelSignature, this);
		this.mon(source, 'update', this.onGridUpdateSignature, this);
	},

	/**
	 * Event handler triggered after rendering settings signatures widget.
	 * If {@link Zarafa.mail.settings.SettingsMailCategory SettingsMailCategory} has
	 * scrollToSignatureWidget to true it will scroll the scroll bar to settings widget.
	 */
	onAfterRender: function()
	{
		if (this.settingsMailCategory.scrollToSignatureWidget) {
			var dom = this.settingsMailCategory.getEl().dom;
			dom.scrollTo('t', dom.scrollHeight);
			delete this.settingsMailCategory.scrollToSignatureWidget;
		}
	},

	/**
	 * Event handler which is fired when the {@link Ext.data.Store} in the
	 * {@link Ext.grid.GridPanel} fires the {@link Ext.data.Store#add add}
	 * event. This will add the corresponding records to the 2
	 * {@link Ext.form.ComboBox comboboxes}.
	 * @param {Ext.data.Store} store The store which fired the event
	 * @param {Ext.data.Record} records The records which were added
	 * @param {Number} index The where the records were added
	 * @private
	 */
	onGridAddSignature: function(store, records, index)
	{
		var d1 = this.newMessageCombo.getStore();
		var d2 = this.replyMessageCombo.getStore();

		if (!Array.isArray(records)) {
			records = [ records ];
		}

		var copy1 = [];
		var copy2 = [];
		for (var i = 0, len = records.length; i < len; i++) {
			var data;

			data = Ext.apply({}, records[i].data);
			copy1.push(new d1.recordType(data, records[i].id));
			data = Ext.apply({}, records[i].data);
			copy2.push(new d2.recordType(data, records[i].id));
		}

		// Insert with a +1 offset, as these 2 stores have a fake
		// entry inside it which is always the first entry in the store.
		d1.insert(index + 1, copy1);
		d2.insert(index + 1, copy2);

		// Change the current selection if this is the first
		// signature to be added.
		if (d1.getCount() === records.length) {
			this.newMessageCombo.setValue(d1.getAt(0).get('id'));
		}
		if (d2.getCount() === records.length) {
			this.replyMessageCombo.setValue(d2.getAt(0).get('id'));
		}
	},

	/**
	 * Event handler which is fired when the {@link Ext.data.Store} in the
	 * {@link Ext.grid.GridPanel} fires the {@link Ext.data.Store#remove remove}
	 * event. This will remove the corresponding records from the 2
	 * {@link Ext.form.ComboBox comboboxes}.
	 * @param {Ext.data.Store} store The store which fired the event
	 * @param {Ext.data.Record} records The records which were deleted
	 * @param {Number} index The index from where the records were deleted
	 * @private
	 */
	onGridDelSignature: function(store, records, index)
	{
		var d1 = this.newMessageCombo.getStore();
		var d2 = this.replyMessageCombo.getStore();

		if (!Array.isArray(records)) {
			records = [ records ];
		}

		for (var i = 0, len = records.length; i < len; i++) {
			var orig;

			orig = d1.getById(records[i].id);
			d1.remove(orig);
			orig = d2.getById(records[i].id);
			d2.remove(orig);
		}

		// Change the current selection when the deleted record
		// was the currently selected record...
		records = d1.getById(this.newMessageCombo.getValue());
		if (!records) {
			this.newMessageCombo.setValue(0);
		}
		records = d1.getById(this.replyMessageCombo.getValue());
		if (!records) {
			this.replyMessageCombo.setValue(0);
		}
	},

	/**
	 * Event handler which is fired when the {@link Ext.data.Store} in the
	 * {@link Ext.grid.GridPanel} fires the {@link Ext.data.Store#update update}
	 * event. This will merge the changes of the given records in the corresponding
	 * records in the 2 {@link Ext.form.ComboBox comboboxes}.
	 * @param {Ext.data.Store} store The store which fired the event
	 * @param {Ext.data.Record} records The records which were updated
	 * @private
	 */
	onGridUpdateSignature: function(store, records)
	{
		var d1 = this.newMessageCombo.getStore();
		var d2 = this.replyMessageCombo.getStore();

		if (!Array.isArray(records)) {
			records = [ records ];
		}

		for (var i = 0, len = records.length; i < len; i++) {
			var orig;

			orig = d1.getById(records[i].id);
			Ext.apply(orig.data, records[i].data);
			orig = d2.getById(records[i].id);
			Ext.apply(orig.data, records[i].data);
		}

		d1.fireEvent('datachanged', d1);
		d2.fireEvent('datachanged', d2);

		// Force reload the current selection, by default the 'datachanged'
		// event will ensure that the dropdownlist of the combobox is changed,
		// however for the currently selected value we must force selection.
		this.newMessageCombo.setValue(this.newMessageCombo.getValue());
		this.replyMessageCombo.setValue(this.replyMessageCombo.getValue());
	},

	/**
	 * Called by the {@link Zarafa.settings.ui.SettingsCategory Category} when
	 * it has been called with {@link zarafa.settings.ui.SettingsCategory#update}.
	 * This is used to load the latest version of the settings from the
	 * {@link Zarafa.settings.SettingsModel} into the UI of this category.
	 * @param {Zarafa.settings.SettingsModel} settingsModel The settings to load
	 */
	update: function(settingsModel)
	{
		this.model = settingsModel;

		// Reset the current selection
		this.doSelectSignature(undefined);

		// Convert the signatures into Store data
		var signatures = settingsModel.get(this.signaturesGrid.name, true);
		var signaturesArray = [];
		for (var key in signatures) {
			signaturesArray.push(Ext.apply({}, signatures[key], { id: key }));
		}

		// Load all signatures into the GridPanel
		var store = this.signaturesGrid.getStore();
		store.loadData(signaturesArray);

		// For the Signatures selection combo, we must add a fake entry called <None>
		// to disable the signatures in some situations.
		var signaturesIdArray = [{ id: 0, name: '<'+ _('None') + '>' }].concat(signaturesArray);

		// Load the signature ids into the New Message, and select the correct value
		// based on the settings.
		store = this.newMessageCombo.getStore();
		store.loadData(signaturesIdArray);
		var newId = settingsModel.get(this.newMessageCombo.name);
		var newRecord = store.getById(newId);
		if (!newRecord) {
			newId = 0; // Select the <None> option
		}
		this.newMessageCombo.setValue(newId);

		// Load the signature ids into the Reply/Forward Message, and select the correct value
		// based on the settings.
		store = this.replyMessageCombo.getStore();
		store.loadData(signaturesIdArray);
		var replyId = settingsModel.get(this.replyMessageCombo.name);
		var replyRecord = store.getById(replyId);
		if (!replyRecord) {
			replyId = 0; // Select the <None> option
		}
		this.replyMessageCombo.setValue(replyId);

		// By default the name and content fields will be disabled, but just for the fun
		// we will toggle the editortype to something we actually expect
		var useHtml = settingsModel.get('zarafa/v1/contexts/mail/dialogs/mailcreate/use_html_editor');
		this.contentField.setHtmlEditor(useHtml, false);

		// We also want to be cool, so when the user toggles the use_html_editor combobox we directly
		// change this editor immediately.
		this.mon(settingsModel, 'set', this.onSettingsSet, this);
	},

	/**
	 * Called by the {@link Zarafa.settings.ui.SettingsCategory Category} when
	 * it has been called with {@link zarafa.settings.ui.SettingsCategory#updateSettings}.
	 * This is used to update the settings from the UI into the {@link Zarafa.settings.SettingsModel settings model}.
	 * @param {Zarafa.settings.SettingsModel} settingsModel The settings to update
	 */
	updateSettings: function(settingsModel)
	{
		settingsModel.beginEdit();

		// In case we were editing a signature, save it now
		this.onSaveSignature();

		// Start reading the Grid store and convert the contents back into
		// an object which can be pushed to the settings.
		var signatures = this.signaturesGrid.getStore().getRange();
		var signaturesData = {};
		for (var i = 0, len = signatures.length; i < len; i++) {
			var signature = signatures[i];

			signaturesData[signature.get('id')] = {
				'name': signature.get('name'),
				'content': signature.get('content'),
				'isHTML': signature.get('isHTML')
			};
		}

		// If all signatures are removed, remove the full setting path. Otherwise the empty JavaScript object {}
		// will be interpreted by PHP as an object and cause 4 empty signatures
		if (signatures.length !== 0) {
			settingsModel.set(this.signaturesGrid.name, signaturesData);
		} else {
			settingsModel.remove(this.signaturesGrid.name);
		}

		// Update the default signatures
		var newId = this.newMessageCombo.getValue();
		if (newId !== 0) {
			settingsModel.set(this.newMessageCombo.name, newId);
		} else {
			settingsModel.remove(this.newMessageCombo.name);
		}

		var replyId = this.replyMessageCombo.getValue();
		if (replyId !== 0) {
			settingsModel.set(this.replyMessageCombo.name, replyId);
		} else {
			settingsModel.remove(this.replyMessageCombo.name);
		}

		settingsModel.endEdit();
	},

	/**
	 * Event handler which is fired when {@link #model} fires the
	 * {@link Zarafa.settings.SettingsModel#set set} event. This will
	 * check if the 'use_html_editor' setting was changed by the user,
	 * and will {@link Zarafa.common.ui.EditorField#setHtmlEditor toggle} the
	 * {@link Zarafa.common.ui.EditorField EditorField} accordingly.
	 * @param {Zarafa.settings.SettingsModel} settingsModel The model which fired the event
	 * @param {Object|Array} settings The modified settings
	 * @private
	 */
	onSettingsSet: function(settingsModel, settings)
	{
		if (!Array.isArray(settings)) {
			settings = [ settings ];
		}

		for (var i = 0, len = settings.length; i < len; i++) {
			var setting = settings[i];
			if (setting.path === 'zarafa/v1/contexts/mail/dialogs/mailcreate/use_html_editor') {
				this.contentField.setHtmlEditor(setting.value);
			}
		}
	},

	/**
	 * Event handler which is called when a selection has been made in the
	 * Signature selection combobox
	 *
	 * @param {Ext.form.ComboBox} field The field which fired the event
	 * @param {Ext.data.Record} record The selected record
	 */
	onSignatureComboSelect: function(field, record)
	{
		if (this.model) {
			var set = record.get(field.valueField);
			if (set === 0) {
				set = undefined;
			}

			// FIXME: The settings model should be able to detect if
			// a change was applied
			if (this.model.get(field.name) !== set) {
				this.model.set(field.name, set);
			}
		}
	},

	/**
	 * Event handler which is fired when the {@link Ext.grid.RowSelectionModel selectionModel}
	 * fires the {@link Ext.grid.RowSelectionModel#beforerowselect} event. This will check
	 * if the {@link #selectedSignature} contains {@link #hasDirtySignature modifications}.
	 * If this is the case, the function will return false to prevent the selection from being
	 * switched, and will display a {@link Ext.MessageBox MessageBox} asking the user if
	 * he wishes to save the signature (This will be handled by {@link #onSignatureBeforeRowSelectConfirm}).
	 *
	 * @param {Ext.grid.RowSelectionModel} selectionModel The model which fired the event
	 * @param {Number} rowIndex The row of the record which was selected
	 * @param {Boolean} keepExisting True if the already existing selection should be preserved
	 * @param {Ext.data.Record} record The selected record
	 * @private
	 */
	onSignatureBeforeRowSelect: function(selectionModel, rowIndex, keepExisting, record)
	{
		// Change focus, this should trigger a change event in the
		// textfield if we currently had our focus there...
		this.signaturesGrid.focus();
		if (this.hasDirtySignature()) {
			Ext.MessageBox.show({
				title: _('Unsaved changes'),
				msg: _('You have unsaved changes. Do you wish to save the changes?'),
				fn: this.onSignatureBeforeRowSelectConfirm.createDelegate(this, [ selectionModel, rowIndex, keepExisting, record ], 1),
				buttons: Ext.MessageBox.YESNOCANCEL
			});

			return false;
		}
	},

	/**
	 * Event handler for the {@link Ext.MessageBox} as created by {@link #onSignatureBeforeRowSelect}.
	 * This will check what the user has responded, if 'cancel' then no action will be taken,
	 * when the user pressed 'yes' the {@link #selectedSignature} will be {@link #onSaveSignature saved},
	 * and if 'no' then we {@link #onRejectSignature reset the changes}. In the last 2 cases we will
	 * {@link Ext.grid.RowSelectionModel#selectRow select the requested row} again.
	 *
	 * @param {String} btn The button string which the user selected
	 * @param {Ext.grid.RowSelectionModel} selectionModel The selectionModel which must be updated
	 * @param {Number} rowIndex The row index which should be selected
	 * @param {Boolean} keepExisting True to preserve the currently existing selection
	 * @param {Ext.data.Record} record The record which should be selected
	 * @private
	 */
	onSignatureBeforeRowSelectConfirm: function(btn, selectionModel, rowIndex, keepExisting, record)
	{
		if (btn === 'cancel') {
			return;
		}

		if (btn === 'yes') {
			this.onSaveSignature();
		} else {
			this.onRejectSignature();
		}

		selectionModel.selectRow(rowIndex, keepExisting);
	},

	/**
	 * Event handler which is fired when the {@link Ext.grid.RowSelectionModel selection model}
	 * fires the {@link Ext.grid.RowSelectionModel#selectionchange selectionchange} event. This
	 * will call {@link #doSelectSignature}.
	 *
	 * @param {Ext.grid.RowSelectionModel} selectionModel The selectionModel which fired the event
	 * @private
	 */
	onSignatureSelectionChange: function(selectionModel)
	{
		var record = selectionModel.getSelected();
		this.doSelectSignature(record);
	},

	/**
	 * This will update the UI to select the given Signature {@link Ext.data.Record record}.
	 * This will {@link Ext.form.Field#enable enable} the form fields and load the
	 * {@link Ext.form.Field#setValue values} into the forms.
	 * It will finally apply the {@link Ext.form.Field#focus focus} on the
	 * {@link Zarafa.common.ui.EditorField Content Field} to ensure the user can directly
	 * start typing
	 *
	 * @param {Ext.data.Record} record The record which is selected
	 * @private
	 */
	doSelectSignature: function(record)
	{
		if (!Ext.isEmpty(record)) {
			this.selectedSignature = record;
			this.dirtySelectedSignature = false;

			// Enable all fields, load the data into them
			// For the Editor we also toggle the editor
			// based on the isHTML flag
			this.delSignatureBtn.enable();
			this.nameField.setValue(record.get('name'));
			this.nameField.enable();
			this.contentField.bindRecord(record);
			this.contentField.setHtmlEditor(record.get('isHTML'), false);
			this.contentField.setValue(record.get('content'));
			this.contentField.enable();
			this.saveSignatureBtn.enable();

			// Allow the user to directly start editing the content
			this.contentField.focus();
		} else {
			delete this.selectedSignature;
			this.dirtySelectedSignature = false;

			this.delSignatureBtn.disable();
			this.nameField.reset();
			this.nameField.disable();
			this.contentField.bindRecord();
			this.contentField.setValue("");
			this.contentField.disable();
			this.saveSignatureBtn.disable();
			this.signaturesGrid.getSelectionModel().clearSelections();
		}
	},

	/**
	 * Event handler which is fired when the user presses the 'Add' {@link Ext.Button button}.
	 * this will check if we are currently busy {@link #selectedSignature editing a signature},
	 * if this signature {@link #dirtySelectedSignature is modified} this will {@link Ext.MessageBox ask}
	 * the user if he wishes to save the changes (this will be handled by {@link #onAddSignatureConfirm}).
	 *
	 * If nothing needs to be saved, {@link #doAddSignature} will be called.
	 * @private
	 */
	onAddSignature: function()
	{
		// Change focus, this should trigger a change event in the
		// textfield if we currently had our focus there...
		this.signaturesGrid.focus();

		if (this.hasDirtySignature()) {
			Ext.MessageBox.show({
				title: _('Unsaved changes'),
				msg: _('You have unsaved changes. Do you wish to save the changes?'),
				fn: this.onAddSignatureConfirm.createDelegate(this),
				buttons: Ext.MessageBox.YESNOCANCEL
			});

			return;
		}

		this.doAddSignature();
	},

	/**
	 * Event handler for the {@link Ext.MessageBox} as created by {@link #onAddSignature}.
	 * If the user pressed 'cancel' no further action will be taken. If the user pressed
	 * 'yes' then the signature will be {@link #onSaveSignature}, if the user pressed 'no',
	 * then the {@link #onRejectSignature changes are reset}. In the last 2 cases, the new
	 * signature will be {@link #doAddSignature created}.
	 *
	 * @param {String} btn The button string which the user selected
	 * @private
	 */
	onAddSignatureConfirm: function(btn)
	{
		if (btn === 'cancel') {
			return;
		}

		if (btn === 'yes') {
			this.onSaveSignature();
		} else {
			this.onRejectSignature();
		}

		this.doAddSignature();
	},

	/**
	 * Create a new signature, this will create a new {@link Ext.data.Record record} which is
	 * directly {@link Ext.grid.RowSelectionModel#selectRecords selected in the grid} and
	 * will be {@link #doSelectSignature loaded into the UI}.
	 * @private
	 */
	doAddSignature: function()
	{
		var isHtmlEditor = this.contentField.isHtmlEditor();
		var store = this.signaturesGrid.getStore();
		var record = new store.recordType({
			id: new Date().getTime(),
			name: _('New signature'),
			isHTML: isHtmlEditor
		});

		store.add(record);

		// Add the signature to the settings
		if (this.model) {
			this.model.set(this.signaturesGrid.name + '/' + record.get('id'), {
				'name': record.get('name'),
				'content': record.get('content'),
				'isHTML': record.get('isHTML')
			});
		}

		// Automatically select the new signature
		this.signaturesGrid.getSelectionModel().selectRecords([ record ]);
		this.doSelectSignature(record);

		/*
		 * For the first time when we initialize htmlEditor with record, for empty value
		 * editor(especially in FF) sets <br> in the field so getValue() function of
		 * editor will return <br>, so for even unchanged new signature, it will so
		 * Message Dialog that signature is edited and not saved (WA-2919).
		 * The other issues is that we are not using defaultValue config for editor field.
		 * So to avoid this situation we are setting initial editor's value in to record.
		 */
		record.set('content', this.contentField.getValue());
	},

	/**
	 * Event handler which is fired when the 'Delete' button has been pressed. This
	 * will grab the {@link #selectedSignature currently selected} {@link Ext.data.Record record}
	 * and {@link Ext.data.Store#remove remove} it.
	 * @private
	 */
	onDeleteSignature: function()
	{
		// Remove the signature from the settings
		if (this.model) {
			this.model.remove(this.signaturesGrid.name + '/' + this.selectedSignature.get('id'));
		}

		var store = this.signaturesGrid.getStore();

		// Obtain the index of the current selection
		var oldIndex = store.indexOf(this.selectedSignature);

		store.remove(this.selectedSignature);
		delete this.selectedSignature;
		this.dirtySelectedSignature = false;

		// Select the next available row, this could either be the exact same
		// rowIndex as the one which we previously deleted (this means we select
		// the "next row" or we select the last row in the grid (which means
		// we select the "previous row".
		if (oldIndex < store.getCount()) {
			this.signaturesGrid.getSelectionModel().selectRow(oldIndex);
		} else {
			this.signaturesGrid.getSelectionModel().selectLastRow();
		}
	},

	/**
	 * Event handler which is fired when the user presses the 'Save' button. This
	 * will load the data from the {@link Ext.form.Field fields} into the currently
	 * {@link #selectedSignature selected} {@link Ext.data.Record record}.
	 * @private
	 */
	onSaveSignature: function()
	{
		var record = this.selectedSignature;
		if (record) {
			record.set(this.nameField.name, this.nameField.getValue());
			record.set(this.contentField.name, this.contentField.getValue());
			record.set('isHTML', this.contentField.isHtmlEditor(), true);
			record.commit();

			// We just saved the signature, reset the dirty flag
			this.dirtySelectedSignature = false;
		}
	},

	/**
	 * Event handler which will reset all changes made to the {@link #selectedSignature selected}
	 * {@link Ext.data.Record record}. This will all data from the signature back into
	 * the [@link Ext.form.Field fields} and reset {@link #dirtySelectedSignature}.
	 * @private
	 */
	onRejectSignature: function()
	{
		var record = this.selectedSignature;
		if (record) {
			this.nameField.setValue(record.get(this.nameField.name));

			// First, remove all the content form editor to avoid conflict in changes before reset the content
			// as saved in record at its original value.
			this.contentField.setValue("");
			this.contentField.setValue(record.get(this.contentField.name));

			// We just reset the signature, reset the dirty flag
			this.dirtySelectedSignature = false;
		}
	},

	/**
	 * Event handler which is called when one of the {@link #nameField} has been changed.
	 * This will apply the new value to the settings.
	 * @param {Ext.form.Field} field The field which has fired the event
	 * @param {String} value The new value
	 * @private
	 */
	onNameFieldChange: function(field, value)
	{
		if (this.selectedSignature) {
			if (this.selectedSignature.get(field.name) !== value) {
				this.dirtySelectedSignature = true;
			}
		}
	},

	/**
	 * Event handler which is called when the {@link #contentField} has been changed.
	 * This will apply the new value to the settings.
	 * @param {Ext.form.Field} field The field which has fired the event
	 * @param {String} value The new value
	 * @private
	 */
	onEditorFieldChange: function(field, value)
	{
		if (this.selectedSignature) {
			if (this.selectedSignature.get(field.name) !== this.contentField.getValue()) {
				this.dirtySelectedSignature = true;
			}
		}
	},

	/**
	 * @return {Boolean} True when there are {@link #dirtySelectedSignature changes}
	 * made to the {@link #selectedSignatuer selected signature} or if any of the
	 * form fields still have pending changes.
	 */
	hasDirtySignature: function()
	{
		if (this.selectedSignature) {
			// If the dirty flag is already enabled, then no
			// need to check for modifications. Otherwise check
			// all fields.
			if (this.dirtySelectedSignature !== true) {
				this.onNameFieldChange(this.nameField, this.nameField.getValue());
				this.onEditorFieldChange(this.contentField, this.contentField.getValue());
			}

			return this.dirtySelectedSignature;
		}

		return false;
	},

	/**
	 * Event handler for the {@link Zarafa.common.ui.HtmlEditor#valuecorrection valuecorrection}
	 * event which is fired when the HTML content gets adjusted automatically by tinyMCE quirks logic
	 * after it has been set into the {@link Zarafa.common.ui.HtmlEditor}.
	 * This will apply the corrected value silently into the {@link #selectedSignature} to prevent considering this as a change.
	 * @param {Zarafa.common.ui.HtmlEditor} field The field which fired the event
	 * @param {String} value The corrected value
	 * @param {String} oldValue the original value which was applied.
	 * @private
	 */
	onValueCorrection: function(field, value, oldValue)
	{
		if ( this.selectedSignature ) {
			this.selectedSignature.data.content = value;
		}
	}
});

Ext.reg('zarafa.settingssignatureswidget', Zarafa.mail.settings.SettingsSignaturesWidget);
/*
 * #dependsFile client/zarafa/common/ui/messagepanel/MessageBody.js
 * #dependsFile client/zarafa/common/ui/messagepanel/MessageHeader.js
 */
Ext.namespace('Zarafa.mail.ui');

/**
 * @class Zarafa.mail.ui.ConversationViewPanel
 * @extends Ext.Panel
 * @xtype zarafa.conversationviewpanel
 *
 * Preview panel content that shows all messages of a conversation stacked,
 * newest at the top. Every message is a collapsible card containing the full
 * standard message view (header with sender, recipients, attachments and the
 * external-content handling, plus the message body). Each card is its own
 * record component root: the message view components inside bind against the
 * card and receive exactly that card's record through the same pipeline the
 * normal single-message preview uses.
 *
 * The newest {@link #autoExpandCount} messages are expanded (and thus loaded)
 * immediately, older ones when their card is expanded.
 */
Zarafa.mail.ui.ConversationViewPanel = Ext.extend(Ext.Panel, {
	/**
	 * @cfg {Function} resolveConversation Function that returns the current
	 * items (newest first) of the conversation the given record belongs to,
	 * or false. Used to refresh the panel when the conversation membership
	 * changes (e.g. the sent items arrive after the preview was opened).
	 */
	resolveConversation: undefined,

	/**
	 * @cfg {Number} autoExpandCount The number of newest messages that are
	 * expanded (and thus loaded) when the conversation is shown.
	 */
	autoExpandCount: 15,

	/**
	 * Key (concatenated entryids) of the conversation that is currently shown.
	 * @property
	 * @type String
	 */
	conversationKey: undefined,

	/**
	 * The record that is selected in the mail grid.
	 * @property
	 * @type Zarafa.mail.MailRecord
	 */
	selectedRecord: undefined,

	/**
	 * Map of entryid to the {@link Ext.Panel card} that shows the message.
	 * @property
	 * @type Object
	 */
	recordCards: undefined,

	/**
	 * The store whose 'datachanged' event is monitored to detect changes of
	 * the conversation membership.
	 * @property
	 * @type Zarafa.mail.MailStore
	 */
	boundStore: undefined,

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

		Ext.applyIf(config, {
			xtype: 'zarafa.conversationviewpanel',
			cls: 'zarafa-mailconversationpanel',
			border: false,
			autoScroll: true,
			bodyStyle: 'padding: 9px;'
		});

		Zarafa.mail.ui.ConversationViewPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Shows the given conversation. When the same conversation is already
	 * shown, only the selected message is scrolled into view.
	 *
	 * @param {Zarafa.mail.MailRecord[]} records The items of the conversation, newest first.
	 * @param {Zarafa.mail.MailRecord} selected The record that is selected in the mail grid.
	 */
	showConversation: function(records, selected)
	{
		var key = records.map(function(record) { return record.get('entryid'); }).join(',');
		this.selectedRecord = selected;
		if (this.conversationKey === key) {
			this.focusRecord(selected);
			return;
		}
		this.conversationKey = key;

		if (records.length > 0) {
			this.bindStore(records[0].getStore());
		}

		this.removeAll(true);
		this.recordCards = {};

		Ext.each(records, function(record, index) {
			var card = this.createCard(record, index >= this.autoExpandCount);
			this.recordCards[record.get('entryid')] = card;
			this.add(card);
		}, this);

		this.doLayout();
		this.focusRecord(selected);
	},

	/**
	 * Monitors the given store to detect membership changes of the shown
	 * conversation (e.g. the sent items arrived after the preview was opened).
	 *
	 * @param {Zarafa.mail.MailStore} store The store to monitor.
	 * @private
	 */
	bindStore: function(store)
	{
		if (this.boundStore === store) {
			return;
		}
		if (this.boundStore) {
			this.mun(this.boundStore, 'datachanged', this.onStoreDataChanged, this);
		}
		this.boundStore = store;
		if (store) {
			this.mon(store, 'datachanged', this.onStoreDataChanged, this, { buffer: 100 });
		}
	},

	/**
	 * Called (buffered) when the monitored store changed: when the membership
	 * of the shown conversation changed, the panel is rebuilt.
	 * @private
	 */
	onStoreDataChanged: function()
	{
		if (!this.selectedRecord || !Ext.isFunction(this.resolveConversation)) {
			return;
		}

		var items = this.resolveConversation(this.selectedRecord);
		if (!Ext.isArray(items) || Ext.isEmpty(items)) {
			return;
		}

		var key = items.map(function(record) { return record.get('entryid'); }).join(',');
		if (key !== this.conversationKey) {
			this.showConversation(items, this.selectedRecord);
		}
	},

	/**
	 * Creates the card for one message of the conversation. The card carries
	 * its own {@link Zarafa.core.plugins.RecordComponentPlugin} so the message
	 * view components inside bind against the card, and thus against this
	 * card's message.
	 *
	 * @param {Zarafa.mail.MailRecord} record The message.
	 * @param {Boolean} collapsed True when the card starts collapsed.
	 * @return {Ext.Panel} The card.
	 * @private
	 */
	createCard: function(record, collapsed)
	{
		var sent = record.get('folder_name') === 'sent_items';
		var card = new Ext.Panel({
			cls: 'k-conversation-item' + (sent ? ' k-own-message' : ''),
			title: this.getCardTitle(record),
			collapsible: true,
			titleCollapse: true,
			animCollapse: false,
			collapsed: collapsed === true,
			border: false,
			plugins: [{
				ptype: 'zarafa.recordcomponentplugin',
				allowWrite: false,
				useShadowStore: true
			}],
			items: [{
				xtype: 'zarafa.messageheader'
			}, {
				xtype: 'zarafa.messagebody'
			}],
			listeners: {
				expand: this.activateCard,
				afterrender: function(panel) {
					if (!panel.collapsed) {
						this.activateCard(panel);
					}
				},
				// Fired by the card's record component plugin whenever the
				// record is set, loaded or updated: (re)fit the body.
				setrecord: this.onCardRecordEvent,
				loadrecord: this.onCardRecordEvent,
				updaterecord: this.onCardRecordEvent,
				scope: this
			}
		});
		card.conversationRecord = record;

		return card;
	},

	/**
	 * Hands the record of the given card to the card's record component
	 * plugin (once), which opens it and updates the message view components.
	 *
	 * @param {Ext.Panel} card The card to activate.
	 * @private
	 */
	activateCard: function(card)
	{
		if (card.recordActivated !== true && card.recordComponentPlugin && card.conversationRecord) {
			card.recordActivated = true;
			card.recordComponentPlugin.setRecord(card.conversationRecord);
		}
		this.settleCard(card);
	},

	/**
	 * Record event handler of a card: refit the body.
	 * @param {Ext.Panel} card The card the event belongs to.
	 * @private
	 */
	onCardRecordEvent: function(card)
	{
		this.settleCard(card);
	},

	/**
	 * Adjusts the body iframe of the card now and after the content settled
	 * (images, fonts, late rewrites of the iframe document).
	 *
	 * @param {Ext.Panel} card The card to adjust.
	 * @private
	 */
	settleCard: function(card)
	{
		var me = this;
		var run = function() {
			me.adjustCardBody(card);
		};
		run();
		setTimeout(run, 300);
		setTimeout(run, 1500);
	},

	/**
	 * Sizes the body iframe of a card to its content, so the whole
	 * conversation scrolls as one document, and makes sure wheel events
	 * inside the iframe scroll this panel.
	 *
	 * @param {Ext.Panel} card The card to adjust.
	 * @private
	 */
	adjustCardBody: function(card)
	{
		if (!card.rendered || card.collapsed || this.isDestroyed) {
			return;
		}

		if (!card.messageBodyCmp) {
			var found = card.findByType('zarafa.messagebody');
			card.messageBodyCmp = found.length > 0 ? found[0] : undefined;
		}
		var messageBody = card.messageBodyCmp;
		if (!messageBody || !messageBody.rendered) {
			return;
		}

		var iframe = messageBody.getEl().dom;
		try {
			var doc = iframe.contentDocument;
			var body = doc ? doc.body : null;
			if (body) {
				// Neutralize percentage heights, which would feed the current
				// iframe height back into the measurement.
				body.style.height = 'auto';
				if (doc.documentElement) {
					doc.documentElement.style.height = 'auto';
				}

				// scrollHeight is floored by the current viewport height, so
				// measure with a collapsed viewport to get the true content
				// height (otherwise any slack compounds on every re-measure).
				iframe.style.height = '10px';
				var contentHeight = body.scrollHeight;

				// Long messages are capped to roughly the pane height and
				// scroll themselves (see #forwardIframeScroll).
				var cap = Math.max(300, (this.body ? this.body.getHeight() : 900) - 140);
				iframe.style.height = Math.max(40, Math.min(contentHeight + 8, cap)) + 'px';
			}
		} catch (e) {
			// Cross-origin or detached iframe: keep the default height.
		}

		this.forwardIframeScroll(card, iframe);
	},

	/**
	 * Forwards mouse wheel events from inside the body iframe of a card to
	 * this panel: the iframes are sized to their content and never scroll
	 * themselves, but wheel events inside an iframe do not reach the parent
	 * document, which would make the conversation impossible to scroll while
	 * the cursor is above a message body.
	 *
	 * @param {Ext.Panel} card The card the iframe belongs to.
	 * @param {HTMLElement} iframe The body iframe of the card.
	 * @private
	 */
	forwardIframeScroll: function(card, iframe)
	{
		var me = this;
		try {
			var doc = iframe.contentDocument;
			if (!doc) {
				return;
			}

			// Writing the iframe document removes listeners but not always the
			// document object itself; unhook a previous handler to avoid
			// forwarding twice when the document was reused.
			if (card.wheelForwardDoc && card.wheelForwardHandler) {
				try {
					card.wheelForwardDoc.removeEventListener('wheel', card.wheelForwardHandler);
				} catch (e) {
					// The old document is gone; nothing to unhook.
				}
			}

			var handler = function(event) {
				// A message that is taller than its (capped) iframe scrolls
				// itself first; only at its top/bottom edge the wheel moves
				// the conversation. Short messages always move the conversation.
				try {
					var innerDoc = iframe.contentDocument;
					var scrollEl = innerDoc.scrollingElement || innerDoc.documentElement;
					var maxScroll = scrollEl.scrollHeight - scrollEl.clientHeight;
					if (maxScroll > 1) {
						var up = event.deltaY < 0;
						var atTop = scrollEl.scrollTop <= 0;
						var atBottom = scrollEl.scrollTop >= maxScroll - 1;
						if ((up && !atTop) || (!up && !atBottom)) {
							// Native scrolling of the message itself.
							return;
						}
					}
				} catch (e) {
					// Fall through to scrolling the conversation.
				}

				var scroller = me.body ? me.body.dom : null;
				if (scroller) {
					scroller.scrollTop += event.deltaY;
					event.preventDefault();
				}
			};
			doc.addEventListener('wheel', handler, { passive: false });
			card.wheelForwardDoc = doc;
			card.wheelForwardHandler = handler;
		} catch (e) {
			// Inaccessible iframe document: scrolling next to the cards still works.
		}
	},

	/**
	 * Builds the header line of a card: avatar, sender (or the recipients for
	 * an own message) and the date.
	 *
	 * @param {Zarafa.mail.MailRecord} record The message.
	 * @return {String} The header html.
	 * @private
	 */
	getCardTitle: function(record)
	{
		var sent = record.get('folder_name') === 'sent_items';
		var name;
		if (sent) {
			name = '→ ' + String(record.get('display_to') || '').split(';').map(function(part) {
				return part.trim();
			}).filter(function(part) {
				return !Ext.isEmpty(part);
			}).join(', ');
		} else {
			name = record.get('sent_representing_name') || record.get('sender_name') || '';
		}

		var initial = String(record.get('sent_representing_name') || record.get('sender_name') || '?').trim().charAt(0).toUpperCase();
		var date = record.get('message_delivery_time') || record.get('client_submit_time');
		var dateText = Ext.isDate(date) ? date.format(_('d-m-Y G:i')) : '';

		return '<span class="k-avatar">' + Ext.util.Format.htmlEncode(initial) + '</span>' +
			'<span class="k-sender">' + Ext.util.Format.htmlEncode(name) + '</span>' +
			'<span class="k-date">' + Ext.util.Format.htmlEncode(dateText) + '</span>';
	},

	/**
	 * Scrolls the card of the given record into view and highlights it.
	 *
	 * @param {Zarafa.mail.MailRecord} record The record to focus.
	 * @private
	 */
	focusRecord: function(record)
	{
		if (!record || !this.recordCards) {
			return;
		}
		var card = this.recordCards[record.get('entryid')];
		if (!card) {
			return;
		}

		if (card.collapsed) {
			card.expand();
		}

		(function() {
			if (this.isDestroyed || !card.el || !card.el.dom) {
				return;
			}
			card.el.scrollIntoView(this.body, false);
			Ext.each(this.items ? this.items.items : [], function(item) {
				item.el.removeClass('k-conversation-item-focus');
			});
			card.el.addClass('k-conversation-item-focus');
		}).defer(50, this);
	},

	/**
	 * Cleans up the store binding.
	 * @private
	 */
	onDestroy: function()
	{
		this.bindStore(undefined);
		Zarafa.mail.ui.ConversationViewPanel.superclass.onDestroy.apply(this, arguments);
	}
});

Ext.reg('zarafa.conversationviewpanel', Zarafa.mail.ui.ConversationViewPanel);
Ext.namespace('Zarafa.mail.ui');

/**
 * @class Zarafa.mail.ui.ExportAsContextMenu
 * @extends Zarafa.core.ui.menu.ConditionalMenu
 * @xtype zarafa.exportascontextmenu
 *
 * The ExportAsContextMenu is the context menu that is shown as submenu
 * of the {@link Zarafa.mail.ui.MailGridContextMenu MailGridContextMenu}.
 */
Zarafa.mail.ui.ExportAsContextMenu = Ext.extend(Zarafa.core.ui.menu.ConditionalMenu, {
	/**
	 * @cfg {Zarafa.core.data.IPMRecord[]} The records to which the actions in
	 * this context menu will apply
	 */
	records: [],

	/**
	 * The {@link Zarafa.mail.MailContextModel} which is obtained from the {@link #context}.
	 * @property
	 * @type Zarafa.mail.MailContextModel
	 */
	model: undefined,

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

		Ext.applyIf(config, {
			xtype: 'zarafa.exportascontextmenu',
			cls: 'k-categories',
			items: [{
				text: _('EML file(s)'),
				iconCls: 'icon_download',
				handler: this.onContextItemEml,
				scope: this
			}, {
				text: _('ZIP file'),
				hideOnDisabled: false,
				iconCls: 'icon_download_zip',
				beforeShow: this.onZipMenuItemBeforeShow,
				handler: this.onContextItemEmlZip,
				scope: this
			}]
		});

		Zarafa.mail.ui.ExportAsContextMenu.superclass.constructor.call(this, config);
	},

	/**
	 * Event handler which determines if menu items should be disable or not.
	 * It will check if records are more than the configured upper limit of
	 * total messages allowed to be included in single ZIP archive.
	 * it changes the display text of menu item by postfixing the maximum limit information.
	 *
	 * @param {Zarafa.core.ui.menu.ConditionalItem} item The item to enable/disable
	 * @param {Zarafa.core.data.IPMRecord[]} records The records which must be checked
	 * to see if the item must be enabled or disabled.
	 * @private
	 */
	onZipMenuItemBeforeShow: function (item, records)
	{
		var serverConfig = container.getServerConfig();
		var maxFiles = serverConfig.getMaxEmlFilesInZIP();

		if (records.length > maxFiles) {
			item.setText(item.text + ' ( ' + _('max. ') + maxFiles + ' )');
			item.disable();
		}
	},

	/**
	 * Event handler which is called when the user selects the 'Download as files'
	 * item in the context menu. This will request to download selected message
	 * as file (RFC822-formatted email stream) with eml extension.
	 * @private
	 */
	onContextItemEml: function ()
	{
		Zarafa.common.Actions.openSaveEmlDialog(this.records);
	},

	/**
	 * Event handler which is called when the user selects the 'Download as ZIP'
	 * item in the context menu. This will request to download selected message
	 * as file (RFC822-formatted email stream) with eml extension included in a ZIP archive.
	 * @private
	 */
	onContextItemEmlZip: function ()
	{
		Zarafa.common.Actions.openSaveEmlDialog(this.records, true);
	}
});

Ext.reg('zarafa.exportascontextmenu', Zarafa.mail.ui.ExportAsContextMenu);
/*
 * #dependsFile client/zarafa/common/ui/grid/Renderers.js
 */
Ext.namespace('Zarafa.mail.ui');

/**
 * @class Zarafa.mail.ui.MailGridColumnModel
 * @extends Zarafa.common.ui.grid.ColumnModel
 *
 * The {@link Zarafa.mail.ui.MailGridColumnModel MailGridColumnModel}
 * is the default {@link Ext.grid.ColumnModel ColumnModel} containing two
 * different sets of {@link Ext.grid.Column columns}. The first set contains
 * all {@link Ext.grid.Column columns} which should be available in the
 * {@link Zarafa.mail.ui.MailGrid MailGrid} (either hidden by default,
 * or directly visible). For a more compact view, a more compact set is
 * provided. Switching between the two sets can be done using
 * {@link Zarafa.mail.ui.MailGridColumnModel.useCompactView useCompactView}
 * during configuration, or {@link Zarafa.mail.ui.MailGridColumnModel.setCompactView setCompactView}
 * when the {@link Zarafa.mail.ui.MailGridColumnModel MailGridColumnModel} is already active.
 */
Zarafa.mail.ui.MailGridColumnModel = Ext.extend(Zarafa.common.ui.grid.ColumnModel, {
	/**
	 * @cfg {Boolean} useCompactView If true the compact column model will be
	 * used by default. Otherwise the default column model will be used which
	 * contains all possible columns.
	 */
	useCompactView: false,
	/**
	 * @constructor
	 * @param config Configuration structure
	 */
	constructor: function(config)
	{
		config = config || {};

		this.defaultColumns = this.createDefaultColumns();
		this.compactColumns = this.createCompactColumns();

		// Insertion point to allow more columns
		var insertColumns = container.populateInsertionPoint('context.mail.griddefaultcolumn', this) || [];
		this.defaultColumns = this.defaultColumns.concat(insertColumns);
		this.compactColumns = this.compactColumns.concat(insertColumns);

		Ext.applyIf(config, {
			columns: this.defaultColumns,
			defaults: {
				sortable: true
			}
		});

		// Switch to compact view if needed
		if (config.useCompactView === true) {
			config.columns = this.compactColumns;
		}

		Zarafa.mail.ui.MailGridColumnModel.superclass.constructor.call(this, config);
	},

	/**
	 * Create an array of {@link Ext.grid.Column columns} which must be visible within
	 * the default view of this {@link Ext.grid.ColumnModel ColumnModel}.
	 *
	 * @return {Ext.grid.Column[]} The array of columns
	 * @private
	 */
	createDefaultColumns: function()
	{
		return [{
			id: 'column_icon',
			header: '<p class="icon_index">&nbsp;<span class="title">' + _('Icon') + '</span></p>',
			headerCls: 'zarafa-icon-column icon',
			dataIndex: 'icon_index',
			width: 24,
			hideable: !container.isEnabledConversation(),
			renderer: Zarafa.common.ui.grid.Renderers.icon,
			fixed: true,
			preventColSwitch: container.isEnabledConversation(),
			tooltip: _('Sort by: Icon'),
			preventRowSelection: true
		},{
			header: '<p class="icon_importance">&nbsp;<span class="title">' + _('Importance') + '</span></p>',
			headerCls: 'zarafa-icon-column importance',
			dataIndex: 'importance',
			width: 24,
			renderer: Zarafa.common.ui.grid.Renderers.importance,
			fixed: true,
			tooltip: _('Sort by: Importance')
		},{
			header: '<p class="icon_paperclip">&nbsp;<span class="title">' + _('Attachment') + '</span></p>',
			headerCls: 'zarafa-icon-column attachment',
			dataIndex: 'hasattach',
			width: 24,
			renderer: Zarafa.common.ui.grid.Renderers.attachment,
			fixed: true,
			tooltip: _('Sort by: Attachment')
		},{
			header: _('From'),
			dataIndex: 'sent_representing_name',
			width: 100,
			renderer: Zarafa.common.ui.grid.Renderers.sender,
			tooltip: _('Sort by: From'),
			groupRenderer:this.groupHeaderBySender
		},{
			header: _('To'),
			dataIndex: 'display_to',
			width: 320,
			renderer: Zarafa.common.ui.grid.Renderers.to,
			tooltip: _('Sort by: To')
		},{
			header: _('Subject'),
			dataIndex: 'subject',
			width: 400,
			renderer: Zarafa.common.ui.grid.Renderers.subject,
			tooltip: _('Sort by: Subject')
		},{
			header: _('Categories'),
			dataIndex: 'categories',
			width: 160,
			renderer: Zarafa.common.ui.grid.Renderers.categories,
			tooltip: _('Sort by: Categories')
		},{
			header: _('Received'),
			dataIndex: 'message_delivery_time',
			width: 160,
			// Setting the renderer with createDelegate to be able to pass a meta object to the renderer.
			// This way we can add a css-class to the element (used by Selenium tests)
			renderer: Zarafa.common.ui.grid.Renderers.datetime.createDelegate(null, [{css: 'mail-received'}], true),
			tooltip: _('Sort by: Received'),
			groupRenderer: this.groupHeaderByDate.createDelegate(this, ['message_delivery_time'], 0)
		},{
			header: _('Sent'),
			dataIndex: 'client_submit_time',
			width: 160,
			// Setting the renderer with createDelegate to be able to pass a meta object to the renderer.
			// This way we can add a css-class to the element (used by Selenium tests)
			renderer: Zarafa.common.ui.grid.Renderers.datetime.createDelegate(null, [{css: 'mail-sent'}], true),
			tooltip: _('Sort by: Sent'),
			groupRenderer : this.groupHeaderByDate.createDelegate(this, ['client_submit_time'], 0)
		},{
			header: _('Scheduled'),
			dataIndex: 'deferred_send_time',
			width: 160,
			// Setting the renderer with createDelegate to be able to pass a meta object to the renderer.
			// This way we can add a css-class to the element (used by Selenium tests)
			renderer: Zarafa.common.ui.grid.Renderers.datetime.createDelegate(null, [{css: 'mail-sent'}], true),
			tooltip: _('Sort by: Scheduled'),
			groupRenderer : this.groupHeaderByDate.createDelegate(this, ['deferred_send_time'], 0)
		},{
			header: _('Modified'),
			dataIndex: 'last_modification_time',
			width: 160,
			// Setting the renderer with createDelegate to be able to pass a meta object to the renderer.
			// This way we can add a css-class to the element (used by Selenium tests)
			renderer: Zarafa.common.ui.grid.Renderers.datetime.createDelegate(null, [{css: 'mail-modified'}], true),
			hidden: true,
			tooltip: _('Sort by: Modified'),
			groupRenderer : this.groupHeaderByDate.createDelegate(this, ['last_modification_time'], 0)
		},{
			header: _('Size'),
			dataIndex: 'message_size',
			width: 80,
			renderer: Zarafa.common.ui.grid.Renderers.size,
			tooltip: _('Sort by: Size'),
			groupRenderer: this.groupHeaderBySize
		},{
			header: '<p class="icon_flag">&nbsp;<span class="title">' + _('Flag') + '</span></p>',
			headerCls: 'zarafa-icon-column flag',
			dataIndex: 'flag_due_by',
			width: 24,
			renderer: Zarafa.common.ui.grid.Renderers.flag,
			fixed: true,
			tooltip: _('Sort by: flag')
		}];
	},

	/**
	 * Create an array of {@link Ext.grid.Column columns} which must be visible within
	 * the compact view of this {@link Ext.grid.ColumnModel ColumnModel}.
	 *
	 * @return {Ext.grid.Column[]} The array of columns
	 * @private
	 */
	createCompactColumns: function()
	{
		return [{
			header: '<p class="icon_index">&nbsp;<span class="title">' + _('Icon') + '</span></p>',
			dataIndex: 'icon_index',
			headerCls: 'zarafa-icon-column icon',
			width: 24,
			renderer: Zarafa.common.ui.grid.Renderers.icon,
			fixed: true,
			hideable: !container.isEnabledConversation(),
			preventColSwitch: container.isEnabledConversation(),
			tooltip: _('Sort by: Icon'),
			preventRowSelection: true
		},{
			header: _('From'),
			dataIndex: 'sent_representing_name',
			width: 160,
			renderer: Zarafa.common.ui.grid.Renderers.sender,
			tooltip: _('Sort by: From'),
			groupRenderer:this.groupHeaderBySender
		},{
			header: _('To'),
			dataIndex: 'display_to',
			width: 160,
			renderer: Zarafa.common.ui.grid.Renderers.to,
			tooltip: _('Sort by: To')
		},{
			header: _('Received'),
			dataIndex: 'message_delivery_time',
			width: 160,
			// Setting the renderer with createDelegate to be able to pass a meta object to the renderer.
			// This way we can add a css-class to the element (used by Selenium tests)
			renderer: Zarafa.common.ui.grid.Renderers.datetime.createDelegate(null, [{css: 'mail-received'}], true),
			tooltip: _('Sort by: Received'),
			groupRenderer: this.groupHeaderByDate.createDelegate(this, ['message_delivery_time'], 0)
		},{
			header: _('Sent'),
			dataIndex: 'client_submit_time',
			width: 160,
			// Setting the renderer with createDelegate to be able to pass a meta object to the renderer.
			// This way we can add a css-class to the element (used by Selenium tests)
			renderer: Zarafa.common.ui.grid.Renderers.datetime.createDelegate(null, [{css: 'mail-sent'}], true),
			tooltip: _('Sort by: Sent'),
			groupRenderer: this.groupHeaderByDate.createDelegate(this, ['client_submit_time'], 0)
		},{
			header: _('Scheduled'),
			dataIndex: 'deferred_send_time',
			width: 160,
			// Setting the renderer with createDelegate to be able to pass a meta object to the renderer.
			// This way we can add a css-class to the element (used by Selenium tests)
			renderer: Zarafa.common.ui.grid.Renderers.datetime.createDelegate(null, [{css: 'mail-sent'}], true),
			tooltip: _('Sort by: Scheduled'),
			groupRenderer: this.groupHeaderByDate.createDelegate(this, ['deferred_send_time'], 0)
		},{
			header: _('Modified'),
			dataIndex: 'last_modification_time',
			width: 160,
			sortable: true,
			// Setting the renderer with createDelegate to be able to pass a meta object to the renderer.
			// This way we can add a css-class to the element (used by Selenium tests)
			renderer: Zarafa.common.ui.grid.Renderers.datetime.createDelegate(null, [{css: 'mail-modified'}], true),
			tooltip: _('Sort by: Modified'),
			groupRenderer: this.groupHeaderByDate.createDelegate(this, ['last_modification_time'], 0)
		},{
			header: _('Size'),
			dataIndex: 'message_size',
			width: 80,
			hidden: true,
			renderer: Zarafa.common.ui.grid.Renderers.size,
			tooltip: _('Sort by: Size'),
			groupRenderer: this.groupHeaderBySize
		},{
			header: '<p class="icon_importance">&nbsp;<span class="title">' + _('Importance') + '</span></p>',
			headerCls: 'zarafa-icon-column importance',
			dataIndex: 'importance',
			width: 24,
			renderer: Zarafa.common.ui.grid.Renderers.importance,
			fixed: true,
			tooltip: _('Sort by: Importance')
		},{
			header: '<p class="icon_paperclip">&nbsp;<span class="title">' + _('Attachment') + '</span></p>',
			headerCls: 'zarafa-icon-column attachment',
			dataIndex: 'hasattach',
			width: 24,
			renderer: Zarafa.common.ui.grid.Renderers.attachment,
			fixed: true,
			tooltip: _('Sort by: Attachment')
		},{
			header: '<p class="icon_flag">&nbsp;<span class="title">' + _('Flag') + '</span></p>',
			headerCls: 'zarafa-icon-column flag',
			dataIndex: 'flag_due_by',
			width: 24,
			renderer: Zarafa.common.ui.grid.Renderers.flag,
			fixed: true,
			tooltip: _('Sort by: flag')
		}];
	},

	/**
	 * This will switch the {@link Zarafa.mail.ui.MailGridColumnModel columnmodel}
	 * configuration to either the compact or extended configuration.
	 *
	 * @param {Boolean} compact True to enable the compact view
	 */
	setCompactView: function(compact)
	{
		var name;
		var isEnabledConversation = container.isEnabledConversation();
		if(compact) {
			name = isEnabledConversation ? 'conversation_compact' : 'compact';
		} else {
			name = isEnabledConversation ? 'conversation_default' : 'default';
		}

		if (this.useCompactView !== compact || this.name !== name) {
			this.useCompactView = compact;

			if (compact) {
				// Extjs will store the this.columns into this.config after it has constructed
				// all the columns. At that point this.columns consists of the configuration objects,
				// while this.columns consists of all the allocated columns.
				if (this.name !== "conversation_compact" && this.name !== 'compact') {
					this.defaultColumns = this.config;
				}
				this.columns = this.compactColumns;
			} else {
				if (this.name !== "conversation_default" && this.name !== 'default') {
					this.compactColumns = this.config;
				}
				this.columns = this.defaultColumns;
			}

			this.name = name;
			this.setConfig(this.columns, false);

			if (!compact && isEnabledConversation) {
        this.updateRenderer('subject', Zarafa.common.ui.grid.Renderers.body);
      }
    }
	},

	/**
	 * Function which will change a renderer for the given column dataIndex.
	 * @param {String} dataIndex dataIndex of column whose renderer needs to be changed.
	 * @param {Zarafa.common.ui.grid.Renderers} renderer which should be applied on column.
	 */
	updateRenderer: function (dataIndex, renderer)
	{
		var colIndex = this.findColumnIndex(dataIndex);
		this.setRenderer(colIndex, renderer);
	},

	/**
	 * Function which prepare the title for the grouping header based on the given sender name.
	 * @param {String} sender The sender which is used to the header.
	 * @return {String} formatted title for the grouping headers.
	 */
	groupHeaderBySender: function(sender)
	{
		return Ext.isEmpty(sender) ? _('Unknown') : sender;
	},

	/**
	 * Function which prepare the title for the grouping header based on the given date.
	 *
	 * @param {String} type The index property for the column
	 * @param {Ext.Date} date The date which is used to format the header.
	 * @param {undefined} unused Unused argument (weirdness by ExtJS)
	 * @param {Zarafa.core.data.MAPIRecord} record The record for which the group header is requested
	 * @return {String} formatted title for the grouping headers.
	 */
	groupHeaderByDate: function (type, date, unused, record)
	{
		if (!Ext.isDate(date)) {
			return _('Older');
		}

		var recordDate = date.clone().setToNoon();
		var today = new Date().setToNoon();
		if (recordDate.getTime() === today.getTime()) {
			return _('Today');
		}

		if (recordDate.getTime() === today.add(Date.DAY, -1).getTime()) {
			return _('Yesterday');
		}

		// Current week.
		var weekStart = container.getSettingsModel().get('zarafa/v1/main/week_start');
		var startDateOfCurrentWeek;
		if (today.getDay() < weekStart) {
			startDateOfCurrentWeek = today.getPreviousWeekDay(weekStart);
		} else {
			var day = (today.getDay() - weekStart ) * -1;
			startDateOfCurrentWeek = today.add(Date.DAY, day);
		}

		var lastDateOfCurrentWeek = startDateOfCurrentWeek.add(Date.DAY, 6);
		if (recordDate.between(startDateOfCurrentWeek,lastDateOfCurrentWeek)) {
			return recordDate.format('l');
		}

		// Previews week
		var firstDateOfLastWeek = startDateOfCurrentWeek.add(Date.DAY, -7);
		var lastDateOfLastWeek = firstDateOfLastWeek.add(Date.DAY, 7);
		if (recordDate.between(firstDateOfLastWeek, lastDateOfLastWeek)) {
			return _('Last Week');
		}

		// Previews Two week
		var firstDateOfTwoWeek = firstDateOfLastWeek.add(Date.DAY, -7);
		if (recordDate.between(firstDateOfTwoWeek, firstDateOfLastWeek)) {
			return _('Two Weeks Ago');
		}

		// Previews Third week
		var firstDateOfThirdWeek = firstDateOfTwoWeek.add(Date.DAY, -7);
		if (recordDate.between(firstDateOfThirdWeek, firstDateOfTwoWeek)) {
			return _('Three Weeks Ago');
		}

		// Last Months
		var firstDateOfLastMonth = today.add(Date.MONTH, -1).getFirstDateOfMonth();
		if (recordDate.between(firstDateOfLastMonth, firstDateOfThirdWeek)) {
			return _('Last Month');
		}

		return _('Older');
	},

	/**
	 * Function which prepare the title for the grouping header based on the given size.
	 * @param {Number} size The size of mail which is used to format the header.
	 * @return {String} formatted title for grouping headers.
	 */
	groupHeaderBySize: function (size)
	{
		if (size <= 1024 * 50) {
			return _('Small') + ' - 50kb';
		} else if (size >= 1024 * 50 && size <= 1024 * 500 ) {
			return _('Medium') + ' 50kb - 500kb';
		} else if (size >= 1024 * 500 && size <= 1024 * 5120) {
			return _('Large') + ' 500kb - 5mb';
		} else if (size >= 1024 * 5120 && size <= 1024 * 20480) {
			return _('Very large') + ' 5mb - 20mb';
		}

		return _('Huge') + ' + 20mb';
	}
});
Ext.namespace('Zarafa.mail.ui');

/**
 * @class Zarafa.mail.ui.MailGridContextMenu
 * @extends Zarafa.core.ui.menu.ConditionalMenu
 * @xtype zarafa.mailgridcontextmenu
 *
 * Extend {@link Zarafa.core.ui.menu.ConditionalMenu ConditionalMenu} to add the
 * {@link Zarafa.core.ui.menu.ConditionalItems ConditionalItems} for the
 * MailContext.
 */
Zarafa.mail.ui.MailGridContextMenu = Ext.extend(Zarafa.core.ui.menu.ConditionalMenu, {
	// Insertion points for this class
	/**
	 * @insert context.mail.contextmenu.actions
	 * Insertion point for adding actions menu items into the context menu
	 * @param {Zarafa.mail.ui.MailGridContextMenu} contextmenu This contextmenu
	 */
	/**
	 * @insert context.mail.contextmenu.options
	 * Insertion point for adding options menu items into the context menu
	 * @param {Zarafa.mail.ui.MailGridContextMenu} contextmenu This contextmenu
	 */

	/**
	 * @cfg {Zarafa.mail.MailContext} context The context to which this panel belongs
	 */
	context: undefined,

	/**
	 * The {@link Zarafa.mail.MailContextModel} which is obtained from the {@link #context}.
	 * @property
	 * @type Zarafa.mail.MailContextModel
	 */
	model: undefined,

	/**
	 * @cfg {Zarafa.core.data.IPMRecord[]} The records on which this context menu acts
	 */
	records: undefined,

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

		if ( !Ext.isDefined(config.model) ){
			if ( Ext.isDefined(config.context) ) {
				config.model = config.context.getModel();
			} else {
				config.model = container.getContextByName('mail').getModel();
			}
		}

		Ext.applyIf(config, {
			items: [
				this.createContextOperationItems(),
				{ xtype: 'menuseparator' },
				this.createContextCategorizedItems(config),
				{ xtype: 'menuseparator' },
				this.createContextCopyMoveItems(),
				{ xtype: 'menuseparator' },
				this.createContextActionItems(config),
				{ xtype: 'menuseparator' },
				this.createContextOptionItems(config.records)
			]
		});

		Zarafa.mail.ui.MailGridContextMenu.superclass.constructor.call(this, config);

		this.mon(this.model.getStore(),'load',this.onLoad,this);
	},

	/**
	 * Create the Operation context menu items
	 * @return {Zarafa.core.ui.menu.ConditionalItem[]} The list of Operation context menu items
	 * @private
	 */
	createContextOperationItems: function()
	{
		return[{
			text: _('Open'),
			iconCls: 'icon_open',
			singleSelectOnly: true,
			handler: this.onContextItemOpen,
			scope: this
		},{
			xtype: 'zarafa.conditionalitem',
			text: _('Reply'),
			iconCls: 'icon_reply',
			beforeShow: this.onMenuItemBeforeShow,
			singleSelectOnly: true,
			responseMode: Zarafa.mail.data.ActionTypes.REPLY,
			handler: this.onContextItemResponse,
			scope: this
		},{
			xtype: 'zarafa.conditionalitem',
			text: _('Reply All'),
			iconCls: 'icon_reply_all',
			singleSelectOnly: true,
			beforeShow: this.onMenuItemBeforeShow,
			responseMode: Zarafa.mail.data.ActionTypes.REPLYALL,
			handler: this.onContextItemResponse,
			scope: this
		},{
			xtype: 'zarafa.conditionalitem',
			text: _('Forward'),
			iconCls: 'icon_forward',
			singleSelectOnly: true,
			beforeShow: this.onMenuItemBeforeShow,
			responseMode: Zarafa.mail.data.ActionTypes.FORWARD,
			handler: this.onContextItemResponse,
			scope: this
		},{
			xtype: 'zarafa.conditionalitem',
			text: _('Delete'),
			iconCls: 'icon_delete',
			handler: this.onContextItemDelete,
			scope: this
		},{
			xtype: 'zarafa.conditionalitem',
			text: _('Mark Read'),
			name: 'mark_read',
			iconCls: 'icon_mail icon_mail_read',
			beforeShow: this.onReadFlagItemBeforeShow,
			handler: this.onReadFlagItemClicked,
			scope: this
		},{
			xtype: 'zarafa.conditionalitem',
			text: _('Mark Unread'),
			name: 'mark_unread',
			iconCls: 'icon_mail icon_mail_unread',
			beforeShow: this.onReadFlagItemBeforeShow,
			handler: this.onReadFlagItemClicked,
			scope: this
		},{
			xtype: 'zarafa.conditionalitem',
			text: _('Edit as New'),
			iconCls: 'icon_edit_as_new_mail',
			singleSelectOnly: true,
			beforeShow: this.onMenuItemBeforeShow,
			responseMode: Zarafa.mail.data.ActionTypes.EDIT_AS_NEW,
			handler: this.onContextItemResponse,
			scope: this
		}];
	},

	/**
	 * Create the Categorized context menu items
	 * @param {Zarafa.core.data.IPMRecord[]} records The records on which this context menu acts.
	 * @return {Zarafa.core.ui.menu.ConditionalItem[]} The list of Categorized context menu items
	 * @private
	 */
	createContextCategorizedItems: function(config)
	{
		return[{
			xtype: 'zarafa.conditionalitem',
			text: _('Categories'),
			cls: 'k-unclickable',
			iconCls: 'icon_categories',
			hideOnClick: false,
			menu: {
				xtype: 'zarafa.categoriescontextmenu',
				records: config.records
			}
		},{
			xtype: 'zarafa.conditionalitem',
			text: _('Follow up'),
			cls: 'k-unclickable',
			iconCls: 'icon_flag_red',
			hideOnClick: false,
			menu: {
				xtype: 'zarafa.flagsmenu',
				records: config.records,
				shadowEdit: config.shadowEdit
			},
			scope: this
		}];
	},

	/**
	 * Create the Copy Move context menu items
	 * @return {Zarafa.core.ui.menu.ConditionalItem[]} The list of Copy Move context menu items
	 * @private
	 */
	createContextCopyMoveItems: function()
	{
		return[{
			xtype: 'zarafa.conditionalitem',
			text: _('Copy/Move'),
			iconCls: 'icon_copy',
			hideOnDisabled: false,
			handler: this.onCopyMove,
			scope: this
		},{
			xtype: 'zarafa.conditionalitem',
			text: _('Move to Junk Folder'),
			iconCls: 'icon_folder_default_junk',
			beforeShow: this.onMoveToJunkBeforeShow,
			handler: this.onContextItemJunk,
			scope: this
		},{
			xtype: 'zarafa.conditionalitem',
			text: _('Not Junk Email'),
			iconCls: 'icon_folder_default_inbox',
			beforeShow: this.onNotJunkBeforeShow,
			handler: this.onContextItemNotJunk,
			scope: this
		}];
	},

	/**
	 * Create the Action context menu items
	 * @param {Object} config The configuration object which used to create action context menu item.
	 * @return {Zarafa.core.ui.menu.ConditionalItem[]} The list of Action context menu items
	 * @private
	 */
	createContextActionItems: function(config)
	{
		return [{
			xtype: 'zarafa.conditionalitem',
			text: _('Send to') + "...",
			iconCls: 'icon_embed_attachment',
			singleSelectOnly: true,
			beforeShow: this.onMenuItemBeforeShow,
			responseMode: Zarafa.mail.data.ActionTypes.FORWARD_ATTACH,
			handler: this.onContextItemResponse,
			scope: this
		}, {
			xtype: 'zarafa.conditionalitem',
			text: _('Export as'),
			cls: 'k-unclickable',
			iconCls: 'icon_export',
			hideOnClick: false,
			menu: {
				xtype: 'zarafa.exportascontextmenu',
				records: config.records,
				model: config.model
			}
		},
			container.populateInsertionPoint('context.mail.contextmenu.actions', this)
		];
	},

	/**
	 * Create the Option context menu items
	 * @param {Zarafa.core.data.IPMRecord[]} records The records on which this context menu acts.
	 * @return {Zarafa.core.ui.menu.ConditionalItem[]} The list of Option context menu items
	 * @private
	 */
	createContextOptionItems: function(records)
	{
		return[{
			xtype: 'zarafa.conditionalitem',
			text: _('Rules'),
			singleSelectOnly: true,
			cls: 'k-unclickable',
			iconCls: 'zarafa-settings-category-rules',
			hideOnClick: false,
			menu: {
				xtype: 'zarafa.rulescontextmenu',
				records: records
			}
		},
		container.populateInsertionPoint('context.mail.contextmenu.topoptions', this), {
			xtype: 'zarafa.conditionalitem',
			text: _('Print'),
			iconCls: 'icon_print',
			singleSelectOnly: true,
			handler: this.onContextItemPrint,
			scope: this
		},{
			xtype: 'zarafa.conditionalitem',
			text: _('Options'),
			iconCls: 'icon_cogwheel',
			beforeShow: this.onMenuItemBeforeShow,
			singleSelectOnly: true,
			handler: this.onContextItemOptions,
			scope: this
		},
			container.populateInsertionPoint('context.mail.contextmenu.options', this)];
	},

	/**
	 * Returns the records a single-message action (open, reply, forward, ...)
	 * of this menu should act on. When the menu was opened for a conversation
	 * header, such actions act on the newest message of the conversation
	 * instead of on every message of it.
	 *
	 * @return {Zarafa.core.data.IPMRecord[]} The records to act on.
	 * @private
	 */
	getResponseRecords: function()
	{
		if (this.conversationHeader === true && !Ext.isEmpty(this.records)) {
			// The conversation items are ordered newest first.
			return [ this.records[0] ];
		}

		return this.records;
	},

	/**
	 * Event handler which is called when the user selects the 'Open in window'
	 * item in the context menu. This will open the item in a new browser window.
	 * @private
	 */
	onContextItemOpen: function()
	{
		Zarafa.common.Actions.openMessageContent(this.getResponseRecords());
	},

	/**
	 * Event handler which is called when the user selects the 'Print'
	 * item in the context menu. This will open the print dialog for this record.
	 * @private
	 */
	onContextItemPrint: function()
	{
		Zarafa.common.Actions.openPrintDialog(this.records);
	},

	/**
	 * Called when one of the "Reply"/"Reply All"/"Forward"/"Edit as New" menuitems are clicked.
	 * @param {Ext.Button} button The button which was clicked
	 * @private
	 */
	onContextItemResponse: function(button)
	{
		var records = this.getResponseRecords();

		// For meeting request messages, the Forward action should do a
		// proper meeting forward (address book selection) rather than
		// a plain email forward.
		if (button.responseMode === Zarafa.mail.data.ActionTypes.FORWARD &&
			records.length === 1) {
			var messageClass = records[0].get('message_class') || '';
			if (messageClass.indexOf('IPM.Schedule.Meeting') === 0 &&
				messageClass.indexOf('IPM.Schedule.Meeting.Notification') !== 0) {
				Zarafa.calendar.Actions.openForwardMeetingRequestContent(records[0]);
				return;
			}
		}
		Zarafa.mail.Actions.openCreateMailResponseContent(records, this.model, button.responseMode);
	},

	/**
	 * Event handler which determines if menu items should be visible or not.
	 * It will check if record is faulty then hide menu items which are not supported.
	 *
	 * @param {Zarafa.core.ui.menu.ConditionalItem} item The item to enable/disable
	 * @param {Zarafa.core.data.IPMRecord[]} records The records which must be checked
	 * to see if the item must be enabled or disabled.
	 * @private
	 */
	onMenuItemBeforeShow: function(item, records)
	{
		Ext.each(records, function(record) {
			if(record.isFaultyMessage()) {
				// hide menu item and break the loop
				item.setVisible(false);

				return false;
			}
		}, this);
	},

	/**
	 * Event handler which determines if the 'Move to Junk Folder' menu item should be visible or not.
	 * If context menu is opened in the "Junk Items" folder, it will not show the menu item.
	 *
	 * @param {Zarafa.core.ui.menu.ConditionalItem} item The item to enable/disable
	 * @param {Zarafa.core.data.IPMRecord[]} records The records which must be checked
	 * to see if the item must be enabled or disabled.
	 * @private
	 */
	onMoveToJunkBeforeShow: function(item, records)
	{
		if ( this.model ){
			var defaultFolder = this.model.getDefaultFolder();
			item.setVisible(!defaultFolder.isSpecialFolder('junk'));
		}
	},

	/**
	 * Event handler which determines if the 'Not Junk Email' menu item should be visible or not.
	 * If context menu is opened in the "Junk Items" folder, it will show the menu item.
	 *
	 * @param {Zarafa.core.ui.menu.ConditionalItem} item The item to enable/disable
	 * @param {Zarafa.core.data.IPMRecord[]} records The records which must be checked
	 * to see if the item must be enabled or disabled.
	 * @private
	 */
	onNotJunkBeforeShow: function(item, records)
	{
		if ( this.model ){
			var defaultFolder = this.model.getDefaultFolder();
			item.setVisible(defaultFolder.isSpecialFolder('junk'));
		}
	},

	/**
	 * Open the {@link Zarafa.common.dialogs.CopyMoveContentPanel CopyMoveContentPanel} for copying
	 * or moving the currently selected folders.
	 * @private
	 */
	onCopyMove: function()
	{
		Zarafa.common.Actions.openCopyMoveContent(this.model.getSelectedRecords());
	},

	/**
	 * Event handler which determines if the Read Flag button must be shown.
	 * There are two kind of read flag buttons which can both make use of this
	 * function (Mark as Read and Mark as Unread buttons).
	 *
	 * This function will loop through all given {@link Zarafa.core.data.IPMRecord records}
	 * and will determine if this button can be applied to any of the records.
	 * For example, if 10 records are selected and one or more are marked as read,
	 * the Mark as Unread button will be enabled. If no records are marked as read,
	 * the button will not be enabled.
	 *
	 * @param {Zarafa.core.ui.menu.ConditionalItem} item The item to enable/disable
	 * @param {Zarafa.core.data.IPMRecord[]} records The records which must be checked
	 * to see if the item must be enabled or disabled.
	 * this item is enabled.
	 * @private
	 */
	onReadFlagItemBeforeShow: function(item, records)
	{
		var count = 0;
		var read = item.name !== 'mark_read';
		Ext.each(records, function(record) {
			if (record.isRead() === read) {
				count++;
			}
		}, this);

		item.setDisabled(count === 0);
	},

	/**
	 * Event handler which is called when the item has been clicked.
	 * This will mark all selected records as read or unread.
	 *
	 * @private
	 */
	onReadFlagItemClicked: function(btn)
	{
		Zarafa.common.Actions.markAsRead(this.records, btn.name === 'mark_read');
	},

	/**
	 * Event handler which is called when the user selects the "Move to Junk"
	 * item in the context menu. This will move all selected items to the junk folder.
	 * @private
	 */
	onContextItemJunk: function()
	{
		var junkfolder = container.getHierarchyStore().getDefaultFolder('junk');
		var store;

		Ext.each(this.records, function(record) {
			store = record.store;
			record.moveTo(junkfolder);
		}, this);

		store.save(this.records);
	},

	/**
	 * Event handler which is called when the user selects the "Not Junk Email"
	 * item in the context menu. This will strip common spam subject prefixes
	 * and move all selected items from the junk folder to the inbox.
	 * @private
	 */
	onContextItemNotJunk: function()
	{
		var inboxFolder = container.getHierarchyStore().getDefaultFolder('inbox');
		var store;

		Ext.each(this.records, function(record) {
			store = record.store;

			// Strip common spam subject prefixes
			var subject = record.get('subject');
			if (subject) {
				var cleanSubject = subject.replace(/^(\s*(\*{1,3}\s*SPAM\s*\*{1,3}|\[SPAM\])\s*)+/i, '');
				if (cleanSubject !== subject) {
					record.set('subject', cleanSubject);
				}
			}

			record.moveTo(inboxFolder);
		}, this);

		store.save(this.records);
		container.selectFolder(inboxFolder);
	},

	/**
	 * Event handler which is called when the user selects the 'Delete'
	 * item in the context menu. This will delete all selected records.
	 * @private
	 */
	onContextItemDelete: function()
	{
		Zarafa.common.Actions.deleteRecords(this.records);
	},

	/**
	 * Event handler which is called when the user selects the 'Mail Options'
	 * item in the context menu. This will open the MailOptions panel.
	 * @private
	 */
	onContextItemOptions: function()
	{
		Zarafa.mail.Actions.openMailOptionsContent(this.records, {
			autoSave: true
		});
	},
});

Ext.reg('zarafa.mailgridcontextmenu', Zarafa.mail.ui.MailGridContextMenu);
Ext.namespace('Zarafa.mail.ui');

/**
 * @class Zarafa.mail.ui.MailGridView
 * @extends Zarafa.common.ui.grid.GridView
 */
Zarafa.mail.ui.MailGridView = Ext.extend(Zarafa.common.ui.grid.GridView, {
	/**
	 * This is called internally, once, by this.render after the HTML elements are added to the grid element.
	 * This is always intended to be called after renderUI. Sets up listeners on the UI elements
	 * and sets up options like column menus, moving and resizing.
	 * This function is overridden to use a {@link Zarafa.common.ui.grid.GridDragZone} for the {@link #dragZone},
	 * rather then the default {@link Ext.grid.GridDragZone}.
	 * This is always intended to be called after renderUI. Sets up listeners on the UI elements
	 * and sets up options like column menus, moving and resizing. It will overwrite to hide the
	 * 'Group By This Field' button from header menu and change css class for the check box.
	 * @private
	 */
	afterRenderUI: function()
	{
		Zarafa.mail.ui.MailGridView.superclass.afterRenderUI.apply(this, arguments);

		// Override the this.columnDrop to avoid switch the icon column in
		// conversation view.
		if (this.grid.enableColumnMove) {
			this.columnDrop = new Zarafa.mail.ui.MailGridHeaderDropZone(this.grid, this.mainHd.dom);
		}
	},

	/**
	 * Function used to set the focus on {@link Zarafa.mail.ui.MailGrid MailGrid}.
	 */
	setFocus : function()
	{
        if (Ext.isGecko) {
            this.focusEl.focus();
        } else {
        	this.focusEl.focus.defer(1, this.focusEl);
        }
	}
});

Ext.namespace('Zarafa.mail.ui');

/**
 * @class Zarafa.mail.ui.MailPanel
 * @extends Zarafa.common.ui.ContextMainPanel
 * @xtype zarafa.mailpanel
 */
Zarafa.mail.ui.MailPanel = Ext.extend(Zarafa.common.ui.ContextMainPanel, {
	// Insertion points for this class
	/**
	 * @insert context.mail.toolbar.item
	 * Insertion point for populating mail context's main toolbar.
	 * This item is only visible when this context is active.
	 * @param {Zarafa.mail.ui.MailPanel} panel This panel
	 */
	/**
	 * @insert context.mail.toolbar.paging
	 *
	 * Insertion point for populating mail context's toolbar with extra
	 * pagination buttons. This can be used to replace the default {@link Ext.PagingToolbar}
	 * with an alternative. Note that by default all paging toolbars will be visible, and
	 * hiding a particular toolbar is the responsibility of the new pagers.
	 * @param {Zarafa.mail.ui.MailPanel} panel This panel
	 */
	/**
	 * @insert context.mail.views
	 * Insertion point for adding views within the main panel of mail context.
	 * This insertion point should be used in combination with 'main.maintoolbar.view.mail'
	 * insertion point, and also view should set its store in the config object, the reference of
	 * {@link Zarafa.note.MailContextModel MailContextModel} is passed as parameter of this
	 * insertion point.
	 * @param {Zarafa.mail.ui.MailPanel} mainpanel This mainpanel
	 * @param {Zarafa.mail.MailContext} context The context for this panel
	 */

	/**
	 * The main panel in which the various views are located.
	 * @property
	 * @type Zarafa.core.ui.SwitchViewContentContainer
	 */
	viewPanel: undefined,

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

		Ext.applyIf(config, {
			// Overridden from Ext.Component
			xtype: 'zarafa.mailpanel',
			layout: 'zarafa.switchborder',
			items: [
				this.initMailGrid(config),
				this.initPreviewPanel(config.context)
			]
		});

		Zarafa.mail.ui.MailPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Initializes the {@link Zarafa.mail.ui.MailGrid MailGrid}
	 *
	 * @param {Object} config Configuration object
	 * @return {Zarafa.mail.ui.MailGrid}
	 * @private
	 */
	initMailGrid: function(config)
	{
		return {
			xtype: 'panel',
			layout: 'zarafa.collapsible',
			cls: 'zarafa-context-mainpanel',
			minWidth: 400,
			minHeight: 400,
			region: 'center',
			collapsible: false,
			split: true,
			items: [{
				xtype: 'zarafa.switchviewcontentcontainer',
				ref: '../viewPanel',
				layout: 'card',
				lazyItems: this.initViews(config.context)
			}],
			tbar: {
				xtype: 'zarafa.contextmainpaneltoolbar',
				id: 'zarafa-main-content-mail-toolbar',
				defaultTitle: _('Mail'),
				paging: container.populateInsertionPoint('context.mail.toolbar.paging', this),
				items: container.populateInsertionPoint('context.mail.toolbar.item', this),
				context: config.context
			},
			listeners: {
				render: function(panel)
				{
					this.topToolbar = panel.getTopToolbar();
				},
				scope: this
			}
		};
	},

	/**
	 * Function will initialize all views associated with mail context
	 * it will also get views added through 3rd party plugins and add it here
	 * @param {Zarafa.mail.MailContextModel} model data part of mail context
	 * @return {Array} array of config objects of different views
	 * @private
	 */
	initViews: function(context)
	{
		// add the standard available views
		var allViews = [{
			xtype: 'zarafa.mailgrid',
			flex: 1,
			id  : 'mail-grid',
			anchor: '100%',
			context: context,
			ref: '../../mailGrid'
		}];

		var additionalViewItems = container.populateInsertionPoint('context.mail.views', this, context);
		allViews = allViews.concat(additionalViewItems);

		return allViews;
	},

	/**
	 * Function is used to get the {@link Zarafa.mail.ui.MailGrid mailgrid}
	 * return {Zarafa.mail.ui.MailGrid} return the mail grid.
	 */
	getGridPanel: function()
	{
		return this.mailGrid;
	},

	/**
	 * Initializes the {@link Zarafa.core.ui.PreviewPanel PreviewPanel}
	 *
	 * @param {Zarafa.mail.MailContext} context The Mail Context
	 * @return {Zarafa.core.ui.PreviewPanel}
	 * @private
	 */
	initPreviewPanel: function(context)
	{
		return {
			xtype: 'zarafa.mailpreviewpanel',
			id: 'zarafa-main-content-mail-preview',
			region: 'south',
			split: true,
			stateful: true,
			stateId: 'mail-preview-pane',
			context: context,
			ref: 'previewPanel'
		};
	},

	/**
	 * Function called by Extjs when the panel has been {@link #render rendered}.
	 * At this time all events can be registered.
	 * @private
	 */
	initEvents: function()
	{
		if (Ext.isDefined(this.context)) {
			this.mon(this.context, 'viewchange', this.onViewChange, this);
			this.mon(this.context, 'viewmodechange', this.onViewModeChange, this);

			this.onViewChange(this.context, this.context.getCurrentView());
			this.onViewModeChange(this.context, this.context.getCurrentViewMode());
		}
		Zarafa.mail.ui.MailPanel.superclass.initEvents.apply(this, arguments);
	},

	/**
	 * Event handler which is fired when the currently active view inside the {@link #context}
	 * has been updated. This will update the call
	 * {@link #viewPanel}#{@link Zarafa.core.ui.SwitchViewContentContainer#switchView}
	 * to make the requested view active.
	 *
	 * @param {Zarafa.core.Context} context The context which fired the event.
	 * @param {Zarafa.mail.data.Views} newView The ID of the selected view.
	 * @param {Zarafa.mail.data.Views} oldView The ID of the previously selected view.
	 */
	onViewChange: function(context, newView, oldView)
	{
		switch (newView) {
			case Zarafa.mail.data.Views.LIST:
				this.viewPanel.switchView('mail-grid');
				break;
		}
	},

	/**
	 * Event handler which is fired when the {@link Zarafa.core.Context} fires the
	 * {@link Zarafa.core.Context#viewmodechange viewmodechange} event. This will
	 * convert the configured {@link Zarafa.mail.data.ViewModes mode} to a
	 * {@link Zarafa.common.ui.layout.SwitchBorderLayout.Orientation orientation}
	 * to be {@link Zarafa.common.ui.layout.SwitchBorderLayout.setOrientation applied}
	 * to the {@link #layout}.
	 * @param {Zarafa.core.Context} context The context which fired the event
	 * @param {Zarafa.mail.data.ViewModes} newViewMode The new active mode
	 * @param {Zarafa.mail.data.ViewModes} oldViewMode The previous mode
	 * @private
	 */
	onViewModeChange: function(context, newViewMode, oldViewMode)
	{
		var orientation;
		var el = this.getEl();

		switch (newViewMode) {
			case Zarafa.mail.data.ViewModes.NO_PREVIEW:
				orientation = Zarafa.common.ui.layout.SwitchBorderLayout.Orientation.OFF;
				// Add a class for styling
				el.removeClass('zarafa-preview-bottom').removeClass('zarafa-preview-right').addClass('zarafa-preview-off');
				if (this.previewPanel) {
					this.previewPanel.hide();
				}
				break;
			case Zarafa.mail.data.ViewModes.RIGHT_PREVIEW:
				orientation = Zarafa.common.ui.layout.SwitchBorderLayout.Orientation.HORIZONTAL;
				// Add a class for styling
				el.removeClass('zarafa-preview-bottom').removeClass('zarafa-preview-off').addClass('zarafa-preview-right');
				if (this.previewPanel) {
					this.previewPanel.show();
				}
				break;
			case Zarafa.mail.data.ViewModes.BOTTOM_PREVIEW:
				orientation = Zarafa.common.ui.layout.SwitchBorderLayout.Orientation.VERTICAL;
				// Add a class for styling
				el.removeClass('zarafa-preview-off').removeClass('zarafa-preview-right').addClass('zarafa-preview-bottom');
				if (this.previewPanel) {
					this.previewPanel.show();
				}
				break;
			case Zarafa.mail.data.ViewModes.SEARCH:
			case Zarafa.mail.data.ViewModes.LIVESCROLL:
				return;
		}

		// This function could be called when the layout has not yet
		// been instantiated. In that case we update the layoutConfig
		// so it will be automatically picked up by the layout when
		// it needs it.
		var layout = this.getLayout();
		if (!Ext.isFunction(layout.setOrientation)) {
			if (Ext.isString(layout)) {
				this.layoutConfig = Ext.apply(this.layoutConfig || {}, { orientation: orientation });
			} else {
				this.layout.orientation = orientation;
			}
		} else {
			layout.setOrientation(orientation);
		}
	}
});

Ext.reg('zarafa.mailpanel', Zarafa.mail.ui.MailPanel);
Ext.namespace('Zarafa.mail.ui');

/**
 * @class Zarafa.mail.ui.MailPreviewPanel
 * @extends Zarafa.core.ui.PreviewPanel
 * @xtype zarafa.mailpreviewpanel
 */
Zarafa.mail.ui.MailPreviewPanel = Ext.extend(Zarafa.core.ui.PreviewPanel, {
	/**
	 * @constructor
	 * @param config Configuration structure
	 */
	constructor: function(config)
	{
		config = config || {};

		if (!Ext.isDefined(config.model) && Ext.isDefined(config.context)) {
			config.model = config.context.getModel();
		}

		Ext.applyIf(config, {
			xtype: 'zarafa.mailpreviewpanel',
			cls: 'zarafa-previewpanel zarafa-context-mainpanel',
			loadMaskDelay: 250,
			recordComponentPluginConfig: Ext.applyIf(config.recordComponentPluginConfig || {}, {
				// Defer loading mail immediately, because the user
				// might be quickly clicking through the list of mails.
				enableOpenLoadTask: true,
				autoOpenLoadTaskDefer: 0
			}),
			width: 600,
			height: 400,
			tbar: {
				items: [
					this.createReplyButton(),
					this.createReplyAllButton(),
					this.createForwardButton()
				]
			}
		});

		Zarafa.mail.ui.MailPreviewPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Returns the conversation the given record belongs to when it should be
	 * previewed as a whole conversation, or false to preview the single record.
	 *
	 * @param {Zarafa.core.data.MAPIRecord} record The record to be previewed.
	 * @return {Object|Boolean} Object with 'items' (newest first), or false.
	 * @private
	 */
	getConversationForRecord: function(record)
	{
		if (!record || record.isSyntheticConversationHeader === true) {
			return false;
		}

		if (container.getSettingsModel().get('zarafa/v1/contexts/mail/enable_conversation_preview', true) === false) {
			return false;
		}

		// Search results carry their conversation siblings directly (see
		// Zarafa.advancesearch.AdvanceSearchStore#dedupeConversations).
		if (Ext.isArray(record.searchConversationRecords) && record.searchConversationRecords.length > 1) {
			return { items: record.searchConversationRecords };
		}

		var store = record.getStore ? record.getStore() : record.store;
		if (!store || !Ext.isFunction(store.containsConversations) || !store.containsConversations() ||
			!Ext.isFunction(store.getHeaderRecordFromItem)) {
			return false;
		}

		var header = store.getHeaderRecordFromItem(record);
		if (header === false) {
			return false;
		}

		// A header means this is a conversation, even when only one of its
		// messages is loaded so far (the sent items of the conversation are
		// fetched asynchronously when it is expanded); the conversation panel
		// refreshes itself when the other messages arrive.
		var items = store.getConversationItemsFromHeaderRecord(header);
		if (Ext.isEmpty(items)) {
			return false;
		}

		return { items: items };
	},

	/**
	 * Overridden to show the entire conversation when the previewed record is
	 * part of one; single messages use the default behavior.
	 *
	 * @param {Zarafa.core.data.MAPIRecord} record The record to preview.
	 * @private
	 */
	showRecordInPanel: function(record)
	{
		var conversation = this.getConversationForRecord(record);
		if (conversation === false) {
			// Leaving conversation mode: force a rebuild of the default view.
			if (this.get(0) instanceof Zarafa.mail.ui.ConversationViewPanel) {
				this.removeAll();
				this.record = undefined;
			}
			Zarafa.mail.ui.MailPreviewPanel.superclass.showRecordInPanel.apply(this, arguments);
			return;
		}

		if (!(this.get(0) instanceof Zarafa.mail.ui.ConversationViewPanel)) {
			this.removeAll();
			this.add(new Zarafa.mail.ui.ConversationViewPanel({
				resolveConversation: (function(rec) {
					var conv = this.getConversationForRecord(rec);
					return conv === false ? false : conv.items;
				}).createDelegate(this)
			}));
			this.doLayout();
		}

		this.get(0).showConversation(conversation.items, record);

		// Standard record handling: toolbar, record bookkeeping and opening of
		// the selected record. The response actions (reply, forward, ...) act
		// on the selected message of the conversation.
		this.setRecord(record);
		this.hideLoadMask();
	},

	/**
	 * Update the components with the given record.
	 *
	 * @param {Zarafa.core.data.MAPIRecord} record The record to update in this component
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 */
	update: function(record, contentReset)
	{
		if ( this.record && this.record !== record && this.record.store && this.record.store !== record.store ){
			Zarafa.core.PresenceManager.unregisterStore(this.record.store);
		}
		if ( record && record.store ){
			Zarafa.core.PresenceManager.registerStore(record.store, 'sender');
		}

		if ( this.record && this.record.subStores && this.record.subStores.recipients ){
			Zarafa.core.PresenceManager.unregisterStore(this.record.subStores.recipients);
		}
		if ( record && record.subStores && record.subStores.recipients ){
			Zarafa.core.PresenceManager.registerStore(record.subStores.recipients);
		}

		Zarafa.mail.ui.MailPreviewPanel.superclass.update.apply(this, arguments);

		if(record) {
			var isFaultyMessage = record.isFaultyMessage();
			var toolbar = this.getTopToolbar();

			toolbar.replyBtn.setVisible(!isFaultyMessage);
			toolbar.replyAllBtn.setVisible(!isFaultyMessage);
			toolbar.forwardBtn.setVisible(!isFaultyMessage);
		}
	},

	/**
	 * Function returns config for the reply button in previewpanel's toolbar
	 *
	 * @return {Object} Configuration object of reply button for the panel toolbar
	 * @private
	 */
	createReplyButton: function()
	{
		return {
			xtype: 'button',
			tooltip: _('Reply') + Zarafa.core.KeyMapMgr.formatShortcutHint('Ctrl + R', false),
			overflowText: _('Reply'),
			text: _('Reply'),
			iconCls: 'icon_reply',
			ref: 'replyBtn',
			responseMode: Zarafa.mail.data.ActionTypes.REPLY,
			handler: this.onResponse,
			scope: this
		};
	},

	/**
	 * Function returns config for the replyall button in previewpanel's toolbar
	 *
	 * @return {Object} Configuration object of replyall button for the panel toolbar
	 * @private
	 */
	createReplyAllButton: function()
	{
		return {
			xtype: 'button',
			tooltip: _('Reply All') + Zarafa.core.KeyMapMgr.formatShortcutHint('Ctrl + Alt + R', false),
			overflowText: _('Reply All'),
			text: _('Reply All'),
			iconCls: 'icon_reply_all',
			ref: 'replyAllBtn',
			responseMode: Zarafa.mail.data.ActionTypes.REPLYALL,
			handler: this.onResponse,
			scope: this
		};
	},

	/**
	 * Function returns config for the forward button in previewpanel's toolbar
	 *
	 * @return {Object} Configuration object of forward button for the panel toolbar
	 * @private
	 */
	createForwardButton: function()
	{
		return {
			xtype: 'button',
			tooltip: _('Forward') + Zarafa.core.KeyMapMgr.formatShortcutHint('Ctrl + F', false),
			overflowText: _('Forward'),
			text: _('Forward'),
			iconCls: 'icon_forward',
			ref: 'forwardBtn',
			responseMode: Zarafa.mail.data.ActionTypes.FORWARD,
			handler: this.onResponse,
			scope: this
		};
	},

	/**
	 * Called when one of the "Reply"/"Reply All"/"Forward"/"Edit as New" menuitems are clicked.
	 * @param {Ext.Button} button The button which was clicked
	 * @private
	 */
	onResponse: function(button)
	{
		if (button.responseMode === Zarafa.mail.data.ActionTypes.FORWARD) {
			var messageClass = this.record.get('message_class') || '';
			if (messageClass.indexOf('IPM.Schedule.Meeting') === 0 &&
				messageClass.indexOf('IPM.Schedule.Meeting.Notification') !== 0) {
				Zarafa.calendar.Actions.openForwardMeetingRequestContent(this.record);
				return;
			}
		}
		Zarafa.mail.Actions.openCreateMailResponseContent(this.record, this.model, button.responseMode);
	}
});

Ext.reg('zarafa.mailpreviewpanel', Zarafa.mail.ui.MailPreviewPanel);
/*
 * #dependsFile client/zarafa/core/KeyMapMgr.js
 */
Ext.namespace('Zarafa.note');

/**
 * @class Zarafa.note.KeyMapping
 * @extends Object
 *
 * The map of keys used in the Note Context.
 * @singleton
 */
Zarafa.note.KeyMapping = Ext.extend(Object, {
	/**
	 * @constructor
	 */
	constructor: function()
	{
		var newItemKeys = [{
			key: Ext.EventObject.S,
			ctrl: true,
			alt: true,
			shift: false,
			stopEvent: true,
			handler: this.onNewNote,
			scope: this,
			settingsCfg: {
				description: _('New note'),
				category: _('Creating an item')
			}
		}];

		Zarafa.core.KeyMapMgr.register('global', newItemKeys);
	},

	/**
	 * Event handler for the keydown event of the {@link Zarafa.core.KeyMap KeyMap} when the user wants to
	 * create a new Sticky Note.
	 * @param {Number} key Key code
	 * @param {Ext.EventObject} event The event
	 * @param {String} context The name of the context the key is pressed in
	 * @param {Ext.Component} component The component on which key event is fired.
	 */
	onNewNote: function(key, event, component)
	{
		Zarafa.note.Actions.openCreateNoteContent(container.getContextByName('note').getModel());
	}

});

Zarafa.note.KeyMapping = new Zarafa.note.KeyMapping();/*
 * #dependsFile client/zarafa/common/ui/grid/Renderers.js
 */
Ext.namespace('Zarafa.note.attachitem');

/**
 * @class Zarafa.note.attachitem.AttachNoteColumnModel
 * @extends Zarafa.common.ui.grid.ColumnModel
 *
 * The {@link Zarafa.note.attachitem.AttachNoteColumnModel AttachNoteColumnModel} is the column model containing
 * sets of {@link Ext.grid.Column columns} for sticky note folders.
 * This column model will be used with {@link Zarafa.common.attachment.dialog.AttachItemGrid AttachItemGrid}.
 */
Zarafa.note.attachitem.AttachNoteColumnModel = Ext.extend(Zarafa.common.ui.grid.ColumnModel, {
	/**
	 * @constructor
	 * @param config Configuration structure
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			columns: this.getColumns(),
			defaults: {
				sortable: true
			}
		});

		Zarafa.note.attachitem.AttachNoteColumnModel.superclass.constructor.call(this, config);
	},

	/**
	 * Create an array of {@link Ext.grid.Column columns} which will be visible when loading data from
	 * sticky note folder into {@link Zarafa.common.attachment.dialog.AttachItemGrid AttachItemGrid}.
	 *
	 * @return {Ext.grid.Column[]} The array of columns
	 * @private
	 */
	getColumns: function()
	{
		return [{
			dataIndex: 'icon_index',
			headerCls: 'zarafa-icon-column',
			header: '<p class=\'icon_index\'>&nbsp;</p>',
			tooltip: _('Sort by: Icon'),
			width: 24,
			fixed: true,
			renderer: Zarafa.common.ui.grid.Renderers.icon
		}, {
			dataIndex: 'subject',
			header: _('Subject'),
			tooltip: _('Sort by: Subject'),
			renderer: Zarafa.common.ui.grid.Renderers.subject
		}];
	}
});Ext.namespace('Zarafa.note.attachitem');

/**
 * @class Zarafa.note.attachitem.AttachNoteRenderer
 * @extends Zarafa.common.attachment.dialogs.AttachItemBaseRenderer
 *
 * Renderer that can be used to get text data from {@link Zarafa.note.NoteRecord NoteRecord}.
 */
Zarafa.note.attachitem.AttachNoteRenderer = Ext.extend(Zarafa.common.attachment.dialogs.AttachItemBaseRenderer, {
	/**
	 * Function should be used to generate template which can be used by {@link #generateText} to add data into template and return the string that is generated.
	 * This will generate template for the html format only.
	 * @return {String} The HTML for the XTemplate to use
	 */
	generateHTMLTemplate: function()
	{
		var html = '';

		html += this.addHTMLRow('categories', '{categories:htmlEncode}');
		html += '{body}';

		return html;
	},

	/**
	 * Function should be used to generate template which can be used by {@link #generateText} to add data into template and return the string that is generated.
	 * This will generate template for the plain text format only.
	 * @return {String} The HTML for the XTemplate to use
	 */
	generatePlainTemplate: function()
	{
		var html = '';

		html += this.addPlainRow('categories', '{categories}');
		html += '{body}';

		return html;
	}
});Ext.namespace('Zarafa.note.data');

/**
 * @class Zarafa.note.data.DataModes
 * @extends Zarafa.core.Enum
 *
 * Enum containing the different data modes of the note context.
 *
 * @singleton
 */
Zarafa.note.data.DataModes = Zarafa.core.Enum.create({
	/**
	 * View all note items from the selected folder(s).
	 *
	 * @property
	 * @type Number
	 */
	ALL: 0,
	/**
	 * View all note items from the selected folder(s) from last seven days.
	 *
	 * @property
	 * @type Number
	 */
	LAST_7_DAYS: 1,
	/**
	 * View all found note items from the selected folder(s).
	 *
	 * @property
	 * @type Number
	 */
	SEARCH: 2
});
Ext.namespace('Zarafa.note.data');

/**
 * @class Zarafa.note.data.ViewModes
 * @extends Zarafa.core.Enum
 *
 * Enum containing the different viewing modes of the note context.
 *
 * @singleton
 */
Zarafa.note.data.ViewModes = Zarafa.core.Enum.create({
	/**
	 * View all note items from the selected folder(s) without grouping.
	 *
	 * @property
	 * @type Number
	 */
	NORMAL: 0,
	/**
	 * View all note items from the selected folder(s) grouped by category.
	 *
	 * @property
	 * @type Number
	 */
	GROUP_CATEGORY: 1,
	/**
	 * View all note items from the selected folder(s) grouped by color.
	 * in the 'grid' view.
	 *
	 * @property
	 * @type Number
	 */
	GROUP_COLOR: 2,
	/**
	 * View all found note items from the selected folder(s).
	 *
	 * @property
	 * @type Number
	 */
	SEARCH: 3
});
Ext.namespace('Zarafa.note.data');

/**
 * @class Zarafa.note.data.Views
 * @extends Zarafa.core.Enum
 *
 * Enum containing the different views of the note context.
 *
 * @singleton
 */
Zarafa.note.data.Views = Zarafa.core.Enum.create({
	/**
	 * View all note items from the selected folder(s) in the 'list' view.
	 *
	 * @property
	 * @type Number
	 */
	LIST: 0,
	/**
	 * View all note items from the selected folder(s) in the 'icon' view.
	 *
	 * @property
	 * @type Number
	 */
	ICON: 1,
	/**
	 * View all found note items from the selected folder(s) in the 'list' view.
	 *
	 * @property
	 * @type Number
	 */
	SEARCH: 2
});
Ext.namespace('Zarafa.note.ui');

/**
 * @class Zarafa.note.ui.NoteContextMenu
 * @extends Zarafa.core.ui.menu.ConditionalMenu
 * @xtype zarafa.notecontextmenu
 */
Zarafa.note.ui.NoteContextMenu = Ext.extend(Zarafa.core.ui.menu.ConditionalMenu, {
	// Insertion points for this class
	/**
	 * @insert context.note.contextmenu.actions
	 * Insertion point for adding actions menu items into the context menu
	 * @param {Zarafa.note.ui.NoteContextMenu} contextmenu This contextmenu
	 */
	/**
	 * @insert context.note.contextmenu.options
	 * Insertion point for adding options menu items into the context menu
	 * @param {Zarafa.note.ui.NoteContextMenu} contextmenu This contextmenu
	 */

	/**
	 * @cfg {Zarafa.core.data.IPMRecord[]} The records on which this context menu acts
	 */
	records: undefined,

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

		if (config.records) {
			if (!Array.isArray(config.records)) {
				config.records = [ config.records ];
			}
		}

		Ext.applyIf(config, {
			items: [
				this.createContextActionItems(config.records),
				{ xtype: 'menuseparator' },
				container.populateInsertionPoint('context.note.contextmenu.actions', this),
				{ xtype: 'menuseparator' },
				container.populateInsertionPoint('context.note.contextmenu.options', this),
				{ xtype: 'menuseparator' },
				this.createOptionsMenuItem()
			],
			defaults: {
				xtype: 'zarafa.conditionalitem',
				hideOnDisabled: false
			}
		});

		Zarafa.note.ui.NoteContextMenu.superclass.constructor.call(this, config);
	},

	/**
	 * Create the Action context menu items
	 * @param {Zarafa.core.data.IPMRecord{}} The records on which this menu acts
	 * @return {Zarafa.core.ui.menu.ConditionalItem[]} The list of Action context menu items
	 * @private
	 */
	createContextActionItems: function(records)
	{
		return [{
			text: _('Open'),
			iconCls: 'icon_open',
			scope: this,
			handler: this.onContextItemOpen,
			singleSelectOnly: true
		}, {
			text: _('Copy/Move'),
			iconCls: 'icon_copy',
			scope: this,
			handler: this.onCopyMove
		}, {
			text: _('Print'),
			iconCls: 'icon_print',
			scope: this,
			handler: this.onContextItemPrint,
			singleSelectOnly: true
		}, {
			xtype: 'menuseparator'
		}, {
			text: _('Categories'),
			cls: 'k-unclickable',
			iconCls: 'icon_categories',
			hideOnClick: false,
			menu: {
				xtype: 'zarafa.categoriescontextmenu',
				records: records
			}
		}, {
			xtype: 'menuseparator'
		}, {
			text: _('Delete'),
			iconCls: 'icon_delete',
			scope: this,
			handler: this.onContextItemDelete
		}];
	},

	/**
	 * Open the {@link Zarafa.common.dialogs.CopyMoveContentPanel CopyMoveContentPanel} for copying
	 * or moving the currently selected notes.
	 * @private
	 */
	onCopyMove: function()
	{
		Zarafa.common.Actions.openCopyMoveContent(this.records);
	},

	/**
	 * Event handler which is called when the user selects the 'Open'
	 * item in the context menu. This will open the item in a new dialog.
	 * @private
	 */
	onContextItemOpen: function()
	{
		Zarafa.note.Actions.openNoteContent(this.records);
	},

	/**
	 * Event handler which is called when the user selects the 'Email message'
	 * item in the context menu. This will open a new mail dialog.
	 * @private
	 */
	onContextItemEmail: function()
	{
		Zarafa.note.Actions.openNoteEmailContent(this.records);
	},

	/**
	 * Event handler which is called when the user selects the 'Categories'
	 * item in the context menu. This will open {@link Zarafa.common.categories.dialogs.CategoriesContentPanel CategoriesContentPanel}.
	 * @private
	 */
	onContextItemCategories: function()
	{
		Zarafa.common.Actions.openCategoriesContent(this.records);
	},

	/**
	 * Create the Options menu entry.
	 * @return {Object} Menu item configuration
	 * @private
	 */
	createOptionsMenuItem: function()
	{
		return {
			text: _('Options'),
			iconCls: 'icon_cogwheel',
			scope: this,
			singleSelectOnly: true,
			handler: this.onContextItemOptions
		};
	},

	/**
	 * Open the options dialog for the selected record.
	 * @private
	 */
	onContextItemOptions: function()
	{
		Zarafa.note.Actions.openOptionsContent(this.records, {
			autoSave: true
		});
	},

	/**
	 * Event handler which is called when the user selects the 'Categories'
	 * item in the context menu. This will open {@link Zarafa.common.dialogs.CategoriesContentPanel CategoriesContentPanel}.
	 * @private
	 */
	onContextItemPrint: function()
	{
		Zarafa.common.Actions.openPrintDialog(this.records);
	},

	/**
	 * Event handler which is called when the user selects the 'Delete'
	 * item in the context menu. This will delete selected notes from view.
	 * @private
	 */
	onContextItemDelete: function()
	{
		Zarafa.common.Actions.deleteRecords(this.records);
	}
});

Ext.reg('zarafa.notecontextmenu', Zarafa.note.ui.NoteContextMenu);
/*
 * #dependsFile client/zarafa/common/ui/grid/Renderers.js
 */
Ext.namespace('Zarafa.note.ui');

/**
 * @class Zarafa.note.ui.NoteGridColumnModel
 * @extends Zarafa.common.ui.grid.ColumnModel
 */
Zarafa.note.ui.NoteGridColumnModel = Ext.extend(Zarafa.common.ui.grid.ColumnModel, {
	/**
	 * @constructor
	 * @param config Configuration structure
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			columns: this.createColumns(),
			defaults: {
				sortable: true
			}
		});

		Zarafa.note.ui.NoteGridColumnModel.superclass.constructor.call(this, config);
	},

	/**
	 * Create an array of {@link Ext.grid.Column columns} which must be visible within
	 * the default view of this {@link Ext.grid.ColumnModel ColumnModel}.
	 * @return {Ext.grid.Column[]} The array of columns
	 * @private
	 */
	createColumns: function()
	{
		return [{
			dataIndex	: 'icon_index',
			headerCls	: 'zarafa-icon-column',
			header		: '<p class="icon_index">&nbsp;<span class="title">Icon</span></p>',
			tooltip		: _('Sort by: Icon'),
			width		: 24,
			fixed		: true,
			renderer	: Zarafa.common.ui.grid.Renderers.icon
		}, {
			dataIndex	: 'subject',
			header		: _('Subject'),
			width		: 400,
			tooltip		: _('Sort by: Subject'),
			renderer	: Zarafa.common.ui.grid.Renderers.subject
		}, {
			dataIndex	: 'creation_time',
			header		: _('Created'),
			tooltip		: _('Sort by: Created'),
			width		: 160,
			renderer	: Zarafa.common.ui.grid.Renderers.datetime
		}, {
			dataIndex	: 'categories',
			id			: 'categories',
			header		: _('Categories'),
			width		: 160,
			tooltip		: _('Sort by: Categories'),
			renderer	: Zarafa.common.ui.grid.Renderers.categories
		}, {
			dataIndex	: 'color',
			header		: _('Color'),
			width		: 160,
			tooltip		: _('Sort by: Color'),
			renderer	: Zarafa.common.ui.grid.Renderers.colorTextValue,
			hidden		: true
		}];
	}
});
Ext.namespace('Zarafa.note.ui');

/**
 * @class Zarafa.note.ui.NoteIconView
 * @extends Zarafa.common.ui.DraggableDataView
 * @xtype zarafa.noteiconview
 */
Zarafa.note.ui.NoteIconView = Ext.extend(Zarafa.common.ui.DraggableDataView, {
	/**
	 * @cfg {Zarafa.note.NoteContext} context The context to which this panel belongs
	 */
	context: undefined,

	/**
	 * The {@link Zarafa.note.NoteContextModel} which is obtained from
	 * the {@link #context}.
	 *
	 * @property
	 * @type Zarafa.note.NoteContextModel
	 */
	model: undefined,

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

		if (!Ext.isDefined(config.model) && Ext.isDefined(config.context)) {
			config.model = config.context.getModel();
		}
		if (!Ext.isDefined(config.store) && Ext.isDefined(config.model)) {
			config.store = config.model.getStore();
		}

		config.store = Ext.StoreMgr.lookup(config.store);

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

		Ext.applyIf(config, {
			xtype		:'zarafa.noteiconview',
			id: 'note-iconview',
			cls: 'zarafa-note-iconview',
			loadingText: _('Loading notes') + '...',
			deferEmptyText: false,
			emptyText	: '<div class="emptytext">' + _('There are no items to show in this list') + '</div>',
			overClass	:'zarafa-note-iconview-over',
			tpl			: this.initTemplate(),
			multiSelect	: true,
			selectedClass:'zarafa-note-iconview-selected',
			itemSelector:'div.zarafa-note-iconview-thumb',
			enableDrag: true,
			ddGroup: 'dd.mapiitem'
		});

		Zarafa.note.ui.NoteIconView.superclass.constructor.call(this, config);

		this.initEvents();
	},

	/*
	 * Initialize html template by setting color icon and note subject
	 * @private
	 */
	initTemplate: function()
	{
		return new Ext.XTemplate(
			'<div style="height: 100%; width: 100%; overflow: auto;">',
				'<tpl for=".">',
					'<div class="zarafa-note-iconview-thumb">',
						'<div class="zarafa-note-iconview-icon {icon_index:this.getTheme}"></div>',
						'<div class="zarafa-note-iconview-subject">{subject:this.encodeSubject}</div>',
					'</div>',
				'</tpl>',
			'</div>',
			{
				getTheme: function(iconIndex)
				{
					switch (iconIndex) {
						case 768:
							return 'icon_note_blue_large';
						case 769:
							return 'icon_note_green_large';
						case 770:
							return 'icon_note_pink_large';
						case 771:
							return 'icon_note_yellow_large';
						case 772:
							return 'icon_note_white_large';
						default:
							return 'icon_note_yellow_large';
					}
				},
				encodeSubject: function(subject)
				{
					// the subject of notes is already 'ellipsed' but since the 2015 redesign
					// this subject is too large to fit (because of the increased font-size)
					// so we make it a little shorter here.
					return Ext.util.Format.ellipsis(Ext.util.Format.htmlEncode(subject), 20);
				}
			}
		);
	},

	/**
	 * initialize events for the grid panel
	 * @private
	 */
	initEvents: function()
	{
		this.on({
			'contextmenu': this.onNoteIconContextMenu,
			'dblclick': this.onIconDblClick,
			'selectionchange': this.onSelectionChange,
			'afterrender': this.onAfterRender,
			scope: this
		});

	},

	/**
	 * Event handler called when note icon view rendered properly.
	 */
	onAfterRender: function()
	{
		this.getEl().on({
			'mouseenter': this.onMouseEnter,
			'mouseleave': this.onMouseLeave,
			scope: this
		});
	},

	/*
	 * Event handler which is triggered when user opens context menu
	 * @param {Ext.DataView} dataview dataview object
	 * @param {Number} rowIndex	index of row
	 * @param {node} target html node
	 * @param {Ext.event} eventObj eventObj object of the event
	 * @private
	 */
	onNoteIconContextMenu: function(dataview, index, node, eventObj)
	{
		// check row is already selected or not, if its not selected then select it first
		if (!dataview.isSelected(node)) {
			dataview.select(node);
		}

		Zarafa.core.data.UIFactory.openDefaultContextMenu(dataview.getSelectedRecords(), { position: eventObj.getXY() });
	},

	/**
	 * Display open dialog on mouse double click
	 * @param {object} dataview object
	 * @param {Number} integer index number of selected record
	 * @param {node} target html node
	 * @param {object} event object
	 * @private
	 */
	onIconDblClick:function(dataview,index,node,event)
	{
		Zarafa.note.Actions.openNoteContent(this.getStore().getAt(index));
	},

	/**
	 * Event handler which is triggered when the {@link Zarafa.note.ui.NoteIconView NoteIconView}
	 * {@link Zarafa.core.data.IPMRecord record} selection is changed. This will inform
	 * the {@link Zarafa.note.NoteContextModel contextmodel} about the change.
	 *
	 * @param {Zarafa.note.ui.NoteIconView} dataView The view object.
	 * @param {HTMLElement[]} selection Array of selected nodes.
	 * @private
	 */
	onSelectionChange: function(dataView, selections)
	{
		this.model.setSelectedRecords(dataView.getSelectedRecords());
	}
});
//register xtype noteiconview
Ext.reg('zarafa.noteiconview',Zarafa.note.ui.NoteIconView);
Ext.namespace('Zarafa.note.ui');

/**
 * @class Zarafa.note.ui.NoteMainPanel
 * @extends Zarafa.common.ui.ContextMainPanel
 * @xtype zarafa.notemainpanel
 *
 * this class will be containing all the views that will be created for notes folder
 * main panel for the note content context
 */
Zarafa.note.ui.NoteMainPanel = Ext.extend(Zarafa.common.ui.ContextMainPanel, {
	// Insertion points for this class
	/**
	 * @insert context.note.toolbar.item
	 * Insertion point for populating Note context's main toolbar.
	 * This item is only visible when this context is active.
	 * @param {Zarafa.note.ui.NoteMainPanel} panel This panel
	 */
	/**
	 * @insert context.note.toolbar.paging
	 *
	 * Insertion point for populating note context's toolbar with extra
	 * pagination buttons. This can be used to replace the default {@link Ext.PagingToolbar}
	 * with an alternative. Note that by default all paging toolbars will be visible, and
	 * hiding a particular toolbar is the responsibility of the new pagers.
	 * @param {Zarafa.note.ui.NoteMainPanel} panel This panel
	 */
	/**
	 * @insert context.note.views
	 * Insertion point for adding views within the main panel of note context.
	 * This insertion point should be used in combination with 'main.maintoolbar.view.note'
	 * insertion point, and also view should set its store in the config object, the reference of
	 * {@link Zarafa.note.NoteContextModel NoteContextModel} is passed as parameter of this
	 * insertion point.
	 * @param {Zarafa.note.ui.NoteMainPanel} mainpanel This mainpanel
	 * @param {Zarafa.note.NoteContext} context The context for this panel
	 */

	/**
	 * The main panel in which the various views are located.
	 * @property
	 * @type Zarafa.core.ui.SwitchViewContentContainer
	 */
	viewPanel: undefined,

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

		Ext.applyIf(config, {
			xtype: 'zarafa.notemainpanel',
			layout: 'fit',
			items: [{
				xtype: 'zarafa.switchviewcontentcontainer',
				ref: 'viewPanel',
				layout: 'card',
				lazyItems: this.initViews(config.context)
			}],
			tbar: {
				xtype: 'zarafa.contextmainpaneltoolbar',
				defaultTitle: _('Notes'),
				paging: container.populateInsertionPoint('context.note.toolbar.paging', this),
				items: container.populateInsertionPoint('context.note.toolbar.item', this),
				context: config.context
			}
		});

		Zarafa.note.ui.NoteMainPanel.superclass.constructor.call(this,config);
	},

	/**
	 * Function will initialize all views associated with note context
	 * it will also get views added through 3rd party plugins and add it here
	 * @param {Zarafa.note.NoteContext} note context
	 * @return {Array} array of config objects of different views
	 * @private
	 */
	initViews: function(context)
	{
		// add the standard available views
		var allViews = [{
			xtype	: 'zarafa.noteiconview',//Icon View
			id	: 'note-iconview',
			context: context
		},{
			xtype	: 'zarafa.notegrid',//Grid View
			id	: 'note-gridview',
			context: context
		}];

		var additionalViewItems = container.populateInsertionPoint('context.note.views', this, context);
		allViews = allViews.concat(additionalViewItems);

		return allViews;
	},

	/**
	 * Function called by Extjs when the panel has been {@link #render rendered}.
	 * At this time all events can be registered.
	 * @private
	 */
	initEvents: function()
	{
		if (Ext.isDefined(this.context)) {
			this.mon(this.context, 'viewchange', this.onViewChange, this);

			this.onViewChange(this.context, this.context.getCurrentView());
		}
	},

	/**
	 * Event handler which is fired when the currently active view inside the {@link #context}
	 * has been updated. This will use the function
	 * {@link #viewPanel}#{@link Zarafa.core.ui.SwitchViewContentContainer#switchView}
	 * to make the requested view active.
	 *
	 * @param {Zarafa.note.NoteContext} context The context which fired the event
	 * @param {Zarafa.note.data.Views} newView The ID of the selected view
	 * @param {Zarafa.note.data.Views} oldView The ID of the previously selected view
	 * @private
	 */
	onViewChange: function(context, newView, oldView)
	{
		switch (newView) {
			case Zarafa.note.data.Views.ICON:
				this.viewPanel.switchView('note-iconview');
				break;
			case Zarafa.note.data.Views.LIST:
			case Zarafa.note.data.Views.SEARCH:
				this.viewPanel.switchView('note-gridview');
				break;
		}
	}
});
//register xtype of note main panel
Ext.reg('zarafa.notemainpanel',Zarafa.note.ui.NoteMainPanel);
Ext.namespace('Zarafa.settings');

/**
 * @class Zarafa.settings.PersistentSettingsModel
 * @extends Zarafa.settings.SettingsModel
 *
 * The PersistentSettingsModel class contains functionality that acts on the persistent settings of the user.
 * The settings are built up in a hierarchical way, where each node in the path is separated using
 * the {@link #pathSeparator}.
 */
Zarafa.settings.PersistentSettingsModel = Ext.extend(Zarafa.settings.SettingsModel, {

	/**
	 * @constructor
	 * @param config Configuration structure
	 */
	constructor: function(config)
	{
		config = config || {};
		if (!Ext.isObject(config.defaults)) {
			config.defaults = Zarafa.settings.data.PersistentSettingsDefaultValue.getDefaultValues();
		}

		Zarafa.settings.PersistentSettingsModel.superclass.constructor.call(this, config);
	},

	/**
	 * Save the persistentsettings to the server, this will call {@link #execute} for
	 * the different {@link Zarafa.core.Actions actions} which are supposed
	 * to be executed on the server.
	 */
	save: function()
	{
		if (!Ext.isEmpty(this.deleted)) {
			this.execute(Zarafa.core.Actions['delete'], this.deleted);
		}

		if (!Ext.isEmpty(this.modified)) {
			this.execute(Zarafa.core.Actions['set'], this.modified);
		}

		if (!Ext.isEmpty(this.resetSettings)) {
			this.execute(Zarafa.core.Actions['reset'], this.resetSettings);
		}
	},

	/**
	 * Send the save action to the server. Overridden to set the actionData in 'persistentSetting' instead of 'setting'.
	 * @param {Zarafa.core.Actions} action The action which must be performed on the server
	 * @param {Object} parameters The action parameters which must be send to the server.
	 * @private
	 */
	execute: function(action, parameters)
	{
		if (this.fireEvent('beforesave', this, { action: parameters}) !== false) {
			container.getRequest().singleRequest(
				Zarafa.core.ModuleNames.getListName('settings'),
				action,
				{
					'persistentSetting': parameters
				},
				new Zarafa.core.data.ProxyResponseHandler({
					proxy: this,
					action: Ext.data.Api.actions['update'],
					options: {
						action: action,
						parameters: parameters
					},
					callback:  this.onExecuteComplete,
					scope: this
				})
			);
		}
	}

});
Ext.namespace('Zarafa.settings.data');

/**
 * @class Zarafa.settings.data.KeyboardSettings
 * @extends Zarafa.core.Enum
 *
 * Enum containing the keyboard settings modes.
 *
 * @singleton
 */
Zarafa.settings.data.KeyboardSettings = Zarafa.core.Enum.create({
	/**
	 * Don't use Keyboard Shortcuts
	 * @property
	 * @type String
	 */
	NO_KEYBOARD_SHORTCUTS: 'disabled',

	/**
	 * All Keyboard Shortcuts
	 * @property
	 * @type String
	 */
	ALL_KEYBOARD_SHORTCUTS: 'full',

	/**
	 * Basic Keyboard Shortcuts
	 *
	 * @property
	 * @type String
	 */
	BASIC_KEYBOARD_SHORTCUTS: 'basic'
});
Ext.namespace('Zarafa.settings.ui');

/**
 * @class Zarafa.settings.ui.SettingsAccountWidget
 * @extends Zarafa.settings.ui.SettingsWidget
 * @xtype zarafa.settingsaccountwidget
 *
 * The Account Information widget
 */
Zarafa.settings.ui.SettingsAccountWidget = Ext.extend(Zarafa.settings.ui.SettingsWidget, {
	/**
	 * The language which is currently active in the interface
	 * @property
	 * @type String
	 * @private
	 */
	origLanguage: '',

	/**
	 * The name that will be shown for the default theme (i.e. no theme selected)
	 * @property
	 * @type String
	 * @private
	 */
	defaultThemeName: _('Basic'),

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

		var user = container.getUser();
		var languageStore = {
			xtype: 'jsonstore',
			autoDestroy: true,
			fields: ['lang', 'name'],
			data: container.getLanguages()
		};

		// Load the items from the maintabbar
		var items = container.populateInsertionPoint('main.maintabbar.left', this);
		items = Zarafa.core.Util.sortArray(items, 'ASC', 'tabOrderIndex');
		var startupStore = {
			xtype: 'jsonstore',
			autoDestroy: true,
			fields: ['context', 'text'],
			data: items
		};

		// Create a store with the available themes
		items = [[0, this.defaultThemeName, 'basic']];
		var plugins = container.getPlugins();
		for ( var i=0; i<plugins.length; i++ ){
			var plugin = plugins[i];
			if ( plugin instanceof Zarafa.core.ThemePlugin ){
				items.push([items.length, plugin.getDisplayName(), plugin.getName()]);
			}
		}
		const jsonThemes = container.getServerConfig().getJsonThemes();
		for ( var theme in jsonThemes ) {
			if ( jsonThemes.hasOwnProperty(theme) ) {
				items.push([items.length, jsonThemes[theme], theme]);
			}
		}
		var themeStore = new Ext.data.ArrayStore({
			fields: ['id', 'displayName', 'name'],
			idIndex: 0,
			data: items,
			sortInfo: {
				field: 'displayName',
				direction: 'ASC'
			}
		});

		var iconsetItems = [];
		const iconsets = container.getServerConfig().getIconsets();
		for ( var iconset in iconsets ) {
			if ( iconsets.hasOwnProperty(iconset) ) {
				iconsetItems.push([iconset, iconsets[iconset]['display-name']]);
			}
		}
		var iconsetStore = new Ext.data.ArrayStore({
			fields: ['id', 'displayName'],
			idIndex: 0,
			data: iconsetItems,
			sortInfo: {
				field: 'displayName',
				direction: 'ASC'
			}
		});

		// Dark mode toggle: light / dark / system
		var darkModeStore = new Ext.data.ArrayStore({
			fields: ['id', 'displayName'],
			idIndex: 0,
			data: [
				['light', _('Light')],
				['dark', _('Dark')],
				['system', _('System')]
			]
		});

		var formItems = [{
			xtype: 'zarafa.compositefield',
			fieldLabel: _('Language'),
			items: [{
				xtype: 'combo',
				name: 'zarafa/v1/main/language',
				ref: '../../languageCombo',
				width: 200,
				store: languageStore,
				mode: 'local',
				triggerAction: 'all',
				displayField: 'name',
				valueField: 'lang',
				lazyInit: false,
				forceSelection: true,
				editable: false,
				autoSelect: true,
				listeners: {
					select: this.onLanguageSelect,
					scope: this
				}
			},{
				xtype: 'displayfield',
				cls: 'zarafa-settings-reload-warning',
				ref: '../../languageWarning'
			}]
		},{
			xtype: 'combo',
			fieldLabel: _('Startup folder'),
			name: 'zarafa/v1/main/default_context',
			ref: '../startupCombo',
			width: 200,
			store: startupStore,
			mode: 'local',
			triggerAction: 'all',
			displayField: 'text',
			valueField: 'context',
			lazyInit: false,
			forceSelection: true,
			editable: false,
			autoSelect: true,
			listeners: {
				select: this.onStartupSelect,
				scope: this
			}
		}];

		if ( themeStore.data.length > 1 && container.getServerConfig().isThemingEnabled()){
			formItems.push({
				xtype: 'combo',
				width: 200,
				editable: false,
				forceSelection: true,
				triggerAction: 'all',
				store: themeStore,
				fieldLabel: _('Theme'),
				mode: 'local',
				valueField: 'name',
				displayField: 'displayName',
				ref: '../themeCombo',
				name: 'zarafa/v1/main/active_theme',
				listeners: {
					select: this.onThemeSelect,
					scope: this
				}
			});
		}

		formItems.push({
			xtype: 'combo',
			id: 'darkmode-combo',
			width: 200,
			editable: false,
			forceSelection: true,
			triggerAction: 'all',
			store: darkModeStore,
			fieldLabel: _('Appearance'),
			mode: 'local',
			valueField: 'id',
			displayField: 'displayName',
			ref: '../darkModeCombo',
			name: 'zarafa/v1/main/dark_mode',
			listeners: {
				select: this.onDarkModeSelect,
				scope: this
			}
		});

		formItems.push({
			xtype: 'combo',
			width: 200,
			editable: false,
			forceSelection: true,
			triggerAction: 'all',
			store: iconsetStore,
			fieldLabel: _('Icons'),
			mode: 'local',
			hidden: !container.getServerConfig().isIconSetsEnabled(),
			valueField: 'id',
			displayField: 'displayName',
			ref: '../iconsetCombo',
			name: 'zarafa/v1/main/active_iconset',
			listeners: {
				select: this.onIconsetSelect,
				scope: this
			}
		});

		formItems.push(
			container.populateInsertionPoint('settings.account.last')
		);

		Ext.applyIf(config, {
			title: _('Profile'),
			cls: 'zarafa-settings-widget k-settings-profile-widget',
			items: [{
				xtype: 'container',
				cls: 'k-settings-profile-header',
				items: [{
					xtype: 'box',
					cls: 'k-settings-profile-photo-wrap',
					autoEl: {
						tag: 'img',
						cls: 'k-settings-profile-photo',
						src: user.getUserImage()
					},
					name: 'zarafa/v1/main/thumbnail_photo',
					ref: '../thumbnail_photo',
					listeners: {
						afterrender: this.onAfterRender,
						scope: this
					}
				},{
					xtype: 'container',
					cls: 'k-settings-profile-info',
					items: [{
						xtype: 'box',
						autoEl: {
							tag: 'div',
							cls: 'k-settings-profile-name',
							html: Ext.util.Format.htmlEncode(user.getDisplayName())
						}
					},{
						xtype: 'box',
						autoEl: {
							tag: 'div',
							cls: 'k-settings-profile-email',
							html: Ext.util.Format.htmlEncode(user.getSMTPAddress())
						}
					},{
						xtype: 'button',
						cls: 'k-settings-profile-details-btn',
						text: _('Personal information'),
						handler: this.onOpenPersonalInfo,
						scope: this
					}]
				}]
			},{
				xtype: 'container',
				cls: 'k-settings-profile-form',
				layout: 'form',
				labelWidth: 200,
				items: formItems
			}]
		});

		Zarafa.settings.ui.SettingsAccountWidget.superclass.constructor.call(this, config);
	},

	/**
	 * Event handler which is fired after the {@link Ext.form.FormPanel FormPanel}
	 * has been {@link Ext.Component#afterrender rendered}. Here thumbnail photo box has
	 * listen {@link Ext.Element#click single click}, {@link Ext.Element#dblclick double click} and
	 * {@link Ext.Element#contextmenu context menu} evetns.
	 * @param {Ext.Component} thumbnailPhotoBox which show the thumbnail picture.
	 * @private
	 */
	onAfterRender : function(thumbnailPhotoBox)
	{
		var el = thumbnailPhotoBox.getEl();
		var imgEl = el.child('img.k-settings-profile-photo') || el;
		this.mon(imgEl, {
			'click' : this.onSingleClick,
			'dblclick' : this.onDoubleClick,
			'scope' : this
		});
	},

	/**
	 * Helper to get the profile photo img element.
	 * @return {HTMLElement} The img DOM element
	 * @private
	 */
	getPhotoImgEl : function()
	{
		var el = this.thumbnail_photo.getEl();
		var imgEl = el.child('img.k-settings-profile-photo');
		return imgEl ? imgEl.dom : el.dom;
	},

	/**
	 * Callback function for {@link Zarafa.common.attachment.ui.UploadAttachmentComponent}.
	 *
	 * @param {Object/Array} files The files is contains file information.
	 * @param {Object} form the form is contains {@link Ext.form.BasicForm bacisform} info.
	 */
	uploadThumbnailPhotoCallback : function(files, form)
	{
		try {
			var imgDom = this.getPhotoImgEl();
			var file = files[0]
			const fr = new FileReader(file)
			fr.readAsDataURL(file)
			fr.onload = function () {
			 	imgDom.src = this.result;
			}
		}  catch(e) {
			console.log('File Upload not supported: ${e}');
		}
	},

	/**
	 * Event handler for opening the Browser's file selection dialog.
	 * See {@link #onFileInputChange} for the handling of the selected files.
	 * @private
	 */
	uploadThumbnailPhoto : function()
	{
		var attachComponent = new Zarafa.common.attachment.ui.UploadAttachmentComponent({
			callback : this.uploadThumbnailPhotoCallback,
			accept : 'image/*',
			scope : this
		});

		attachComponent.openAttachmentDialog();
	},

	/**
	 * Event handler which is fired when the thumbnail
	 * picture field is being clicked this will call the
	 * {@link #uploadThumbnailPhoto} function to open upload dialog.
	 *
	 * @param {Ext.EventObject} eventObj eventObj object of the event
	 * @param {Element} target Event target
	 * @param {Object} object Configuration object
	 */
	onSingleClick : function(eventObj, target, object)
	{
		this.uploadThumbnailPhoto();
	},

	/**
	 * Event handler which is fired when thumbnail picture field is being
	 * double-clicked and this will call the {@link #uploadThumbnailPhoto}
	 * function to open upload dialog.
	 *
	 * @param {Ext.EventObject} eventObj eventObj object of the event
	 * @param {Element} target Event target
	 * @param {Object} object Configuration object
	 */
	onDoubleClick : function(eventObj, target, object)
	{
		this.uploadThumbnailPhoto();
	},

	/**
	 * Opens the current user's address book detail dialog showing
	 * full personal information from the GAL.
	 * @private
	 */
	onOpenPersonalInfo : function()
	{
		var user = container.getUser();
		var entryid = user.getEntryId();
		var record = Zarafa.core.data.RecordFactory.createRecordObjectByObjectType(
			Zarafa.core.mapi.ObjectType.MAPI_MAILUSER, {
				entryid: entryid,
				display_name: user.getDisplayName(),
				object_type: Zarafa.core.mapi.ObjectType.MAPI_MAILUSER
			}, entryid);

		container.getShadowStore().add(record);
		Zarafa.core.data.UIFactory.openViewRecord(record, { modal: true });
	},

	/**
	 * Event handler which is fired when a language in the {@link Ext.form.ComboBox combobox}
	 * has been selected. This will inform the user that this setting requires a reload of the
	 * webapp to become active.
	 * @param {Ext.form.ComboBox} combo The combobox which fired the event
	 * @param {Ext.data.Record} record The selected record in the combobox
	 * @param {Number} index The selected index in the store
	 * @private
	 */
	onLanguageSelect: function(combo, record, index)
	{
		var value = record.get(combo.valueField);

		if (this.origLanguage !== value) {
			this.model.requiresReload = true;
		}

		if (this.model) {
			this.model.set(combo.name, value);
		}
	},

	/**
	 * Event handler which is fired when a Startup Context in the {@link Ext.form.ComboBox combobox}
	 * has been selected.
	 * @param {Ext.form.ComboBox} combo The combobox which fired the event
	 * @param {Ext.data.Record} record The selected record in the combobox
	 * @param {Number} index The selected index in the store
	 * @private
	 */
	onStartupSelect: function(combo, record, index)
	{
		var value = record.get(combo.valueField);
		if (this.model) {
			this.model.set(combo.name, value);
		}
	},

	/**
	 * Event handler which is fired when a theme in the {@link Ext.form.ComboBox combobox}
	 * has been selected. This will dynamically apply the new theme without requiring a reload.
	 * @param {Ext.form.ComboBox} combo The combobox which fired the event
	 * @param {Ext.data.Record} record The selected record in the combobox
	 * @param {Number} index The selected index in the store
	 * @private
	 */
	onThemeSelect: function(combo, record, index)
	{
		var value = record.get(combo.valueField);

		if (this.activeTheme !== value) {
			// Dynamically switch theme by updating the body class
			var bodyEl = Ext.getBody();

			// Remove ALL existing theme-* classes
			var bodyClasses = bodyEl.dom.className.split(' ');
			for (var i = 0; i < bodyClasses.length; i++) {
				if (bodyClasses[i].indexOf('theme-') === 0) {
					bodyEl.removeClass(bodyClasses[i]);
				}
			}

			// Add new theme class if not 'basic'
			if (value && value !== 'basic') {
				bodyEl.addClass('theme-' + value);
			}

			this.activeTheme = value;
		}

		if (this.model) {
			this.model.set(combo.name, value);
		}
	},

	/**
	 * Event handler for dark mode combo selection.
	 * @param {Ext.form.ComboBox} combo
	 * @param {Ext.data.Record} record
	 * @param {Number} index
	 * @private
	 */
	onDarkModeSelect: function(combo, record, index)
	{
		var value = record.get(combo.valueField);
		Zarafa.core.DarkMode.setMode(value);

		if (this.model) {
			this.model.set(combo.name, value);
		}
	},

	/**
	 * Event handler which is fired when an iconset in the {@link Ext.form.ComboBox combobox}
	 * has been selected. This will inform the user that this setting requires a reload of the
	 * webapp to become active.
	 * @param {Ext.form.ComboBox} combo The combobox which fired the event
	 * @param {Ext.data.Record} record The selected record in the combobox
	 * @param {Number} index The selected index in the store
	 * @private
	 */
	onIconsetSelect: function(combo, record, index)
	{
		var value = record.get(combo.valueField);

		if (this.activeIconset !== value) {
			this.model.requiresReload = true;
		}

		if (this.model) {
			this.model.set(combo.name, value);
		}
	},

	/**
	 * Called by the {@link Zarafa.settings.ui.SettingsCategoryWidgetPanel widget panel}
	 * to load the latest version of the settings from the
	 * {@link Zarafa.settings.SettingsModel} into the UI of this category.
	 * @param {Zarafa.settings.SettingsModel} settingsModel The settings to load
	 */
	update: function(settingsModel)
	{
		Zarafa.settings.ui.SettingsAccountWidget.superclass.update.apply(this, arguments);

		this.model = settingsModel;

		// Load the original language from the settings
		this.origLanguage = settingsModel.get(this.languageCombo.name);

		this.languageCombo.setValue(this.origLanguage);
		this.languageWarning.reset();

		this.startupCombo.setValue(settingsModel.get(this.startupCombo.name));

		// Set the currently active theme only when themes are available and the themecombo exists
		if ( Ext.isDefined(this.themeCombo) ){
			this.activeTheme = settingsModel.get(this.themeCombo.name);
			// Check if a theme was set and if this theme has not been removed by the admin
			if ( !this.activeTheme || this.themeCombo.store.find('name', this.activeTheme)===-1 ){
				this.activeTheme = container.getServerConfig().getActiveTheme() || this.defaultThemeName;
			}
			this.themeCombo.setValue(this.activeTheme);
		}

		this.activeIconset = settingsModel.get(this.iconsetCombo.name);
		// Check if an iconset was set
		if ( !this.activeIconset || this.iconsetCombo.store.find('id', this.activeIconset)===-1 ){
			this.activeIconset = container.getServerConfig().getActiveIconset();
		}
		this.iconsetCombo.setValue(this.activeIconset);

		// Set the dark mode combo value
		var darkMode = settingsModel.get(this.darkModeCombo.name) || Zarafa.core.DarkMode.getMode();
		this.darkModeCombo.setValue(darkMode);
	},

	/**
	 * Called by the {@link Zarafa.settings.ui.SettingsCategoryWidgetPanel widget panel}
	 * to update the settings from the UI into the {@link Zarafa.settings.SettingsModel settings model}.
	 * @param {Zarafa.settings.SettingsModel} settingsModel The settings to update
	 */
	updateSettings: function(settingsModel)
	{
		Zarafa.settings.ui.SettingsAccountWidget.superclass.updateSettings.apply(this, arguments);

		settingsModel.beginEdit();
		var user = container.getUser();
		var photoSrc = this.getPhotoImgEl().src;
		user.setUserImage(photoSrc);
		settingsModel.set(this.thumbnail_photo.name, photoSrc);
		settingsModel.set(this.languageCombo.name, this.languageCombo.getValue());
		settingsModel.set(this.startupCombo.name, this.startupCombo.getValue());

		// Save the selected theme only when themes are available and the themecombo exists
		if ( Ext.isDefined(this.themeCombo) ){
			settingsModel.set(this.themeCombo.name, this.themeCombo.getValue());
		}

		settingsModel.set(this.iconsetCombo.name, this.iconsetCombo.getValue());
		settingsModel.set(this.darkModeCombo.name, this.darkModeCombo.getValue());
		settingsModel.endEdit();
	}
});

Ext.reg('zarafa.settingsaccountwidget', Zarafa.settings.ui.SettingsAccountWidget);
Ext.namespace('Zarafa.settings.ui');

/**
 * @class Zarafa.settings.ui.SettingsAddressBookWidget
 * @extends Zarafa.settings.ui.SettingsWidget
 * @xtype zarafa.settingsaddressbookwidget
 *
 * The default Addressbook Configuration widget
 */
Zarafa.settings.ui.SettingsAddressBookWidget = Ext.extend(Zarafa.settings.ui.SettingsWidget, {

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

		var hierarchyTpl = new Ext.XTemplate(
			'<tpl for=".">',
				'<div class="x-combo-list-item<tpl if="group_header"> k-combo-list-item-header</tpl>">',
					'{depth:indent}{display_name:htmlEncode}',
				'</div>',
			'</tpl>',
			{
				compiled: true
			}
		);

		var nameFormatStore = new Ext.data.ArrayStore({
			fields: ['value', 'label'],
			data: [
				['lastfirst', _('Last Name, First Name')],
				['firstlast', _('First Name Last Name')]
			]
		});

		Ext.applyIf(config, {
			xtype: 'zarafa.settingsaddressbookwidget',
			title: _('Address Book'),
			layout: 'form',
			items: [{
				xtype: 'combo',
				fieldLabel: _('Select Default Folder'),
				name: 'zarafa/v1/main/default_addressbook',
				ref: 'defaultABCombo',
				width: 200,
				store: Zarafa.addressbook.AddressBookHierarchyStore,
				mode: 'local',
				triggerAction: 'all',
				displayField: 'display_name',
				valueField: 'entryid',
				tpl: hierarchyTpl,
				lazyInit: false,
				forceSelection: true,
				editable: false,
				listeners: {
					beforeselect: this.onBeforeDefaultABSelect,
					select: this.onDefaultABSelect,
					scope: this
				}
			},{
				xtype: 'combo',
				fieldLabel: _('Name Display Format'),
				name: 'zarafa/v1/main/addressbook_name_format',
				ref: 'nameFormatCombo',
				width: 200,
				store: nameFormatStore,
				mode: 'local',
				triggerAction: 'all',
				displayField: 'label',
				valueField: 'value',
				forceSelection: true,
				editable: false,
				listeners: {
					select: this.onNameFormatSelect,
					scope: this
				}
			}]
		});

		Zarafa.settings.ui.SettingsAddressBookWidget.superclass.constructor.call(this, config);
	},

	/**
	 * Event handler for the onbeforeselect event of the Address Book combo. Will
	 * make sure group headers cannot be selected.
	 *
	 * @param {Ext.form.ComboBox} combo The Address Book combobox
	 * @param {Zarafa.core.data.IPMRecord IPMRecord} record The selected Address Book record
	 * @param {Number} index The index of the selected record in the combo
	 */
	onBeforeDefaultABSelect: function(combo, record, index)
	{
		return !record.get('group_header');
	},

	/**
	 * Event handler which is fired when a Address book name in the {@link Ext.form.ComboBox combobox}
	 * has been selected.
	 * @param {Ext.form.ComboBox} combo The combobox which fired the event
	 * @param {Ext.data.Record} record The selected record in the combobox
	 * @param {Number} index The selected index in the store
	 * @private
	 */
	onDefaultABSelect: function(combo, record, index)
	{
		var value = record.get(combo.valueField);
		if (this.model) {
			this.model.set(combo.name, value);
		}
	},

	/**
	 * Event handler which is fired when a name format in the {@link Ext.form.ComboBox combobox}
	 * has been selected.
	 * @param {Ext.form.ComboBox} combo The combobox which fired the event
	 * @param {Ext.data.Record} record The selected record in the combobox
	 * @param {Number} index The selected index in the store
	 * @private
	 */
	onNameFormatSelect: function(combo, record, index)
	{
		if (this.model) {
			this.model.set(combo.name, record.get(combo.valueField));
		}
	},

	/**
	 * Called by the {@link Zarafa.settings.ui.SettingsCategoryWidgetPanel widget panel}
	 * to load the latest version of the settings from the
	 * {@link Zarafa.settings.SettingsModel} into the UI of this category.
	 * @param {Zarafa.settings.SettingsModel} settingsModel The settings to load
	 */
	update: function(settingsModel)
	{
		Zarafa.settings.ui.SettingsAddressBookWidget.superclass.update.apply(this, arguments);
		this.model = settingsModel;

		if (Zarafa.addressbook.AddressBookHierarchyStore.getCount() === 0) {
			// there aren't any records in the store
			// we can not select any value
			return;
		}

		var combo = this.defaultABCombo;

		var entryid = this.model.get(combo.name);
		var record;

		// get corresponding record from combo store as we want to use id comparison functions
		if (!Ext.isEmpty(entryid)) {
			record = Zarafa.addressbook.AddressBookHierarchyStore.getById(entryid);
		}

		if (Ext.isEmpty(record)){
			record = Zarafa.addressbook.AddressBookHierarchyStore.getAt(0);
		}

		combo.setValue(record.get(combo.valueField));

		var nameFormat = this.model.get(this.nameFormatCombo.name) || 'lastfirst';
		this.nameFormatCombo.setValue(nameFormat);
	},

	/**
	 * Called by the {@link Zarafa.settings.ui.SettingsCategoryWidgetPanel widget panel}
	 * to update the settings from the UI into the {@link Zarafa.settings.SettingsModel settings model}.
	 * @param {Zarafa.settings.SettingsModel} settingsModel The settings to update
	 */
	updateSettings: function(settingsModel)
	{
		Zarafa.settings.ui.SettingsAddressBookWidget.superclass.updateSettings.apply(this, arguments);
		settingsModel.set(this.defaultABCombo.name, this.defaultABCombo.getValue());
		settingsModel.set(this.nameFormatCombo.name, this.nameFormatCombo.getValue());
	}
});

Ext.reg('zarafa.settingsaddressbookwidget', Zarafa.settings.ui.SettingsAddressBookWidget);
Ext.namespace('Zarafa.settings.ui');

/**
 * @class Zarafa.settings.ui.SettingsAdvancedCategory
 * @extends Zarafa.settings.ui.SettingsCategory
 * @xtype zarafa.settingsadvancedcategory
 *
 * The default available category for users which will
 * load the {@link Zarafa.settings.ui.SettingsTreePanel Settings Tree}
 * to display all available settings which are available for the user.
 */
Zarafa.settings.ui.SettingsAdvancedCategory = Ext.extend(Zarafa.settings.ui.SettingsCategory, {

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

		Ext.applyIf(config, {
			title: _('Advanced'),
			categoryIndex: 9999,
			iconCls: 'zarafa-settings-category-advanced',
			cls: 'k-settings-category-advanced-container',
			layout: 'fit',
			// scrolling is supplied by the treepanel
			autoScroll: false,
			defaults: {
				margins: "0 0 5 0"
			},
			items: [{
				xtype:'container',
				layout: {
					type: 'border',
				},
				items: [{
					xtype: 'zarafa.settingswidget',
					title: _('Advanced settings'),
					layout:{
						type: 'vbox',
						align: 'stretch'
					},
					region:'center',
					items: [{
						xtype: 'textfield',
						emptyText: _('Search…'),
						anchor: '100%',
						cls: 'k-settings-search-field',
						enableKeyEvents: true,
						listeners: {
							scope: this,
							keyup: {
								fn: this.onSearchTextFiledKeyUp,
								buffer: 250
							}
						}
					}, {
						xtype: 'tbspacer',
						height: 10
					},{
						xtype: 'zarafa.settingstreepanel',
						treeFilter: true,
						ref: '../../treePanel',
						flex: 1
					}]
				}, {
					xtype: 'zarafa.settingswidget',
					title: _('Developer tools'),
					height: 100,
					region: 'north',
					items:[{
						xtype: 'checkbox',
						name: 'zarafa/v1/main/kdeveloper_tool/kdeveloper',
						boxLabel: _('Show insertion points in grommunio Web'),
						ref: '../../showInsertionCheck',
						hideLabel: true,
						listeners: {
							change: this.onFieldChange,
							scope: this
						}
					},{
						xtype: 'checkbox',
						name: 'zarafa/v1/main/kdeveloper_tool/itemdata',
						boxLabel: _('Show "item data" button in context menu (mail only)'),
						ref: '../../showItemData',
						hideLabel: true,
						listeners: {
							change: this.onFieldChange,
							scope: this
						}
					}]
				}]
			}]
		});

		Zarafa.settings.ui.SettingsAdvancedCategory.superclass.constructor.call(this, config);
	},

	/**
	 * Called by the {@link Zarafa.settings.ui.SettingsCategoryWidgetPanel widget panel}
	 * to load the latest version of the settings from the
	 * {@link Zarafa.settings.SettingsModel} into the UI of this category.
	 * @param {Zarafa.settings.SettingsModel} settingsModel The settings to load
	 */
	update: function(settingsModel)
	{
		this.model = settingsModel;
		Zarafa.settings.ui.SettingsAdvancedCategory.superclass.update.apply(this, arguments);

		this.treePanel.bindModel(settingsModel);

		this.showInsertionCheck.setValue(settingsModel.get(this.showInsertionCheck.name));
		this.showItemData.setValue(settingsModel.get(this.showItemData.name));
	},

	/**
	 * Called by the {@link Zarafa.settings.ui.SettingsCategory Category} when
	 * it has been called with {@link zarafa.settings.ui.SettingsCategory#updateSettings}.
	 * This is used to update the settings from the UI into the {@link Zarafa.settings.SettingsModel settings model}.
	 * @param {Zarafa.settings.SettingsModel} settingsModel The settings to update
	 */
	updateSettings: function(settingsModel)
	{
		Zarafa.settings.ui.SettingsAdvancedCategory.superclass.updateSettings.apply(this, arguments);

		settingsModel.set(this.showInsertionCheck.name, this.showInsertionCheck.getValue());
		settingsModel.set(this.showItemData.name, this.showItemData.getValue());
	},

	/**
	 * Event handler which is triggered when
	 * a key is pressed in the filterSearchTextBox.
	 *
	 * @param {Ext.form.TextField} field
	 * @param {Ext.EventObject} eventObj
	 * @private
	 */
	onSearchTextFiledKeyUp: function (field, eventObj)
	{
		var value = field.getRawValue();
		if (Ext.isEmpty(value) && !Ext.isEmpty(this.treePanel.treeFilter)) {
			this.treePanel.treeFilter.clear();
			return;
		}
		var re = new RegExp('' + value + '', 'i');
		this.treePanel.treeFilter.filter(re);
	},

	/**
	 * Event handler which is called when "Show insertion points in grommunio Web" has been checked/unchecked.
	 * This will apply the new value to the settings.
	 * @param {Ext.form.Field} field The field which has fired the event
	 * @param {String} checked The new value of check box.
	 * @private
	 */
	onFieldChange: function(field, checked)
	{
		if (this.model) {
			if (this.model.get(field.name) !== checked) {
				this.model.set(field.name, checked);
				this.model.requiresReload = true;
			}
		}
	}
});

Ext.reg('zarafa.settingsadvancedcategory', Zarafa.settings.ui.SettingsAdvancedCategory);
Ext.namespace('Zarafa.settings.ui');

/**
 * @class Zarafa.settings.ui.SettingsContextMenu
 * @extends Zarafa.core.ui.menu.ConditionalMenu
 * @xtype zarafa.settingscontextmenu
 */
Zarafa.settings.ui.SettingsContextMenu = Ext.extend(Zarafa.core.ui.menu.ConditionalMenu, {
	// Insertion points for this class
	/**
	 * @insert context.settings.tree.contextmenu.actions
	 * @param {Zarafa.settings.ui.SettingsContextMenu} contextmenu This contextmenu
	 */

	/**
	 * @cfg {Zarafa.settings.SettingsModel} settingsModel The settingsModel on which
	 * to work with the settings.
	 */
	settingsModel: undefined,

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

		var isRoot = false;
		var isEditable = false;
		if (config.records) {
			if (Array.isArray(config.records)) {
				config.records = config.records[0];
			}

			isRoot = Ext.isDefined(config.records.isRoot) && config.records.isRoot;
			isEditable = config.records.isEditable();
		}

		Ext.applyIf(config, {
			settingsModel: container.getSettingsModel(),
			items: [{
				xtype: 'zarafa.conditionalitem',
				text: _('Edit setting'),
				hidden: isRoot || !isEditable,
				handler: this.onEditSetting,
				scope: this
			},{
				xtype: 'zarafa.conditionalitem',
				text: _('Restore to defaults'),
				hidden: isRoot,
				handler: this.onRestoreSettings,
				scope: this
			},{
				xtype: 'zarafa.conditionalitem',
				text: _('Delete settings'),
				hidden: isRoot,
				handler: this.onDeleteSettings,
				scope: this
			},{
				xtype: 'menuseparator'
			},
			container.populateInsertionPoint('context.settings.tree.contextmenu.actions', this)
			]
		});

		Zarafa.settings.ui.SettingsContextMenu.superclass.constructor.call(this, config);
	},

	/**
	 * Event handler when the "Edit Setting" option was selected.
	 * This will enable the {@link Zarafa.settings.ui.SettingsTreeNode#startEdit editor}.
	 * @private
	 */
	onEditSetting: function()
	{
		this.records.startEdit();
	},

	/**
	 * Event handler when the "Restore default" option was selected.
	 * This will {@link Zarafa.settings.SettingsModel#restore restore the default values} of the settings.
	 * @private
	 */
	onRestoreSettings: function()
	{
		this.settingsModel.restore(this.records.id);
	},

	/**
	 * Event handler when the "Delete settings" option was selected.
	 * This will {@link Zarafa.settings.SettingsModel#remove remove} settings.
	 * @private
	 */
	onDeleteSettings: function()
	{
		this.settingsModel.remove(this.records.id);
	}

});

Ext.reg('zarafa.settingscontextmenu', Zarafa.settings.ui.SettingsContextMenu);
Ext.namespace('Zarafa.settings.ui');

/**
 * @class Zarafa.settings.ui.SettingsCopyrightCategory
 * @extends Zarafa.settings.ui.SettingsCategory
 * @xtype zarafa.settingscopyrightcategory
 *
 * Special Settings Category which shows the copyright notice
 * of grommunio Web and any possible plugins which register their own
 * copyright notices.
 */
Zarafa.settings.ui.SettingsCopyrightCategory = Ext.extend(Zarafa.settings.ui.SettingsCategory, {
	// Insertion points for this class
	/**
	 * @insert context.settings.category.copyright
	 * Insertion point to register new {@link Zarafa.settings.ui.SettingsWidget widgets}
	 * for the {@link Zarafa.settings.ui.SettingsCopyrightCategory Copyright Category}.
	 * @param {Zarafa.settings.ui.SettingsCopyrightCategory} category The copyright
	 * category to which the widgets will be added.
	 */

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

		// Get the about texts of iconsets
		var iconsetAbout = container.getServerConfig().getIconsetAbouts();
		var iconsetAboutWidgets = [];
		for ( var iconset in iconsetAbout ) {
			iconsetAboutWidgets.push({
				xtype: 'zarafa.settingscopyrightwidget',
				title: String.format(_('{0} Iconset'), iconsetAbout[iconset]['displayName']),
				about: iconsetAbout[iconset]['about']
			});
		}

		Ext.applyIf(config, {
			title: _("Legal Information"),
			categoryIndex: 10000,
			iconCls: 'zarafa-settings-category-copyright',
			items: [
				{
					xtype: 'zarafa.settingscopyrightwidget',
					about: Zarafa.ABOUT
				},
				container.populateInsertionPoint('context.settings.category.copyright', this)
			].concat(iconsetAboutWidgets)
		});

		Zarafa.settings.ui.SettingsCopyrightCategory.superclass.constructor.call(this, config);
	}
});

Ext.reg('zarafa.settingscopyrightcategory', Zarafa.settings.ui.SettingsCopyrightCategory);
Ext.namespace('Zarafa.settings.ui');

/**
 * @class Zarafa.settings.ui.SettingsCopyrightWidget
 * @extends Zarafa.settings.ui.SettingsWidget
 * @xtype zarafa.settingscopyrightwidget
 *
 * The grommunio Web copyright notice widget
 */
Zarafa.settings.ui.SettingsCopyrightWidget = Ext.extend(Zarafa.settings.ui.SettingsWidget, {

	/**
	 * @cfg {String} about The Copyright notice which must be displayed by this widget.
	 * Defaults to {@link Zarafa#ABOUT}
	 */
	about: Zarafa.ABOUT,

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

		Ext.applyIf(config, {
			title : _('grommunio Web'),
			layout: 'form',
			items: [{
				xtype: 'displayfield',
				cls: 'zarafa-settings-about-text',
				value: config.about || this.about,
				hideLabel: true,
				htmlEncode: false
			}]
		});

		Zarafa.settings.ui.SettingsCopyrightWidget.superclass.constructor.call(this, config);
	}
});

Ext.reg('zarafa.settingscopyrightwidget', Zarafa.settings.ui.SettingsCopyrightWidget);
Ext.namespace('Zarafa.settings.ui');

/**
 * @class Zarafa.settings.ui.SettingsDisplayWidget
 * @extends Zarafa.settings.ui.SettingsWidget
 * @xtype zarafa.settingsdisplaywidget
 *
 * The Webapp Display settings widget. Gives the option to show dates in long or short format.
 */
Zarafa.settings.ui.SettingsDisplayWidget = Ext.extend(Zarafa.settings.ui.SettingsWidget, {

	/**
	 * @cfg {Zarafa.settings.SettingsModel} settingsModel The settingsModel on which
	 * to work with the settings.
	 */
	settingsModel: undefined,

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

		Ext.applyIf(config, {
			xtype: 'zarafa.settingsdisplaywidget',
			title: _('Display'),
			layout: 'form',
			items: [{
				xtype: 'displayfield',
				cls: 'k-settings-section-header',
				hideLabel: true,
				value: _('Date & Time')
			},{
				xtype: 'radiogroup',
				name: 'zarafa/v1/main/datetime_display_format',
				ref: 'datetimeDisplayFormat',
				fieldLabel: _('Date format'),
				hideLabel: true,
				columns: 1,
				items: [{
					xtype: 'radio',
					inputValue: 'short',
					name: 'datetimeDisplayFormat',
					boxLabel: _('Short') + '<span class="k-settings-label-minor">(' + new Date().formatDefaultTime(_('D {0}')) + ')</span>'
				},{
					xtype: 'radio',
					name: 'datetimeDisplayFormat',
					inputValue: 'long',
					boxLabel: _('Long') + '<span class="k-settings-label-minor">(' + new Date().formatDefaultTime(_('l d/m/Y {0}')) + ')</span>'
				}],
				listeners: {
					change: this.onRadioChange,
					scope: this
				}
			},{
				xtype: 'radiogroup',
				name: 'zarafa/v1/main/datetime_time_format',
				ref: 'datetimeTimeformat',
				fieldLabel: _('Time format'),
				hideLabel: true,
				width: 200,
				columns: 2,
				items: [{
					xtype: 'radio',
					inputValue: Zarafa.common.data.TimeFormat.TWELVEHOUR,
					name: 'datetimeTimeformat',
					boxLabel: _('12h clock')
				},{
					xtype: 'radio',
					inputValue: Zarafa.common.data.TimeFormat.TWENTYFOURHOUR,
					name: 'datetimeTimeformat',
					boxLabel: _('24h clock'),
					checked: true
				}],
				listeners: {
					change: this.onRadioChangeTimeFormat,
					scope: this
				}
			},{
				xtype: 'displayfield',
				cls: 'k-settings-section-header',
				hideLabel: true,
				value: _('Favorites')
			},{
				xtype: 'checkbox',
				name: 'zarafa/v1/contexts/hierarchy/hide_favorites',
				ref: 'hideFavorites',
				boxLabel: _('Hide favorites'),
				hideLabel: true,
				listeners: {
					change: this.onFieldChange,
					scope: this
				}
			},{
				xtype: 'checkbox',
				name: 'zarafa/v1/contexts/hierarchy/scroll_favorites',
				ref: 'scrollFavorites',
				boxLabel: _('Unpin favorites from top'),
				hideLabel: true,
				listeners: {
					change: this.onFieldChange,
					scope: this
				}
			},{
				xtype: 'displayfield',
				cls: 'k-settings-section-header',
				hideLabel: true,
				value: _('Interface')
			},{
				xtype: 'checkbox',
				name: 'zarafa/v1/main/unread_borders',
				ref: 'unreadBorders',
				boxLabel: _('Give unread items a colored border'),
				hideLabel: true,
				listeners: {
					change: this.onFieldChange,
					scope: this
				}
			},{
				xtype: 'checkbox',
				name: 'zarafa/v1/main/help_manual/show',
				ref: 'helpManual',
				hideLabel: true,
				requiresReload: true,
				appliedPluginSetting: false,
				pluginSettingPath: 'zarafa/v1/plugins/webappmanual/enable',
				boxLabel: _('Show \'help\' button in top-right corner'),
				listeners: {
					change: this.onFieldChange,
					scope: this
				}
			},{
				xtype: 'checkbox',
				name: 'zarafa/v1/main/title_counter/show',
				ref: 'titleCounter',
				hideLabel: true,
				requiresReload: true,
				appliedPluginSetting: false,
				pluginSettingPath: 'zarafa/v1/plugins/titlecounter/enable',
				boxLabel: _('Show unread mail counter in application title'),
				listeners: {
					change: this.onFieldChange,
					scope: this
				}
			},{
				xtype: 'checkbox',
				name: 'zarafa/v1/widgets/sidebar/hide_widgetpanel',
				ref: 'hideWidgetpanel',
				hideLabel: true,
				hidden: !container.getServerConfig().isWidgetEnabled(),
				requiresReload: true,
				boxLabel: _('Hide widget panel'),
				listeners: {
					change: this.onFieldChange,
					scope: this
				}
			},

			// Insertion point at the end of the display widget
			container.populateInsertionPoint('settings.display.last')]
		});

		Zarafa.settings.ui.SettingsDisplayWidget.superclass.constructor.call(this, config);
	},

	/**
	 * Called by the {@link Zarafa.settings.ui.SettingsCategory Category} when
	 * it has been called with {@link zarafa.settings.ui.SettingsCategory#update}.
	 * This is used to load the latest version of the settings from the
	 * {@link Zarafa.settings.SettingsModel} into the UI of this category.
	 * @param {Zarafa.settings.SettingsModel} settingsModel The settings to load
	 */
	update: function (settingsModel)
	{
		this.model = settingsModel;
		this.datetimeDisplayFormat.setValue(settingsModel.get(this.datetimeDisplayFormat.name));
		this.datetimeTimeformat.setValue(settingsModel.get(this.datetimeTimeformat.name));
		this.hideFavorites.setValue(settingsModel.get(this.hideFavorites.name));
		this.scrollFavorites.setValue(settingsModel.get(this.scrollFavorites.name));
		this.unreadBorders.setValue(settingsModel.get(this.unreadBorders.name));
		this.hideWidgetpanel.setValue(settingsModel.get(this.hideWidgetpanel.name));
		// Check if help manual plugin's settings available else check main settings.
		var helpManualPluginSetting = settingsModel.get(this.helpManual.pluginSettingPath);
		this.helpManual.appliedPluginSetting = Ext.isDefined(helpManualPluginSetting);
		this.helpManual.setValue(this.helpManual.appliedPluginSetting ? helpManualPluginSetting : settingsModel.get(this.helpManual.name));

		// Check if title counter plugin's settings available else check main settings.
		var titleCounterPluginSetting = settingsModel.get(this.titleCounter.pluginSettingPath);
		this.titleCounter.appliedPluginSetting = Ext.isDefined(titleCounterPluginSetting);
		this.titleCounter.setValue(this.titleCounter.appliedPluginSetting ? titleCounterPluginSetting : settingsModel.get(this.titleCounter.name));
	},

	/**
	 * Called by the {@link Zarafa.settings.ui.SettingsCategory Category} when
	 * it has been called with {@link zarafa.settings.ui.SettingsCategory#updateSettings}.
	 * This is used to update the settings from the UI into the {@link Zarafa.settings.SettingsModel settings model}.
	 * @param {Zarafa.settings.SettingsModel} settingsModel The settings to update
	 */
	updateSettings: function (settingsModel)
	{
		var datetimeDisplayFormat = this.datetimeDisplayFormat.getValue().inputValue;
		var datetimeTimeFormat = settingsModel.get(this.datetimeTimeformat.name);
		settingsModel.set(this.datetimeDisplayFormat.name, datetimeDisplayFormat);
		settingsModel.set(this.datetimeTimeformat.name, datetimeTimeFormat);
		settingsModel.set(this.hideFavorites.name, this.hideFavorites.getValue());
		settingsModel.set(this.scrollFavorites.name, this.scrollFavorites.getValue());
		settingsModel.set(this.unreadBorders.name, this.unreadBorders.getValue());
		settingsModel.set(this.hideWidgetpanel.name, this.hideWidgetpanel.getValue());

		// Remove webapp manual plugin settings.
		if (this.helpManual.appliedPluginSetting) {
			settingsModel.remove('zarafa/v1/plugins/webappmanual', {type: 'deprecated'});
		}
		settingsModel.set(this.helpManual.name, this.helpManual.getValue());

		// Remove title counter plugin settings.
		if (this.titleCounter.appliedPluginSetting) {
			settingsModel.remove('zarafa/v1/plugins/titlecounter', {type: 'deprecated'});
		}
		settingsModel.set(this.titleCounter.name, this.titleCounter.getValue());

		// Hide favorites
		if (this.hideFavorites.getValue() === true) {
			Ext.getBody().addClass('hideFavorites');
		} else {
			Ext.getBody().removeClass('hideFavorites');
		}

		// Scroll favorites
		if (this.scrollFavorites.getValue() === true) {
			Ext.getBody().addClass('scrollFavorites');
		} else {
			Ext.getBody().removeClass('scrollFavorites');
		}

		// Unread borders
		if (this.unreadBorders.getValue() === true) {
			Ext.getBody().addClass('k-unreadborders');
		} else {
			Ext.getBody().removeClass('k-unreadborders');
		}
	},

	/**
	 * Event handler which is fired when the value of the datetime format radio button has been changed.
	 * @param {Ext.form.Field} field The field which has fired the event
	 * @param {Ext.form.Radio} radio The radio which was enabled
	 * @private
	 */
	onRadioChange: function (field, radio)
	{
		if (this.model) {
			// FIXME: The settings model should be able to detect if
			// a change was applied
			if (this.model.get(field.name) !== radio.inputValue) {
				this.model.set(field.name, radio.inputValue);
			}
		}
	},

	/**
	 * Event handler which is fired when the value of the datetime time format radio button has been changed.
	 * @param {Ext.form.Field} field The field which has fired the event
	 * @param {Ext.form.Radio} radio The radio which was enabled
	 */
	onRadioChangeTimeFormat: function(field, radio)
	{
		if (this.model) {
			var timeFormatString = container.settingsModel.get('zarafa/v1/main/datetime_time_format');
			if (timeFormatString !== radio.inputValue) {
				if (radio.inputValue === 'G:i') {
					this.model.set(this.datetimeTimeformat.name, Zarafa.common.data.TimeFormat.TWENTYFOURHOUR);
				} else {
					this.model.set(this.datetimeTimeformat.name, Zarafa.common.data.TimeFormat.TWELVEHOUR);
				}
			}
		}
	},

	/**
	 * Event handler which is called when one of the textfields has been changed.
	 * This will apply the new value to the settings.
	 * @param {Ext.form.Field} field The field which has fired the event
	 * @param {String} value The new value
	 * @private
	 */
	onFieldChange: function(field, value)
	{
		if (this.model) {
			// FIXME: The settings model should be able to detect if
			// a change was applied
			var property = field.appliedPluginSetting === true && Ext.isDefined(field['pluginSettingPath']) ? 'pluginSettingPath' : 'name';
			if (this.model.get(field[property]) !== value) {
				this.model.set(field[property], value);

				// Reload only when this config has been set in the field component.
				if (Ext.isDefined(field.requiresReload) && field.requiresReload) {
					this.model.requiresReload = true;
				}
			}
		}
	}
});

Ext.reg('zarafa.settingsdisplaywidget', Zarafa.settings.ui.SettingsDisplayWidget);
Ext.namespace('Zarafa.settings.ui');

/**
 * @class Zarafa.settings.ui.SettingsFilePreviewerWidget
 * @extends Zarafa.settings.ui.SettingsWidget
 * @xtype zarafa.settingsfilepreviewerwidget
 *
 * The Webapp Display settings widget. Gives the option to preview the supported files.
 */
Zarafa.settings.ui.SettingsFilePreviewerWidget = Ext.extend(Zarafa.settings.ui.SettingsWidget, {
	/**
	 * This property will only be true if filepreviewer plugin's settings has been applied on UI.
	 * @property
	 * @type Boolean
	 */
	pluginSettingsApplied: false,

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

	    var zoomStore = new Ext.data.JsonStore({
	        fields: ['name', 'value'],
	        data: [{
	            'name': _('Auto'),
	            'value': 'auto'
	        },{
	            'name': _('Actual size'),
	            'value': 'page-actual'
	        },{
	            'name': _('Page width'),
	            'value': 'page-width'
	        }]
	    });

	    var items = [{
	        xtype: 'checkbox',
	        name: 'zarafa/v1/main/file_previewer/enable',
	        pluginSettingPath: 'zarafa/v1/plugins/filepreviewer/enable',
	        ref: 'enableFilePreviewer',
	        boxLabel: _('Attachment preview (PDF and Open formats)'),
			handler: this.onClickEnableFilePreviewer,
	        hideLabel: true,
			scope: this,
	        listeners: {
	            change: this.onFieldChange,
	            scope: this
	        }
	    }];

	    items = items.concat(this.createZoomDropdownBox([{
	            name:'zarafa/v1/main/file_previewer/odf_zoom',
	            ref: 'odfZoom',
	            store: zoomStore,
	            fieldLabel: _('Document default zoom'),
	        },{
	            name:'zarafa/v1/main/file_previewer/pdf_zoom',
	            ref: 'pdfZoom',
	            store: zoomStore,
	            fieldLabel: _('PDF zoom')
	        }
	    ]));

	    Ext.applyIf(config, {
	        xtype: 'zarafa.settingsdisplaywidget',
	        title: _('File previewing'),
	        layout: 'form',
	        hidden: !container.getServerConfig().isFilePreviewerEnabled(),
	        items: items
	    });
	    Zarafa.settings.ui.SettingsFilePreviewerWidget.superclass.constructor.call(this, config);
	},

	/**
	 * Creates and returns a dropdown box for the required combo.
	 *
	 * @param {Object} configs The configuration object used for the combo.
	 */
	createZoomDropdownBox: function(configs)
	{
	    var items = [];
	    for (var config of configs) {
	        items.push(
				Object.assign(config, {
	            	xtype: 'combo',
	            	width: 125,
	            	mode: 'local',
	            	triggerAction: 'all',
	            	displayField: 'name',
	            	valueField: 'value',
	            	lazyInit: false,
	            	forceSelection: true,
	            	editable: false,
	            	autoSelect: true,
	            	listeners: {
	            	    change: this.onDefaultZoomSelect,
	            	    scope: this
	            	}
	        	})
			);
	    }
	    return items;
	},

	/**
	 * Handler can uncheck and disable the {@link #odfZoom Document default Zoom} and {@link #pdfZoom PDF Zoom}
	 * combo if {@link #enableFilePreviewer Enable File Previewer} checkbox is unchecked and vice versa
	 * based on user settings.
	 *
	 * @param {Ext.form.CheckBox} checkbox The Enable File Previewer checkbox element
	 * @param {Boolean} checked State of the checkbox
	 */
	onClickEnableFilePreviewer: function(checkbox, checked)
	{
		this.odfZoom.setDisabled(!checked);
		this.pdfZoom.setDisabled(!checked);
		this.odfZoom.disableLabel(!checked);
		this.pdfZoom.disableLabel(!checked);
	},

	/**
	 * Called by the {@link Zarafa.settings.ui.SettingsCategory Category} when
	 * it has been called with {@link zarafa.settings.ui.SettingsCategory#update}.
	 * This is used to load the latest version of the settings from the
	 * {@link Zarafa.settings.SettingsModel} into the UI of this category.
	 * @param {Zarafa.settings.SettingsModel} settingsModel The settings to load
	 */
	update: function (settingsModel)
	{
		this.model = settingsModel;
		// Check if filepreviewer plugin's settings are available then apply them on UI.
		var pluginSetting = settingsModel.get('zarafa/v1/plugins/filepreviewer/enable');
		this.pluginSettingsApplied = Ext.isDefined(pluginSetting);

		var enableFilePreviewer = this.pluginSettingsApplied ? pluginSetting : settingsModel.get(this.enableFilePreviewer.name);
		this.enableFilePreviewer.setValue(enableFilePreviewer);

		// PDF zoom
		this.pdfZoom.setValue(settingsModel.get(this.pdfZoom.name));
		this.pdfZoom.setDisabled(!enableFilePreviewer);
		this.pdfZoom.disableLabel(!enableFilePreviewer);

		// ODF zoom
		this.odfZoom.setValue(settingsModel.get(this.odfZoom.name));
		this.odfZoom.setDisabled(!enableFilePreviewer);
		this.odfZoom.disableLabel(!enableFilePreviewer);
	},

	/**
	 * Called by the {@link Zarafa.settings.ui.SettingsCategory Category} when
	 * it has been called with {@link zarafa.settings.ui.SettingsCategory#updateSettings}.
	 * This is used to update the settings from the UI into the {@link Zarafa.settings.SettingsModel settings model}.
	 * @param {Zarafa.settings.SettingsModel} settingsModel The settings to update
	 */
	updateSettings: function (settingsModel)
	{
		// Remove unnecessary plugin's settings as we have removed file previewer plugin.
		// And save current settings in webapp's main settings.
		if (this.pluginSettingsApplied) {
			settingsModel.remove('zarafa/v1/plugins/filepreviewer', {type: 'deprecated'});
		}
		settingsModel.set(this.enableFilePreviewer.name, this.enableFilePreviewer.getValue());

		// Set the value for PDF zoom
		settingsModel.set(this.pdfZoom.name, this.pdfZoom.getValue());

		// Set the value for ODFzoom
		settingsModel.set(this.odfZoom.name, this.odfZoom.getValue());
	},

	/**
	 * Event handler which is called when one of the textfields has been changed.
	 * This will apply the new value to the settings.
	 * @param {Ext.form.Field} field The field which has fired the event
	 * @param {String} value The new value
	 * @private
	 */
	onFieldChange: function(field, value)
	{
		if (this.model) {
			// FIXME: The settings model should be able to detect if
			// a change was applied
			var property = this.pluginSettingsApplied && Ext.isDefined(field['pluginSettingPath']) ? 'pluginSettingPath' : 'name';
			if (this.model.get(field[property]) !== value) {
				this.model.set(field[property], value);
			}
		}
	},

	/**
	 * Event handler which is fired when a default zoom in the {@link Ext.form.ComboBox combobox}
	 * has been selected.
	 * @param {Ext.form.ComboBox} field The field which fired the event
	 * @param {Ext.data.Record} record The selected record
	 */
	onDefaultZoomSelect : function(field, record)
	{
		if (this.model) {
			if (this.model.get(field.name) !== field.getValue()) {
				this.model.set(field.name, field.getValue());
			}
		}
	}

});

Ext.reg('zarafa.settingsfilepreviewerwidget', Zarafa.settings.ui.SettingsFilePreviewerWidget);
Ext.namespace('Zarafa.settings.ui');

/**
 * @class Zarafa.settings.ui.SettingsGeneralCategory
 * @extends Zarafa.settings.ui.SettingsCategory
 * @xtype zarafa.settingsgeneralcategory
 *
 * The default available category for users which will
 * load the default settings options which are available for the user.
 */
Zarafa.settings.ui.SettingsGeneralCategory = Ext.extend(Zarafa.settings.ui.SettingsCategory, {
	// Insertion points for this class
	/**
	 * @insert context.settings.category.general
	 * Insertion point to register new {@link Zarafa.settings.ui.SettingsWidget widgets}
	 * for the {@link Zarafa.settings.ui.SettingsGeneralCategory General Category}.
	 * @param {Zarafa.settings.ui.SettingsGeneralCategory} category The general
	 * category to which the widgets will be added.
	 */

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

		Ext.applyIf(config, {
			title: _('General'),
			categoryIndex: 0,
			iconCls: 'icon_cogwheel',
			items: [{
					xtype: 'zarafa.settingsaccountwidget'
				},{
					xtype: 'zarafa.settingsdisplaywidget'
				},{
					xtype: 'zarafa.settingsinboxnavigationwidget'
				},{
					xtype: 'zarafa.settingsfilepreviewerwidget'
				},{
					xtype: 'zarafa.settingsaddressbookwidget'
				},{
					xtype: 'zarafa.settingsquotainfowidget'
				},{
					xtype: 'zarafa.settingsversionwidget',
					settingsContext: config.settingsContext
				}, container.populateInsertionPoint('context.settings.category.general', this)
			]
		});

		Zarafa.settings.ui.SettingsGeneralCategory.superclass.constructor.call(this, config);
	}
});

Ext.reg('zarafa.settingsgeneralcategory', Zarafa.settings.ui.SettingsGeneralCategory);
Ext.namespace('Zarafa.settings.ui');

/**
 * @class Zarafa.settings.ui.SettingsInboxNavigationWidget
 * @extends Zarafa.settings.ui.SettingsWidget
 * @xtype zarafa.settingsinboxnavigationwidget
 *
 * The Webapp Inbox Navigation widget
 */
Zarafa.settings.ui.SettingsInboxNavigationWidget = Ext.extend(Zarafa.settings.ui.SettingsWidget, {

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

		Ext.applyIf(config, {
			xtype: 'zarafa.settingsinboxnavigationwidget',
			title: _('Inbox navigation'),
			layout: 'form',
			items: [{
				xtype: 'displayfield',
				hideLabel: true,
				value: _('I want to navigate through my items by using:')
			},{
				xtype: 'radiogroup',
				name: 'zarafa/v1/contexts/mail/enable_live_scroll',
				ref: 'liveScroll',
				fieldLabel: _('Navigation method'),
				hideLabel: true,
				columns: 1,
				style: { marginBottom: '10px'},
				items: [{
					xtype: 'radio',
					name: 'enablescroll',
					inputValue: 'true',
					boxLabel: _('Infinite Scroll') + '<span class="k-settings-label-minor">(' + _('grommunio Web automatically loads additional items when you scroll down') + ')</span>'
				},{
					xtype: 'radio',
					name: 'enablescroll',
					inputValue: 'false',
					boxLabel: _('Pagination') + '<span class="k-settings-label-minor">(' + _('grommunio Web displays items on pages and you can use navigation controls to navigate') + ')</span>'
				}],
				listeners: {
					change: this.onRadioChange,
					scope: this
				}
			},
			this.createPageSizeField()]
		});

		Zarafa.settings.ui.SettingsInboxNavigationWidget.superclass.constructor.call(this, config);
	},

	/**
	 * Creates a composite field for the page size. Uses a different label depending
	 * on whether the user has set to use infinite scroll or pagination.
	 * @return {Zarafa.common.ui.CompositeField}
	 */
	createPageSizeField: function()
	{
		var enableScroll = Ext.isDefined(this.liveScroll) ? this.liveScroll.getValue().getRawValue() === 'true' : true;
		var fieldLabel = enableScroll ?	_('Load items in blocks of {A}'): _('Load {A} items per page.');

		var pageSizeStore = {
			xtype: 'jsonstore',
			autoDestroy: true,
			fields: ['size'],
			data: [{
				'size': 50
			},{
				'size': 100
			},{
				'size': 150
			},{
				'size': 200
			},{
				'size' : 1000
			}]
		};

		return new Zarafa.common.ui.CompositeField({
				xtype: 'zarafa.compositefield',
				plugins: ['zarafa.splitfieldlabeler'],
				ref: 'pageSizeContainer',
				fieldLabel: fieldLabel,
				items: [{
					labelSplitter: '{A}',
					xtype: 'combo',
					name: 'zarafa/v1/main/page_size',
					ref: 'pageSize',
					width: 70,
					hideLabel: true,
					store: pageSizeStore,
					mode: 'local',
					triggerAction: 'all',
					displayField: 'size',
					valueField: 'size',
					lazyInit: false,
					forceSelection: true,
					editable: false,
					autoSelect: true,
					listeners: {
						select: this.onPageSizeSelect,
						scope: this
					}
				}]
			});
	},

	/**
	 * Called by the {@link Zarafa.settings.ui.SettingsCategory Category} when
	 * it has been called with {@link zarafa.settings.ui.SettingsCategory#update}.
	 * This is used to load the latest version of the settings from the
	 * {@link Zarafa.settings.SettingsModel} into the UI of this category.
	 * @param {Zarafa.settings.SettingsModel} settingsModel The settings to load
	 */
	update: function (settingsModel)
	{
		this.model = settingsModel;
		this.liveScroll.setValue(settingsModel.get(this.liveScroll.name));
		this.pageSizeContainer.pageSize.setValue(settingsModel.get(this.pageSizeContainer.pageSize.name));
	},

	/**
	 * Called by the {@link Zarafa.settings.ui.SettingsCategory Category} when
	 * it has been called with {@link zarafa.settings.ui.SettingsCategory#updateSettings}.
	 * This is used to update the settings from the UI into the {@link Zarafa.settings.SettingsModel settings model}.
	 * @param {Zarafa.settings.SettingsModel} settingsModel The settings to update
	 */
	updateSettings: function (settingsModel)
	{

		var enableScroll = this.liveScroll.getValue().inputValue === 'true';

		settingsModel.beginEdit();
		settingsModel.set(this.liveScroll.name, enableScroll);
		settingsModel.set(this.pageSizeContainer.pageSize.name, this.pageSizeContainer.pageSize.getValue());
		settingsModel.endEdit();
	},

	/**
	 * event handler which is fired when the value of "Enable Endless scroll" checkbox has been changed.
	 * @param {Ext.form.Field} field The field which has fired the event
	 * @param {Ext.form.Radio} radio The radio which was enabled
	 * @private
	 */
	onRadioChange: function (field, radio)
	{
		var enableScroll = radio.inputValue === 'true';

		// Let's change the label of the pageSize component
		// We cannot simply change it and call doLayout, because the compositeField
		// has created a few child components to display the field. So we must
		// destroy the existing component and create brand new one.
		var pageSize = this.pageSizeContainer.pageSize.getValue();
		this.pageSizeContainer.destroy();
		this.pageSizeContainer = this.createPageSizeField();
		this.pageSizeContainer.pageSize.setValue(pageSize);
		this.add(this.pageSizeContainer);
		this.doLayout();

		if (this.model) {
			// FIXME: The settings model should be able to detect if
			// a change was applied
			if (this.model.get(field.name) !== enableScroll) {
				this.model.set(field.name, enableScroll);
			}
		}
	},

	/**
	 * Event handler which is fired when a page size in the {@link Ext.form.ComboBox combobox}
	 * has been selected.
	 * @param {Ext.form.ComboBox} field The field which fired the event
	 * @param {Ext.data.Record} record The selected record
	 */
	onPageSizeSelect: function (field, record)
	{
		if (this.model) {
			if (this.model.get(field.name) !== field.getValue()) {
				this.model.set(field.name, field.getValue());
			}
		}
	}
});

// Infinite scroll is called live scroll in the code

Ext.reg('zarafa.settingsinboxnavigationwidget', Zarafa.settings.ui.SettingsInboxNavigationWidget);
Ext.namespace('Zarafa.settings.ui');

/**
 * @class Zarafa.settings.ui.SettingsKeyShortcutCategory
 * @extends Zarafa.settings.ui.SettingsCategory
 * @xtype zarafa.settingskeyshortcutcategory
 *
 * Special Settings Category which shows the list and description of keyboard shortcuts
 * of grommunio Web.
 */
Zarafa.settings.ui.SettingsKeyShortcutCategory = Ext.extend(Zarafa.settings.ui.SettingsCategory, {

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

		Ext.applyIf(config, {
			title: _('Keyboard Shortcuts'),
			categoryIndex: 9998,
			iconCls: 'zarafa-settings-category-keyshortcut',
			items: this.generateWidgets(config)
		});

		Zarafa.settings.ui.SettingsKeyShortcutCategory.superclass.constructor.call(this, config);
	},

	/**
	 * Retrieve The list of {@link Zarafa.core.KeyMapMgr#keys key} configurations.
	 * Generate the key Combinations and Description based on the
	 * available {@link Zarafa.core.KeyMap KeyMap} configurations.
	 * Preparing a Key Combination of keys along with some Special keys like ENTER, F5, DELETE.
	 * @return {Object} keyDescription An object contains the Key-Combination and Description.
	 * @private
	 */
	getShortcutDescription: function()
	{
		var keyDescription = {};

		Ext.iterate(Zarafa.core.KeyMapMgr.keys, function(key, bindings) {
			for (var i = 0, len = bindings.length; i < len; i++)
			{
				var keyCombination = this.getKeyCombination(bindings[i]);
				var settingsCfg = bindings[i].settingsCfg;

				if(Ext.isEmpty(keyCombination) || !Ext.isDefined(settingsCfg)) {
					// there was some problem getting key combination
					// don't add the row
					continue;
				}

				var category = settingsCfg.category;

				if(!Ext.isObject(keyDescription[category])) {
					keyDescription[category] = {};
				}

				keyDescription[category][keyCombination] = {
					shortcutDesc: settingsCfg.description,
					fieldLabel: keyCombination,
					basicShortcut: Ext.isDefined(bindings[i].basic)
				};
			}
		}, this);

		return keyDescription;
	},

	/**
	 * Creates the {@link Zarafa.settings.ui.SettingsWidget widget} for 'Plugins'
	 * category of Settings context, In which the key combination and its respective
	 * description will be displayed as per the different Description categories.
	 * It will also include {@link Zarafa.settings.ui.SettingsKeyShortcutWidget widget} to
	 * enable/ disable the entire Keyboard control.
	 * @param {Object} config Configuration object
	 * @return {Array} widgets The widget components.
	 * @private
	 */
	generateWidgets: function(config)
	{
		var keyDescription = this.getShortcutDescription();

		var widgets = [{
			xtype: 'zarafa.settingskeyshortcutwidget',
			settingsContext: config.settingsContext
		}];

		Ext.iterate(keyDescription, function(key, bindings) {
			widgets.push({
				xtype: 'zarafa.settingswidget',
				title: key,
				layout: 'form',
				itemCls: 'zarafa-settings-keyboard-shortcut-row',
				items: this.generateRows(keyDescription[key]),
				// we don't want to show label separators
				labelSeparator: ''
			});
		}, this);

		return widgets;
	},

	/**
	 * Generate the UI components which contains the Key Combination and its respective description,
	 * to insert it into the category wise widgets.
	 * @param {Object} categoryDescription The object containing description about a particular category.
	 * @return {Array} categoryRows The array of components to display rows for the widgets.
	 * @private
	 */
	generateRows: function(categoryDescription)
	{
		var categoryRows = [];

		Ext.iterate(categoryDescription, function(descKey, description) {
			categoryRows.push({
				xtype: 'displayfield',
				hideLabel: true,
				html: this.generateDisplayValue(description)
			});
		},this);

		return categoryRows;
	},

	/**
	 * Generates the HTML for a keyboard shortcut row with level chiplet,
	 * key combination styled as keyboard keys, and description.
	 *
	 * @param {Object} description The object containing the description of a keybinding
	 * @private
	 */
	generateDisplayValue: function(description)
	{
		var levelCls = description.basicShortcut ? 'k-kbd-level-basic' : 'k-kbd-level-extended';
		var levelText = description.basicShortcut ? _('Basic') : _('Extended');
		var html = '<span class="k-kbd-level ' + levelCls + '">' + levelText + '</span>';

		// Style each key in the combination as a key cap
		var keys = description.fieldLabel.split(' + ');
		html += '<span class="k-kbd-keys">';
		for (var i = 0; i < keys.length; i++) {
			if (i > 0) {
				html += '<span class="k-kbd-plus">+</span>';
			}
			html += '<kbd>' + Ext.util.Format.htmlEncode(keys[i]) + '</kbd>';
		}
		html += '</span>';

		html += '<span class="keyboard-desc">' + description.shortcutDesc + '</span>';
		return html;
	},

	/**
	 * Generate the key Combinations based on available {@link Zarafa.core.KeyMap KeyMap} configurations.
	 * It will also concate the combination for special keys like ENTER, F5, DELETE accordingly.
	 * @param {Object|Array} keyConfig The {@link Zarafa.core.KeyMap KeyMap} configurations.
	 * @return {Object} {@link #keyDescription} keyConfig contains the Key-Combination and Description.
	 * @private
	 */
	getKeyCombination: function(keyConfig)
	{
		var keyCombination = [];

		if(keyConfig.ctrl) {
			// check if we have a mac OS then show CMD instead of CTRL
			keyCombination.push(Ext.isMac ? 'CMD' : 'CTRL');
		}

		if(keyConfig.alt) {
			keyCombination.push('ALT');
		}

		if(keyConfig.shift) {
			keyCombination.push('SHIFT');
		}

		var key = keyConfig.key;
		if(!Ext.isEmpty(key)) {
			// if any special key is there in configuration, then we need to prepare the combination accordingly
			if (Array.isArray(key)) {
				if (key[0] === Ext.EventObject.LEFT && key[1] === Ext.EventObject.RIGHT){
					keyCombination.push('ARROW KEYS');
				} else {
					// it is use to prepare key combination Ctrl + 0..9 and Ctrl + Alt + 1..9.
					// which is use for context switching and different view switching respectively.
					keyCombination.push(keyConfig.ctrl && keyConfig.alt ? '1...9' : '0...9');
				}
			} else {
				if (key === Ext.EventObject.F5) {
					keyCombination.push('F5');
				} else if (key === Ext.EventObject.F2) {
					keyCombination.push('F2');
				} else if (key === Ext.EventObject.LEFT) {
					keyCombination.push('LEFT ARROW');
				} else if (key === Ext.EventObject.RIGHT) {
					keyCombination.push('RIGHT ARROW');
				} else if (key === Ext.EventObject.ENTER) {
					keyCombination.push('ENTER');
				} else if (key === Ext.EventObject.HOME) {
					keyCombination.push('HOME');
				} else if (key === Ext.EventObject.END) {
					keyCombination.push('END');
				} else if (key === Ext.EventObject.DELETE) {
					// Check if Mac OS is used, then show FN + BACKSPACE (Mac OS does not have a delete key)
					if(Ext.isMac) {
						keyCombination.push('FN');
						keyCombination.push('BACKSPACE');
					} else {
						keyCombination.push('DELETE');
					}
				} else {
					if(Ext.isNumber(key)) {
						keyCombination.push(String.fromCharCode(key));
					} else {
						// assume that we have a string as key
						keyCombination.push(key);
					}
				}
			}

			return keyCombination.join(' + ');
		}

		// hmmm there is some problem, you shouldn't arrive here
		return '';
	}
});

Ext.reg('zarafa.settingskeyshortcutcategory', Zarafa.settings.ui.SettingsKeyShortcutCategory);
Ext.namespace('Zarafa.settings.ui');

/**
 * @class Zarafa.settings.ui.SettingsKeyShortcutWidget
 * @extends Zarafa.settings.ui.SettingsWidget
 * @xtype zarafa.settingskeyshortcutwidget
 *
 * The grommunio Web Keyboard Shortcut widget
 */
Zarafa.settings.ui.SettingsKeyShortcutWidget = Ext.extend(Zarafa.settings.ui.SettingsWidget, {

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

		Ext.applyIf(config, {
			title: _('Keyboard Shortcuts'),
			layout: 'form',
			items: [{
				xtype: 'zarafa.compositefield',
				hideLabel: true,
				height: 75,
				items: [{
					xtype: 'radiogroup',
					ref: '../keyShortcutGroup',
					columns: 1,
					fieldLabel: _('Keyboard shortcuts'),
					hideLabel: true,
					name: 'zarafa/v1/main/keycontrols',
					items: [{
						xtype: 'radio',
						boxLabel: _('Keyboard shortcuts off'),
						name: 'keyboardshortcut',
						inputValue: Zarafa.settings.data.KeyboardSettings.NO_KEYBOARD_SHORTCUTS
					},{
						xtype: 'radio',
						boxLabel: _('Basic keyboard shortcuts on'),
						name: 'keyboardshortcut',
						inputValue: Zarafa.settings.data.KeyboardSettings.BASIC_KEYBOARD_SHORTCUTS
					},{
						xtype: 'radio',
						boxLabel: _('Extended shortcuts on'),
						name: 'keyboardshortcut',
						inputValue: Zarafa.settings.data.KeyboardSettings.ALL_KEYBOARD_SHORTCUTS
					}],
					listeners: {
						change: this.enableKeyboardShortcuts,
						scope: this
					},
					flex: 1
				},{
					xtype: 'displayfield',
					hideLabel: true,
					height: 20,
					ref: '../keyShortcutWarning',
					flex: 2
				}]
			}]
		});

		Zarafa.settings.ui.SettingsKeyShortcutWidget.superclass.constructor.call(this, config);
	},

	/**
	 * Function initializes 'savesettings' event on {link #settingsContext} for the
	 * {@link Zarafa.settings.ui.SettingsKeyShortcutWidget}.
	 * @private
	 */
	initEvents: function()
	{
		Zarafa.settings.ui.SettingsKeyShortcutWidget.superclass.initEvents.call(this);

		// listen to savesettings to enable/disable Keyboard controls.
		var contextModel = this.settingsContext.getModel();

		this.mon(contextModel, 'savesettings', this.onSaveSettings, this);
	},

	/**
	 * Event handler will be called when
	 * {@link Zarafa.settings.SettingsContextModel#savesettings} event is fired.
	 * Function will enable/disable all keymaps registered with {@link Zarafa.core.KeyMapMgr}
	 * based on setting zarafa/v1/main/keycontrols.
	 *
	 * @param {Zarafa.settings.SettingsContextModel} settingsContextModel The
	 * context model of settings context.
	 * @param {Zarafa.settings.SettingsModel} settingsEditModel The settingsModel which is being saved.
	 *
	 * @private
	 */
	onSaveSettings: function(settingsContextModel, settingsEditModel)
	{
		var changed = false;
		var modifiedSettings = settingsEditModel.modified;

		if(!Ext.isEmpty(modifiedSettings)) {
			// Check whether keyboard settings are changed or not.
			for(var i = 0; i < modifiedSettings.length; i++) {
				if (modifiedSettings[i].path === this.keyShortcutGroup.name) {
					changed = true;
					break;
				}
			}
		}

		// keyboard control setting is toggled.
		if(changed === true) {
			// FIXME use isGloballyEnabled: function() in core/KeyMapMgr?
			if (settingsEditModel.get(this.keyShortcutGroup.name) !== Zarafa.settings.data.KeyboardSettings.NO_KEYBOARD_SHORTCUTS) {
				Zarafa.core.KeyMapMgr.enableAllKeymaps();
			} else {
				Zarafa.core.KeyMapMgr.disableAllKeymaps();
			}
		}
	},

	/**
	 * Called by the {@link Zarafa.settings.ui.SettingsCategory Category} when
	 * it has been called with {@link zarafa.settings.ui.SettingsCategory#update}.
	 * This is used to load the latest version of the settings from the
	 * {@link Zarafa.settings.SettingsModel} into the UI of this category.
	 * @param {Zarafa.settings.SettingsModel} settingsModel The settings to load
	 */
	update: function(settingsModel)
	{
		this.model = settingsModel;
		this.keyShortcutGroup.setValue(settingsModel.get(this.keyShortcutGroup.name));
		this.keyShortcutWarning.reset();
	},

	/**
	 * Called by the {@link Zarafa.settings.ui.SettingsCategory Category} when
	 * it has been called with {@link zarafa.settings.ui.SettingsCategory#updateSettings}.
	 * This is used to update the settings from the UI into the {@link Zarafa.settings.SettingsModel settings model}.
	 * @param {Zarafa.settings.SettingsModel} settingsModel The settings to update
	 */
	updateSettings: function(settingsModel)
	{
		settingsModel.set(this.keyShortcutGroup.name, this.keyShortcutGroup.getValue().inputValue);
	},

	/**
	 * Event handler which is fired when the radiogroup has changed.
	 * If the radiogroup value has been changed it displays a warning which,
	 * informs the user that he needs to reload grommunio Web.
	 *
	 * @param {Ext.form.RadioGroup} group The radio group which fired the event
	 * @param {Ext.form.Radio} radio The radio which was enabled
	 * @private
	 */
	enableKeyboardShortcuts: function(group, radio)
	{
		if (this.model.get(group.name) !== radio.inputValue) {
			this.model.set(group.name, radio.inputValue);
		}

		// If settingsmodel has been modified, display a warning
		if(!Ext.isEmpty(this.model.modified)) {
			this.keyShortcutWarning.setValue(_('This change requires a reload of grommunio Web'));
			this.model.requiresReload = true;
		} else {
			this.keyShortcutWarning.reset();
		}
	}
});

Ext.reg('zarafa.settingskeyshortcutwidget', Zarafa.settings.ui.SettingsKeyShortcutWidget);
Ext.namespace('Zarafa.settings.ui');

/**
 * @class Zarafa.settings.ui.SettingsMainPanel
 * @extends Zarafa.common.ui.ContextMainPanel
 * @xtype zarafa.settingsmainpanel
 */
Zarafa.settings.ui.SettingsMainPanel = Ext.extend(Zarafa.common.ui.ContextMainPanel, {
	// Insertion points for this class
	/**
	 * @insert context.settings.categories
	 * Insertion point to register new {@link Zarafa.settings.ui.SettingsGeneralCategory categories}
	 * for the {@link Zarafa.settings.ui.SettingsCategoryWidgetPanel}.
	 * @param {Zarafa.settings.ui.SettingsMainPanel} panel The Settings Main Panel
	 */

	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};
		Ext.apply(config, {iconCls: 'icon_cogwheel'});

		// Load all category categories which we will
		// display in the content panel.
		var items = container.populateInsertionPoint('context.settings.categories', this, config.context);

		// Create all content, extract the titles to generate
		// the list of tabs for the category panel.
		var tabs = [];
		for (var i = 0, len = items.length; i < len; i++) {
			// Create the item to ensure we will have
			// all desired information in our object.
			var category = Ext.create(items[i]);

			// Create the category tab which we use to
			// switch to this particular category.
			tabs.push({
				xtype: 'zarafa.settingscategorytab',
				title: category.title,
				categoryIndex: category.categoryIndex,
				iconCls: category.iconCls,
				hidden: category.hidden,
				context: config.context,
				category: category
			});

			// Make sure our created item is saved
			// back into our main array.
			items[i] = category;
		}

		tabs = Zarafa.core.Util.sortArray(tabs, 'ASC', 'categoryIndex');
		items = Zarafa.core.Util.sortArray(items, 'ASC', 'categoryIndex');

		Ext.applyIf(config, {
			cls: 'zarafa-setting-mainpanel',
			header: false,
			layout: {
				type: 'hbox',
				align: 'stretch'
			},
			items: [{
				xtype: 'zarafa.settingscategorypanel',
				width: 250,
				context: config.context,
				autoScroll: true,
				items: tabs
			},{
				xtype: 'container',
				flex : 1,
				layout: {
					type: 'vbox',
					align: 'stretch'
				},
				items: [{
					// Render the main contents component, we have
					// a widget content panel containing the widgets
					// for the categories.
					xtype: 'container',
					flex: 1,
					layout: {
						type: 'fit',
						align: 'stretch'
					},
					items: [{
						xtype: 'zarafa.settingscategorywidgetpanel',
						context: config.context,
						items: items
					}]
				},{
					// Render the toolbar component, the sizes of the
					// 2 components inside this toolbar match the sizes
					// of the 2 components in the other main content.
					xtype: 'container',
					height: 50,
					layout: {
						type: 'fit',
						align: 'stretch'
					},
					items: [{
						// This section of the toolbar actually
						// has contents. By changing the cls,
						// and toolbarCls configuration objects
						// we emulate that this contains looks
						// similar to a Ext.Panel#buttons toolbar
						xtype: 'container',
						cls: 'x-panel-btns',
						layout: 'fit',
						items: [{
							xtype: 'toolbar',
							toolbarCls: 'x-panel-footer',
							buttonAlign: 'right',
							items: [{
								xtype: 'button',
								cls: 'zarafa-action',
								text: _('Apply'),
								handler: this.onApply,
								scope: this
							},{
								xtype: 'button',
								text: _('Discard'),
								handler: this.onDiscard,
								scope: this
							}]
						}]
					}]
				}]
			}]
		});

		Zarafa.settings.ui.SettingsMainPanel.superclass.constructor.call(this, config);
	},

	/**
	 * {@link Zarafa.settings.SettingsContextModel#applyChanges Save all changes} to the server.
	 * @private
	 */
	onApply: function()
	{
		// Only save the settings when there are actual modifications to be saved.
		if (this.context) {
			this.context.getModel().applyChanges();
		}
	},

	/**
	 * {@link Zarafa.settings.SettingsContextModel#discardChanges Discard all unsaved changes}.
	 * @private
	 */
	onDiscard: function()
	{
		if (this.context) {
			this.context.getModel().discardChanges();
		}
	}
});

Ext.reg('zarafa.settingsmainpanel', Zarafa.settings.ui.SettingsMainPanel);
Ext.namespace('Zarafa.settings.ui');

/**
 * @class Zarafa.settings.ui.SettingsPluginsCategory
 * @extends Zarafa.settings.ui.SettingsCategory
 * @xtype zarafa.settingspluginscategory
 *
 * The default available category for users which will
 * load the miscellaneous settings for the plugins. If plugins
 * don't wish to create a separate category, then they can
 * decide to only register a single widget to this common
 * Plugins category.
 */
Zarafa.settings.ui.SettingsPluginsCategory = Ext.extend(Zarafa.settings.ui.SettingsCategory, {
	// Insertion points for this class
	/**
	 * @insert context.settings.category.plugins
	 * Insertion point to register new {@link Zarafa.settings.ui.SettingsWidget widgets}
	 * for the {@link Zarafa.settings.ui.SettingsPluginsCategory Plugins Category}.
	 * @param {Zarafa.settings.ui.SettingsGeneralCategory} category The plugins
	 * category to which the widgets will be added.
	 */

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

		Ext.applyIf(config, {
			title: _('Plugins'),
			categoryIndex: 9998,
			iconCls: 'zarafa-settings-category-plugins',
			autoScroll: false,
			layout: {
				type: 'vbox',
				align: 'stretch',
				pack: 'start'
			},
			items: [
				{
					xtype: 'zarafa.settingspluginswidget',
					settingsContext: config.settingsContext,
					flex: 1
				},
				container.populateInsertionPoint('context.settings.category.plugins', this)
			]
		});

		Zarafa.settings.ui.SettingsPluginsCategory.superclass.constructor.call(this, config);
	}
});

Ext.reg('zarafa.settingspluginscategory', Zarafa.settings.ui.SettingsPluginsCategory);
Ext.namespace('Zarafa.settings.ui');

/**
 * @class Zarafa.settings.ui.SettingsPluginsWidget
 * @extends Zarafa.settings.ui.SettingsWidget
 * @xtype zarafa.settingspluginswidget
 *
 * The grommunio Web available plugins widgets
 */
Zarafa.settings.ui.SettingsPluginsWidget = Ext.extend(Zarafa.settings.ui.SettingsWidget, {

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

		var store = new Ext.data.ArrayStore({
			fields: [{
				'name': 'name'
			},{
				'name': 'display_name'
			},{
				'name': 'version'
			},{
				'name': 'enabled',
				'type': 'boolean'
			},{
				'name': 'allow_disable',
				'type': 'boolean'
			},{
				'name': 'settings_base'
			}]
		});

		var plugins = container.getPluginsMetaData();
		var server = container.getServerConfig();
		var pluginsVersion = server.getPluginsVersion();
		var versionInfo;
		for (var i = 0, len = plugins.length; i < len; i++) {
			var plugin = plugins[i];
			if (!plugin.isPrivate()) {
				if( Ext.isEmpty(pluginsVersion[plugin.getName()]) ) {
					versionInfo = _('Unknown');
				} else {
					versionInfo = pluginsVersion[plugin.getName()];
				}

				store.add(new Ext.data.Record({
					'name': plugin.getName(),
					'display_name': plugin.getDisplayName(),
					'version': versionInfo,
					'enabled': plugin.isEnabled(),
					'allow_disable': plugin.allowUserDisable,
					'settings_base': plugin.getSettingsBase()
				}));
			}
		}

		store.sort('display_name', 'ASC');

		var model = new Ext.grid.CheckboxSelectionModel({
			checkOnly: true,
			header: '&#160;',
			renderer: this.onEnabledRenderer,
			// Disable grid traversal using key events because we are using grid selection to
			// indicate plugin's enable/disable state, so that shouldn't be modified
			// when traversing using up/down arrow keys
			onKeyPress: Ext.emptyFn,
			listeners: {
				rowselect: this.onRowSelect,
				rowdeselect: this.onRowDeselect,
				scope: this
			}
		});

		Ext.applyIf(config, {
			title: _('Available plugins'),
			layout: 'fit',
			cls: 'zarafa-settings-widget k-settings-pluginpanel k-settings-nogap',
			items: [{
				xtype: 'panel',
				border: false,
				cls: 'zarafa-settings-pluginavailable',
				ref: 'pluginsPanel',
				layout: {
					type: 'vbox',
					align: 'stretch',
					pack: 'start'
				},
				items: [{
					xtype: 'grid',
					border: true,
					flex: 1,
					enableHdMenu: false,
					deferRowRender:false,
					autoExpandColumn: 'display_name',
					cls: 'k-settings-plugingrid',
					ref: '../pluginsGrid',
					viewConfig: {
						forceFit: true,
						emptyText: '<div class=\'emptytext\'>' + _('No plugins available') + '</div>'
					},
					store: store,
					columns: [model, {
						id: 'display_name',
						header: _('Display Name'),
						dataIndex: 'display_name',
						headerCls: 'k-unsortable',
						sortable: false,
						renderer: this.onDisplayNameRenderer
					},{
						id: 'display_name',
						header: _('Version'),
						dataIndex: 'version',
						headerCls: 'k-unsortable',
						sortable: false
					}],
					selModel: model
				}],
				listeners: {
					scope: this,
					resize: this.onResizePluginsPanel
				}
			}]
		});

		Zarafa.settings.ui.SettingsPluginsWidget.superclass.constructor.call(this, config);
		this.pluginsGrid.on('rowclick', this.onRowClick, this);
	},

	/**
	 * Event handler for the resize event of the plugins panel and trigger a layout refresh.
	 * @param {Ext.panel} pluginsPanel The panel that contains the grid
	 * with the plugins.
	 */
	onResizePluginsPanel: function(pluginsPanel)
	{
		// Now trigger a layout refresh to have the grid rendered with the correct height
		pluginsPanel.doLayout();
	},

	/**
	 * Event handler is called when user clicks on row of grid.
	 * Function toggles plugin selection.
	 * @param {Ext.grid.GridPanel} grid grid panel object.
	 * @param {Number} rowIndex The index of the row which was double clicked
	 * @param {Ext.EventObject} eventObj object of the event.
	 * @private
	 */
	onRowClick: function(grid, rowIndex, eventObj)
	{
		grid.getView().focusRow(rowIndex);

		var model = grid.getSelectionModel();
		var store = grid.getStore();
		var record = store.getAt(rowIndex);

		// Here it will check selected plugins is not default plugin.
		// user could not disable default plugin.
		if(!record.get('allow_disable')) {
			return;
		}

		if(!record.get('enabled')) {
			model.selectRow(rowIndex, true);
		} else {
			model.deselectRow(rowIndex);
		}
	},

	/**
	 * Called by the {@link Zarafa.settings.ui.SettingsCategory Category} when
	 * it has been called with {@link zarafa.settings.ui.SettingsCategory#update}.
	 * This is used to load the latest version of the settings from the
	 * {@link Zarafa.settings.SettingsModel} into the UI of this category.
	 * @param {Zarafa.settings.SettingsModel} settingsModel The settings to load
	 */
	update: function(settingsModel)
	{
		this.model = settingsModel;
		var selModel = this.pluginsGrid.getSelectionModel();
		var store = this.pluginsGrid.getStore();
		var records = [];

		store.each(function(plugin) {
			// Only update the 'enabled' property if the plugin is allowed
			// to be disabled. If not, then the 'enabled' flag cannot have
			// been changed.
			if (plugin.get('allow_disable')) {
				plugin.set('enabled', settingsModel.get(plugin.get('settings_base') + '/enable'));
			}

			// If the plugin is enabled, add it to the list of
			// records which is going to be selected.
			if (plugin.get('enabled')) {
				records.push(plugin);
			}
		});

		// Disable events, as we don't want our own
		// event handlers for 'selectrow' and 'deselectrow'
		// to be fired during update.
		selModel.suspendEvents(false);
		selModel.selectRecords(records);
		selModel.resumeEvents();
	},

	/**
	 * Called by the {@link Zarafa.settings.ui.SettingsCategory Category} when
	 * it has been called with {@link zarafa.settings.ui.SettingsCategory#updateSettings}.
	 * This is used to update the settings from the UI into the {@link Zarafa.settings.SettingsModel settings model}.
	 * @param {Zarafa.settings.SettingsModel} settingsModel The settings to update
	 */
	updateSettings: function(settingsModel)
	{
		var store = this.pluginsGrid.getStore();

		settingsModel.beginEdit();

		store.each(function(plugin) {
			if (plugin.get('allow_disable')) {
				settingsModel.set(plugin.get('settings_base') + '/enable', plugin.get('enabled'));
			}
		});

		settingsModel.endEdit();
	},

	/**
	 * Render the display name in the cell.
	 * This will generate an additional <span> element, if this is a non-editable row
	 * which serves as replacement for the "Enabled" column
	 *
	 * @param {Object} value The data value for the cell.
	 * @param {Object} p An object with metadata
	 * @param {Ext.data.record} record The {Ext.data.Record} from which the data was extracted.
	 * @return {String} The formatted string
	 */
	onDisplayNameRenderer: function(value, p, record)
	{
		value = Ext.util.Format.htmlEncode(value);

		if (!record.get('allow_disable')) {
			// Add CSS class that this plugin cannot be disabled
			p.css += ' zarafa-settings-pluginavailable-fixed';
			// Add message that this plugin cannot be disabled
			if ( record.get('enabled') === true ) {
				value += ' <span>' + _('This plugin cannot be disabled') + '</span>';
			} else {
				value += ' <span>' + _('This plugin cannot be enabled') + '</span>';
			}
		}

		return value;
	},

	/**
	 * Render the 'enabled' property in the cell.
	 * This will make itself invisible if this is a non-editable row.
	 *
	 * @param {Object} value The data value for the cell.
	 * @param {Object} p An object with metadata
	 * @param {Ext.data.record} record The {Ext.data.Record} from which the data was extracted.
	 * @return {String} The formatted string
	 */
	onEnabledRenderer: function(value, p, record)
	{
		if (record.get('allow_disable')) {
			return Ext.grid.CheckboxSelectionModel.prototype.renderer.apply(this, arguments);
		} else {
			// Add CSS class that this plugin cannot be disabled
			p.css += ' zarafa-settings-pluginavailable-fixed';
			return '';
		}
	},

	/**
	 * Event handler which is fired when the row has been selected by the user.
	 * This will toggle the 'enabled' property on the passed record, and update
	 * the {@link #model settings}.
	 * @param {Ext.grid.CheckboxSelectionModel} model The model which fired the event
	 * @param {Number} index The index which was selected
	 * @param {Ext.data.Record} record The record which was selected
	 * @private
	 */
	onRowSelect: function(model, index, record)
	{
		record.set('enabled', true);
		this.model.set(record.get('settings_base') + '/enable', true);
		this.model.requiresReload = true;
	},

	/**
	 * Event handler which is fired when the row has been deselected by the user.
	 * This will toggle the 'enabled' property on the passed record, and update
	 * the {@link #model settings}.
	 * @param {Ext.grid.CheckboxSelectionModel} model The model which fired the event
	 * @param {Number} index The index which was selected
	 * @param {Ext.data.Record} record The record which was selected
	 * @private
	 */
	onRowDeselect: function(model, index, record)
	{
		record.set('enabled', false);
		this.model.set(record.get('settings_base') + '/enable', false);
		this.model.requiresReload = true;
	}
});

Ext.reg('zarafa.settingspluginswidget', Zarafa.settings.ui.SettingsPluginsWidget);
Ext.namespace('Zarafa.settings.ui');

/**
 * @class Zarafa.settings.ui.SettingsQuotaInfoWidget
 * @extends Zarafa.settings.ui.SettingsWidget
 * @xtype zarafa.settingsquotainfowidget
 *
 * The Quota Info widget which shows usage of mailbox and quota bar.
 */
Zarafa.settings.ui.SettingsQuotaInfoWidget = Ext.extend(Zarafa.settings.ui.SettingsWidget, {
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			xtype: 'zarafa.settingswidget',
			title: _('Mailbox Usage'),
			layout: 'form',
			items: [{
						xtype: 'zarafa.quotabar',
						width: 400,
						hidden: true,
						ref: 'quotaBar',
						userStore: container.getHierarchyStore().getDefaultStore()
					},{
						xtype: 'displayfield',
						width: 400,
						hideLabel: true,
						ref: 'unavailableQuotaInfo',
						hidden: true,
						value: _("A quota is not set on your store.")
					},{
						xtype: 'displayfield',
						hideLabel: true,
						width: 400,
						ref: 'quotaInfo'
					}]
		});

		Zarafa.settings.ui.SettingsQuotaInfoWidget.superclass.constructor.call(this, config);

		this.on('afterrender', this.updateQuotaInfo, this);
		this.mon(container.getHierarchyStore(), 'update', this.onUpdateHierarchyStore, this);
	},

	/**
	 * Function sets information in quota bar widget,
	 * it updates quota bar and information string as well.
	 * @private
	 */
	updateQuotaInfo: function()
	{
		var defaultMesageStore = container.getHierarchyStore().getDefaultStore();
		var storeSize = defaultMesageStore.get('store_size');
		var softQuota = defaultMesageStore.get('quota_soft');
		var hardQuota = defaultMesageStore.get('quota_hard');

		// Create quota-info string to display in displayfield.
		var quotaInfo = String.format(_('{0} of mailbox space is used.'), Ext.util.Format.fileSize(storeSize * 1024));
		var quotaInfoHTML = '<span class="zarafa-quota-string">' + quotaInfo + '</span>';

		if(softQuota || hardQuota) {
			// If soft or hard quota is set then show quotabar.
			this.quotaBar.setVisible(true);
			this.quotaBar.updateQuotaBar();
			this.unavailableQuotaInfo.setVisible(false);

			// Add soft or hard quota info in quota-info string.
			quotaInfo = this.getQuotaSuggestionString(softQuota, hardQuota, storeSize);
			if(!Ext.isEmpty(quotaInfo)) {
				quotaInfoHTML += ' ' + quotaInfo;
			}
		} else {
			// If any of the soft or hard quota is not set then show message.
			this.quotaBar.setVisible(false);
			this.unavailableQuotaInfo.setVisible(true);
		}

		this.quotaInfo.setValue(quotaInfoHTML);
	},

	/**
	 * Function returns info/warning message according to
	 * store's usage and quota information.
	 * @param {Int} softQuota soft quota limit for user
	 * @param {Int} hardQuota hard quota limit for user
	 * @param {Int} storeSize size of user's store
	 * @return {String} info/warning message.
	 * @private
	 */
	getQuotaSuggestionString: function(softQuota, hardQuota, storeSize)
	{
		if(!Ext.isDefined(storeSize)) {
			return;
		}

		// If softQuota is greater then hardQuota then ignore softQuota
		if (hardQuota && softQuota >= hardQuota) {
			softQuota = null;
		}

		/*
		 * There are seven cases for displaying messages.
		 * 1) store-size < soft-quota < hard-quota
		 * 2) soft-quota < store-size < hard-quota
		 * 3) soft-quota < hard-quota < store-size
		 * 4) soft-quota is not set and store-size < hard-quota
		 * 5) soft-quota is not set and hard-quota < store-size
		 * 6) hard-quota is not set and store-size < soft-quota
		 * 7) hard-quota is not set and soft-quota < store-size
		 */
		if (hardQuota && storeSize > hardQuota) {
			// Case 3,5
			return _('You have exceeded hard quota, you can not send or receive mails.');
		} else if (softQuota && storeSize < softQuota) {
			// Case 1,6
			return String.format(_("At {0} you won't be able to send mails."), Ext.util.Format.fileSize(softQuota * 1024));
		} else if (softQuota && hardQuota && softQuota < storeSize && storeSize < hardQuota) {
			// Case 2
			return String.format(_("You can not send mails as you have exceeded soft quota, at {0} you won't be able to receive mails."), Ext.util.Format.fileSize(hardQuota * 1024));
		} else if (!softQuota && hardQuota && storeSize < hardQuota) {
			// Case 4
			return String.format(_("At {0} you won't be able to send or receive mails."), Ext.util.Format.fileSize(hardQuota * 1024));
		} else if (softQuota && !hardQuota && softQuota < storeSize) {
			// Case 7
			return _('You can not send mails as you have exceeded soft quota.');
		}
	},

	/**
	 * Function is called when data is update in {@link Zarafa.hierarchy.data.HierarchyStore}.
	 * If user's default store is changed then it will update quotabar ui.
	 * @param {Zarafa.hierarchy.data.HierarchyStore} store, the hierarchy store
	 * @param {Zarafa.hierarchy.data.MAPIStoreRecord} storeRecord, record of the hierarchy store
	 * @param {String} op, operation string
	 * @private
	 */
	onUpdateHierarchyStore: function(store, storeRecord, op) {
		// Check whether default store is changed or not.
		if(storeRecord.isDefaultStore()){
			this.updateQuotaInfo();
		}
	}
});

Ext.reg('zarafa.settingsquotainfowidget', Zarafa.settings.ui.SettingsQuotaInfoWidget);
Ext.namespace('Zarafa.settings.ui');

/**
 * @class Zarafa.settings.ui.SettingsResetSettingsWidget
 * @extends Zarafa.settings.ui.SettingsWidget
 * @xtype zarafa.settingsresetsettingswidget
 *
 * The Reset Settings widget
 */
Zarafa.settings.ui.SettingsResetSettingsWidget= Ext.extend(Zarafa.settings.ui.SettingsWidget, {

	/**
	 * The loadMask object which will be shown when reset request is being sent to the server.
	 * @property
	 * @type Zarafa.common.ui.LoadMask
	 */
	loadMask: undefined,

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

		Ext.applyIf(config, {
			title: _('Reset settings'),
			layout: 'column',
			items: [{
				xtype: 'displayfield',
				columnWidth: 1,
				hideLabel: true,
				value: _('Reset settings to their original defaults.')
			},{
				xtype: 'button',
				text: _('Reset settings'),
				columnWidth: 1,
				autoWidth: true,
				handler: this.onResetSettings,
				scope: this
			}]
		});

		Zarafa.settings.ui.SettingsResetSettingsWidget.superclass.constructor.call(this, config);
	},

	/**
	 * Event handler when the "Reset settings" button was clicked.
	 * This will {@link Zarafa.settings.SettingsModel#reset reset} the
	 * {@link Zarafa.settings.data.SettingsDefaultValue values} of the settings.
	 * @private
	 */
	onResetSettings: function()
	{
		var message = _('This will close all opened shared stores and resets all settings to the default value.');
		message += '<br/><br/>';
		message += _('grommunio Web will automatically reload in order for these changes to take effect');
		message += '<br/>';

		Zarafa.common.dialogs.MessageBox.addCustomButtons({
			title: _('Reset settings'),
			msg: message,
			cls: Ext.MessageBox.WARNING_CLS,
			fn: this.resetDefaultSettings,
			customButton: [{
				text: _('Reset'),
				name: 'reset'
			}, {
				text: _('Cancel'),
				name: 'cancel'
			}],
			scope: this
		});

	},

	/**
	 * Event handler for {@link #onResetSettings}. This will check if the user
	 * wishes to reset the default settings or not.
	 * @param {String} button The button which user pressed.
	 * @private
	 */
	resetDefaultSettings: function(button)
	{
		if (button === 'reset') {
			var contextModel = this.settingsContext.getModel();
			var realModel = contextModel.getRealSettingsModel();

			realModel.reset('zarafa/v1');
			realModel.save();

			this.loadMask = new Zarafa.common.ui.LoadMask(Ext.getBody(), {
				msg: '<b>' + _('Reloading, Please wait.') + '</b>'
			});

			this.loadMask.show();

			this.mon(realModel, 'save', this.onSettingsSave, this);
			this.mon(realModel, 'exception', this.onSettingsException, this);
		}

	},

	/**
	 * Called when the {@link Zarafa.settings.SettingsModel} fires the {@link Zarafa.settings.SettingsModel#save save}
	 * event to indicate the settings were successfully saved and it will forcefully realod the webapp.
	 * @param {Zarafa.settings.SettingsModel} model The model which fired the event.
	 * @param {Object} parameters The key-value object containing the action and the corresponding
	 * settings which were saved to the server.
	 * @private
	 */
	onSettingsSave: function(model, parameters)
	{
		if(parameters.action === Zarafa.core.Actions['reset']) {
			this.mun(model, 'save', this.onSettingsSave, this);
			this.mun(model, 'exception', this.onSettingsException, this);
			Zarafa.core.Util.reloadWebapp();
		}
	},

	/**
	 * Called when the {@link Zarafa.settings.SettingsModel} fires the {@link Zarafa.settings.SettingsModel#exception exception}
	 * event to indicate the settings were not successfully saved.
	 * @param {Zarafa.settings.SettingsModel} model The settings model 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 a 'path' and 'value' field indicating
	 * respectively the Setting and corresponding value for the setting which was being saved.
	 * @param {Object} response The response object as received from the PHP-side
	 * @private
	 */
	onSettingsException: function(model, type, action, options, response)
	{
		if(options.action === Zarafa.core.Actions['reset']) {
			this.loadMask.hide();
			// Remove event handlers
			this.mun(model, 'save', this.onSettingsSave, this);
			this.mun(model, 'exception', this.onSettingsException, this);
		}
	}
});

Ext.reg('zarafa.settingsresetsettingswidget', Zarafa.settings.ui.SettingsResetSettingsWidget);
Ext.namespace('Zarafa.settings.ui');

/**
 * @class Zarafa.settings.ui.SettingsVersionWidget
 * @extends Zarafa.settings.ui.SettingsWidget
 * @xtype zarafa.settingsversionwidget
 *
 * grommunio Web About widget — shows version info and reset button.
 */
Zarafa.settings.ui.SettingsVersionWidget = Ext.extend(Zarafa.settings.ui.SettingsWidget, {

	/**
	 * The loadMask object which will be shown when reset request is being sent to the server.
	 * @property
	 * @type Zarafa.common.ui.LoadMask
	 */
	loadMask: undefined,

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

		var version = container.getVersion();
		var serverConfig = container.getServerConfig();
		var webVersion = version.getWebApp() + (!Ext.isEmpty(version.getGit()) ? ('-' + version.getGit()) : '');
		var gromoxVersion = version.getZCP();

		// Build diagnostic info
		var authMethod = serverConfig.getAuthMethod();
		var authLabel = authMethod === 'oidc' ? _('OpenID Connect') : _('Basic Authentication');
		var userAgent = navigator.userAgent;
		var browserName = this.detectBrowser(userAgent);

		Ext.applyIf(config, {
			title: _('About'),
			items: [{
				xtype: 'box',
				cls: 'k-settings-about-section',
				autoEl: {
					tag: 'div',
					children: [{
						tag: 'div',
						cls: 'k-settings-about-section-title',
						html: _('System information')
					},{
						tag: 'div',
						cls: 'k-settings-about-info',
						children: [{
							tag: 'span',
							html: '<b>' + _('grommunio Web') + '</b> ' +
								Ext.util.Format.htmlEncode(webVersion)
						},{
							tag: 'span',
							html: '<b>' + _('Gromox') + '</b> ' +
								Ext.util.Format.htmlEncode(gromoxVersion)
						},{
							tag: 'span',
							html: '<b>' + _('Logged in via') + '</b> ' +
								Ext.util.Format.htmlEncode(authLabel)
						},{
							tag: 'span',
							html: '<b>' + _('Browser') + '</b> ' +
								Ext.util.Format.htmlEncode(browserName)
						}]
					}]
				}
			},{
				xtype: 'box',
				cls: 'k-settings-about-section',
				autoEl: {
					tag: 'div',
					children: [{
						tag: 'div',
						cls: 'k-settings-about-section-title',
						html: _('Maintenance')
					}]
				}
			},{
				xtype: 'button',
				cls: 'k-settings-about-reset-btn',
				text: _('Reset all settings to defaults'),
				handler: this.onResetSettings,
				scope: this
			},
				container.populateInsertionPoint('settings.versioninformation')
			]
		});

		Zarafa.settings.ui.SettingsVersionWidget.superclass.constructor.call(this, config);
	},

	/**
	 * Detect browser name and version from user agent string.
	 * @param {String} ua The user agent string
	 * @return {String} Human-readable browser name
	 * @private
	 */
	detectBrowser: function(ua)
	{
		if (ua.indexOf('Firefox') > -1) {
			var match = ua.match(/Firefox\/(\d+)/);
			return 'Firefox' + (match ? ' ' + match[1] : '');
		} else if (ua.indexOf('Edg/') > -1) {
			var match = ua.match(/Edg\/(\d+)/);
			return 'Microsoft Edge' + (match ? ' ' + match[1] : '');
		} else if (ua.indexOf('Chrome') > -1) {
			var match = ua.match(/Chrome\/(\d+)/);
			return 'Chrome' + (match ? ' ' + match[1] : '');
		} else if (ua.indexOf('Safari') > -1) {
			var match = ua.match(/Version\/(\d+)/);
			return 'Safari' + (match ? ' ' + match[1] : '');
		}
		return _('Unknown');
	},

	/**
	 * Event handler when the "Reset settings" button was clicked.
	 * @private
	 */
	onResetSettings: function()
	{
		var message = _('This will close all opened shared stores and resets all settings to the default value.');
		message += '<br/><br/>';
		message += _('grommunio Web will automatically reload in order for these changes to take effect');
		message += '<br/>';

		Zarafa.common.dialogs.MessageBox.addCustomButtons({
			title: _('Reset settings'),
			msg: message,
			cls: Ext.MessageBox.WARNING_CLS,
			fn: this.resetDefaultSettings,
			customButton: [{
				text: _('Reset'),
				name: 'reset'
			}, {
				text: _('Cancel'),
				name: 'cancel'
			}],
			scope: this
		});
	},

	/**
	 * Event handler for {@link #onResetSettings}.
	 * @param {String} button The button which user pressed.
	 * @private
	 */
	resetDefaultSettings: function(button)
	{
		if (button === 'reset') {
			var contextModel = this.settingsContext.getModel();
			var realModel = contextModel.getRealSettingsModel();

			realModel.reset('zarafa/v1');
			realModel.save();

			this.loadMask = new Zarafa.common.ui.LoadMask(Ext.getBody(), {
				msg: '<b>' + _('Reloading, Please wait.') + '</b>'
			});

			this.loadMask.show();

			this.mon(realModel, 'save', this.onSettingsSave, this);
			this.mon(realModel, 'exception', this.onSettingsException, this);
		}
	},

	/**
	 * Called when settings were successfully saved — forces reload.
	 * @param {Zarafa.settings.SettingsModel} model
	 * @param {Object} parameters
	 * @private
	 */
	onSettingsSave: function(model, parameters)
	{
		if(parameters.action === Zarafa.core.Actions['reset']) {
			this.mun(model, 'save', this.onSettingsSave, this);
			this.mun(model, 'exception', this.onSettingsException, this);
			Zarafa.core.Util.reloadWebapp();
		}
	},

	/**
	 * Called when settings save failed.
	 * @param {Zarafa.settings.SettingsModel} model
	 * @param {String} type
	 * @param {String} action
	 * @param {Object} options
	 * @param {Object} response
	 * @private
	 */
	onSettingsException: function(model, type, action, options, response)
	{
		if(options.action === Zarafa.core.Actions['reset']) {
			this.loadMask.hide();
			this.mun(model, 'save', this.onSettingsSave, this);
			this.mun(model, 'exception', this.onSettingsException, this);
		}
	}
});

Ext.reg('zarafa.settingsversionwidget', Zarafa.settings.ui.SettingsVersionWidget);
Ext.namespace('Zarafa.settings.ui');

/**
 * @class Zarafa.settings.ui.SettingsWelcomeCategory
 * @extends Zarafa.settings.ui.SettingsCategory
 * @xtype zarafa.settingswelcomecategory
 *
 * The category which will be shown the user user who is logging in for the
 * first time, and is presented with the {@link Zarafa.core.ui.WelcomeViewport Welcome page}.
 */
Zarafa.settings.ui.SettingsWelcomeCategory = Ext.extend(Zarafa.settings.ui.SettingsCategory, {
	// Insertion points for this class
	/**
	 * @insert context.settings.category.welcome
	 * Insertion point to register new {@link Zarafa.settings.ui.SettingsWidget widgets}
	 * for the {@link Zarafa.settings.ui.SettingsWelcomeCategory Welcome Category}.
	 * @param {Zarafa.settings.ui.SettingsWelcomeCategory} category The welcome
	 * category to which the widgets will be added.
	 */

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

		Ext.applyIf(config, {
			categoryIndex: 0,
			iconCls: 'zarafa-settings-category-welcome',
			layout: 'column',
			defaults: {
				cls: 'zarafa-settings-widget-welcome'
			},
			items: [{
				xtype: 'zarafa.settingsaccountwidget',
				width: 450,
				style: 'padding-right: 6px;'
			},{
				xtype: 'zarafa.settingscalendarwidget',
				columnWidth: 1,
				style: 'padding-left: 6px;'
			},
			container.populateInsertionPoint('context.settings.category.welcome', this)
			]
		});

		Zarafa.settings.ui.SettingsWelcomeCategory.superclass.constructor.call(this, config);
	},

	/**
	 * Called during rendering of the component.
	 * This will hide the {@link Ext.form.Field Field} which corresponds
	 * to the 'zarafa/v1/contexts/calendar/datepicker_show_busy' setting.
	 * @protected
	 */
	onRender: function()
	{
		Zarafa.settings.ui.SettingsWelcomeCategory.superclass.onRender.apply(this, arguments);

		// We want don't want allow the configuration of the "Show Busy Dates" setting.
		var item = this.find('name', 'zarafa/v1/contexts/calendar/datepicker_show_busy');
		if (!Ext.isEmpty(item)) {
			item[0].hide();
		}
	}
});

Ext.reg('zarafa.settingswelcomecategory', Zarafa.settings.ui.SettingsWelcomeCategory);
/*
 * #dependsFile client/zarafa/core/KeyMapMgr.js
 */
Ext.namespace('Zarafa.task');

/**
 * @class Zarafa.task.KeyMapping
 * @extends Object
 *
 * The map of keys used in the Task Context.
 * @singleton
 */
Zarafa.task.KeyMapping = Ext.extend(Object, {
	/**
	 * @constructor
	 */
	constructor: function()
	{
		var newItemKeys = [{
			key: Ext.EventObject.K,
			ctrl: true,
			alt: true,
			shift: false,
			stopEvent: true,
			handler: this.onNewTask,
			scope: this,
			settingsCfg: {
				description: _('New task'),
				category: _('Creating an item')
			}
		}];

		Zarafa.core.KeyMapMgr.register('global', newItemKeys);
	},

	/**
	 * Event handler for the keydown event of the {@link Zarafa.core.KeyMap KeyMap} when the user wants to
	 * create a new task.
	 * @param {Number} key Key code
	 * @param {Ext.EventObject} event The event
	 * @param {Ext.Component} component The component on which key event is fired.
	 */
	onNewTask: function(key, event, component)
	{
		Zarafa.task.Actions.openCreateTaskContent(container.getContextByName('task').getModel());
	}

});

Zarafa.task.KeyMapping = new Zarafa.task.KeyMapping();/*
 * #dependsFile client/zarafa/common/ui/grid/Renderers.js
 */
Ext.namespace('Zarafa.task.attachitem');

/**
 * @class Zarafa.task.attachitem.AttachTaskColumnModel
 * @extends Zarafa.common.ui.grid.ColumnModel
 *
 * The {@link Zarafa.task.attachitem.AttachTaskColumnModel AttachTaskColumnModel} is the column model containing
 * sets of {@link Ext.grid.Column columns} for task folders.
 * This column model will be used with {@link Zarafa.common.attachment.dialog.AttachItemGrid AttachItemGrid}.
 */
Zarafa.task.attachitem.AttachTaskColumnModel = Ext.extend(Zarafa.common.ui.grid.ColumnModel, {
	/**
	 * @constructor
	 * @param config Configuration structure
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			columns: this.getColumns(),
			defaults: {
				sortable: true
			}
		});

		Zarafa.task.attachitem.AttachTaskColumnModel.superclass.constructor.call(this, config);
	},

	/**
	 * Create an array of {@link Ext.grid.Column columns} which will be visible when loading data from
	 * task folder into {@link Zarafa.common.attachment.dialog.AttachItemGrid AttachItemGrid}.
	 *
	 * @return {Ext.grid.Column[]} The array of columns
	 * @private
	 */
	getColumns: function()
	{
		return[{
			dataIndex: 'icon_index',
			headerCls: 'zarafa-icon-column',
			header: '<p class=\'icon_index\'>&nbsp;</p>',
			width: 24,
			fixed: true,
			tooltip: _('Sort by: Icon'),
			renderer: Zarafa.common.ui.grid.Renderers.icon
		}, {
			dataIndex: 'importance',
			headerCls: 'zarafa-icon-column',
			header: '<p class=\'icon_importance\'>&nbsp;</p>',
			width: 24,
			fixed: true,
			tooltip: _('Sort by: Priority'),
			renderer: Zarafa.common.ui.grid.Renderers.importance
		}, {
			header: '<p class=\'icon_paperclip\'>&nbsp;</p>',
			headerCls: 'zarafa-icon-column',
			dataIndex: 'hasattach',
			width: 24,
			fixed: true,
			renderer: Zarafa.common.ui.grid.Renderers.attachment,
			tooltip: _('Sort by: Attachment')
		}, {
			dataIndex: 'subject',
			header: _('Subject'),
			tooltip: _('Sort by: Subject'),
			renderer: Zarafa.common.ui.grid.Renderers.subject
		}, {
			header: _('Status'),
			dataIndex: 'status',
			width: 160,
			renderer: Zarafa.common.ui.grid.Renderers.taskstatus,
			tooltip: _('Sort by: Status')
		}, {
			dataIndex: 'duedate',
			header: _('Due Date'),
			tooltip: _('Sort by: Due Date'),
			renderer: Zarafa.common.ui.grid.Renderers.utcdate
		}, {
			dataIndex: 'percent_complete',
			header: _('% Completed'),
			width: 75,
			tooltip: _('Sort by: Percent Completed'),
			renderer: Zarafa.common.ui.grid.Renderers.percentage
		}, {
			dataIndex: 'categories',
			header: _('Categories'),
			tooltip: _('Sort by: Categories'),
			renderer: Zarafa.common.ui.grid.Renderers.text
		}];
	}
});
Ext.namespace('Zarafa.task.attachitem');

/**
 * @class Zarafa.task.attachitem.AttachTaskRenderer
 * @extends Zarafa.common.attachment.dialogs.AttachItemBaseRenderer
 *
 * Renderer that can be used to get text data from {@link Zarafa.task.TaskRecord TaskRecord}.
 */
Zarafa.task.attachitem.AttachTaskRenderer = Ext.extend(Zarafa.common.attachment.dialogs.AttachItemBaseRenderer, {
	/**
	 * Constructor will initialize default properties
	 * @param {Object} config configuration object
	 */
	constructor: function(config)
	{
		Zarafa.task.attachitem.AttachTaskRenderer.superclass.constructor.call(this, config);

		Ext.apply(this.labels, {
			'startdate': _('Start Date'),
			'duedate': _('Due Date'),
			'status': _('Task Status'),
			'percent_complete': _('Percent Complete'),
			'totalwork': _('Total Work'),
			'actualwork': _('Actual Work'),
			'owner': _('Owner'),
			'companies': _('Companies'),
			'billinginformation': _('Billing Information'),
			'mileage': _('Mileage')
		});
	},

	/**
	 * Function should be used to generate template which can be used by {@link #generateText} to add data into template and return the string that is generated.
	 * This will generate template for the html format only.
	 * @return {String} The HTML for the XTemplate to use
	 */
	generateHTMLTemplate: function()
	{
		var html = '';

		html += this.addHTMLRowGroup({
			'subject': '{subject:htmlEncode}',
			// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
			'startdate': '{startdate:formatDefaultTimeString("' + _("l jS F Y {0}") + '")}',
			// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
			'duedate': '{duedate:formatDefaultTimeString("' + _("l jS F Y {0}") + '")}',
			'importance': '{importance:importanceString}',
			'sensitivity': '{sensitivity:sensitivityString}'
		});

		html += this.addHTMLRowGroup({
			'status': '{status:taskStatusString}',
			'percent_complete': '{percent_complete:percentage}',
			// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
			'date_completed': '{date_completed:formatDefaultTimeString("' + _("l jS F Y {0}") + '")}'
		});

		html += this.addHTMLRowGroup({
			'totalwork': '{totalwork:duration}',
			'actualwork': '{actualwork:duration}',
			'owner': '{owner:htmlEncode}',
			'categories': '{categories:htmlEncode}',
			'companies': '{companies:htmlEncode}',
			'billinginformation': '{billinginformation:htmlEncode}',
			'mileage': '{mileage:htmlEncode}'
		});

		html += this.addHTMLRowGroup({
			'categories': '{categories:htmlEncode}',
			'companies': '{companies:htmlEncode}',
			'billinginformation': '{billinginformation:htmlEncode}',
			'mileage': '{mileage:htmlEncode}'
		});

		html += this.addHTMLRow('attachment_names', '{attachment_names:htmlEncode}');
		html += '{body}';

		return html;
	},

	/**
	 * Function should be used to generate template which can be used by {@link #generateText} to add data into template and return the string that is generated.
	 * This will generate template for the plain text format only.
	 * @return {String} The HTML for the XTemplate to use
	 */
	generatePlainTemplate: function()
	{
		var html = '';

		html += this.addPlainRowGroup({
			'subject': '{subject}',
			// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
			'startdate': '{startdate:formatDefaultTimeString("' + _("l jS F Y {0}") + '")}',
			// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
			'duedate': '{duedate:formatDefaultTimeString("' + _("l jS F Y {0}") + '")}',
			'importance': '{importance:importanceString}',
			'sensitivity': '{sensitivity:sensitivityString}'
		});

		html += this.addPlainRowGroup({
			'status': '{status:taskStatusString}',
			'percent_complete': '{percent_complete:percentage}',
			// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
			'date_completed': '{date_completed:formatDefaultTimeString("' + _("l jS F Y {0}") + '")}'
		});

		html += this.addPlainRowGroup({
			'totalwork': '{totalwork:duration}',
			'actualwork': '{actualwork:duration}',
			'owner': '{owner}',
			'categories': '{categories}',
			'companies': '{companies}',
			'billinginformation': '{billinginformation}',
			'mileage': '{mileage}'
		});

		html += this.addPlainRowGroup({
			'categories': '{categories}',
			'companies': '{companies}',
			'billinginformation': '{billinginformation}',
			'mileage': '{mileage}'
		});

		html += this.addPlainRow('attachment_names', '{attachment_names}');
		html += '{body}';

		return html;
	},

	/**
	 * Prepares data suitable for use in an XTemplate from the record.
	 * @param {Zarafa.core.data.IPMRecord} record The record to acquire data from.
	 * @param {Boolean} htmlFormat decide if html or plain data is requested, this will depend on the user settings.
	 * @return {Array} An array of data which is customized for our purpose.
	 */
	prepareData: function(record, htmlFormat)
	{
		var data = Zarafa.task.attachitem.AttachTaskRenderer.superclass.prepareData.apply(this, arguments);

		// remove default values
		if (data['status'] === Zarafa.core.mapi.TaskStatus.NOT_STARTED) {
			delete data['status'];
		}

		if (data['percent_complete'] === 0) {
			delete data['percent_complete'];
		}

		if (data['actualwork'] === 0) {
			delete data['actualwork'];
		}

		if (data['totalwork'] === 0) {
			delete data['totalwork'];
		}

		return data;
	}
});Ext.namespace('Zarafa.task.data');

/**
 * @class Zarafa.task.data.DataModes
 * @extends Zarafa.core.Enum
 *
 * Enum containing the different data modes of the task context.
 *
 * @singleton
 */
Zarafa.task.data.DataModes = Zarafa.core.Enum.create({
	/**
	 * List all task items from the selected folder(s).
	 *
	 * @property
	 * @type Number
	 */
	ALL: 0,

	/**
	 * List all task items from the selected folder(s) which are not completed.
	 * @property
	 * @type Number
	 */
	ACTIVE: 1,

	/**
	 * List all task items from the selected folder(s) from next seven days.
	 *
	 * @property
	 * @type Number
	 */
	NEXT_7_DAYS: 2,

	/**
	 * List all task items from the selected folder(s) which are overdue.
	 *
	 * @property
	 * @type Number
	 */
	OVERDUE: 3,

	/**
	 * List all task items from the selected folder(s) which are completed.
	 *
	 * @property
	 * @type Number
	 */
	COMPLETED: 4
});
Ext.namespace('Zarafa.task.data');

/**
 * @class Zarafa.task.data.TaskRequestButtonNames
 * @extends Zarafa.core.Enum
 *
 * Enum containing all the taskrequest button names.
 * Which will be helpful in distinguishing task request buttons
 * in {@link Zarafa.task.ui.TaskRequestButton TaskRequestButton} base class.
 *
 * @singleton
 */
Zarafa.task.data.TaskRequestButtonNames = Zarafa.core.Enum.create({
  /**
	 * Preserve name for {@link Zarafa.calendar.ui.AcceptButton AcceptButton}.
	 *
	 * @property
	 * @type String
	 */
  ACCEPT: 'acceptButton',

  /**
	 * Preserve name for {@link Zarafa.calendar.ui.DeclineButton DeclineButton}.
	 *
	 * @property
	 * @type String
	 */
  DECLINE: 'declineButton'
});Ext.namespace('Zarafa.task.data');

/**
 * @class Zarafa.task.data.ViewModes
 * @extends Zarafa.core.Enum
 *
 * Enum containing the different viewing modes of the task context.
 *
 * @singleton
 */
Zarafa.task.data.ViewModes = Zarafa.core.Enum.create({
	/**
	 * View all task items from the selected folder(s)) with some limited columns.
	 *
	 * @property
	 * @type Number
	 */
	SIMPLE: 0,
	/**
	 * View all found task items from the selected folder(s).
	 *
	 * @property
	 * @type Number
	 */
	SEARCH: 1,
	/**
	 * View all task items from the selected folder(s) with all default columns, user may
	 * show/hide columns, if required.
	 *
	 * @property
	 * @type Number
	 */
	DETAILED: 2
});
Ext.namespace('Zarafa.task.data');

/**
 * @class Zarafa.task.data.Views
 * @extends Zarafa.core.Enum
 *
 * Enum containing the different views of the task context.
 *
 * @singleton
 */
Zarafa.task.data.Views = Zarafa.core.Enum.create({
	/**
	 * View all task items from the selected folder(s) in the 'list' view.
	 *
	 * @property
	 * @type Number
	 */
	LIST: 0,
	/**
	 * View all found task items from the selected folder(s).
	 *
	 * @property
	 * @type Number
	 */
	SEARCH: 1
});
Ext.namespace('Zarafa.task.dialogs');

/**
 * @class Zarafa.task.dialogs.SendTaskRequestConfirmationContentPanel
 * @extends Zarafa.core.ui.ContentPanel
 * @xtype zarafa.sendtaskrequestconfirmcontentpanel
 */
Zarafa.task.dialogs.SendTaskRequestConfirmationContentPanel = Ext.extend(Zarafa.core.ui.ContentPanel, {
	/**
	 * @cfg {Zarafa.core.ui.IPMRecord} record The record for which the
	 * send task request confirmation content panel is opened.
	 */
	record: undefined,

	/**
	 * @cfg {Zarafa.core.mapi.ResponseStatus} responseType The response type value selected by user.
	 */
	responseType: undefined,

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

		var title = _('Accepting Task');
		if (config.responseType === Zarafa.core.mapi.TaskMode.DECLINE) {
			title = _('Declining Task');
		}
		config = Ext.applyIf(config, {
			xtype: 'zarafa.sendtaskrequestconfirmcontentpanel',
			layout: 'fit',
			title: title,
			modal: true,
			width: 350,
			height: 250,
			items: [{
				xtype: 'zarafa.sendtaskrequestconfirmationpanel',
				record: config.record,
				responseType: config.responseType,
				ref: 'sendTaskConfirmationPanel',
				buttons: [{
					text: _('Ok'),
					handler: this.onOk,
					scope: this
				},{
					text: _('Cancel'),
					handler: this.onCancel,
					scope: this
				}]
			}]
		});

		Zarafa.task.dialogs.SendTaskRequestConfirmationContentPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Event handler which is raised when the user clicks the "Ok" {@link Ext.Button button}
	 * @private
	 */
	onOk: function()
	{
		this.sendTaskConfirmationPanel.updateRecord(this.record);
		this.close();
	},

	/**
	 * Event handler which is raised when the user clicks the "Cancel" {@link Ext.Button button}
	 *
	 * This will close the panel.
	 * @private
	 */
	onCancel: function()
	{
		this.close();
	}
});

Ext.reg('zarafa.sendtaskrequestconfirmcontentpanel', Zarafa.task.dialogs.SendTaskRequestConfirmationContentPanel);
// -*- coding: utf-8; indent-tabs-mode: nil -*-
Ext.namespace('Zarafa.task.printer');

/**
 * @class Zarafa.calendar.task.TaskListViewRenderer
 * @extends Zarafa.common.printer.renderers.BaseRenderer
 *
 * Prints a list of all tasks in a folder
 */
Zarafa.task.printer.TaskListViewRenderer = Ext.extend(Zarafa.common.printer.renderers.BaseRenderer, {
	/**
	 * @property customStylesheetPath
	 * @type String
	 * The path at which the print stylesheets can be found for this renderer
	 */
	customStylesheetPath: 'client/resources/css/external/print.list.css',

	/**
	 * Generate the XTemplate HTML text for printing a task list.
	 * This prints every task given in the context view.
	 * @param {Zarafa.task.TaskContext} context The task view in the webapp
	 * @return {String} The HTML for the XTemplate to print
	 */
	generateBodyTemplate: function(context) {
		var html = '';

		// +----------------------------------------------------+
		// | priority | % complete | due date | subject | owner |
		// ....
		// +----------------------------------------------------+
		// [name]                  [print date]

		html += '<table id="k-printlist" cellpadding=0 cellspacing=0>\n';

		html += '<tr>'
			+ '<th>' + _('Priority') + '</th>'
			+ '<th>' + _('Completed') + '</th>'
			+ '<th>' + _('Subject') + '</th>'
			+ '<th>' + _('Owner') + '</th>'
			+ '<th>' + _('Due date') + '</th>'
			+ '</tr>\n';

		// date format l jS F == Monday 1st January
		html += '<tpl for="tasks">'
			+ '<tr>'
			+ '<td>{values.data.importance:importanceString}</td>'
			+ '<td>{values.data.percent_complete:percentage(0)}</td>'
			+ '<td>{values.data.subject:htmlEncode}</td>'
			+ '<td>{values.data.owner:htmlEncode}</td>'
			// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
			+ '<td>{values.data.duedate:date("' + _("l jS F Y") + '")}</td>'
			+ '</tr>\n'
			+ '</tpl>';

		html += '</table>\n';

		// Bottom table with username and date
		html += '<table id="k-printlistbottom">'
		+ '<tr>'
		+ '<td>' + _('Printed by: ') +'{fullname:htmlEncode}</td>'
		// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
		+ '<td class="right">' + _('Printed on: ') + '{currenttime:formatDefaultTimeString("' + _("l jS F Y {0}") + '")}</td>'
		+ '</tr>'
		+ '</table>\n';
		return html;
	},

	/**
	 * Returns the data for the XTemplate used in generateBodyTemplate()
	 * @param {Zarafa.task.TaskContext} context The task view in the webapp
	 * @return {Object} XTemplate data
	 */
	prepareData: function(context) {
		var data = {
			fullname: container.getUser().getDisplayName()
		};
		var model = context.getModel();

		data['currenttime'] = new Date();
		data['tasks'] = model.getStore().getRange();

		return data;
	}
});
Ext.namespace('Zarafa.task.ui');

/**
 * @class Zarafa.task.ui.TaskContextMenu
 * @extends Zarafa.core.ui.menu.ConditionalMenu
 * @xtype zarafa.taskcontextmenu
 */
Zarafa.task.ui.TaskContextMenu = Ext.extend(Zarafa.core.ui.menu.ConditionalMenu, {
	// Insertion points for this class
	/**
	 * @insert context.task.contextmenu.actions
	 * Insertion point for adding actions menu items into the context menu
	 * @param {Zarafa.task.ui.TaskContextMenu} contextmenu This contextmenu
	 */
	/**
	 * @insert context.task.contextmenu.options
	 * Insertion point for adding options menu items into the context menu
	 * @param {Zarafa.task.ui.TaskContextMenu} contextmenu This contextmenu
	 */

	/**
	 * @cfg {Zarafa.core.data.IPMRecord[]} The records on which this context menu acts
	 */
	records: undefined,

	/**
	 * @cfg {Boolean} actsOnTodoListFolder when this context menu used by To-do list
	 * folder this config will be true. default is false.
	 */
	actsOnTodoListFolder: false,

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

		if (Ext.isDefined(config.records) && !Array.isArray(config.records)) {
			config.records = [ config.records ];
		}

		this.records = config.records;
		Ext.applyIf(config, {
			items: [
				this.createContextActionItems(),
				{ xtype: 'menuseparator' },
				container.populateInsertionPoint('context.task.contextmenu.actions', this),
				{ xtype: 'menuseparator' },
				this.createContextOptionsItems(),
				{ xtype: 'menuseparator' },
				container.populateInsertionPoint('context.task.contextmenu.options', this),
				{ xtype: 'menuseparator' },
				this.createOptionsMenuItem()
			]
		});

		Zarafa.task.ui.TaskContextMenu.superclass.constructor.call(this, config);
	},

	/**
	 * Create the Action context menu items
	 * @return {Zarafa.core.ui.menu.ConditionalItem[]} The list of Action context menu items
	 * @private
	 */
	createContextActionItems: function()
	{
		return [{
			xtype: 'zarafa.conditionalitem',
			text: _('Open'),
			iconCls: 'icon_open',
			singleSelectOnly: true,
			handler: this.onOpenTask,
			scope: this
		},{
			xtype: 'zarafa.conditionalitem',
			text: _('Follow up'),
			iconCls: 'icon_flag_red',
			cls: 'k-unclickable',
			hideOnClick: false,
			beforeShow: this.onBeforeShowFollowUp,
			menu: {
				xtype: 'zarafa.taskflagsmenu',
				records: this.records
			},
			scope: this
		},{
			xtype: 'zarafa.conditionalitem',
			text: _('Mark Complete'),
			iconCls: 'icon_task_complete',
			isMarkComplete: true,
			beforeShow: this.onMarkCompleteItemBeforeShow,
			handler: this.onMarkCompleteItemClick,
			scope: this
		},{
			xtype: 'zarafa.conditionalitem',
			text: _('Mark Incomplete'),
			iconCls: 'icon_task_incomplete',
			isMarkComplete: false,
			beforeShow: this.onMarkCompleteItemBeforeShow,
			handler: this.onMarkCompleteItemClick,
			scope: this
		},{
			text: _('Copy/Move'),
			iconCls: 'icon_copy',
			handler: this.onCopyMove,
			scope: this
		}];
	},

	/**
	 * Create the Option context menu items
	 *
	 * @return {Zarafa.core.ui.menu.ConditionalItem[]} The list of Option context menu items
	 * @private
	 */
	createContextOptionsItems: function()
	{
		return [{
			xtype: 'zarafa.conditionalitem',
			text: _('Categories'),
			cls: 'k-unclickable',
			iconCls: 'icon_categories',
			hideOnClick: false,
			menu: {
				xtype: 'zarafa.categoriescontextmenu',
				records: this.records
			}
		},{
			xtype: 'zarafa.conditionalitem',
			text: _('Delete'),
			iconCls: 'icon_delete',
			nonEmptySelectOnly: true,
			handler: this.onContextItemDelete,
			scope: this
		},{
			xtype: 'zarafa.conditionalitem',
			text: _('Remove from To-Do List'),
			iconCls: 'icon_cross_large',
			beforeShow: this.onBeforeShowRemoveTodo,
			handler: this.onContextRemoveTodo,
			scope: this
		}];
	},

	/**
	 * Event handler triggers before the item shows. Disable the
	 * item If the selected record(s) has an organizer task copy
	 * or received task which was not yet accepted.
	 *
	 * @param {Zarafa.core.ui.menu.ConditionalItem} item The item to enable/disable
	 * @param {Zarafa.core.data.IPMRecord[]} records The records which must be checked
	 * to see if the item must be enabled or disabled.
	 * @private
	 */
	onBeforeShowRemoveTodo: function (item, records)
	{
		if(!Array.isArray(records)) {
			records = [records];
		}
		var hierarchyStore = container.getHierarchyStore();
		var isDisabled = records.some(function (record) {
			var recordFolderEntryid = record.getStore().entryId;
			var folder = hierarchyStore.getFolder(recordFolderEntryid);
			return record.isMessageClass('IPM.Task', true) || !folder.isTodoListFolder();
		});
		item.setDisabled(isDisabled);
	},

	/**
	 * Event handler triggers before the item shows. Disable the
	 * item If the selected record(s) has an organizer task copy
	 * or received task which was not yet accepted.
	 *
	 * @param {Zarafa.core.ui.menu.ConditionalItem} item The item to enable/disable
	 * @param {Zarafa.core.data.IPMRecord[]} records The records which must be checked
	 * to see if the item must be enabled or disabled.
	 * @private
	 */
	onBeforeShowFollowUp: function (item,records)
	{
		if(!Array.isArray(records)) {
			records = [records];
		}
		var isDisabled = records.some(function (record) {
			return record.isMessageClass('IPM.Task', true) && (record.isTaskOrganized() || record.isTaskNotResponded());
		});
		item.setDisabled(isDisabled);
	},

	/**
	 * Event handler called when the user clicks the 'open' button
	 * @private
	 */
	onOpenTask: function()
	{
		Zarafa.common.Actions.openMessageContent(this.records);
	},

	/**
	 * Open the {@link Zarafa.common.dialogs.CopyMoveContentPanel CopyMoveContentPanel} for copying
	 * or moving the currently selected tasks/task requests.
	 * @private
	 */
	onCopyMove: function()
	{
		Zarafa.common.Actions.openCopyMoveContent(this.records);
	},

	/**
	 * Create the Options menu entry.
	 * @return {Object} Menu item configuration
	 * @private
	 */
	createOptionsMenuItem: function()
	{
		return {
			xtype: 'zarafa.conditionalitem',
			text: _('Options'),
			iconCls: 'icon_cogwheel',
			singleSelectOnly: true,
			handler: this.onContextItemOptions,
			scope: this
		};
	},

	/**
	 * Open the options dialog for the selected record.
	 * @private
	 */
	onContextItemOptions: function()
	{
		Zarafa.task.Actions.openOptionsContent(this.records, {
			autoSave: true
		});
	},

	/**
	 * Event handler which is called when the user selects the 'delete'
	 * item in the context menu. This will delete all selected records.
	 * @private
	 */
	onContextItemDelete: function()
	{
		Zarafa.common.Actions.deleteRecords(this.records);
	},

	/**
	 * Event handler which is called when the user selects the 'Remove from To-Do List'
	 * item in the context menu. This will remove the flag from all selected records.
	 * @private
	 */
	onContextRemoveTodo: function()
	{
		const flagProperties = Zarafa.common.flags.Util.getFlagBaseProperties();
		Ext.apply(flagProperties, Zarafa.common.flags.Util.getFlagPropertiesRemoveFlag());

		this.records.forEach(function(record){
			record.beginEdit();
			for (var property in flagProperties) {
				record.set(property, flagProperties[property]);
			}
			record.endEdit();
			record.save();
		}, this);
	},

	/**
	 * Event handler which is called when the user select "Mark Complete / Mark InComplete" item in the context menu.
	 * This will mark the all selected records as "complete/IncComplete".
	 * @param {Zarafa.core.ui.menu.ConditionalItem} item The item to Mark Complete/Mark InComplete
	 * @private
	 */
	onMarkCompleteItemClick: function (item)
	{
		var complete = item.isMarkComplete;
		var showWarning = false;
		Ext.each(this.records, function (record) {
			record.beginEdit();
			record.set('complete', complete);
			record.set('percent_complete', complete);
			record.set('status', complete ? Zarafa.core.mapi.TaskStatus.COMPLETE : Zarafa.core.mapi.TaskStatus.NOT_STARTED);
			record.set('date_completed', complete ? new Date() : null);
			record.set('flag_icon', complete ? Zarafa.core.mapi.FlagIcon.clear : Zarafa.core.mapi.FlagIcon.red);
			record.set('flag_complete_time', complete ? new Date() : null);
			record.set('flag_request', complete ? '' : 'Follow up');
			record.set('flag_status', complete ? Zarafa.core.mapi.FlagStatus.completed : Zarafa.core.mapi.FlagStatus.flagged);
			record.endEdit();

			if (!record.isNormalTask()) {
				if (!record.isTaskOwner() && !record.isTaskRequest()) {
					showWarning = true;
				} else {
					record.addMessageAction('response_type', Zarafa.core.mapi.TaskMode.UPDATE);
				}
			}
		}, this);

		if (showWarning) {
			Ext.MessageBox.show({
				title: _('Changes to assigned task'),
				msg:_('Please note that assigned task(s) will be overwritten when the assignee makes changes.'),
				buttons: Ext.MessageBox.OK
			});
		}
		if (!Ext.isEmpty(this.records) && Ext.isDefined(this.records[0])) {
			this.records[0].getStore().save();
		}
	},

  /**
   * Function will loop through all given {@link Zarafa.core.data.IPMRecord records}
   * and will determine if this button can be applied to any of the records.
   * For example, Selected task is marked completed then 'Mark Incomplete' button enabled,
	 * if selected task is incomplete then 'Mark complete' button enabled.
   *
   * @param {Zarafa.core.ui.menu.ConditionalItem} item The item to enable/disable
   * @param {Zarafa.core.data.IPMRecord[]} records The records which must be checked
   * to see if the item must be enabled or disabled.
   * @private
   */
  onMarkCompleteItemBeforeShow: function (item, records)
	{
		var isDisabled = true;
		if(this.actsOnTodoListFolder !== true) {
			isDisabled = !records.some(function (record) {
				return record.get('complete') !== item.isMarkComplete;
			});
		}
		item.setDisabled(isDisabled);
  }
});

Ext.reg('zarafa.taskcontextmenu', Zarafa.task.ui.TaskContextMenu);
Ext.namespace('Zarafa.task.ui');

/**
 * @class Zarafa.task.ui.TaskGridColumnModel
 * @extends Zarafa.common.ui.grid.ColumnModel
 */
Zarafa.task.ui.TaskGridColumnModel = Ext.extend(Zarafa.common.ui.grid.ColumnModel, {
	/**
	 * @cfg {Array} simpleColumns The array of {@link Ext.grid.Column column} elements which must be visible within
	 * the simple view of this {@link Ext.grid.ColumnModel ColumnModel}.
	 */
	simpleColumns: [],
	/**
	 * @cfg {Array} simpleColumns The array of {@link Ext.grid.Column column} elements which must be visible within
	 * the detailed view of this {@link Ext.grid.ColumnModel ColumnModel}.
	 */
	 detailedColumns: [],

	/**
	 * @cfg {Zarafa.task.TaskContextModel} model data handling part of context
	 */
	model: undefined,

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

		this.simpleColumns = this.createSimpleColumns();
		this.detailedColumns = this.createDetailedColumns();

		Ext.applyIf(config, {
			columns: this.simpleColumns,
			defaults: {
				sortable: true
			}
		});

		Zarafa.task.ui.TaskGridColumnModel.superclass.constructor.call(this, config);
	},

	/**
	 * Create an array of {@link Ext.grid.Column columns} which must be visible within
	 * the simple view of this {@link Ext.grid.ColumnModel ColumnModel}.
	 * @return {Ext.grid.Column[]} The array of columns
	 * @private
	 */
	createSimpleColumns: function()
	{
		return[{
			dataIndex: 'icon_index',
			headerCls: 'zarafa-icon-column icon',
			header: '<p class="icon_index">&nbsp;<span class="title">Icon</span></p>',
			width: 25,
			tooltip: _('Sort by: Icon'),
			fixed: true,
			hideable: false,
			renderer: Zarafa.common.ui.grid.Renderers.icon
		}, {
			headerCls: 'zarafa-icon-column',
			header: '<p class="icon_reminder">&nbsp;</p>',
			dataIndex: 'reminder',
			tooltip: _('Sort by: Reminder'),
			width: 24,
			renderer: Zarafa.common.ui.grid.Renderers.reminder,
			fixed: true
		}, {
			xtype: 'checkcolumn',
			dataIndex: 'complete',
			headerCls: 'zarafa-icon-column',
			header: '<p class="icon_complete">&nbsp;</p>',
			width: 25,
			fixed: true,
			// override renderer so we can hide strikethough line displayed in empty cell, when task is complete
			renderer: this.completeColumnRenderer.createDelegate(this),
			tooltip: _('Sort by: Complete'),
			// override processEvent so we can save the change in the record
			processEvent: Ext.ux.grid.CheckColumn.prototype.processEvent.createSequence(this.onCompleteColumnProcessEvent)
		}, {
			id: 'subject',
			dataIndex: 'subject',
			header: _('Subject'),
			tooltip: _('Sort by: Subject'),
			renderer: this.columnRenderer
		}, {
			dataIndex: 'duedate',
			header: _('Due Date'),
			tooltip: _('Sort by: Due Date'),
			renderer: this.columnRenderer
		}, {
			header: _('Reminder Time'),
			dataIndex: 'reminder_time',
			width: 160,
			renderer: this.columnRenderer,
			tooltip: _('Sort by: Reminder Time'),
			hidden: true
		}, {
			header: _('Assigned To'),
			dataIndex: 'display_to',
			width: 100,
			renderer: this.columnRenderer,
			tooltip: _('Sort by: Assignee'),
			hidden: true
		}, {
			dataIndex: 'startdate',
			header: _('Start Date'),
			tooltip: _('Sort by: Start Date'),
			renderer: this.columnRenderer,
			hidden: true
		}, {
			dataIndex: 'percent_complete',
			header: _('% Completed'),
			width: 75,
			tooltip: _('Sort by: Percent Completed'),
			renderer: this.columnRenderer,
			hidden: true
		}, {
			dataIndex: 'categories',
			header: _('Categories'),
			tooltip: _('Sort by: Categories'),
			renderer: Zarafa.common.ui.grid.Renderers.categories
		}, {
			headerCls: 'zarafa-icon-column',
			header: '<p class="icon_paperclip">&nbsp;<span class="title">Attachment</span></p>',
			dataIndex: 'hasattach',
			width: 24,
			renderer: Zarafa.common.ui.grid.Renderers.attachment,
			fixed: true,
			tooltip: _('Sort by: Attachment'),
			hidden: true
		}, {
			dataIndex: 'importance',
			headerCls: 'zarafa-icon-column importance',
			header: '<p class="icon_importance">&nbsp;<span class="title">Importance</span></p>',
			width: 24,
			tooltip: _('Sort by: Priority'),
			fixed: true,
			renderer: Zarafa.common.ui.grid.Renderers.importance,
			hidden: true
		}, {
			headerCls: 'zarafa-icon-column',
			header: '<p class="icon_recurrence">&nbsp;</p>',
			dataIndex: 'recurring',
			width: 24,
			renderer: Zarafa.common.ui.grid.Renderers.recurrence,
			fixed: true,
			hidden: true
		}, {
			dataIndex: 'owner',
			header: _('Owner'),
			tooltip: _('Sort by: Owner'),
			renderer: this.columnRenderer,
			hidden: true
		}, {
			header: '<p class="icon_flag">&nbsp;<span class="title">' + _('Flag') + '</span></p>',
			headerCls: 'zarafa-icon-column flag k-unsortable',
			dataIndex: 'flag_due_by',
			width: 24,
			renderer: Zarafa.common.ui.grid.Renderers.flag,
			fixed: true,
			sortable: false
		}];
	},

	/**
	 * Create an array of {@link Ext.grid.Column columns} which must be visible within
	 * the detailed view of this {@link Ext.grid.ColumnModel ColumnModel}.
	 * @return {Ext.grid.Column[]} The array of columns
	 * @private
	 */
	createDetailedColumns: function()
	{
		return[{
			dataIndex: 'icon_index',
			headerCls: 'zarafa-icon-column icon',
			header: '<p class="icon_index">&nbsp;<span class="title">Icon</span></p>',
			width: 25,
			tooltip: _('Sort by: Icon'),
			fixed: true,
			hideable: false,
			renderer: Zarafa.common.ui.grid.Renderers.icon
		}, {
			headerCls: 'zarafa-icon-column',
			header: '<p class="icon_reminder">&nbsp;</p>',
			dataIndex: 'reminder',
			tooltip: _('Sort by: Reminder'),
			width: 24,
			renderer: Zarafa.common.ui.grid.Renderers.reminder,
			fixed: true
		},{
			header: _('Reminder Time'),
			dataIndex: 'reminder_time',
			width: 160,
			renderer: this.columnRenderer,
			tooltip: _('Sort by: Reminder Time'),
			hidden: true
		}, {
			xtype: 'checkcolumn',
			dataIndex: 'complete',
			headerCls: 'zarafa-icon-column',
			header: '<p class="icon_complete">&nbsp;</p>',
			width: 25,
			fixed: true,
			// override renderer so we can hide strikethough line displayed in empty cell, when task is complete
			renderer: this.completeColumnRenderer.createDelegate(this),
			tooltip: _('Sort by: Complete'),
			// override processEvent so we can save the change in the record
			processEvent: Ext.ux.grid.CheckColumn.prototype.processEvent.createSequence(this.onCompleteColumnProcessEvent)
		}, {
			id: 'subject',
			dataIndex: 'subject',
			header: _('Subject'),
			tooltip: _('Sort by: Subject'),
			renderer: this.columnRenderer
		}, {
			header: _('Assigned To'),
			dataIndex: 'display_to',
			width: 100,
			renderer: this.columnRenderer,
			tooltip: _('Sort by: Assignee')
		}, {
			dataIndex: 'startdate',
			header: _('Start Date'),
			tooltip: _('Sort by: Start Date'),
			renderer: this.columnRenderer
		}, {
			dataIndex: 'duedate',
			header: _('Due Date'),
			tooltip: _('Sort by: Due Date'),
			renderer: this.columnRenderer
		},{
			dataIndex: 'percent_complete',
			header: _('% Completed'),
			width: 75,
			tooltip: _('Sort by: Percentage Completed'),
			renderer: this.columnRenderer
		}, {
			dataIndex: 'categories',
			header: _('Categories'),
			tooltip: _('Sort by: Categories'),
			renderer: Zarafa.common.ui.grid.Renderers.categories
		}, {
			headerCls: 'zarafa-icon-column',
			header: '<p class="icon_paperclip">&nbsp;<span class="title">Attachment</span></p>',
			dataIndex: 'hasattach',
			width: 24,
			renderer: Zarafa.common.ui.grid.Renderers.attachment,
			fixed: true,
			tooltip: _('Sort by: Attachment')
		}, {
			dataIndex: 'importance',
			headerCls: 'zarafa-icon-column importance',
			header: '<p class="icon_importance">&nbsp;<span class="title">Importance</span></p>',
			width: 24,
			tooltip: _('Sort by: Priority'),
			fixed: true,
			renderer: Zarafa.common.ui.grid.Renderers.importance,
			hidden: true
		}, {
			headerCls: 'zarafa-icon-column',
			header: '<p class="icon_recurrence">&nbsp;</p>',
			dataIndex: 'recurring',
			width: 24,
			renderer: Zarafa.common.ui.grid.Renderers.recurrence,
			fixed: true,
			hidden: true
		}, {
			dataIndex: 'owner',
			header: _('Owner'),
			tooltip: _('Sort by: Owner'),
			renderer: this.columnRenderer,
			hidden: true
		}, {
			header: '<p class="icon_flag">&nbsp;<span class="title">' + _('Flag') + '</span></p>',
			headerCls: 'zarafa-icon-column flag k-unsortable',
			dataIndex: 'flag_due_by',
			width: 24,
			renderer: Zarafa.common.ui.grid.Renderers.flag,
			fixed: true,
			sortable: false
		}];
	},

	/**
	 * Render the cell based on dataIndex if record is completed
	 * task then strikethrough text of that column.
	 *
	 * @param {Object} value The data value for the cell.
	 * @param {Object} p An object with metadata
	 * @param {Ext.data.record} record The {Ext.data.Record} from which the data was extracted.
	 * @return {String} The formatted string
	 */
	columnRenderer: function (value, p, record)
	{
		switch (this.dataIndex) {
			case 'subject':
				value = Zarafa.common.ui.grid.Renderers.subject(value, p, record);
				break;
			case 'display_to':
				value = Zarafa.common.ui.grid.Renderers.name(value, p, record);
				break;
			case 'startdate':
			case 'duedate':
				value = Zarafa.common.ui.grid.Renderers.utcdate(value, p, record);
				break;
			case 'percent_complete':
				value = Zarafa.common.ui.grid.Renderers.percentage(value, p, record);
				break;
			case 'owner':
				value = Zarafa.common.ui.grid.Renderers.text(value, p, record);
				break;
			case 'reminder_time':
				value = Zarafa.common.ui.grid.Renderers.dateWithTime(value, p);
				break;
		}

		if((record.get('complete')
				|| record.get('flag_status') === Zarafa.core.mapi.FlagStatus.completed)
			&& !Ext.isEmpty(value)) {
			p.css += ' k-task-complete';
		}
		return value;
	},

	/**
	 * Override from the {@link Ext.ux.grid.CheckColumn#renderer} function
	 * to add 'zarafa-grid-empty-cell' css class to grid cell. This will hide strikethrough line that will appear
	 * when task is marked as complete.
	 * @param {Object} value The data value for the cell.
	 * @param {Object} p An object with metadata
	 * @param {Ext.data.record} record The {Ext.data.Record} from which the data was extracted.
	 * @return {String} The formatted string
	 * @private
	 */
	completeColumnRenderer: function(value, p, record)
	{
		p.css += ' zarafa-grid-empty-cell';

		// Value will be undefined as there is no 'complete' property while
		// rendering this column for mail record, using 'flag_status' instead.
		if (!Ext.isDefined(value) && record.get('flag_status') === Zarafa.core.mapi.FlagStatus.completed ) {
			value = true;
		}

		return Ext.ux.grid.CheckColumn.prototype.renderer.apply(this, arguments);
	},

	/**
	 * Sequence of the {@link Ext.ux.grid.CheckColumn#processEvent} function
	 * this will toggle the completed state of the task and directly save this
	 * to the server.
	 * @param {String} name name of the event which triggered this function
	 * @param {Ext.EventObject} e event object
	 * @param {Zarafa.common.ui.grid.GridPanel} grid grid panel which holds this {@link Zarafa.common.ui.grid.ColumnModel ColumnModel}
	 * @param {Number} rowIndex index of the row which is toggled
	 * @param {Number} colIndex index of the column which is toggled
	 * @private
	 */
	onCompleteColumnProcessEvent: function(name, e, grid, rowIndex, colIndex)
	{
		if (name !== 'mousedown') {
			return;
		}

		var record = grid.store.getAt(rowIndex);

		// Check if the record is a mail record
		if ( !(record instanceof Zarafa.task.TaskRecord) ) {
			Zarafa.common.flags.Util.updateCategories(record);

			// we need different set of flag properties as this is
			// the mail record we are dealing with.
			var mailFlagProps = {
				flag_icon: Zarafa.core.mapi.FlagIcon.clear,
				flag_complete_time: new Date(),
				flag_request: '',
				flag_status: Zarafa.core.mapi.FlagStatus.completed,
				reminder: false
			};

			grid.store.suspendEvents(false);
			record.beginEdit();
			for ( var property in mailFlagProps ){
				record.set(property, mailFlagProps[property]);
			}
			record.endEdit();
			record.save();
			grid.store.resumeEvents();
			return;
		}

		var complete = record.get('complete');
		record.beginEdit();
		record.set('percent_complete', complete ? 1: 0);
		record.set('status', complete ? Zarafa.core.mapi.TaskStatus.COMPLETE : Zarafa.core.mapi.TaskStatus.NOT_STARTED);
		record.set('date_completed', complete ? new Date() : null);
		record.set('flag_icon', complete ? Zarafa.core.mapi.FlagIcon.clear : Zarafa.core.mapi.FlagIcon.red);
		record.set('flag_complete_time', complete ? new Date() : null);
		record.set('flag_request', complete ? '' : 'Follow up');
		record.set('flag_status', complete ? Zarafa.core.mapi.FlagStatus.completed : Zarafa.core.mapi.FlagStatus.flagged);
		record.endEdit();

		if (!record.isNormalTask()) {
			if (!record.isTaskOwner() && !record.isTaskRequest()) {
				Ext.MessageBox.show({
					title: _('Assigned task changes'),
					msg:_('Please note that assigned task(s) will be overwritten when the assignee makes changes.'),
					scope: this,
					buttons: Ext.MessageBox.OK
				});
				record.save();
			} else {
				record.respondToTaskRequest(Zarafa.core.mapi.TaskMode.UPDATE);
			}
		} else {
			// save changes
			record.save();
		}
	},

	/**
	 * This will switch the {@link Zarafa.task.ui.TaskGridColumnModel columnmodel}
	 * configuration to either simple or detailed configuration.
	 *
	 * @param {Boolean} isSimple True to enable the simple view, false otherwise
	 */
	setSimpleView: function(isSimple)
	{
		if (isSimple) {
			this.name = 'simple';
			this.columns = this.simpleColumns;
			this.setConfig(this.simpleColumns, false);
		} else {
			this.name = 'detailed';
			this.columns = this.detailedColumns;
			this.setConfig(this.detailedColumns, false);
		}
	},

	/**
	 * Reconfigures this column model according to selected folder in hierarchy.
	 * if selected folder is other than To-Do list folder then filter out the
	 * percent complete column from column configuration object.
	 *
	 * @param {Object} config The configuration to set on the columnmodel
	 * @param {Boolean} initial True if this is the initial configuration of the columnmodel
	 * @protected
	 */
	setConfig: function(config, initial)
	{
		// If we have model, default folder and which
		// is To-do list folder then don't show
		// percent completed columns.
		if (Ext.isDefined(this.model) &&
			this.model.getDefaultFolder() &&
			this.model.getDefaultFolder().isTodoListFolder()) {
			config = config.filter(function(c) {
				return c.dataIndex !== 'percent_complete';
			});
		}
		Zarafa.task.ui.TaskGridColumnModel.superclass.setConfig.call(this, config, initial);
	}
});
/*
 * #dependsFile client/zarafa/task/data/SearchFields.js
 */
Ext.namespace('Zarafa.task.ui');

/**
 * @class Zarafa.task.ui.TaskMainPanel
 * @extends Zarafa.common.ui.ContextMainPanel
 * @xtype zarafa.taskmainpanel
 *
 * This class will be containing all the views that will be created for tasks folder.
 */
Zarafa.task.ui.TaskMainPanel = Ext.extend(Zarafa.common.ui.ContextMainPanel, {
	// Insertion points for this class
	/**
	 * @insert context.task.toolbar.item
	 * Insertion point for populating Task context's main toolbar.
	 * This item is only visible when this context is active.
	 * @param {Zarafa.task.ui.TaskMainPanel} panel This panel
	 */
	/**
	 * @insert context.task.toolbar.paging
	 *
	 * Insertion point for populating task context's toolbar with extra
	 * pagination buttons. This can be used to replace the default {@link Ext.PagingToolbar}
	 * with an alternative. Note that by default all paging toolbars will be visible, and
	 * hiding a particular toolbar is the responsibility of the new pagers.
	 * @param {Zarafa.task.ui.TaskMainPanel} panel This panel
	 */
	/**
	 * @insert context.task.views
	 * Insertion point for adding views within the main panel of task context.
	 * This insertion point should be used in combination with 'main.maintoolbar.view.task'
	 * insertion point, and also view should set its store in the config object, the reference of
	 * {@link Zarafa.note.TaskContextModel TaskContextModel} is passed as parameter of this
	 * insertion point.
	 * @param {Zarafa.task.ui.TaskMainPanel} mainpanel This mainpanel
	 * @param {Zarafa.task.TaskContext} context The context for this panel
	 */

	/**
	 * The main panel in which the various views are located.
	 * @property
	 * @type Zarafa.core.ui.SwitchViewContentContainer
	 */
	viewPanel: undefined,

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

		Ext.applyIf(config, {
			xtype: 'zarafa.taskmainpanel',
			layout: 'fit',
			items: [{
				xtype: 'zarafa.switchviewcontentcontainer',
				ref: 'viewPanel',
				layout: 'card',
				lazyItems: this.initViews(config.context)
			}],
			tbar: {
				xtype: 'zarafa.contextmainpaneltoolbar',
				defaultTitle: _('Tasks'),
				paging: container.populateInsertionPoint('context.task.toolbar.paging', this),
				items: container.populateInsertionPoint('context.task.toolbar.item', this),
				context: config.context
			}
		});

		Zarafa.task.ui.TaskMainPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Function will initialize all views associated with task context
	 * it will also get views added through 3rd party plugins and add it here
	 * @param {Zarafa.task.TaskContextModel} model data part of task context
	 * @return {Array} array of config objects of different views
	 * @private
	 */
	initViews: function(context)
	{
		// add the standard available views
		var allViews = [{
			xtype: 'zarafa.taskgrid',
			id  : 'task-grid',
			context: context
		}];

		var additionalViewItems = container.populateInsertionPoint('context.task.views', this, context);
		allViews = allViews.concat(additionalViewItems);

		return allViews;
	},

	/**
	 * Function called by Extjs when the panel has been {@link #render rendered}.
	 * At this time all events can be registered.
	 * @private
	 */
	initEvents: function()
	{
		if (Ext.isDefined(this.context)) {
			this.mon(this.context, 'viewchange', this.onViewChange, this);

			this.onViewChange(this.context, this.context.getCurrentView());
		}
	},

	/**
	 * Event handler which is fired when the currently active view inside the {@link #context}
	 * has been updated. This will update the call
	 * {@link #viewPanel}#{@link Zarafa.core.ui.SwitchViewContentContainer#switchView}
	 * to make the requested view active.
	 *
	 * @param {Zarafa.core.Context} context The context which fired the event.
	 * @param {Zarafa.task.data.Views} newView The ID of the selected view.
	 * @param {Zarafa.task.data.Views} oldView The ID of the previously selected view.
	 */
	onViewChange: function(context, newView, oldView)
	{
		switch (newView) {
			case Zarafa.task.data.Views.SEARCH:
			case Zarafa.task.data.Views.LIST:
				this.viewPanel.switchView('task-grid');
				break;
		}
	}
});

Ext.reg('zarafa.taskmainpanel', Zarafa.task.ui.TaskMainPanel);
Ext.namespace('Zarafa.core');

/**
 * @class Zarafa.core.Context
 * @extends Zarafa.core.Plugin
 *
 * A context is special plug-in that provides a set of standard components such
 * as a tool bar and content panel, and implements the bid() method. Contexts are
 * generally used to display a certain type of content such as mail, notes,
 * appointments, etc.
 * <p>
 * A context implements the bid() method which is called when a folder is selected
 * from the hierarchy. Each registered Context is allowed to bid on the folder and
 * the context that bids the highest is allowed to display that folder.
 * <p>
 * Before a context is selected the enable() method is called, allowing the context
 * to set itself up. Usually this means initialising data stores and hooking them
 * to UI components. The disable() method is called before the context is switched
 * out again.
 * <p>
 * This class was meant to be overridden.
 */
Zarafa.core.Context = Ext.extend(Zarafa.core.Plugin, {
	/**
	 * The currently active view, this is updated through {@link #setView} and when
	 * this field changes, the {@link #viewchange} event will be fired.
	 * When this context is {@link #stateful stateful}, this option will be
	 * saved in the settings.
	 * @property
	 * @type Mixed
	 */
	current_view: undefined,

	/**
	 * The currently active viewmode, this is updated through {@link #setViewMode}
	 * and when this field changes, the {@link #viewmodechange} event will be fired.
	 * When this context is {@link #stateful stateful}, this option will be
	 * saved in the settings.
	 * @property
	 * @type Mixed
	 */
	current_view_mode: undefined,

	/**
	 * @cfg {Boolean} hasContentPanel Indicates if this context offers a content panel, this panel
	 * will be requested by {@link #createContentPanel} when the {@link main.content} insertion
	 * point is used.
	 */
	hasContentPanel: true,

	/**
	 * @cfg {Boolean} groupViewBtns True if the buttons as returned by {@link #getMainToolbarViewButtons}
	 * should be grouped together into a single {@link Ext.Button} using the {@link Ext.Button#menu} option.
	 * If false, the buttons will be placed side by side in the panel.
	 */
	groupViewBtns: true,

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

		Ext.applyIf(config, {
			stateful: true
		});

		this.addEvents([
			/**
			 * @event viewchange
			 * This event is fired when the user changes the active view inside the context,
			 * this change is done in the function {@link #setView}. Since the function
			 * itself not perform the switch itself, the listeners to this event must
			 * assure that the {@link Ext.Component component} they are managing is updated
			 * correctly.
			 * @param {Zarafa.core.Context} context The context which fired the event
			 * @param {Mixed} newView The new View id
			 * @param {Mixed} oldView The old View id
			 */
			'viewchange',
			/**
			 * @event viewmodechange
			 * This event is fired when the user switches the active viewmode inside the context,
			 * this change is done in the function {@link #setViewMode}. Since the function
			 * itself does not perform the switch itself, the listeners to this event must
			 * assure that the {@link Ext.Component component} they are managing is updated
			 * correctly.
			 * @param {Zarafa.core.Context} context The context which fired the event
			 * @param {Mixed} newMode The new ViewMode
			 * @param {Mixed} oldMode The old ViewMode
			 */
			'viewmodechange'
		]);

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

		if (this.hasContentPanel === true) {
			this.registerInsertionPoint('main.content', this.createContentPanel, this);
		}
	},

	/**
	 * Override this method to return a new instance Ext.Panel.
	 * This instance will be placed at the center of the screen when the
	 * context is active.
	 * <p>
	 * The default implementation of getComponents() calls this method to
	 * lazily construct the toolbar.
	 * @return {Ext.Panel} a new panel instance
	 */
	createContentPanel: function()
	{
		return undefined;
	},

	/**
	 * Override this method to define buttons in the dropdown list of the VIEW button in the main toolbar.
	 *
	 * @return {Ext.Component[]} an array of components
	 */
	getMainToolbarViewButtons: function()
	{
		return [];
	},

	/**
	 * Override this method to define buttons in the dropdown list of the Print button in the main toolbar.
	 *
	 * @return {Ext.Component[]} an array of components
	 */
	getMainToolbarPrintButtons: function()
	{
		return [];
	},

	/**
	 * Function will be used as a default handler for "Print" button of {@link Zarafa.core.ui.MainToolbar MainToolbar}.
	 * It will check if active tab is {@link Zarafa.core.ui.ContextContainer ContextContainer} then it will print the selected record
	 * else it will print record of activetab panel.
	 *
	 *
	 * @param {Object} button The button which user pressed.
	 * @param {Ext.EventObject} evt The mouse event
	 * @param {String} msg message which needs to be shown if no record is found to print.
	 */
	onPrintSelected: function (button, evt, msg)
	{
		var activeTab = container.getTabPanel().getActiveTab();
		var records;

		// If main context is opened in active tab
		// then get selected record and print it.
		if (activeTab instanceof Zarafa.core.ui.ContextContainer || button.name !== 'defaultPrintBtn') {
			records = this.getModel().getSelectedRecords();
			if (Ext.isEmpty(records)) {
				Ext.MessageBox.alert(_('Print'), Ext.isEmpty(msg) ? _('No item selected') : msg);
				return;
			}
		} else {
			records = activeTab.record;
		}

		Zarafa.common.Actions.openPrintDialog(records);
	},

	/**
	 * Switches the {@link #current_view} and {@link #current_view_mode} at the same time.
	 * This delays the {@link #viewchange} and {@link #viewmodechange} events until both
	 * properties have been changed.
	 *
	 * @param {Mixed} viewId The view identification
	 * @param {Mixed} mode view mode (context should define modes and its numeric values).
	 * @param {Boolean} init (optional) True when this function is called during initialization
	 * and it should force the change of the view.
	 */
	switchView: function(viewId, mode, init)
	{
		this.suspendEvents(true);
		this.setView(viewId, init);
		this.setViewMode(mode, init);
		this.resumeEvents();
	},

	/**
	 * Set the currently active view inside the {@link Zarafa.core.Context context}.
	 * This will fire the {@link #viewchange} event.
	 *
	 * When this is called together with {@link #setViewMode}, {@link #switchView} should
	 * be used instead.
	 *
	 * @param {Mixed} viewId The view identification
	 * @param {Boolean} init (optional) True when this function is called during initialization
	 * and it should force the change of the view.
	 */
	setView: function(viewId, init)
	{
		if (init === true || this.current_view !== viewId) {
			var oldView = this.current_view;
			this.current_view = viewId;

			this.onViewChange(this, this.current_view, oldView);

			// Fire the viewchange event
			this.fireEvent('viewchange', this, this.current_view, oldView);
		}
	},

	/**
	 * Event handler which is executed right before the {@link #viewchange}
	 * event is fired. This allows subclasses to initialize the view.
	 *
	 * @param {Zarafa.core.Context} context The context which fired the event.
	 * @param {Mixed} newView The selected View.
	 * @param {Mixed} oldView The previously selected View.
	 * @private
	 */
	onViewChange: Ext.emptyFn,

	/**
	 * Returns the currently active {@link #current_view view} as configured
	 * through {@link #setView}.
	 * @return {Mixed} The view id of the currently active view.
	 */
	getCurrentView: function()
	{
		return this.current_view;
	},

	/**
	 * Sets the current view mode from the available view modes.
	 *
	 * When this is called together with {@link #setView}, {@link #switchView} should
	 * be used instead.
	 *
	 * Fires the {@link #viewmodechange} event.
	 * @param {Number} mode view mode (context should define modes and its numeric values).
	 * @param {Boolean} init (optional) True when this function is called during initialization
	 * and it should force the change of the view mode.
	 */
	setViewMode: function(mode, init)
	{
		if (init === true || this.current_view_mode !== mode) {
			var oldMode = this.current_view_mode;
			this.current_view_mode = mode;

			this.onViewModeChange(this, this.current_view_mode, oldMode);

			// Fire the viewmodechange event
			this.fireEvent('viewmodechange', this, this.current_view_mode, oldMode);
		}
	},

	/**
	 * Event handler which is executed right before the {@link #viewmodechange}
	 * event is fired. This allows subclasses to initialize the view mode.
	 *
	 * @param {Zarafa.core.Context} context The context which fired the event.
	 * @param {Mixed} newViewMode The selected View Mode.
	 * @param {Mixed} oldViewMode The previously selected View Mode.
	 * @private
	 */
	onViewModeChange: Ext.emptyFn,

	/**
	 * Returns the currently active {@link #current_view_mode viewmode} as configured
	 * through {@link #setViewMode}.
	 * @return {Mixed} The viewmode id of the currently active viewmode.
	 */
	getCurrentViewMode: function()
	{
		return this.current_view_mode;
	},

	/**
	 * Obtain the {@link Zarafa.core.ContextModel mode} which is associated
	 * to this context.
	 * @return {Zarafa.core.ContextModel} The model associated to this context
	 */
	getModel: Ext.emptyFn,

	/**
	 * Called before the context is switched in.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder MAPI folder to show.
	 * @param {Boolean} suspended True to enable the ContextModel {@link Zarafa.core.ContextModel#suspendLoading suspended}
	 */
	enable: function(folder, suspended)
	{
		if (this.stateful) {
			this.initState();
		}

		// First enable the model, when we apply the new
		// view modes to the context, it might want to
		// access data on the model which must be fully
		// initialized by that time.
		var model = this.getModel();
		if (model) {
			model.enable(folder, suspended);
		}

		// Force the data we loaded from the settings
		// or was applied during configuration time
		// to be forwarded to all interested parties.
		this.switchView(this.getCurrentView(), this.getCurrentViewMode(), true);
	},

	/**
	 * Called before the context is switched out.
	 */
	disable: function()
	{
		var model = this.getModel();
		if (model) {
			model.disable();
		}
	},

	/**
	 * Produces a bid on a given folder. A negative bid (-1) indicates that this
	 * context cannot display the folder contents. A positive bid (1) indicates that it
	 * can, and a higher bid (>1) can be used to override default context plug-ins.
	 * The context that bids the highest is selected to display a given folder.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder to bid on.
	 * @return {Number} a bid on the folder.
	 */
	bid: function(folder)
	{
		return -1;
	},

	/**
	 * Obtain the path in which the {@link #getState state} must be saved.
	 * This option is only used when the {@link Zarafa.core.data.SettingsStateProvider SettingsStateProvider} is
	 * used in the {@link Ext.state.Manager}. This returns {@link #statefulName} if provided, or else generates
	 * a custom name.
	 * @return {String} The unique name for this component by which the {@link #getState state} must be saved.
	 */
	getStateName: function()
	{
		var name = this.statefulName;
		if (!name) {
			name = this.getName();
		}

		return 'contexts/' + name;
	},

	/**
	 * Register the {@link #stateEvents state events} to the {@link #saveState} callback function.
	 * @protected
	 */
	initStateEvents: function()
	{
		Zarafa.core.Context.superclass.initStateEvents.call(this);
		this.on('viewchange', this.saveViewModeState, this, {delay: 100});
		this.on('viewmodechange', this.saveViewModeState, this, {delay: 100});
	},

	/**
	 * Event handler for 'viewchange' and 'viewmodechange', only saves
	 * state when the view mode has changed.
	 *
	 * @param {Zarafa.core.Context} context the current context
	 * @param {Number} current_view_mode the current view mode
	 * @param {Number} oldMode the old view mode.
	 */
	saveViewModeState: function(context, current_view_mode, oldMode)
	{
		if (current_view_mode != oldMode) {
			this.saveState();
		}
	},

	/**
	 * When {@link #stateful} the State object which should be saved into the
	 * {@link Ext.state.Manager}.
	 * @return {Object} The state object
	 * @protected
	 */
	getState: function()
	{
		var state = Zarafa.core.Context.superclass.getState.call(this) || {};
		var model = this.getModel();
		var searching = model && model.isSearching();
		var scrolling = model && model.isLiveScrolling();

		/*
		 * True when live scroll is performed and and current view mode should not be
		 * one of the main view mode (NO_PREVIEW, RIGHT_PREVIEW, BOTTOM_PREVIEW).
		 * it gets false when user search something, use live scroll and then close the search
		 */
		var isOnlyScrolling = (scrolling && !searching && !Zarafa.mail.data.ViewModes.isMainViewMode(this.current_view_mode));

		/*
		 * True when live scroll and searching both are performed also
		 * old view mode was one of the main view mode(NO_PREVIEW, RIGHT_PREVIEW, BOTTOM_PREVIEW).
		 * it will gets false when user close the search or switch the context, folder and view.
		 */
		var isSearchingAndScrolling = (scrolling && searching && Zarafa.mail.data.ViewModes.isMainViewMode(this.oldViewMode));

		return Ext.apply(state, isOnlyScrolling || isSearchingAndScrolling ?{
			current_view: this.oldView,
			current_view_mode: this.oldViewMode
		}: {
			current_view: this.current_view,
			current_view_mode: this.current_view_mode
		});
	}
});
Ext.namespace('Zarafa.core');

/**
 * @class Zarafa.core.HtmlEditorPlugin
 * @extends Zarafa.core.Plugin
 *
 * Class is consider as a interface for all html editor plugins all
 * functions are override by the respective html editor plugins.
 */
Zarafa.core.HtmlEditorPlugin = Ext.extend(Zarafa.core.Plugin, {
	/**
	 * Editor plugins should overwrite this value with the xtype of their editor.
	 * @property
	 */
	editorXType: '',

	/**
	 * Sets the underlying DOM field's value directly, bypassing validation.
	 * To set the value with validation see {@link #setValue}.
	 * @param {Mixed} value The value to set
	 * @return {Ext.form.Field} this
	 */
	setRawValue: Ext.emptyFn,

	/**
	 * Sets a data value into the field and validates it.
	 * To set the value directly without validation see {@link #setRawValue}
	 * @param {Mixed} value The value to set
	 * @return {Ext.form.Field} this
	 */
	setValue: Ext.emptyFn,

	/**
	 * Returns the raw data value which may or may not be a valid, defined value.
	 * To return a normalized value see {@link #getValue}.
	 * @return {Mixed} value The field value
	 */
	getRawValue: Ext.emptyFn,

	/**
	 * Returns the normalized data value (undefined or emptyText will be returned as '').
	 * To return the raw value see {@link #getRawValue}.
	 * @return {Mixed} value The field value
	 */
	getValue: Ext.emptyFn,

	/**
	 * Try to focus this component.
	 * @param {Boolean} selectText (optional) If applicable, true to also select the text in this component
	 * @param {Boolean/Number} delay (optional) Delay the focus this number of milliseconds (true for 10 milliseconds)
	 * @return {Ext.Component} this
	 */
	focus: Ext.emptyFn,

	/**
	 * Resets the current field value to the originally loaded value and clears any validation messages.
	 * See {@link Ext.form.BasicForm}.{@link Ext.form.BasicForm#trackResetOnLoad trackResetOnLoad}
	 */
	reset: Ext.emptyFn,

	/**
	 * Function sets the cursor position to the start of the text
	 */
	setCursorLocation: Ext.emptyFn,

	/**
	 * Function inserts HTML text into the editor field where cursor is positioned.
	 * @param {String} value The text which must be inserted at the cursor position
	 */
	insertAtCursor: Ext.emptyFn,

	/**
	 * Function select the text in editor by given selector.
	 *
	 * @param {String} selector The selector query which used to select the text in editor.
	 * @return {boolean} return true if text is selected in editor else false.
	 */
	selectBySelector: Ext.emptyFn,
});Ext.namespace('Zarafa.core');

/**
 * @class Zarafa.core.MultiFolderContextModel
 * @extends Zarafa.core.ContextModel
 *
 * Extension to the context model to support maintaining
 * multiple folders which can be grouped together in a sort
 * of tabbed view.
 */
Zarafa.core.MultiFolderContextModel = Ext.extend(Zarafa.core.ContextModel, {
	/**
	 * @cfg {Object[]} colorScheme The color definition objects which is applied within
	 * the context. Each folder will be assigned with an entry from this array to
	 * define the colors applied to it.
	 */
	colorScheme: undefined,

	/**
	 * A flag that denotes if colors are being assigned, thereby letting
	 * the setColor function know that it shouldn't fire the 'colormapchanged'
	 * event.
	 * @property
	 * @type Boolean
	 * @private
	 */
	assigningColors: false,

	/**
	 * @cfg {Boolean} default_merge_state True if the contents of each folder is currently merged into a single view.
	 * This is updated through {@link #setMergeState} and when this field changes,
	 * the {@link #foldermergestatechanged} event will be fired.
	 */
	default_merge_state: false,

	/**
	 * The currently active grouping of all active {@link #folders}.
	 * Each key inside this object refers to another object containing the
	 * 'folders' properties which is the list of all entryids which are
	 * grouped together into a single view. The 'active' property denotes which
	 * folder is currently the active folder.
	 * When this context is {@link #stateful stateful}, this option will be
	 * saved in the settings.
	 * @property
	 * @type Object
	 * @private
	 */
	groupings: undefined,

	/**
	 * Mapping of folder entryids to color schemes
	 * @property
	 * @type Object
	 */
	colorMap: undefined,

	/**
	 * The currently active group out of all groupings in this context model
	 * Outlook has a single selected folder at any time, regardless of how folders are grouped
	 * This property is used to index into the active group
	 * When this context is {@link #stateful stateful}, this option will be
	 * saved in the settings.
	 * @property
	 * @type String
	 * @private
	 */
	active_group: undefined,

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

		this.addEvents(
			/**
			 * @event foldermergestatechanged
			 * Fires when the contents of the selected folders must be merged into a single view
			 * @param {Zarafa.core.ContextModel} model this context model.
			 * @param {Boolean} mergeState The merge state, true to merge all folders into
			 * a single view.
			 */
			'foldermergestatechanged',
			/**
			 * @event foldergroupingchanged
			 * Fires when the groupings for the selected folders has been changed.
			 * @param {Zarafa.core.ContextModel} model this context model.
			 * @param {Object} groupings The groupings object
			 * @param {String} active The active group
			 */
			'foldergroupingchanged',
			/**
			 * @event colormapchanged
			 * Fires when a new color schema for a folder has been set.
			 * @param {Zarafa.core.ContextModel} model this context model.
			 * @param {Object} colorMap The new colorMap
			 */
			'colormapchanged'
		);

		// Initialize groupings
		this.groupings = {};
		this.colorMap = {};

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

		var hierarchyStore = container.getHierarchyStore();
		hierarchyStore.on('addFolder', this.onHierarchyAddFolder, this);
	},

	/**
	 * Called during the {@link Zarafa.core.Context#enable enabling} of the {@link Zarafa.core.Context context}.
	 * Secondly it will {@link #setFolders set the} {@link #folders folder} to this object to {@link #load} the {@link #store}.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder MAPI folder to show.
	 * @param {Boolean} suspended True to enable the ContextModel {@link #suspendLoading suspended}
	 */
	enable: function(folder, suspended)
	{
		Zarafa.core.MultiFolderContextModel.superclass.enable.apply(this, arguments);

		this.setMergeState(this.getCurrentMergeState(), true);
	},

	/**
	 * Set the mergeState to force eiher the UI components to merge or separate
	 * all folders currently in the view. If the lock argument is not given, or
	 * is currently This will only act upon the currently open folders in the view.
	 * @param {Boolean} mergeState True to merge all folders into a single view
	 * @param {Boolean} init (optional) True when this function is called during initialization
	 * and it should force the change of the data mode.
	 */
	setMergeState: function(mergeState, init)
	{
		if (init === true || this.default_merge_state !== mergeState) {
			this.default_merge_state = mergeState;

			this.fireEvent('foldermergestatechanged', this, this.default_merge_state);
		}
	},

	/**
	 * Get the current merge state of all opened folders
	 * @return {Boolean} The current merge state
	 */
	getCurrentMergeState: function()
	{
		return this.default_merge_state;
	},

	/**
	 * Event handler which is executed right before the {@link #folderchange}
	 * event is fired. This allows subclasses to update the folders.
	 *
	 * @param {Zarafa.core.ContextModel} model The model which fired the event.
	 * @param {Array} folders selected folders as an array of {@link Zarafa.hierarchy.data.MAPIFolderRecord Folder} objects.
	 * @private
	 */
	onFolderChange: function(model, folders)
	{
		Zarafa.core.MultiFolderContextModel.superclass.onFolderChange.apply(this, arguments);

		this.sortFolders();
		this.assignColors();
		this.applyGrouping();
	},

	/**
	 * Overridden in order to assign colors to the loaded folders
	 *
 	 * Sets {@link #defaultFolder default folder} for the particular {@link Zarafa.core.Context context}.
	 * This will help while opening new item dialog from other contexts
	 * e.g. Create new Contact from Inbox, at this moment we need {@link #defaultFolder} to create the item.
	 *
	 * When the {@link Zarafa.core.Context context} was opened without any folders,
	 * this also means we can now {@link #addFolder load} the {@link #defaultFolder}.
	 *
	 * @param {Zarafa.core.hierarchyStore} store that holds hierarchy data.
	 * @private
	 * @override
	 */
	onHierarchyLoad: function(hierarchyStore)
	{
		// only continue when hierarchyStore has data
		if (hierarchyStore.getCount() === 0) {
			return;
		}

		Zarafa.core.MultiFolderContextModel.superclass.onHierarchyLoad.apply(this, arguments);

		this.sortFolders();
		this.assignColors();
	},

 	onHierarchyAddFolder : function(hierarchyStore, storeRecord, folder)
	{
		this.assignColors();
	},

	/**
	 * Returns the default {@link Zarafa.hierarchy.data.MAPIFolderRecord folder} which is
	 * used within the current selection of folders. If multiple folders are grouped together then
	 * this function will return the active group folder. If this ContextModel is not enabled then
	 * this will always return default calendar folder from default store.
	 * @return {Zarafa.hierarchy.data.MAPIFolderRecord} The default folder
	 */
	getDefaultFolder: function()
	{
		if(this.enabled) {
			var activeGroup = this.getActiveGroup();

			if(activeGroup && this.groupings[activeGroup]) {
				var folder = this.getFolder(this.groupings[activeGroup].active);
				if(!Ext.isEmpty(folder)) {
					return folder;
				}
			}
		}

		// fallback to parent
		return Zarafa.core.MultiFolderContextModel.superclass.getDefaultFolder.apply(this, arguments);
	},

	/**
	 * Updates a group in the {@link #groupings} to mark it as active.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder The folder which is activated
	 * @param {String} groupId The group to which the folder belongs
	 */
	activateFolderInGroup: function(folder, groupId)
	{
		this.active_group = groupId;
		this.groupings[groupId].active = folder.get('entryid');

		this.fireEvent('foldergroupingchanged', this, this.groupings, this.active_group);
	},

	/**
	 * Moves a {@link Zarafa.hierarchy.data.MAPIFolderRecord folder} from an existing
	 * group to a new Group in the {@link #groupings}.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder The folder to move to the other group
	 * @param {String} newGroup The group to which the folder should be moved
	 * @param {String} oldGroup The group from where the folder is moved
	 * @return {String} The groupID to which the folder was added.
	 */
	mergeFolderToGroup: function(folder, newGroup, oldGroup)
	{
		var entryid = folder.get('entryid');
		if (this.groupings[oldGroup]) {
			this.groupings[oldGroup].folders.remove(entryid);
			if (Ext.isEmpty(this.groupings[oldGroup].folders)) {
				delete this.groupings[oldGroup];
			} else if (this.groupings[oldGroup].active == entryid) {
				this.groupings[oldGroup].active = this.groupings[oldGroup].folders[0];
			}
		}
		this.groupings[newGroup].folders.push(entryid);
		this.groupings[newGroup].active = entryid;
		this.active_group = newGroup;

		this.fireEvent('foldergroupingchanged', this, this.groupings, this.active_group);
		return newGroup;
	},

	/**
	 * Move a {@link Zarafa.hierarchy.data.MAPIFolderRecord folder} out of the current group and
	 * create a new group into which the folder should be moved.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder The folder to move to a new group
	 * @param {String} oldGroup The group from where the folder is moved
	 * @return {String} The groupID to which the folder was added.
	 */
	separateFolderFromGroup: function(folder, oldGroup)
	{
		var groupId = Ext.id(null, 'group-');
		var entryid = folder.get('entryid');
		if (this.groupings[oldGroup]) {
			this.groupings[oldGroup].folders.remove(entryid);
			if (Ext.isEmpty(this.groupings[oldGroup].folders)) {
				delete this.groupings[oldGroup];
			} else if (this.groupings[oldGroup].active == entryid) {
				this.groupings[oldGroup].active = this.groupings[oldGroup].folders[0];
			}
		}
		this.groupings[groupId] = { folders: [ entryid ], active: entryid };
		this.active_group = groupId;

		this.fireEvent('foldergroupingchanged', this, this.groupings, this.active_group);
		return groupId;
	},

	/**
	 * Remove a {@link Zarafa.hierarchy.data.MAPIFolderRecord folder} out of the current group.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder The folder to move to a new group
	 * @param {String} oldGroup The group from where the folder is moved
	 */
	removeFolderFromGroup: function(folder, oldGroup)
	{
		var entryid = folder.get('entryid');
		if (this.groupings[oldGroup]) {
			this.groupings[oldGroup].folders.remove(entryid);
			if (Ext.isEmpty(this.groupings[oldGroup].folders)) {
				delete this.groupings[oldGroup];
			} else if (this.groupings[oldGroup].active == entryid) {
				this.groupings[oldGroup].active = this.groupings[oldGroup].folders[0];
			}
		}
		this.fireEvent('foldergroupingchanged', this, this.groupings, this.active_group);
	},

	/**
	 * Add an item to a new Group to the {@link #groupings}
	 * @param {String} entryid The entryid to add to a new group
	 * @private
	 */
	addItemToGroup: function(entryid)
	{
		if (this.default_merge_state) {
			// attempt to merge to an existing group
			var keys = Object.keys(this.groupings);
			if (Ext.isEmpty(keys)) {
				// if no other groups - create a new group and set it in groupings
				this.active_group = Ext.id(null, 'group-');
				this.groupings[this.active_group] = { folders: [ entryid ], active: entryid };
			} else {
				this.active_group = keys[0];
				this.groupings[keys[0]].folders.push(entryid);
			}
		} else {
			// create a new group and add it to groupings
			this.active_group = Ext.id(null, 'group-');
			this.groupings[this.active_group] = { folders: [ entryid ], active: entryid };
		}
	},

	/**
	 * Remove an item from the group (and delete the group if empty) in {@link #groupings}.
	 * @param {String} entryid THe entryid to remove from the group
	 * @private
	 */
	removeItemFromGroup: function(entryid)
	{
		Ext.iterate(this.groupings, function(key, group) {
			if(Ext.isEmpty(group.folders)) {
				// continue loop for other groups
				return true;
			}

			var index = group.folders.indexOf(entryid);

			if (index > -1) {
				group.folders.splice(index, 1);

				if (Ext.isEmpty(group.folders)) {
					delete this.groupings[key];

					// If we are removing the currently active group,
					if (this.active_group == key) {
						var keys = Object.keys(this.groupings);
						// then set the last group available as active,
						// or undefined if there are no groups left
						this.active_group = keys.pop();
					}
				}

				// break looping
				return false;
			}
		}, this);
	},

	/**
	 * Update the {@link #groupings} based by removing the folders in the
	 * {@link #groupings} which are no longer listed in {@link #folders}
	 * and add folders to the {@link #groupings} if they were not yet assigned
	 * to a group.
	 * @private
	 */
	applyGrouping: function()
	{
		var entryids = [];
		for (var i = 0, len = this.folders.length; i < len; i++) {
			entryids.push(this.folders[i].get('entryid'));
		}

		var grouped = [];
		Ext.iterate(this.groupings, function(key, group) {
			grouped = grouped.concat(group.folders);
		}, this);

		for (var i = 0, len = grouped.length; i < len; i++) {
			var entryid = grouped[i];
			var index = entryids.indexOf(entryid);

			if (index < 0) {
				// The grouped entryid was not found in the folderslist,
				// it has been deleted so clean the group.
				this.removeItemFromGroup(entryid);
			} else {
				// The grouped entryid was found in the folderslist,
				// remove it from the entryids list.
				entryids.remove(entryid);
			}
		}

		// All items remaining in the entryids list, are the new
		// folders which haven't been assigned to a group yet.
		for (var i = 0, len = entryids.length; i < len; i++) {
			this.addItemToGroup(entryids[i]);
		}
		this.fireEvent('foldergroupingchanged', this, this.groupings, this.active_group);
	},

	/**
	 * Obtain the currently active {@link #groupings}
	 * @return {Object} The folder groupings.
	 */
	getGroupings: function()
	{
		return this.groupings;
	},

	/**
	 * Sorts the folders in the selected folder list so that they are
	 * in the same order as they appear in the folder hierarchy.
	 * @private
	 */
	sortFolders: function()
	{
		// If one folder is selected, sorting doesn't make sense,
		// so only sort when multiple folders are selected.
		if (this.folders.length > 1) {
			this.folders = container.getHierarchyStore().getSortedFolders(this.hasFolder, this);
		}
	},

	/**
	 * Loop through all folders, assigning a color for each folder,
	 * if we don't have sufficient colors, we have to start again from
	 * the start (This obviously means that some colors will appear twice).
	 * A color scheme is assigned for each folder entry id
	 * Note: This functionality has changed after colors became persistent
	 * Now assigning colors will only be done once for existing users that had
	 * calendars open. And it will assign a color for the default calendar
	 * folder for every new user.
	 * @private
	 */
	assignColors: function()
	{
		this.assigningColors = true;
		for (var i = 0, len = this.folders.length; i < len; i++) {
			// check if folder is already in mapping and add it if it isn't
			this.getColorScheme(this.folders[i].get('entryid'));
		}
		this.assigningColors = false;
	},

	/**
	 * Obtain color scheme for given folder entryid. Fall back to first scheme if folder not in mapping
	 * @param {String} folderId Entry id of a folder
	 * @return {Object} The color scheme as defined in {@link Zarafa.calendar.ui.ColorSchemes color scheme}
	 * for this folder
	 */
	getColorScheme: function(folderId)
	{
		// Remove 'favorites-' prefix before getting respective color scheme.
		folderId = folderId.replace('favorites-','');

		if (!this.colorMap) {
			return undefined;
		}

		var colorSchemeName = this.colorMap[folderId];
		var colorScheme = Zarafa.core.ColorSchemes.getColorScheme(colorSchemeName);
		if ( !Ext.isDefined(colorScheme) ){
			// No scheme defined yet. Let's just use a random color scheme.
			colorScheme = this.colorScheme[Math.floor(Math.random()*this.colorScheme.length)];
			this.setColorScheme(folderId, colorScheme);
		}

		return colorScheme;
	},

	/**
	 * Map a folder to a color scheme
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} Folder object to map
	 * @param {Object} Color scheme to map to
	 */
	setColorScheme: function(folderId, scheme)
	{
		this.colorMap[folderId] = scheme.name;

		if ( !this.assigningColors ){
			this.fireEvent('colormapchanged', this, this.colorMap);
		}
	},

	/**
	 * Obtain the currently active group out of this.groupings
	 */
	getActiveGroup: function()
	{
		return this.active_group;
	},

	/**
	 * Handler for 'foldergroupinchanged' event, which calls {@link #saveState} only
	 * when the groupings or active group has changed.
	 *
	 * @param {Zarafa.core.ContextModel} contextModel the contextModel which states needs to be saved.
	 * @param {Object} groupings The groupings object
	 * @param {String} active The active group
	 */
	saveFolderGroupinChanged: function(model, groupings, active)
	{
		if (this.groupings != groupings || active != this.active_group) {
			this.saveState();
		}
	},

	/**
	 * Register the {@link #stateEvents state events} to the {@link #saveState} callback function.
	 * @protected
	 */
	initStateEvents: function()
	{
		Zarafa.core.MultiFolderContextModel.superclass.initStateEvents.call(this);
		this.on('foldergroupingchanged', this.saveFolderGroupinChanged, this, { delay: 100 });
		// The colorMap is actually does not change when we switch to the calendar context,
		// therefore checking if it's changed is not required.
		this.on('colormapchanged', this.saveState, this, { delay: 100 });
	},

	/**
	 * When {@link #stateful} the State object which should be saved into the
	 * {@link Ext.state.Manager}.
	 * @return {Object} The state object
	 * @protected
	 */
	getState: function()
	{
		var state = Zarafa.core.MultiFolderContextModel.superclass.getState.call(this) || {};

		return Ext.apply(state, {
			groupings: this.groupings,
			active_group: this.active_group,
			colorMap: this.colorMap
		});
	},

	/**
	 * Apply the given state to this object activating the properties which were previously
	 * saved in {@link Ext.state.Manager}.
	 * @param {Object} state The state object
	 * @protected
	 */
	applyState: function(state)
	{
		if ( Ext.isDefined(state.groupings) ){
			if ( Array.isArray(state.groupings) ){
				// PHP has probably converted an empty object to an empty array since it doesn't know
				// the difference. But we need an object or things will go wrong!
				state.groupings = {};
			}
			// Check the active folders in the groups
			for ( var groupId in state.groupings ) {
				if ( state.groupings.hasOwnProperty(groupId) ){
					if ( state.groupings[groupId].folders.indexOf(state.groupings[groupId].active) < 0 ){
						// If the active folder was not found in the folders of the group, then we will
						// change the active folder to the first folder in the group
						state.groupings[groupId].active = state.groupings[groupId].folders[0];
					}
				}
			}
		}

		Zarafa.core.MultiFolderContextModel.superclass.applyState.call(this, state);
	},

	/**
	 * Reset all the grouping information
	 */
	resetGroupings: function()
	{
		this.groupings = {};
	}
});
Ext.namespace('Zarafa.core');

/**
 * @class Zarafa.core.PresencePlugin
 * @extends Zarafa.core.Plugin
 *
 * A {@link Zarafa.core.PresencePlugin PresencePlugin} is a special kind of plugin
 * that should implement a method to communicate presence information about
 * users to grommunio Web core. The core will then use this information to display
 * a presence indication in the UI.
 *
 * This class was intended to be overridden.
 */
Zarafa.core.PresencePlugin = Ext.extend(Zarafa.core.Plugin, {
	/**
	 * Presence plugins should overwrite this function and return an array with an
	 * entry for each of the users defines in userInfos. An entry should be one of
	 * the statuses defined in {@link Zarafa.core.data.PresenceStatus}
	 * @param {Zarafa.core.data.UserIdObject[]} users An array of users
	 * for whom a {@link Zarafa.core.data.PresenceStatus presence status} is
	 * requested,
	 * @return {Array} Array of {@link Zarafa.core.data.PresenceStatus}
	 */
	getPresenceStatuses: function(users) {
		// This function should be overwritten, but just in case we will return a
		// status unknown for all users
		return users.map(function() {
			return Zarafa.core.data.PresenceStatus.UNKNOWN;
		});
	}
});
Ext.namespace('Zarafa.core');

/**
 * This is the class that theme plugins must extend to be found
 * by the themes plugin.
 *
 * @class Zarafa.core.ThemePlugin
 * @extends Zarafa.core.Plugin
 *
 */
Zarafa.core.ThemePlugin = Ext.extend(Zarafa.core.Plugin, {
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		Zarafa.core.Plugin.superclass.constructor.call(this, config);

		// Only initialize the plugin when it is selected as running theme
		var theme = container.getServerConfig().getActiveTheme();
		if ( theme === this.getName() ){
			this.initPlugin();
		}
	}
});
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.DummyResponseHandler
 * @extends Zarafa.core.data.ProxyResponseHandler
 *
 * A special response handler to handle responses of requests that are already cancelled.
 * as a special requirement for cancelling requests we need to ignore the response coming for that
 * request and process notifications properly for that request (like new mail notifications).
 */
Zarafa.core.data.DummyResponseHandler = Ext.extend(Zarafa.core.data.ProxyResponseHandler, {
	/**
	 * The main handler to begin a Response processing transaction. Checks the whether the {@link #proxy}
	 * is defined and if not returns false to cancel all processing by this handler.
	 * @param {String} moduleName The name of the PHP module from which this response originated.
	 * @param {String} moduleId The unique identifier for the PHP response.
	 * @param {Object} data The entire response object which will be processed during this transaction.
	 * @param {Number} timestamp The {@link Date#getTime timestamp} on which the response was received
	 * @return {Boolean} False when the given data object cannot be handled by this response handler,
	 * and the transaction must be canceled.
	 * @override
	 */
	start: function(moduleName, moduleId, data, timestamp)
	{
		// Inform the proxy the response for a given request has been returned.
		if (this.proxy && Ext.isFunction(this.proxy.deleteRequestId)) {
			this.proxy.deleteRequestId(moduleId);
		}

		return false;
	}
});
/*
 * #dependsFile client/zarafa/core/mapi/ObjectType.js
 * #dependsFile client/zarafa/core/data/Record.js
 * #dependsFile client/zarafa/core/data/RecordFactory.js
 */
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.IPMAttachmentRecordFields
 *
 * Array of default fields for the {@link Zarafa.core.data.IPMAttachmentRecord} object.
 * These fields will always be added, regardless of the exact type of
 * {@link Zarafa.core.data.IPMAttachmentRecord record}.
 *
 * @FIXME attach_num is used to identify attachment number in the attachments table, only for saved attachments
 * for temporary uploaded attachments (not yet saved in attachments table), we use random string with file name as attach_num
 */
Zarafa.core.data.IPMAttachmentRecordFields = [
	{name: 'object_type', type: 'int', defaultValue: Zarafa.core.mapi.ObjectType.MAPI_ATTACH},
	{name: 'attach_num', type: 'int', defaultValue: -1},
	{name: 'attach_method', type: 'int', defaultValue: Zarafa.core.mapi.AttachMethod.NO_ATTACHMENT},
	{name: 'attach_message_class', type: 'string', defaultValue: 'IPM.Note'},
	{name: 'size', type: 'int', defaultValue: 0},
	{name: 'cid'},
	{name: 'attach_id'},
	{name: 'filetype'},
	{name: 'hidden', type: 'boolean', defaultValue: false},
	{name: 'name', type: 'string', defaultValue: _('Untitled')},
	{name: 'tmpname'},

	{name: 'attachment_contactphoto', type: 'boolean', defaultValue: false},	// rename to contactphoto ???
	// Properties that will only be used when opening embedded attachment from unsaved message
	{name: 'entryid'},
	{name: 'store_entryid'},
	{name: 'extension', type: 'string'}
];

/**
 * @class Zarafa.core.data.IPMAttachmentRecord
 * @extends Ext.data.Record
 */
Zarafa.core.data.IPMAttachmentRecord = Ext.extend(Ext.data.Record, {
	/**
	 * Indicates whether it has been attempted to upload this record.
	 * @property
	 * @type Boolean
	 */
	uploadAttempted: false,

	/**
	 * Indicates this attachment is an inline image for an HTML mail
	 * @property
	 * @type private
	 */
	inline: false,

	/**
	 * Holds request id of all attachment requests made to server.
	 * @property
	 * @type private
	 */
	requestId: undefined,

	/**
	 * Applies all data from an {@link Zarafa.core.data.IPMAttachmentRecord IPMAttachmentRecord}
	 * to this instance. This will update all data.
	 *
	 * @param {Zarafa.core.data.IPMAttachmentRecord} record The record to apply to this
	 * @return {Zarafa.core.data.IPMAttachmentRecord} this
	 */
	applyData: function(record)
	{
		this.beginEdit();

		Ext.apply(this.data, record.data);
		Ext.apply(this.modified, record.modified);

		this.inline = record.inline;
		this.uploadAttempted = record.uploadAttempted;
		this.dirty = record.dirty;

		this.endEdit(false);

		return this;
	},
	/**
	 * @param {Boolean} inline
	 */
	setInline: function(inline)
	{
		this.inline = inline;
	},

	/**
	 * @return {Boolean} True if this attachment is uploaded or otherwise
	 * present on the server
	 */
	isUploaded: function()
	{
		return !Ext.isEmpty(this.get('tmpname')) || !this.isTmpFile();
	},

	/**
	 * @return {Boolean} True if this attachment is a temporary file.
	 */
	isTmpFile: function()
	{
		return this.get('attach_num') <= -1;
	},

	/**
	 * @return {Boolean} True if the attachment is inline
	 */
	isInline: function()
	{
		return this.inline;
	},

	/**
	 * @return {Boolean} True if the attachment is hidden
	 */
	isHidden: function()
	{
		return this.get('hidden');
	},

	/**
	 * @return {Boolean} True if the attachment is an exception to a recurring appointment
	 */
	isRecurrenceException: function()
	{
		return this.isHidden() && Zarafa.core.MessageClass.isClass(this.get('attach_message_class'), 'IPM.OLE.CLASS.{00061055-0000-0000-C000-000000000046}');
	},

	/**
	 * @return {Boolean} True if the attachment is contact photo.
	 */
	isContactPhoto: function()
	{
		return this.get('attachment_contactphoto');
	},

	/**
	 * Checks if {@link Zarafa.core.data.IPMAttachmentRecord IPMAttachmentRecord} is embedded message
	 * @return {Boolean} true if attachment record is an embedded message
	 */
	isEmbeddedMessage: function()
	{
		return this.get('attach_method') === Zarafa.core.mapi.AttachMethod.ATTACH_EMBEDDED_MSG && !this.isRecurrenceException();
	},

	/**
	 * @return {Boolean} True if this attachment can be imported, false otherwise.
	 */
	canBeImported: function()
	{
		var isSupportedExtension = false;
		if (this.isEmbeddedMessage()) {
			isSupportedExtension = Zarafa.core.MessageClass.isClass(this.get('attach_message_class'), ['IPM.Note', 'IPM.Contact', 'IPM.TaskRequest', 'IPM.Schedule.Meeting.Resp', 'IPM.Schedule.Meeting.Request' ,'REPORT.IPM.Note'], true);
		} else {
			isSupportedExtension = this.isEmlAttachment() || this.isVCFAttachment() || this.isICSAttachment();
		}

		return container.getServerConfig().isVCfImportSupported() && isSupportedExtension;
	},

	/**
	 * Function used to check that attachment is ICS or VCS file or not.
	 *
	 * @return {boolean} true if attachment is ICS Or VCS else false
	 */
	isICSAttachment: function()
	{
		var fileExtension = this.get('extension');
		if (!Ext.isEmpty(fileExtension)) {
			fileExtension = fileExtension.split(".").pop().toLowerCase();
			return fileExtension === 'ics' || fileExtension === 'vcs';
		}

		return this.get('type') === 'text/calendar';
	},

	/**
	 * Function used to check that attachment is EML file or not.
	 *
	 * @return {boolean} true if attachment is EML else false
	 */
	isEmlAttachment: function()
	{
		var fileExtension = this.get('extension').toLowerCase();
		if (!Ext.isEmpty(fileExtension)) {
			return fileExtension === 'eml';
		}

		return this.get('type') === 'application/octet-stream';
	},

	/**
	 * Function used to check that attachment is VCF file or not.
	 *
	 * @return {boolean} true if attachment is VCF else false
	 */
	isVCFAttachment: function()
	{
		var fileExtension = this.get('extension').toLowerCase();
		if (!Ext.isEmpty(fileExtension)) {
			return fileExtension === 'vcf';
		}

		return this.get('type') === 'text/vcard';
	},

	/**
	 * @return {Ext.data.Store} data store this record belongs to.
	 */
	getStore: function()
	{
		return this.store;
	},

	/**
	 * Builds and returns inline image URL to download inline images,
	 * it uses {@link Zarafa.core.data.IPMRecord IPMRecord} to get store and message entryids.
	 * @return {String} URL for downloading inline images.
	 */
	getInlineImageUrl: function()
	{
		return this.store.getInlineImageUrl(this);
	},

	/**
	 * Builds and returns attachment URL to download attachment,
	 * it uses {@link Zarafa.core.data.IPMRecord IPMRecord} to get store and message entryids.
	 * @param {Boolean} allAsZip (optional) True to downloading all the attachments as ZIP
	 * @return {String} URL for downloading attachment.
	 */
	getAttachmentUrl: function(allAsZip)
	{
		return this.store.getAttachmentUrl(this, allAsZip);
	},

	/**
	 * Returns attach_num property of all the parents of this record.
	 * @param {Zarafa.core.data.IPMRecord} parentRecord parent record that holds this attachment
	 * @return {Array} attach_num of parent
	 */
	getParentAttachNum: function(parentRecord)
	{
		if(!parentRecord) {
			parentRecord = this.store.getParentRecord();
		}

		var attachNum = parentRecord.get('attach_num');

		// check if attachNum is not -1, because if it is then we are opening an embedded message from unsaved message
		// so we need to break chain after getting topmost parent object
		return !Ext.isEmpty(attachNum) && attachNum[0] !== -1 ? [].concat(attachNum) : [];
	},

	/**
	 * @return {Boolean} True if it was attempted to upload this attachment
	 */
	attemptedToUpload: function()
	{
		return this.uploadAttempted;
	},

	/**
	 * Convert this attachment into a {@link Zarafa.core.data.IPMRecord record}.
	 *
	 * @return {Zarafa.core.data.IPMRecord} The IPMRecord which is an embedded message as attachment.
	 */
	convertToIPMRecord: function()
	{
		var messageClass = this.get('attach_message_class');

		if(Ext.isEmpty(messageClass)) {
			// no messageclass then we can't do anything
			return;
		}

		if(!this.isTmpFile()) {
			// Embedded attachment is saved in message so use attach_num and entryids of parent record to open the record
			var parentRecord = this.store.getParentRecord();
			var parentAttachNum = [];

			// Don't go for attachNum property of parent in case the parent record is an exception of meeting request.
			// Because, exception of meeting request it self is an attachment.
			if(!Ext.isFunction(parentRecord.isRecurringException) || (Ext.isFunction(parentRecord.isRecurringException) && !parentRecord.isRecurringException())){
				parentAttachNum = this.getParentAttachNum(parentRecord);
			}

			// always pass attach_num as an array
			var attachNum = parentAttachNum.concat(this.get('attach_num'));
			var parentRecordEntryId = parentRecord.get('entryid');

			if (Ext.isEmpty(parentRecordEntryId) && parentRecord.hasMessageAction('source_entryid')) {
				parentRecordEntryId = parentRecord.getMessageAction('source_entryid');
			}

			return Zarafa.core.data.RecordFactory.createRecordObjectByMessageClass(messageClass, {
				message_class: messageClass,
				object_type: Zarafa.core.mapi.ObjectType.MAPI_MESSAGE,
				attach_num: attachNum,

				// pass entryids of parent record
				entryid: parentRecordEntryId,
				store_entryid: parentRecord.get('store_entryid')
			}, attachNum.join('') + parentRecordEntryId);
		} else {
			// Embedded attachment is not saved yet in message, so we can use entryids from the attachment record itself to open the message
			var originalRecordEntryId = this.get('entryid');
			// HACK ALERT!!!
			// parent message is not saved so attachment is also not saved so we don't have attach_num value yet
			// but its important to make sure that this message will be treated as sub message so add -1 as attach_num
			// (-1 will come from default value of field)
			var attachNum = [].concat(this.get('attach_num'));

			return Zarafa.core.data.RecordFactory.createRecordObjectByMessageClass(messageClass, {
				message_class: messageClass,
				object_type: Zarafa.core.mapi.ObjectType.MAPI_MESSAGE,
				attach_num: attachNum,

				// pass entryids of original record from which this embedded attachment record has been made
				entryid: originalRecordEntryId,
				store_entryid: this.get('store_entryid')
			}, attachNum.join('') + originalRecordEntryId);
		}
	}
});

Zarafa.core.data.RecordFactory.setBaseClassToObjectType(Zarafa.core.mapi.ObjectType.MAPI_ATTACH, Zarafa.core.data.IPMAttachmentRecord);
Zarafa.core.data.RecordFactory.addFieldToObjectType(Zarafa.core.mapi.ObjectType.MAPI_ATTACH, Zarafa.core.data.IPMAttachmentRecordFields);
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.IPMAttachmentResponseHandler
 * @extends Zarafa.core.data.ProxyResponseHandler
 *
 * A special response handler for the {@link Zarafa.core.data.IPMAttachmentProxy Attachment Proxy}
 * which can handle responses from the 'upload_attachments' server instruction.
 */
Zarafa.core.data.IPMAttachmentResponseHandler = Ext.extend(Zarafa.core.data.ProxyResponseHandler, {
	/**
	 * Handles the 'update' response. This will check if the item in the response is
	 * inside the {@link #sendRecords} and will convert the Response data into the
	 * updated {@link Ext.data.Record record} using {@link #correlateRecordFromResponse},
	 * and pushes them into the {@link #receivedRecords records list}.
	 * @param {Object} response The response object belonging to the given command.
	 * @return {Boolean} False when action could not be handled successfully. This will
	 * not cancel the transaction itself, but rather causes the 'success' argument for the
	 * {@link #done} function to be false.
	 */
	doUpdate: function(response)
	{
		this.receivedRecords = this.receivedRecords.concat(this.correlateRecordFromResponse(response));
	},

	/**
	 * Used by {@link #correlateRecordFromResponse} to determine if the given item data
	 * matches the record which was send to the server. This is used to determine if the
	 * given data can be applied to the record.
	 * @param {Object} data The response data
	 * @param {Zarafa.core.data.MAPIRecord} record The record which was send to the server
	 * @private
	 */
	compareResponseDataToRecord: function(data, record)
	{
		return data.props.name.localeCompare(record.get('name')) === 0;
	}
});
Ext.namespace('Zarafa.core.data');
/**
 * #dependsFile client/zarafa/core/mapi/ObjectType.js
 */

/**
 * @class Zarafa.core.data.IPMAttachmentStore
 * @extends Zarafa.core.data.MAPISubStore
 */
Zarafa.core.data.IPMAttachmentStore = Ext.extend(Zarafa.core.data.MAPISubStore, {
	/**
	 * @cfg {String} id ID to communicate with the server for the location of the cached attachments.
	 */
	id: null,

	/**
	 * @cfg {Zarafa.core.ObjectType} attachmentRecordType to create a custom {Zarafa.core.data.IPMAttachmentRecord}
	 */
	attachmentRecordType: Zarafa.core.mapi.ObjectType.MAPI_ATTACH,

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

		Ext.applyIf(config, {
			id: Zarafa.generateId(32),
			// provide a default proxy
			proxy: new Zarafa.core.data.IPMAttachmentProxy({
				listModuleName: 'attachments',
				itemModuleName: 'attachments'
			}),
			// provide a default writer
			writer: new Zarafa.core.data.JsonAttachmentWriter(),
			// provide a default reader
			reader: new Zarafa.core.data.JsonAttachmentReader({}, Zarafa.core.data.RecordFactory.getRecordClassByCustomType(this.attachmentRecordType))
		});

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

		// Update the 'hasattach' property whenever the store changes
		this.on({
			'update': this.onAttachmentsChange,
			'add': this.onAttachmentsChange,
			'remove': this.onAttachmentsChange,
			'datachanged': this.onAttachmentsChange,
			'scope': this
		});

		// add the custom events of the Attachmentstore to the EventDispatcher
		Zarafa.core.data.ZarafaCustomEventDispatcher.addEvents(
			'attachmentstorebeforecheck',
			'attachmentstorebeforeupload',
			'attachmentstorebeforedestroyrecord',
			'attachmentstorebeforegetbaseurl'
		);
	},

	/**
	 * Event handler which is fired when data in this store has changed.
	 *
	 * This will update hasattach property of parentrecord
	 * If there are any attachments in store then,
	 * it will set hasattach property of record to true,
	 * otherwise it will set it to false.
	 *
	 * @param {Store} store
	 * @param {Ext.data.Record/Ext.data.Record[]} record
	 * @param {Number} index
	 * @private
	 */
	onAttachmentsChange: function()
	{
		if(this.getParentRecord()){
			if(this.getCount() > 0) {
				this.getParentRecord().set('hasattach', true);
			} else {
				this.getParentRecord().set('hasattach', false);
			}
		}
	},

	/**
	 * Obtain the ID to communicate with the
	 * server for the location of cached attachments
	 * @return {String} The ID of the server attachment location
	 */
	getId: function()
	{
		return this.id;
	},

	/**
	 * Set the ID to communicate with the server
	 * for the location of cached attachments
	 * @param {String} id The ID of the server attachment location
	 */
	setId: function(id)
	{
		this.id = id;
	},

	/**
	 * Builds and returns inline image URL to download inline images,
	 * it uses {@link Zarafa.core.data.IPMRecord IPMRecord} to get store and message entryids.
	 * @param {Zarafa.core.data.IPMAttachmentRecord} attachmentRecord Attachment record.
	 * @return {String} URL for downloading inline images.
	 */
	getInlineImageUrl: function(attachmentRecord)
	{
		var url = this.getDownloadAttachmentUrl(attachmentRecord);
		return Ext.urlAppend(url, 'contentDispositionType=inline');
	},

	/**
	 * Builds and returns attachment URL to download attachment,
	 * it uses {@link Zarafa.core.data.IPMRecord IPMRecord} to get store and message entryids.
	 * @param {Zarafa.core.data.IPMAttachmentRecord} attachmentRecord Attachment record.
	 * @param {Boolean} allAsZip (optional) True to downloading all the attachments as ZIP
	 * @return {String} URL for downloading attachment.
	 */
	getAttachmentUrl: function(attachmentRecord, allAsZip)
	{
		var url = this.getDownloadAttachmentUrl(attachmentRecord, allAsZip);
		return Ext.urlAppend(url, 'contentDispositionType=attachment');
	},

	/**
	 * Builds and returns attachment URL to download inline images/attachments,
	 * it uses {@link Zarafa.core.data.IPMRecord IPMRecord} to get store and message entryids.
	 * @param {Zarafa.core.data.IPMAttachmentRecord} attachmentRecord Attachment record.
	 * @param {Boolean} allAsZip (optional) True to downloading all the attachments as ZIP
	 * @return {String} URL for downloading attachments.
	 */
	getDownloadAttachmentUrl: function(attachmentRecord, allAsZip)
	{
		var parentRecord = this.getParentRecord();
		var isSubMessage = parentRecord.isSubMessage();

		var url = this.getAttachmentBaseUrl();
		url = Ext.urlAppend(url, 'load=download_attachment');

		// If all the attachments are requested to be downloaded in a ZIP, then there is no need to
		// send 'attach_num' property.
		if(!allAsZip || (allAsZip && isSubMessage)){

			var attachNum = [];
			// Don't go for attachNum property of parent in case the parent record is an exception of meeting request.
			// Because, exception of meeting request it self is an attachment.
			if(!Ext.isFunction(parentRecord.isRecurringException) || (Ext.isFunction(parentRecord.isRecurringException) && !parentRecord.isRecurringException())) {
				attachNum = attachmentRecord.getParentAttachNum(parentRecord);
			}

			if (attachmentRecord.get('attach_num') != -1) {
				// add attachment number of parent sub messages
				attachNum.push(attachmentRecord.get('attach_num'));

				// Encode each attachment number so special characters in temp names survive the query string
				for(var index = 0, len = attachNum.length; index < len; index++) {
					url = Ext.urlAppend(url, 'attachNum[]=' + encodeURIComponent(attachNum[index]));
				}
			} else {
				url = Ext.urlAppend(url, 'attachNum[]=' + encodeURIComponent(attachmentRecord.get('tmpname')));
			}
		}

		if(allAsZip) {
			url = Ext.urlAppend(url, 'AllAsZip=true');
			if(isSubMessage){
				url = Ext.urlAppend(url, 'isSubMessage=true');
			}
			url = Ext.urlAppend(url, 'subject=' + encodeURIComponent(parentRecord.get('subject') || ''));
		}

		// fire the 'attachmentstorebeforegetbaseurl' event.
		var eventData = {
			url: url
		};
		Zarafa.core.data.ZarafaCustomEventDispatcher.fireEvent('attachmentstorebeforegetbaseurl', this, attachmentRecord, allAsZip, eventData);

		return eventData.url;
	},

	/**
	 * Function returns parent/source record entryid and store entryid for the attachment.
	 * If parent record has an entryid then return that entryid and respective store entryid.
	 * For phantom records i.e. forward mail, parent record won't have entryid so get
	 * source_entryid and source_store_entryid from record's message action data and return it.
	 * @return {Object} The object which container parent/source message entry id and store entryid.
	 * @private
	 */
	getAttachmentParentRecordIds: function()
	{
		var parentRecord = this.getParentRecord();
		var entryID = parentRecord.get('entryid') || '';
		var storeEntryId = parentRecord.get('store_entryid') || '';
		var messageAction = parentRecord.getMessageActions();

		if (Ext.isEmpty(entryID) && messageAction) {
			switch(messageAction.action_type) {
				case 'forward':
				case 'edit_as_new':
					entryID = messageAction.source_entryid;
					storeEntryId = messageAction.source_store_entryid;
					break;
				case 'reply':
				case 'replyall':
				// @TODO: Need to check for inline images, whether we
				// should pass entryid or any need in future.
			}
		}

		return {
			'entryid': entryID,
			'store_entryid': storeEntryId
		};
	},

	/**
	 * Function returns parent record entry id for the attachment.
	 * @return {String} entryid parent record entryid.
	 * @private
	 */
	getAttachmentParentRecordEntryId: function()
	{
		// Function is used by the Files plugin.
		return this.getAttachmentParentRecordIds().entryid;
	},

	/**
	 * Builds and returns base attachment URL.
	 * @return {String} URL for attachments
	 * @private
	 */
	getAttachmentBaseUrl: function()
	{
		var url = container.getBaseURL();
		var source = this.getAttachmentParentRecordIds();
		url = Ext.urlAppend(url, 'store=' + source.store_entryid);
		url = Ext.urlAppend(url, 'entryid=' + source.entryid);
		url = Ext.urlAppend(url, 'dialog_attachments=' + this.getId());
		return url;
	},

	/**
	 * Builds and returns attachment URL to upload files.
	 * @return {String} URL for uploading attachments
	 * @private
	 */
	getUploadAttachmentUrl: function()
	{
		return Ext.urlAppend(this.getAttachmentBaseUrl(), 'load=upload_attachment');
	},

	/**
	 * Builds and returns attachment-URL to import attachments.
	 * @param {Zarafa.core.data.IPMAttachmentRecord} attachmentRecord The attachment to import into given folder
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder The selected folder
	 * @return {String} URL for importing attachments to respective folder
	 * @private
	 */
	getImportAttachmentUrl: function(attachmentRecord, folder)
	{
		var url = this.getDownloadAttachmentUrl(attachmentRecord);
		var isEmbedded = attachmentRecord.isEmbeddedMessage();
		url = Ext.urlAppend(url, 'import=true');
		url = Ext.urlAppend(url, 'destination_folder=' + folder.get('entryid'));

		if (isEmbedded) {
			url = Ext.urlAppend(url, 'is_embedded=' + isEmbedded);
		}

		return url;
	},

	/**
	 * Check if the given files can be added to this store, this is
	 * only possible when none of the limits from the {@link Zarafa.core.data.ServerConfig ServerConfig}
	 * configuration have been exceeded.
	 *
	 * @param {FileList|Array} files The array of Files objects from the file input field.
	 * @param {Object} options (optional) Additional parameters for the call to
	 * {@link Zarafa.core.ui.notifier.Notifier#notify} if one of the limits was
	 * exceeded.
	 * @return {Boolean} True if the files can be uploaded, false otherwise
	 * @private
	 */
	canUploadFiles: function(files, options)
	{
		// If there are no files we can exit. Ext.isEmpty()
		// checks if files is null or undefined. If files is
		// a FileList, then Ext.isEmpty() will not perform
		// a proper check for the length of the list.
		if (Ext.isEmpty(files) || files.length === 0) {
			return false;
		}

		// fire the 'beforeuploadcheck' event.
		var eventData = {
			handledfiles: [],
			checkfailed: false
		};
		Zarafa.core.data.ZarafaCustomEventDispatcher.fireEvent('attachmentstorebeforecheck', this, files, options, eventData);

		// do a fast check if one of the plugins failed checking the files
		// error handling (notifying the used for example is handled by the plugin)
		if(eventData.checkfailed) {
			return false;
		}

		var server = container.getServerConfig();
		// Maximum number of attachments that can be uploaded in message.
		var max_attachments = server.getMaxAttachments();
		// Maximum size of single file that can be uploaded in message.
		var max_attachment_size = server.getMaxAttachmentSize();
		// Maximum size of attachments that can be uploaded in message.
		var max_attachment_total_size = server.getMaxAttachmentTotalSize();

		/**
		 * Here all three checks are regarding maximum upload attachments in message.
		 *
		 * first check is check's that maximum number of attachments are possible to attach in single message.
		 * by default there is no limit for number of attachments in message.
		 *
		 * second check is check's that each attachment should not be more than 30 MB size.
		 *
		 * third check is check's that total maximum attachment size in single message.
		 * by default there is no limit for maximum attachments in message.
		 */
		// 1) Check for the total number of attachments
		if (Ext.isDefined(max_attachments)) {
			if ((this.getCount() + files.length - eventData.handledfiles.length) >= max_attachments) {
				container.getNotifier().notify('error.attachment', _('Attachment error'),
					String.format(_('Cannot upload attachment, only {0} attachments are allowed to be added to the message'), max_attachments),
					options);
				return false;
			}
		}

		// total Post request size in single request.
		var totalPostSize = 0;
		var totalSize = this.sum('size');

		for (var i = 0; i < files.length; i++) {
			var file = files[i];
			var fileSize = file.fileSize || file.size;

			// check if file was already handled by any other plugin
			if(eventData.handledfiles.indexOf(file) !== -1) {
				continue; // skip already handled files
			}

			// Update totalPostSize
			totalPostSize += fileSize;

			// Update totalSize
			totalSize += fileSize;

			// 2) Check if the size exceeds the maximum upload size
			if (Ext.isDefined(max_attachment_size)) {
				if (fileSize > max_attachment_size) {
					container.getNotifier().notify('error.attachment', _('Attachment error'),
						String.format(_('Cannot upload attachment, attachment is {0} while the allowed maximum is {1}.'),
							Ext.util.Format.fileSize(fileSize), Ext.util.Format.fileSize(max_attachment_size)),
						options);
					return false;
				}
			}
		}

		// 3) Check if the size exceeds the total maximum attachment size
		if (Ext.isDefined(max_attachment_total_size)) {
			if (totalSize > max_attachment_total_size) {
				container.getNotifier().notify('error.attachment', _('Attachment error'),
					String.format(_('Cannot upload attachment, the total attachment size is {0} while the allowed maximum is {1}'),
						Ext.util.Format.fileSize(totalSize), Ext.util.Format.fileSize(max_attachment_total_size)),
					options);
				return false;
			}
		}

		// Maximum number of files that can be uploaded in a single request.
		var max_file_uploads = server.getMaxFileUploads();
		// Maximum size of post request that can be send in a single request.
		var max_post_size = server.getMaxPostRequestSize();

		/**
		 * Here both the checks are for post request size in single message,
		 *
		 * first check is check that user can not
		 * able to send post request more then 31 MB size in single request.
		 * max_post_size has default configuration in php ini(Apache) settings.
		 *
		 * second check is check that user can not able to send post request
		 * more then 20 files in single request. max_file_uploads has default
		 * configuration in php ini (Apache) settings.
		 */
		// 1) Check if the size exceeds the maximum post size.
		if(Ext.isDefined(max_post_size)) {
			if (totalPostSize > max_post_size) {
				container.getNotifier().notify('error.attachment', _('Attachment error'),
					String.format(_('Cannot upload attachment, total attachment is {0} while the allowed maximum attachment in single request is {1}.'),
						Ext.util.Format.fileSize(totalPostSize), Ext.util.Format.fileSize(max_post_size)),
					options);
				return false;
			}
		}

		var totalFilesUploads = files.length;
		// 2) check if the maximum file uploads in single request.
		if(Ext.isDefined(max_file_uploads)) {
			if (totalFilesUploads > max_file_uploads) {
				container.getNotifier().notify('error.attachment', _('Attachment error'),
					String.format(_('Cannot upload attachment, total attachments are {0} files while the maximum {1} files are allowed in single request.'),
						totalFilesUploads , max_file_uploads),
					options);
				return false;
			}
		}

		return true;
	},

	/**
	 * Upload the given files to the server, this will generate new
	 * {@link Zarafa.core.data.IPMAttachmentRecord attachment records} and
	 * add those to the store.
	 * @param {FileList/String} files The array of file objects to upload
	 * @param {Ext.form.BasicForm} form (optional) If only filenames were provided in files
	 * argument, then this form must be the form which contains all file input elements
	 * @param {Boolean} isHidden if isHidden is true it will hide the attachments.
	 * @param {Object} params (optional) the params which contains source type of the attachment,
	 * i.e 'contactphoto', 'embedded' attachment or file attachment 'default'
	 */
	uploadFiles: function(files, form, isHidden, params)
	{
		var attachments = [];
		var uploadFiles = [];

		// For a proper instance check demanded by some of the browser, we must have to
		// use relevant FileList object which belongs to respective browser window.
		var activeBrowserWindow = Zarafa.core.BrowserWindowMgr.getActive() || window;
		var isFileList = files instanceof activeBrowserWindow.FileList;

		// fire the 'attachmentstorebeforeupload' event.
		// the eventhandler can not directly remove the handled files from the "files" FileList as this property is read
		// only. So we have to check the eventData for handled files.
		var eventData = {
			handledfiles: []
		};
		Zarafa.core.data.ZarafaCustomEventDispatcher.fireEvent('attachmentstorebeforeupload', this, files, form, isHidden, params, eventData);

		for (var i = 0; i < files.length; i++) {
			var file = files[i];
			var originalName = file.name;

			if (!(Ext.isIE || Ext.isEdge)) {
				file = this.cloneFile(file);
			}

			// check if file was already handled by any other plugin
			if(eventData.handledfiles.indexOf(file) !== -1) {
				continue; // skip already handled files
			}

			var attach;
			if (isFileList) {
				// add the file to the upload parameters
				uploadFiles.push(file);

				// Create an attachment record, with all the passed data, use record factory so default values will be applied
				attach = Zarafa.core.data.RecordFactory.createRecordObjectByCustomType(this.attachmentRecordType, {
					'name': originalName,
					'size': file['size'],
					'filetype': file['type'],
					'hidden': Ext.isDefined(isHidden) ? isHidden : false,
					'attach_method': Zarafa.core.mapi.AttachMethod.ATTACH_BY_VALUE,
					'attach_id': file['attach_id']
				});
			} else {
				// If the file is a String, it is the file name including a fake path
				// ('C:\fakepath\<filename>') which must be stripped of its silly path.
				attach = Zarafa.core.data.RecordFactory.createRecordObjectByCustomType(this.attachmentRecordType, {
					'name': Ext.util.Format.basename(file),
					'hidden': Ext.isDefined(isHidden) ? isHidden : false
				});
			}

			attach.file = file;

			attachments.push(attach);
		}

		// Add it to the store
		this.add(attachments);

		// Prepare the upload
		var data = {};

		// Add source type if attachment record is contact photo.
		data = Ext.apply(data, params);

		// If we received a FileList we can should provide
		// the files into the data so it can be added to the request.
		// Otherwise we expect the form object to hold the files.
		if (isFileList) {
			data['attachments'] = uploadFiles;
		}

		var options = {
			'params': data,
			'requestUrl': this.getUploadAttachmentUrl(),
			'requestForm': form
		};

		if (this.batch) {
			this.addToBatch(++this.batchCounter);
		}

		// if the user has only uploaded files that where handled by external plugins then this array is empty
		if(attachments.length > 0) {
			var action = Ext.data.Api.actions['create'];
			var callback = this.createCallback(action, attachments, false);
			this.proxy.request(action, attachments, options.params, this.reader, callback, this, options);
		}
	},

	/**
	 * Clone given file to postfix an eight digit unique id to filename.
	 * This particular unique id will then extracted on server side and act as attach_id for unsaved attachments.
	 * @param {File} record File which needs to be cloned to pass uid postfixed to filename
	 * @return {File} Cloned file.
	 */
	cloneFile: function(file)
	{
		var postfixId = Zarafa.generateId(8);
		// create a Blob object from existing file
		var blob = [file.slice(0, file.size, file.type)];
		var postfixedName = file.name + postfixId;
		var clonedFile = new File(blob, postfixedName, {type: file.type});
		clonedFile.attach_id = postfixId;

		return clonedFile;
	},


	/**
	 * Wrapper function which call either {@link #addIcsAsAttachment} or {@link #addEmbeddedAttachment}.
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record which needs to be added as an attachment into store.
	 * @param {Object} config (optional) The configuration object
	 */
	addAsAttachment: function(record, config)
	{
		var data = [];
		if(Ext.isDefined(config) && config.attachAsIcs) {
			data = this.addIcsAsAttachment(record);
		} else {
			data = this.addEmbeddedAttachment(record);
		}

		// Create an attachment record, with all the passed data, use record factory so default values will be applied
		var attach = Zarafa.core.data.RecordFactory.createRecordObjectByCustomType(Zarafa.core.mapi.ObjectType.MAPI_ATTACH, data);

		// Add it to the store
		this.add(attach);

		var options = {
			'params': attach.data,
			'requestUrl': this.getUploadAttachmentUrl()
		};

		var action = Ext.data.Api.actions['create'];
		var callback = this.createCallback(action, attach, false);
		this.proxy.request(action, attach, options.params, this.reader, callback, this, options);
	},

	/**
	 * Add given {@link Zarafa.core.data.IPMRecord IPMRecord} as embedded attachment to
	 * {@link Zarafa.core.data.IPMAttachmentStore IPMAttachmentStore}, this will generate new
	 * {@link Zarafa.core.data.IPMAttachmentRecord attachment record} and add those to the store and
	 * will send request to server to save ics attachment info to state file.
	 * @param {Zarafa.core.data.IPMRecord} record The record which needs to be added as ics attachment into store.
	 *
	 * @return data object which is used to create attachment record.
	 */
	addIcsAsAttachment: function(record)
	{
		var data = {
			'entryid': record.get('entryid'),
			'store_entryid': record.get('store_entryid'),
			// attach method to indicate this is an embedded attachment
			'attach_method': Zarafa.core.mapi.AttachMethod.ATTACH_BY_VALUE,
			'attach_id': Zarafa.generateId(8)
		};

		// Some optional data which should be present only if it is not empty
		if(!Ext.isEmpty(record.get('subject'))) {
			data['name'] = record.get('subject') + ".ics";
		}

		data['extension'] = "ics";

		if(!Ext.isEmpty(record.get('message_class'))) {
			data['attach_message_class'] = record.get('message_class');
		}

		if(!Ext.isEmpty(record.get('message_size'))) {
			data['size'] = record.get('message_size');
		}

		return data;
	},

 	/**
	 * Add given {@link Zarafa.core.data.IPMRecord IPMRecord} as embedded attachment to {@link Zarafa.core.data.IPMAttachmentStore IPMAttachmentStore}, this will generate new
	 * {@link Zarafa.core.data.IPMAttachmentRecord attachment record} and
	 * add those to the store and will send request to server to save embedded attachment info to state file.
	 * @param {Zarafa.core.data.IPMRecord} record The record which needs to be added as embedded attachment into store.
	 *
	 * @return data object which is used to create attachment record.
	 */
	addEmbeddedAttachment: function(record)
	{
		var data = {
			'entryid': record.get('entryid'),
			'store_entryid': record.get('store_entryid'),
			// attach method to indicate this is an embedded attachment
			'attach_method': Zarafa.core.mapi.AttachMethod.ATTACH_EMBEDDED_MSG,
			'attach_id': Zarafa.generateId(8)
		};

		// Some optional data which should be present only if it is not empty
		if(!Ext.isEmpty(record.get('subject'))) {
			data['name'] = record.get('subject');
		}

		if(!Ext.isEmpty(record.get('message_class'))) {
			data['attach_message_class'] = record.get('message_class');
		}

		if(!Ext.isEmpty(record.get('message_size'))) {
			data['size'] = record.get('message_size');
		}

		return data;
	},

	/**
	 * 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)
	{
		Zarafa.core.data.IPMAttachmentStore.superclass.destroyRecord.apply(this, arguments);

		var data = {};

		if (!record.isUploaded()) {
			data['deleteattachment'] = true;
			data['attach_id'] = record.get('attach_id');
		} else if (record.isTmpFile()) {
			data['deleteattachment'] = true;
			data['attach_num'] = record.get('tmpname');
		} else {
			data['deleteattachment'] = true;
			data['attach_num'] = record.get('attach_num');
		}

		var options = {
			'params': data,
			'requestUrl': this.getUploadAttachmentUrl()
		};

		// fire the 'attachmentstorebeforedestroyrecord' event that can change the event options.
		var eventData = {
			options: options
		};
		Zarafa.core.data.ZarafaCustomEventDispatcher.fireEvent('attachmentstorebeforedestroyrecord', this, record, eventData);

		var action = Ext.data.Api.actions['destroy'];
		var callback = this.createCallback(action, record, false);
		this.proxy.request(action, record, options.params, this.reader, callback, this, eventData.options);
	},

	/**
	 * Imports an attachment into given folder.
	 * @param {Zarafa.core.data.IPMAttachmentRecord} attachmentRecord The attachment to import into given folder
	 * @param {Zarafa.core.data.IPMRecord IPMRecord} message The record to which given attachment belongs
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder The selected folder
	 * @private
	 */
	importRecord: function(attachmentRecord, message, folder)
	{
		var options = {
			'params': {},
			'requestUrl': this.getImportAttachmentUrl(attachmentRecord, folder)
		};

		var action = 'import';
		var callback = this.createCallback(action, folder, false);
		this.proxy.request(action, message, options.params, this.reader, callback, this, options);
	},

	/**
	 * Possibly temporary until Ext framework has an exception-handler.
	 * See {@link Ext.data.Store}.
	 * @protected
	 */
	handleException: Ext.data.Store.prototype.handleException,

	/**
	 * callback-handler for remote CRUD actions.
	 * Do not override -- override loadRecords, onCreateRecords, onDestroyRecords and onUpdateRecords instead.
	 * See {@link Ext.data.Store}.
	 * @private
	 */
	createCallback: Ext.data.Store.prototype.createCallback,

	/**
	 * Proxy callback for create action.
	 * Callback function as created by {@Link #createCallback}.
	 * See {@link Ext.data.Store}.
	 * @protected
	 */
	onCreateRecords: Ext.data.Store.prototype.onCreateRecords,

	/**
	 * Proxy callback for destroy action
	 * Callback function as created by {@Link #createCallback}.
	 * See {@link Ext.data.Store}.
	 * @protected
	 */
	onDestroyRecords: Ext.data.Store.prototype.onDestroyRecords,

	/**
	 * remap record ids in MixedCollection after records have been realized. See {@link Ext.dataStore#onCreateRecords}, and
	 * {@link Ext.data.DataReader#realize}
	 * @private
	 */
	reMap: Ext.data.Store.prototype.reMap,

	/**
	 * Add request to batch
	 * See {@link Ext.data.Store}.
	 * @private
	 */
	addToBatch: Ext.emptyFn,

	/**
	 * Remove request from batch
	 * See {@link Ext.data.Store}.
	 * @private
	 */
	removeFromBatch: Ext.emptyFn,

	/**
	 * Proxy callback for import action
	 * Callback function as created by {@Link #createCallback}.
	 * See {@link Ext.data.Store}.
	 * @protected
	 */
	onImportRecords: function(success, rs, data)
	{
		if (success === true) {
			container.getNotifier().notify('info.import', _('Import'), String.format(_('Successfully imported item(s) to {0}'), rs.getFullyQualifiedDisplayName()));
		} else {
			container.getNotifier().notify('info.import', _('Import'), String.format(_('Failed to import item(s) to {0}'), rs.getFullyQualifiedDisplayName()));
		}
	}
});
/*
 * #dependsFile client/zarafa/core/mapi/ObjectType.js
 */
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.IPMExpandDistlistRecord
 * @extends Ext.data.Record
 *
 * Contains a description of what a single member of expanded distribution item looks like.
 * Is used by the JSON reader in the {@link Zarafa.core.data.IPMRecipientStore#expandReader}.
 */
Zarafa.core.data.IPMExpandDistlistRecord = Ext.data.Record.create([
	{name: 'display_name'},
	{name: 'address_type'},
	{name: 'smtp_address'},
	{name: 'email_address'},
	{name: 'entryid'},
	{name: 'search_key'},
	{name: 'object_type', type: 'int'},
	{name: 'display_type', type: 'int'},
	{name: 'display_type_ex', type: 'int'}
]);

Zarafa.core.data.IPMExpandDistlistRecord = Ext.extend(Zarafa.core.data.IPMExpandDistlistRecord, {
	/**
	 * Convert this record into a {@link Zarafa.core.data.IPMRecipientRecord recipient}
	 * which can be used for composing news mails and meeting requests.
	 *
	 * @param {Zarafa.core.mapi.RecipientType} recipientType (optional) The recipient type which should
	 * be applied to this recipient. Defaults to {@link Zarafa.core.mapi.RecipientType#MAPI_TO}.
	 * @return {Zarafa.core.data.IPMRecipientRecord} The recipientRecord for this addressbook item
	 */
	convertToRecipient: function(recipientType)
	{
		var recipientRecord = Zarafa.core.data.RecordFactory.createRecordObjectByCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_RECIPIENT, {
			object_type: this.get('object_type'),
			display_name: this.get('display_name'),
			email_address: this.get('email_address'),
			smtp_address: this.get('smtp_address'),
			address_type: this.get('address_type'),
			entryid: this.get('entryid'),
			search_key: this.get('search_key'),
			display_type_ex: this.get('display_type_ex'),
			recipient_type: recipientType || Zarafa.core.mapi.RecipientType.MAPI_TO
		});

		return recipientRecord;
	}
});
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.IPMExpandDistlistResponseHandler
 * @extends Zarafa.core.data.ProxyResponseHandler
 */
Zarafa.core.data.IPMExpandDistlistResponseHandler = Ext.extend(Zarafa.core.data.ProxyResponseHandler, {
	/**
	 * Handles the 'expand' response. Gathers the expanded recipient(s) information from the
	 * response data, this will simply check the 'results' field in the response
	 * and pushes them into the {@link #receivedRecords records list}.
	 * @param {Object} data The response object belonging to the given command.
	 */
	doExpand: function(response) {
		var results = response.results || [];

		// Force results to be an array.
		if (!Array.isArray(results)) {
			results = [ results ];
		}

		var recipientData = this.reader.readRecords({
			result: results
		});

		this.receivedRecords = recipientData.records;
	}
});
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.IPMNotificationResponseHandler
 * @extends Zarafa.core.data.AbstractNotificationResponseHandler
 *
 * The default {@link Zarafa.core.data.AbstractNotificationResponseHandler ResponseHandler}
 * for {@link Zarafa.core.data.IPMStore IPMStore} Notifications.
 */
Zarafa.core.data.IPMNotificationResponseHandler = Ext.extend(Zarafa.core.data.AbstractNotificationResponseHandler, {
	/**
	 * Handle the {@link Zarafa.core.Actions#update 'update'} action.
	 * This will check if an item was created or updated,
	 * and generate the correct notification.
	 * @param {Object} data The entire response object which will be processed during this transaction.
	 * @private
	 */
	doUpdate: function(response)
	{
		var responseObj = this.reader.readResponse(Ext.data.Api.actions.read, response);
		if (responseObj) {
			var recordData = responseObj.data;

			for (var j = 0, len = recordData.length; j < len; j++) {
				var item = recordData[j];

				var record = this.store.getById(item.entryid);
				if (!Ext.isDefined(record)) {
					this.addNotification(Zarafa.core.data.Notifications.objectCreated, null, item);
				} else {
					this.addNotification(Zarafa.core.data.Notifications.objectModified, record, item);
				}
			}
		}
	},

	/**
	 * Handle the {@link Zarafa.core.Actions#delete 'delete'} action.
	 * This will generate the correct objectDeleted notification.
	 * @param {Object} data The entire response object which will be processed during this transaction.
	 * @private
	 */
	doDelete: function(response)
	{
		var items = response.item;

		if (!Array.isArray(items)) {
			items = [ items ];
		}

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

			var record = this.store.getById(item.entryid);
			if (record) {
				this.addNotification(Zarafa.core.data.Notifications.objectDeleted, record, item);
			}
		}
	},

	/**
	 * Handle the {@link Zarafa.core.Actions#newobject 'newobject'} action.
	 * This will obtain the list of {@link Zarafa.core.data.IPMStore stores}
	 * which must be updated, and generate the Notification.
	 * @param {Object} data The entire response object which will be processed during this transaction.
	 * @private
	 */
	doNewobject: function(data)
	{
		var folderEntryids = Ext.pluck(data.item, 'entryid');
		var folderStores = Zarafa.core.data.IPMStoreMgr.getStoresForFolders(folderEntryids);

		this.addNotification(Zarafa.core.data.Notifications.objectCreated, folderStores, data);
	}
});
/*
 * #dependsFile client/zarafa/core/mapi/ObjectType.js
 * #dependsFile client/zarafa/core/mapi/DisplayType.js
 * #dependsFile client/zarafa/core/data/RecordCustomObjectType.js
 * #dependsFile client/zarafa/core/data/Record.js
 * #dependsFile client/zarafa/core/data/RecordFactory.js
 */
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.IPMRecipientRecordFields
 *
 * Array of default fields for the {@link Zarafa.core.data.IPMRecipientRecord} object.
 * These fields will always be added, regardless of the exact type of
 * {@link Zarafa.core.data.IPMRecipientRecord record}.
 */
Zarafa.core.data.IPMRecipientRecordFields = [
	{name: 'entryid'},
	{name: 'search_key'},
	{name: 'rowid', type: 'int'},
	{name: 'object_type', type: 'int', defaultValue: Zarafa.core.mapi.ObjectType.MAPI_MAILUSER},
	{name: 'display_name'},
	{name: 'display_type', type: 'int', defaultValue: Zarafa.core.mapi.DisplayType.DT_MAILUSER},
	{name: 'display_type_ex', type: 'int', defaultValue: Zarafa.core.mapi.DisplayType.DT_MAILUSER},
	{name: 'email_address'},
	{name: 'user_image', type: 'string'},
	{name: 'smtp_address'},
	{name: 'address_type', type: 'string', defaultValue: 'SMTP'},
	{name: 'presence_status', type: 'int'}, // Note: this field will not be filled by the back-end

	{name: 'recipient_type', type: 'int'},
	{name: 'recipient_flags', type: 'int'},
	{name: 'recipient_trackstatus', type: 'int'},
	{name: 'recipient_trackstatus_time', type: 'date', dateFormat: 'timestamp', defaultValue: null},

	{name: 'proposednewtime', type: 'boolean', defaultValue: false},
	{name: 'proposednewtime_start', type: 'date', dateFormat: 'timestamp', defaultValue: null},
	{name: 'proposednewtime_end', type: 'date', dateFormat: 'timestamp', defaultValue: null}
];

/**
 * @class Zarafa.core.data.IPMRecipientRecord
 * @extends Ext.data.Record
 */
Zarafa.core.data.IPMRecipientRecord = Ext.extend(Ext.data.Record, {
	/**
	 * Indicates whether it has been attempted to resolve this record.
	 * @property
	 * @type Boolean
	 */
	resolveAttempted: false,

	/**
	 * Indicates whether it has been attempted to resolve this record,
	 * but multiple potential recipients were found (out of which the
	 * user has not yet selected the desired recipient).
	 * This property is only valid when {@link #resolveAttempted} is true.
	 * @property
	 * @type Boolean
	 */
	resolveAttemptAmbiguous: false,

	/**
	 * Copy the {@link Zarafa.core.data.IPMRecipientRecord Record} to a new instance
	 * @param {String} newId (optional) A new Record id, defaults to the id of the record being copied. See id.
	 * @return {Zarafa.core.data.IPMRecipientRecord} The copy of the record.
	 */
	copy: function(newId)
	{
		var copy = Zarafa.core.data.RecordFactory.createRecordObjectByCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_RECIPIENT, this.data, newId || this.id);

		copy.phantom = this.phantom;

		return copy.applyData(this);
	},

	/**
	 * Applies all data from an {@link Zarafa.core.data.IPMRecipientRecord IPMRecipientRecord}
	 * to this instance. This will update all data.
	 *
	 * @param {Zarafa.core.data.IPMRecipientRecord} record The record to apply to this
	 * @return {Zarafa.core.data.IPMRecipientRecord} this
	 */
	applyData: function(record)
	{
		this.beginEdit();

		Ext.apply(this.data, record.data);
		Ext.apply(this.modified, record.modified);

		this.resolveAttempted = record.resolveAttempted;
		this.resolveAttemptAmbiguous = record.resolveAttemptAmbiguous;
		this.dirty = record.dirty;

		this.endEdit(false);

		return this;
	},

	/**
	 * See {@link Ext.data.Record.isResolved}. Recipients which meeting organizer are always considered
	 * invalid to prevent them to be saved to the server.
	 * @return {Boolean} False if this this is the meeting organizer recipient.
	 */
	isValid: function()
	{
		if (this.isMeetingOrganizer()) {
			return false;
		} else {
			return Zarafa.core.data.IPMRecipientRecord.superclass.isValid.call(this);
		}
	},

	/**
	 * Resolve recipient.
	 */
	resolve: function()
	{
		if (this.store) {
			this.store.resolve(this);
		}
	},

	/**
	 * A recipient has been resolved when an entryid has been set that is not a one-off.
	 * When a one-off entryid has been set, the email address will be tested with a regexp
	 * to check its validity.
	 * @return {Boolean} True if this recipient has been resolved.
	 */
	isResolved: function()
	{
		if (Ext.isEmpty(this.get('entryid'))) {
			return false;
		}
		if (!this.isOneOff() && this.get('address_type') !== 'ZARAFA') {
			return true;
		}

		var smtp = this.get('smtp_address');
		return Zarafa.reSingleEmailAddress.test(smtp);
	},

	/**
	 * A {@link #isResolved resolved} recipient could also imply that it is an OneOff entryid,
	 * which is a recipient which is not inside the addressbook.
	 * @return {Boolean} True if this recipient has an OneOff entryid.
	 */
	isOneOff: function()
	{
		return Zarafa.core.EntryId.isOneOffEntryId(this.get('entryid'));
	},

	/**
	 * @return {Boolean} True if the smtp address is a valid email address.
	 */
	isValidSMTP: function()
	{
		return Zarafa.core.Util.validateEmailAddress(this.get('smtp_address'));
	},

	/**
	 * @return {Boolean} True if this recipient has been {@link #attemptedToResolve attempted to resolve},
	 * but turned out to be ambiguous.
	 */
	isAmbiguous: function()
	{
		return this.resolveAttempted && this.resolveAttemptAmbiguous;
	},

	/**
	 * This determines if the Recipient refers to a local Contact from the users own store
	 * @return {Boolean} True when the recipient represents a local contact
	 */
	isPersonalContact: function()
	{
		if (this.get('object_type') == Zarafa.core.mapi.ObjectType.MAPI_MAILUSER && Zarafa.core.EntryId.hasContactProviderGUID(this.get('entryid'))) {
			return true;
		}
		return false;
	},

	/**
	 * This determines if the Recipient refers to a local Distlist from the users own store
	 * @return {Boolean} True when the recipient represents a local distlist
	 */
	isPersonalDistList: function()
	{
		if (this.get('object_type') == Zarafa.core.mapi.ObjectType.MAPI_DISTLIST && Zarafa.core.EntryId.hasContactProviderGUID(this.get('entryid'))) {
			return true;
		}
		return false;
	},

	/**
	 * @return {Boolean} True if it was attempted to resolve this recipient.
	 */
	attemptedToResolve: function()
	{
		return this.resolveAttempted;
	},

	/**
	 * @return {Boolean} True when the given recipientType is the Organizer
	 */
	isMeetingOrganizer: function()
	{
		var recipientFlags = this.get('recipient_flags');
		return (recipientFlags & Zarafa.core.mapi.RecipientFlags.recipOrganizer) === Zarafa.core.mapi.RecipientFlags.recipOrganizer;
	},

	/**
	 * Compare this {@link Zarafa.core.data.IPMRecipientRecord record} instance with another one to see
	 * if they are the same IPM recipient.
	 *
	 * @param {Zarafa.core.data.IPMRecipientRecord} record The IPMRecipientRecord to compare with
	 * @return {Boolean} True if the records are the same.
	 */
	equals: function(record)
	{
		var equalStrA = this.get('smtp_address');
		var equalStrB = record.get('smtp_address');

		// If we can't compare on smtp address, we should compare
		// on display name.
		if (Ext.isEmpty(equalStrA) || Ext.isEmpty(equalStrB)) {
			equalStrA = this.get('display_name') || '';
			equalStrB = record.get('display_name') || '';
		}

		if (equalStrA.toUpperCase() == equalStrB.toUpperCase()) {
			return true;
		}

		// TODO: Compare on entryid?

		return false;
	},

	/**
	 * Convert this recipient into a {@link Zarafa.core.data.MAPIRecord record}
	 * which can be used in the addressbook. This can only work if this
	 * recipient is {@link #isResolved resolved}.
	 *
	 * @return {Zarafa.core.data.MAPIRecord} The addressbook record which
	 * is represented by this recipient.
	 */
	convertToABRecord: function()
	{
		var objectType = Zarafa.core.mapi.ObjectType.MAPI_MAILUSER;

		if (!this.isResolved()) {
			return false;
		}

		var entryid = this.get('entryid');
		var displayType = this.get('display_type');

		// use the display_type to determine which ObjectType the
		// addressbook entry is. This will allow is to open the
		// correct dialog.

		switch (displayType) {
			case Zarafa.core.mapi.DisplayType.DT_MAILUSER:
			case Zarafa.core.mapi.DisplayType.DT_REMOTE_MAILUSER:
				objectType = Zarafa.core.mapi.ObjectType.MAPI_MAILUSER;
				break;
			case Zarafa.core.mapi.DisplayType.DT_DISTLIST:
			case Zarafa.core.mapi.DisplayType.DT_ORGANIZATION:
			case Zarafa.core.mapi.DisplayType.DT_PRIVATE_DISTLIST:
				objectType = Zarafa.core.mapi.ObjectType.MAPI_DISTLIST;
				break;
		}

		return Zarafa.core.data.RecordFactory.createRecordObjectByObjectType(objectType, {
			entryid: entryid,
			display_type: displayType,
			display_name: this.get('display_name'),
			display_type_ex: this.get('display_type_ex')
		}, entryid);
	},

	/**
	 * Convert this recipient into a {@link Zarafa.core.data.IPMRecord record}.
	 * This can only work if this recipient is {@link #isResolved resolved}.
	 *
	 * @return {Zarafa.core.data.IPMRecord} The addressbook record which
	 * is represented by this recipient.
	 */
	convertToContactRecord: function()
	{
		// Entryids of personal contacts are suffixed with email id (1, 2, 3), so remove that id
		// this is done in php to ensure that we will always have unique entryids
		var entryid = this.get('entryid');
		var uscoreIndex = entryid.indexOf('_');
		if(uscoreIndex > 0) {
			entryid = entryid.substr(0, uscoreIndex);
		}

		// When selected from the Address Book, the Contact will contain the Contact Provider
		// GUID inside the Entryid. To correctly open the Contact, we have to unwrap this entryid
		// to get the normal entryid back.
		if (Zarafa.core.EntryId.hasContactProviderGUID(entryid)) {
			entryid = Zarafa.core.EntryId.unwrapContactProviderEntryId(entryid);
		}

		return Zarafa.core.data.RecordFactory.createRecordObjectByMessageClass('IPM.Contact', {
			entryid: entryid,
			message_class: 'IPM.Contact',
			object_type: Zarafa.core.mapi.ObjectType.MAPI_MESSAGE,
			store_entryid: this.get('store_entryid'),
			parent_entryid: this.get('parent_entryid')
		}, entryid);
	},

	/**
	 * Convert this recipient into a {@link Zarafa.core.data.IPMRecord record}.
	 * This can only work if this recipient is {@link #isResolved resolved}.
	 *
	 * @return {Zarafa.core.data.IPMRecord} The addressbook record which
	 * is represented by this recipient.
	 */
	convertToDistListRecord: function()
	{
		// Entryids of personal contacts are suffixed with email id (1, 2, 3), so remove that id
		// this is done in php to ensure that we will always have unique entryids
		var entryid = this.get('entryid');
		var uscoreIndex = entryid.indexOf('_');
		if(uscoreIndex > 0) {
			entryid = entryid.substr(0, uscoreIndex);
		}

		// When selected from the Address Book, the Distlist will contain the Contact Provider
		// GUID inside the Entryid. To correctly open the Distlist, we have to unwrap this entryid
		// to get the normal entryid back.
		if (Zarafa.core.EntryId.hasContactProviderGUID(entryid)) {
			entryid = Zarafa.core.EntryId.unwrapContactProviderEntryId(entryid);
		}

		return Zarafa.core.data.RecordFactory.createRecordObjectByMessageClass('IPM.DistList', {
			entryid: entryid,
			message_class: 'IPM.DistList',
			object_type: Zarafa.core.mapi.ObjectType.MAPI_MESSAGE,
			store_entryid: this.get('store_entryid'),
			parent_entryid: this.get('parent_entryid')
		}, entryid);
	},

	/**
	 * Applies the data from an {@link Zarafa.core.data.IPMRecipientResolveRecord IPMRecipientResolveRecord}
	 * to this IPMRecipient.
	 * @param {Zarafa.core.data.IPMRecipientResolveRecord} record The record with resolve data
	 * @param {Boolean} silent True if the record should fire the resolved event on the store. Defaults to true
	 */
	applyResolveRecord: function(record, silent)
	{
		this.beginEdit();

		this.set('entryid', record.get('entryid'));
		this.set('display_name', record.get('display_name'));
		this.set('smtp_address', record.get('smtp_address'));
		this.set('email_address', record.get('email_address'));
		this.set('object_type', record.get('object_type'));
		this.set('display_type', record.get('display_type'));
		this.set('display_type_ex', record.get('display_type_ex'));
		this.set('address_type', record.get('address_type'));
		this.set('search_key', record.get('search_key'));

		this.endEdit();

		if(!Ext.isDefined(silent) || silent === false) {
			this.store.fireEvent('resolved', this.store, [ this ]);
		}
	},

	/**
	 * Convenience method to get {@link Zarafa.core.mapi.DisplayType} or {@link Zarafa.core.mapi.DisplayTypeEx}
	 * property value from {@link Zarafa.core.data.IPMRecipientRecord}.
	 *
	 * @return {Zarafa.core.mapi.DisplayType|Zarafa.core.mapi.DisplayTypeEx} The display type value.
	 */
	getDisplayType: function()
	{
		var displayType = this.get('display_type');
		var displayTypeEx = this.get('display_type_ex');
		var returnValue;

		switch(displayType) {
			case Zarafa.core.mapi.DisplayType.DT_MAILUSER:
			case Zarafa.core.mapi.DisplayType.DT_DISTLIST:
				returnValue = displayTypeEx & ~Zarafa.core.mapi.DisplayTypeEx.DTE_FLAG_ACL_CAPABLE;
				break;
			default:
				returnValue = displayType;
				break;
		}

		return returnValue;
	},

	/**
	 * Format a {@link Zarafa.core.data.IPMRecipientRecord} to a String.
	 * @param {Boolean} useHtmlEncode True if the record should use {@link Ext.util.Format#htmlEncode}
	 * @return {String} The formatted string
	 */
	formatRecipient: function(useHtmlEncode)
	{
		var value = [];
		var name = this.get('display_name');
		var addr = this.get('smtp_address');
		var formatedRecipient;

		if (!Ext.isEmpty(name)) {
			value.push(name);
		}

		if (!Ext.isEmpty(addr) && name != addr) {
			value.push('<' + addr + '>');
		}

		formatedRecipient = value.join(' ');

		if (useHtmlEncode) {
			formatedRecipient = Ext.util.Format.htmlEncode(formatedRecipient);
		}

		return formatedRecipient;
	},

	/**
	 * This will call {@link Zarafa.core.EntryId#createOneOffEntryId} with required parameters
	 * from which oneoff entryid will be generated.
	 */
	generateOneOffEntryId: function()
	{
		var displayName = this.get('display_name');
		var addrType = this.get('address_type');
		var emailAddress = this.get('smtp_address');

		this.set('entryid', Zarafa.core.EntryId.createOneOffEntryId(displayName, addrType, emailAddress));
	},

	/**
	 * End an edit. If any data was modified, the containing store is notified
	 * (ie, the store's <code>update</code> event will fire).
	 * @param {Boolean} clearResolveAttempt (optional) false to prevent the {@link #resolveAttempted}
	 * to be cleared.
	 */
	endEdit: function(clearResolveAttempt)
	{
		this.editing = false;
		if (clearResolveAttempt !== false) {
			this.resolveAttempted = false;
			this.resolveAttemptAmbiguous = false;
		}
		if (this.dirty) {
			this.afterEdit();
		}
	},

	/**
	 * This determines if the Recipient refers to a shared record (contact or distlist)
	 * @return {Boolean} True when the recipient represents a shared record
	 */
	isSharedRecord: function(type = Zarafa.core.mapi.ObjectType.MAPI_MAILUSER)
	{
		return this.get('object_type') == type &&
			!Zarafa.core.EntryId.hasContactProviderGUID(this.get('entryid')) &&
			!Zarafa.core.EntryId.hasABProviderGUID(this.get('entryid')) &&
			!Zarafa.core.EntryId.isOneOffEntryId(this.get('entryid'));
	}
});

// Register a custom type to be used by the Record Factory
Zarafa.core.data.RecordCustomObjectType.addProperty('ZARAFA_RECIPIENT');

Zarafa.core.data.RecordFactory.setBaseClassToCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_RECIPIENT, Zarafa.core.data.IPMRecipientRecord);
Zarafa.core.data.RecordFactory.addFieldToCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_RECIPIENT, Zarafa.core.data.IPMRecipientRecordFields);
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.IPMRecipientResolveResponseHandler
 * @extends Zarafa.core.data.ProxyResponseHandler
 */
Zarafa.core.data.IPMRecipientResolveResponseHandler = Ext.extend(Zarafa.core.data.ProxyResponseHandler, {
	/**
	 * Handles the 'checknames' response. Gathers the resolved recipient information from the
	 * response data, this will simply check the 'resolveresponse' field in the response
	 * and pushes them into the {@link #receivedRecords records list}.
	 * @param {Object} data The response object belonging to the given command.
	 * @return {Boolean} False when action could not be handled successfully. This will
	 * not cancel the transaction itself, but rather causes the 'success' argument for the
	 */
	doChecknames: function(response) {
		this.receivedRecords = [];
		if (Ext.isDefined(response.resolveresponse)) {
			for(var i=0;i<response.resolveresponse.length;i++){
				this.receivedRecords.push({
					id: response.resolveresponse[i].id,
					result: this.readRecordsFromResponse(response.resolveresponse[i], 'result')
				});
			}
		}
	},

	/**
	 * Reads all {@link Zarafa.core.data.MAPIRecord records} from the response data from the server.
	 * @param {Object} response The response data from the server containing the
	 * {@link Zarafa.core.data.MAPIRecord records}
	 * @param {String} field The fieldname of the array of items in the Response object which
	 * must be converted into {@link Zarafa.core.data.MAPIRecord records}.
	 * @return {Zarafa.core.data.MAPIRecord[]} The {@link Zarafa.core.data.MAPIRecord records}
	 * read from the server response.
	 * @private
	 */
	readRecordsFromResponse: function(response, field)
	{
		var items = response[field] || [];
		if (!Array.isArray(items)) {
			items = [ items ];
		}

		var o = {};
		// Add 'field' property to response data, this will fix this.getRoot(o) in JSONReader
		o[field] = items;

		// Use the reader to turn the raw JavaScript objects into a set of Ext.data.Record instances.
		return this.reader.readRecords(o);
	}
});
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.IPMResponseHandler
 * @extends Zarafa.core.data.ProxyResponseHandler
 */
Zarafa.core.data.IPMResponseHandler = Ext.extend(Zarafa.core.data.ProxyResponseHandler, {
	/**
	 * Handles the 'list' response. Gathers the records from the response data,
	 * converts each entry into a record (using {@link #readRecordsFromResponse})
	 * and pushes them into the {@link #receivedRecords records list}.
	 * @param {Object} response The response object belonging to the given command.
	 * @return {Boolean} False when action could not be handled successfully. This will
	 * not cancel the transaction itself, but rather causes the 'success' argument for the
	 * {@link #done} function to be false.
	 */
	doList: function(response)
	{
		if (response.folder) {
			this.metaData.folder = Ext.apply({}, response.folder);
		}
		if (response.error) {
			this.metaData.error = response.error;
		}
		if (response.page) {
			this.metaData.page = Ext.apply({}, response.page);
		}
		if (response.warning) {
			this.metaData.warning = Ext.apply({}, response.warning);
			if (response.warning.display_message) {
				container.getNotifier().notify(
					'warning.list',
					response.warning.title || _('Warning'),
					response.warning.display_message,
					{ persistent: true }
				);
			}
		}
		this.receivedRecords = this.readRecordsFromResponse(response, 'item');
	},

	/**
	 * Handles the 'updatelist' response. Gathers the records from the response data,
	 * converts each entry into a record (using {@link #readRecordsFromResponse})
	 * and pushes them into the {@link #receivedRecords records list}.
	 * @param {Object} response The response object belonging to the given command.
	 */
	doUpdatelist: function(response)
	{
		if (response.folder) {
			this.metaData.folder = Ext.apply({}, response.folder);
		}
		if (response.page) {
			this.metaData.page = Ext.apply({}, response.page);
		}
		this.receivedRecords = this.readRecordsFromResponse(response, 'item');
	},

	/**
	 * Handles the 'item' response. This will check if the item in the response is
	 * inside the {@link #sendRecords} and will convert the Response data into the
	 * updated {@link Ext.data.Record record} using {@link #correlateRecordFromResponse},
	 * and pushes them into the {@link #receivedRecords records list}.
	 * @param {Object} response The response object belonging to the given command.
	 * @return {Boolean} False when action could not be handled successfully. This will
	 * not cancel the transaction itself, but rather causes the 'success' argument for the
	 * {@link #done} function to be false.
	 */
	doItem: function(response)
	{
		this.receivedRecords = this.receivedRecords.concat(this.correlateRecordFromResponse(response));
	},

	/**
	 * Handles the 'update' response. This will check if the item in the response is
	 * inside the {@link #sendRecords} and will convert the Response data into the
	 * updated {@link Ext.data.Record record} using {@link #correlateRecordFromResponse},
	 * and pushes them into the {@link #receivedRecords records list}.
	 * @param {Object} response The response object belonging to the given command.
	 * @return {Boolean} False when action could not be handled successfully. This will
	 * not cancel the transaction itself, but rather causes the 'success' argument for the
	 * {@link #done} function to be false.
	 */
	doUpdate: function(response)
	{
		this.receivedRecords = this.receivedRecords.concat(this.correlateRecordFromResponse(response));
	},

	/**
	 * Handles the 'search' response. Gathers the records from the response data,
	 * converts each entry into a record (using {@link #readRecordsFromResponse})
	 * and pushes them into the {@link #receivedRecords records list}.
	 * @param {Object} response The response object belonging to the given command.
	 * @return {Boolean} False when action could not be handled successfully. This will
	 * not cancel the transaction itself, but rather causes the 'success' argument for the
	 * {@link #done} function to be false.
	 */
	doSearch: function(response)
	{
		this.receivedRecords = this.readRecordsFromResponse(response, 'item');

		// get the metadata
		Ext.iterate(response, function(responseItem, metaObj) {
			if(responseItem != 'item') {
				this.metaData[responseItem] = metaObj;
			}
		}, this);
	},

	/**
	 * Handles the 'updatesearch' response. Gathers the records from the response data,
	 * converts each entry into a record (using {@link #readRecordsFromResponse})
	 * and pushes them into the {@link #receivedRecords records list}. This is the same
	 * as {@link #doSearch} with the exception that the items read from the response will be added to
	 * the previous list in the {@link Ext.data.Store store} which is collecting the results.
	 * @param {Object} response The response object belonging to the given command.
	 * @return {Boolean} False when action could not be handled successfully. This will
	 * not cancel the transaction itself, but rather causes the 'success' argument for the
	 * {@link #done} function to be false.
	 */
	doUpdatesearch: function(response)
	{
		this.receivedRecords = this.readRecordsFromResponse(response, 'item');

		// get the metadata
		Ext.iterate(response, function(responseItem, metaObj) {
			if(responseItem != 'item') {
				this.metaData[responseItem] = metaObj;
			}
		}, this);

		// Make sure the new results are appended to the previous search results.
		Ext.apply(this.options, {add: true});
	},

	/**
	 * Handles the 'stopsearch' response. This initializes {@link #receivedRecords}
	 * as if records have been returned by the server (using {@link #readRecordsFromResponse}).
	 * This ensures that the store will correctly handle the response in the callback function
	 * and correctly handles the 'success' property from the response.
	 *
	 * @param {Object} response The response object belonging to the given command.
	 * @return {Boolean} False when action could not be handled successfully. This will
	 * not cancel the transaction itself, but rather causes the 'success' argument for the
	 * {@link #done} function to be false.
	 */
	doStopsearch: function(response)
	{
		this.receivedRecords = this.readRecordsFromResponse(response, 'item');
	},

	/**
	 * Handles the 'search_error' response. This means that the Search Request has failed
	 * due to a problem on the PHP-side. This will fire the {@link Ext.data.DataProxy#exception exception}
	 * event on the {@link #proxy} object.
	 * @param {Object} response The response object belonging to the given command.
	 * @return {Boolean} False when action could not be handled successfully. This will
	 * not cancel the transaction itself, but rather causes the 'success' argument for the
	 * {@link #done} function to be false.
	 */
	doSearch_error: function(response)
	{
		this.doError(response);
		return false;
	},

	/**
	 * Used by {@link #correlateRecordFromResponse} to determine if the given item data
	 * matches the record which was send to the server. This is used to determine if the
	 * given data can be applied to the record.
	 * @param {Object} data The response data
	 * @param {Zarafa.core.data.MAPIRecord} record The record which was send to the server
	 * @protected
	 */
	compareResponseDataToRecord: function(data, record)
	{
		// special handling if attach_num is present and we are processing response of embedded message
		if(!record.phantom) {
			if(!Ext.isEmpty(record.get('attach_num'))) {
				if(Zarafa.core.EntryId.compareEntryIds(data.entryid, record.get('entryid'))) {
					// for embedded messages we need to compare attach_num additionally also
					if(record.get('attach_num').equals(data.props.attach_num)) {
						return true;
					}
				}
			} else if(Ext.isDefined(data.props['task_goid'])) {
				// Condition gets true only if open request sent by IPM.TaskRequest record and in response we got the
				// associated task data. when user try to open IPM.TaskRequest record in dialog.
				// we have to open associated task(IPM.Task) in dialog
				if(!Zarafa.core.EntryId.compareEntryIds(data.entryid, record.get('entryid'))) {
					if(record.get('task_goid') === data.props['task_goid'] ) {
						return true;
					}
				}
			}
		}

		return Zarafa.core.data.IPMResponseHandler.superclass.compareResponseDataToRecord.apply(this, arguments);
	}
});
/*
 * #dependsFile client/zarafa/core/data/RecordCustomObjectType.js
 */
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.JsonRecipientReader
 * @extends Zarafa.core.data.JsonReader
 */
Zarafa.core.data.JsonRecipientReader = Ext.extend(Zarafa.core.data.JsonReader, {
	/**
	 * @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,

	/**
	 * @constructor
	 * @param {Object} meta Metadata configuration options.
	 * @param {Object} recordType (optional) Optional Record type matches the type
	 * which must be read from response. If no type is given, it will use the
	 * record type for the {@link Zarafa.core.data.RecordCustomObjectType#ZARAFA_RECIPIENT}.
	 */
	constructor: function(meta, recordType)
	{
		meta = Ext.applyIf(meta || {}, {
			id: 'rowid',
			idProperty: 'rowid',
			dynamicRecord: false
		});

		// If no recordType is provided, force the type to be a recipient
		if (!Ext.isDefined(recordType)) {
			recordType = Zarafa.core.data.RecordFactory.getRecordClassByCustomType(meta.customObjectType || this.customObjectType);
		}

		Zarafa.core.data.JsonRecipientReader.superclass.constructor.call(this, meta, recordType);
	}
});
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.ListModuleStore
 * @extends Zarafa.core.data.IPMStore
 * @xtype zarafa.listmodulestore
 *
 * A store that communicates with a list module on the php side. It supports listing items,
 * pagination, etc.
 * <p>
 * Pagination is not properly supported since there is no way to pass the desired page size
 * to the server side. Therefore the page size has to be hard-coded to 50 items.
 */
Zarafa.core.data.ListModuleStore = Ext.extend(Zarafa.core.data.IPMStore, {
	/**
	 * Read-only. boolean to indicate store contains results of search.
	 * @property
	 * @type Boolean
	 */
	hasSearchResults: false,

	/**
	 * used in search to indicate that we should use search folder or not.
	 * without search folder we can search by applying restriction to 'list' action.
	 * @property
	 * @type Boolean
	 */
	useSearchFolder: false,

	/**
	 * @cfg {String} actionType type of action that should be used to send request to server,
	 * valid action types are defined in {@link Zarafa.core.Actions Actions}, default value is 'list'.
	 */
	actionType: undefined,

	/**
	 * @cfg {Boolean} subfolders specifies subfolders should be included in search or not.
	 */
	subfolders: false,

	/**
	 * @cfg {HexString} entryId entry id of the folder.
	 */
	entryId: undefined,

	/**
	 * @cfg {HexString} storeEntryId entry id of store.
	 */
	storeEntryId: undefined,

	/**
	 * @cfg {Zarafa.hierarchy.data.MAPIFolderRecord} folder instead of passing entryId and storeEntryId,
	 * we can pass folder also from which this store will load data.
	 */
	folder: undefined,

	/**
	 * Used to indicate that filter is already applied in store.
	 * @property
	 */
	hasFilterApplied: false,

	/**
	 * used in synchronizing {@link Zarafa.core.data.ListModuleStore store} which indicate true after deleting {@link Zarafa.core.data.IPMRecords[] records}
	 * from {@link Zarafa.core.data.ListModuleStore store}.
	 * @property
	 * @type Boolean
	 */
	syncStore: false,

	/**
	 * @cfg {Number} which is hold number of records loaded in {@link Zarafa.core.data.ListModuleStore store}
	 */
	totalLoadedRecord: undefined,

	/**
	 * The LoadMask object which will be shown when the {@link Zarafa.core.data.ListModuleStore store}
	 * is being delete records, and the dialog is waiting for the server to respond with the desired data.
	 * @property
	 * @type Zarafa.common.ui.LoadMask
	 */
	loadMask: undefined,

	/**
	 * @cfg {String} preferredMessageClass message class that will be used to derive module name
	 * that should be used when requesting data using {@link Zarafa.core.data.IPMProxy IPMProxy).
	 */
	preferredMessageClass: 'IPM.Note',

	/**
	 * @cfg {Object} defaultSortInfo When no sorting is explicitly provided by the user.
	 * See {@link #sortInfo}.
	 */
	defaultSortInfo: undefined,

	/**
	 * Timer function that will be used to update search results after specified interval.
	 * this property should be cleared when {#stopSearch} has been called so it will cancel further
	 * requests for updating search results.
	 * @property
	 * @type Function
	 */
	searchUpdateTimer: undefined,

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

		config.restriction = {};

		config.preferredMessageClass = config.preferredMessageClass || this.preferredMessageClass;

		// Get the desired module, if the module is not set, check if the preferredMessageClass came
		// from the config object, if so, we should retry to get the module based on this.preferredMessageClass.
		var module = Zarafa.core.ModuleNames.getModule(config.preferredMessageClass, true);
		if (Ext.isEmpty(module) && (config.preferredMessageClass != this.preferredMessageClass)) {
			module = Zarafa.core.ModuleNames.getModule(this.preferredMessageClass, true);
		}

		// Default recordtype
		// This will be the basic recordtype for the store, Store will support those fields
		// which are bind with the record type and allow sorting on those fields.
		var recordType = Zarafa.core.data.RecordFactory.getRecordClassByMessageClass(config.preferredMessageClass);

		// Apply default settings.
		Ext.applyIf(config, {
			remoteSort: true,

			actionType: Zarafa.core.Actions['list'],

			// default writer.
			writer: new Zarafa.core.data.JsonWriter(),

			// default reader
			reader: new Zarafa.core.data.JsonReader({}, recordType),

			// default proxy
			proxy: new Zarafa.core.data.IPMProxy({
				listModuleName: module.list,
				itemModuleName: module.item
			})
		});

		this.addEvents(
			/**
			 * @event search
			 * Fires when a search request has been issued to store and store starts search.
			 * @param {Zarafa.core.data.ListModuleStore} store store that started search.
			 * @param {Object} options options object that is passed to {@link #load} event.
			 */
			'search',
			/**
			 * @event beforeupdatesearch
			 * Fires updated status of search process is received from server. This event will be fired whenever
			 * updated status of search is received despite of search has finished or not. so this can be used to
			 * check search has been finished or not.
			 * @param {Zarafa.core.data.ListModuleStore} store store that started search.
			 * @param {Object} searchResponse object that is received as updated search status.
			 */
			'beforeupdatesearch',
			/**
			 * @event updatesearch
			 * Fires when server sends status of the search in the 'search' response. This event will
			 * be fired when {@link Zarafa.core.data.ListModuleStore store} needs to send an 'updatesearch'
			 * to get status of the search when search is still not finished.
			 * @param {Zarafa.core.data.ListModuleStore} store store that started search.
			 * @param {Object} options options object that is passed to {@link #load} event.
			 */
			'updatesearch',
			/**
			 * @event stopsearch
			 * Fires when a stop search request is issued.
			 * @param {Zarafa.core.data.ListModuleStore} store store that started search.
			 * @param {Object} options options object that is passed to {@link #load} event.
			 */
			'stopsearch'
		);

		this.totalLoadedRecord = container.getSettingsModel().get('zarafa/v1/main/page_size');

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

		if(!Ext.isEmpty(this.folder)) {
			// If a folder was provided in the config, we apply the folder
			this.setFolder(this.folder);
		}
	},

	/**
	 * Compare a {@link Ext.data.Record#id ids} to determine if they are equal.
	 * This will apply the {@link Zarafa.core.EntryId#compareEntryIds compareEntryIds} function
	 * on both ids, as all records in this store will have a EntryId as unique key.
	 * @param {String} a The first id to compare
	 * @param {String} b The second id to compare
	 * @protected
	 */
	idComparison: function(a, b)
	{
		return Zarafa.core.EntryId.compareEntryIds(a, b);
	},

	/**
	 * Function will set action type.
	 * @param {String} actionType action type that will be used for sending request to server,
	 * valid values are defined in {@link Zarafa.core.Actions Actions}.
	 */
	setActionType: function(actionType)
	{
		this.actionType = actionType;
	},

	/**
	 * Function will set folder entryid and store entryid.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord[]} mapiFolder mapi folder that should be used to load data.
	 */
	setFolder: function(mapiFolder)
	{
		Ext.each(mapiFolder, function(folder, index) {
			this.setEntryId(folder.get('entryid'), index !== 0);
			this.setStoreEntryId(folder.get('store_entryid'), index !== 0);
		}, this);
	},

	/**
	 * Function will set folder entryid.
	 * @param {HexString} entryId entry id of mapi folder.
	 * @param {Boolean} add append entryids instead of overwriting it.
	 */
	setEntryId: function(entryId, add)
	{
		if(!Ext.isEmpty(add) && add) {
			// multiple entryids
			if(Ext.isEmpty(this.entryId)) {
				this.entryId = [];
			}

			if(!Ext.isEmpty(this.entryId) && !Array.isArray(this.entryId)) {
				this.entryId = [ this.entryId ];
			}

			this.entryId.push(entryId);
		} else {
			// single entryid
			this.entryId = entryId;
		}
	},

	/**
	 * Function will get the folder entryid
	 * @return {HexString[]} The entryid/entryids of the mapi folder.
	 */
	getEntryId: function()
	{
		return this.entryId;
	},

	/**
	 * Function will set entryid of mapi store.
	 * @param {HexString} storeEntryId entry id of mapi store.
 	 * @param {Boolean} add append store entryids instead of overwriting it.
	 */
	setStoreEntryId: function(storeEntryId, add)
	{
		if(!Ext.isEmpty(add) && add) {
			// multiple entryids
			if(Ext.isEmpty(this.storeEntryId)) {
				this.storeEntryId = [];
			}

			if(!Ext.isEmpty(this.storeEntryId) && !Array.isArray(this.storeEntryId)) {
				this.storeEntryId = [ this.storeEntryId ];
			}

			this.storeEntryId.push(storeEntryId);
		} else {
			// single store entryid
			this.storeEntryId = storeEntryId;
		}
	},

	/**
	 * Function will get the entryid of mapi store.
	 * @return {HexString} The entryid of the mapi store
	 */
	getStoreEntryId: function()
	{
		return this.storeEntryId;
	},

	/**
	 * Function will set restriction that should be used in consecutive requests.
	 * @param {Object} restriction restriction object that will contain paging info.
	 */
	setRestriction: function(restriction)
	{
		this.restriction = restriction;
	},

	/**
	 * Function will set restriction to use in searching.
	 * @param {Object} searchRestriction restriction for search.
	 */
	setSearchRestriction: function(searchRestriction)
	{
		if(!Ext.isEmpty(searchRestriction)) {
			this.restriction = Ext.apply(this.restriction || {}, {
				search: searchRestriction
			});
		} else {
			// remove blank search restriction
			delete this.restriction.search;
		}
	},

	/**
	 * Function will set boolean to use subfolders in search or not.
	 * @param {Boolean} subfolders true if subfolders should be used else false.
	 */
	setSubfolders: function(subfolders)
	{
		this.subfolders = subfolders;
	},

	/**
	 * Function will set boolean to use search folders or not.
	 * @param {Boolean} useSearchFolder true if search folder should be used else false.
	 */
	setUseSearchFolder: function(useSearchFolder)
	{
		this.useSearchFolder = useSearchFolder;
	},

	/**
	 * <p>Loads the Record cache from the configured <tt>{@link #proxy}</tt> using the configured <tt>{@link #reader}</tt>.</p>
	 * <br><p>Notes:</p><div class="mdetail-params"><ul>
	 * <li><b><u>Important</u></b>: loading is asynchronous! This call will return before the new data has been
	 * loaded. To perform any post-processing where information from the load call is required, specify
	 * the <tt>callback</tt> function to be called, or use a {@link Ext.util.Observable#listeners a 'load' event handler}.</li>
	 * <li>If using {@link Ext.PagingToolbar remote paging}, the first load call must specify the <tt>start</tt> and <tt>limit</tt>
	 * properties in the <code>options.params</code> property to establish the initial position within the
	 * dataset, and the number of Records to cache on each read from the Proxy.</li>
	 * <li>If using {@link #remoteSort remote sorting}, the configured <code>{@link #sortInfo}</code>
	 * will be automatically included with the posted parameters according to the specified
	 * <code>{@link #paramNames}</code>.</li>
	 * </ul></div>
	 * @param {Object} options An object containing properties which control loading options:<ul>
	 * <li><b><tt>params</tt></b>:Object<div class="sub-desc"><p>An object containing properties to pass as HTTP
	 * parameters to a remote data source. <b>Note</b>: <code>params</code> will override any
	 * <code>{@link #baseParams}</code> of the same name.</p>
	 * <p>Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode}.</p></div></li>
	 * <li><b>callback</b>: Function<div class="sub-desc"><p>A function to be called after the Records
	 * have been loaded. The callback is called after the load event is fired, and is passed the following arguments:<ul>
	 * <li>r: Ext.data.Record[] An Array of Records loaded.</li>
	 * <li>options: Options object from the load call.</li>
	 * <li>success: Boolean success indicator.</li></ul></p></div></li>
	 * <li><b>scope</b>: Object<div class="sub-desc"><p>Scope with which to call the callback (defaults
	 * to the Store object)</p></div></li>
	 * <li><b>add</b>: Boolean<div class="sub-desc"><p>Indicator to append loaded records rather than
	 * replace the current cache. <b>Note</b>: see note for <tt>{@link #loadData}</tt></p></div></li>
	 * </ul>
	 * @return {Boolean} If the <i>developer</i> provided <tt>{@link #beforeload}</tt> event handler returns
	 * <tt>false</tt>, the load call will abort and will return <tt>false</tt>; otherwise will return <tt>true</tt>.
	 */
	load: function(options)
	{
		if (!Ext.isObject(options)) {
			options = {};
		}

		if (!Ext.isObject(options.params)) {
			options.params = {};
		}

		// By default 'load' must cancel the previous request.
		if (!Ext.isDefined(options.cancelPreviousRequest)) {
			options.cancelPreviousRequest = true;
		}

		// If we are searching, don't update the active entryid
		if (!this.hasSearchResults ) {
			if(!Ext.isEmpty(options.folder)) {
				// If a folder was provided in the options, we apply the folder
				this.setFolder(options.folder);
			} else if(Ext.isDefined(options.params.entryid) && Ext.isDefined(options.params.store_entryid)){
				// If the entryid was provided in the parameters we apply the params
				this.setEntryId(options.params.entryid, false);
				this.setStoreEntryId(options.params.store_entryid, false);
			}
		}

		// Override the given entryid and store entryid.
		Ext.apply(options.params, {
			entryid: this.entryId,
			search_folder_entryid: this.searchFolderEntryId,
			store_entryid: this.storeEntryId
		});

		/*
		 * these options can be passed in arguments, or it can be set by setter methods of
		 * {@link Zarafa.core.data.ListModuleStore ListModuleStore}, like {@link #setRestriction}
		 * and {@link #setActionType}, advantage of using setter methods would be that
		 * all consecutive requestswill use that options if its not passed in arguments.
		 * but load method doesn't store these options automatically (like in case of entryids), so
		 * you have to call setter methods to actually set these options.
		 */
		Ext.applyIf(options, {
			actionType: this.actionType
		});

		Ext.applyIf(options.params, {
			restriction: this.restriction
		});

		// Apply the search restriction only when search is performed, remove otherwise, if any
		if(this.restriction.search) {
			if(!this.isAdvanceSearchStore()) {
				delete options.params.restriction.search;
			} else {
				options.params.restriction = Ext.apply(options.params.restriction || {}, {
					search: this.restriction.search
				});
			}
		}

		// search specific parameters
		if (options.actionType == Zarafa.core.Actions['search']) {
			/*
			 * below parameters are required for search so if its not passed in arguments
			 * then we have to add its default values
			 */
			Ext.applyIf(options.params, {
				use_searchfolder: this.useSearchFolder,
				subfolders: this.subfolders,
				forceCreateSearchFolder: options.forceCreateSearchFolder
			});
		}

		/**
		 * We don't required search restriction while navigate using page navigation tool bar in search result grid.
 		 */
		if(options.actionType == Zarafa.core.Actions['list'] && Array.isArray(options.params.restriction.search) && Ext.isDefined(options.params.search_folder_entryid)) {
			delete options.params.restriction.search;
		}

		// remove options that are not needed, although sending it doesn't hurt
		if (options.actionType == Zarafa.core.Actions['updatesearch'] ||
			options.actionType == Zarafa.core.Actions['stopsearch']) {
				delete options.params.restriction;
		}

		// remove search restriction when we already have a search folder entryid, otherwise the Operation::getTable() function
		// will restrict the search folder which removes some search results.
		if (options.actionType == Zarafa.core.Actions['list'] && Array.isArray(options.params.restriction.search) && Ext.isDefined(this.searchFolderEntryId) && this.useSearchFolder) {
			delete options.params.restriction.search;
		}

		options.params.timezone_iana = Intl.DateTimeFormat().resolvedOptions().timeZone;

		return Zarafa.core.data.ListModuleStore.superclass.load.call(this, options);
	},

	/**
	 * <p>Reloads the Record cache from the configured Proxy using the configured
	 * {@link Ext.data.Reader Reader} and the options from the last load operation
	 * performed.</p>
	 * <p><b>Note</b>: see the Important note in {@link #load}.</p>
	 * @param {Object} options <p>(optional) An <tt>Object</tt> containing
	 * {@link #load loading options} which may override the {@link #lastOptions options}
	 * used in the last {@link #load} operation. See {@link #load} for details
	 * (defaults to <tt>null</tt>, in which case the {@link #lastOptions} are
	 * used).</p>
	 * <br><p>To add new params to the existing params:</p><pre><code>
lastOptions = myStore.lastOptions;
Ext.apply(lastOptions.params, {
  myNewParam: true
});
myStore.reload(lastOptions);
	 * </code></pre>
	 */
	reload: function(options)
	{
		if (this.hasSearchResults ) {
			// The superclass for reload will apply this.lastOptions,
			// but for updateSearch we have to do that here manually.
			this.updateSearch(Ext.applyIf(options||{}, this.lastOptions));
		} else {
			Zarafa.core.data.ListModuleStore.superclass.reload.apply(this, arguments);
		}
	},

	/**
	 * Helper function to detect if {@link #this} is the {@link Zarafa.advancesearch.AdvanceSearchStore AdvanceSearchStore} or not.
	 * @return {Boolean} True if this is referred to an instance of {@link Zarafa.advancesearch.AdvanceSearchStore AdvanceSearchStore}, false otherwise.
	 */
	isAdvanceSearchStore: function()
	{
		return this.preferredMessageClass === "IPM.Search";
	},

	/**
	 * Function will be used to issue a update list request to server to retrieve next batch of records,
	 * this will internally call {@link #load} method but with some different options.
	 * @param {Object} options options object that must contain restriction to apply for live scroll.
	 */
	liveScroll: function(options)
	{
		if (!Ext.isObject(options)) {
			options = {};
		}

		if (!Ext.isObject(options.params)) {
			options.params = {};
		}

		// cancel pending load request when live scroll is started.
		if (this.isExecuting('list')) {
			this.proxy.cancelRequests('list');
		}

		Ext.apply(options, {
			actionType: Zarafa.core.Actions['updatelist']
		});

		/*
		 * If the search was done using a search folder, we do not need to apply a restriction and
		 * therefore remove the search restriction. Otherwise this would cause an restriction on the
		 * search folder itself.
		 */
		if (Ext.isDefined(this.searchFolderEntryId) && this.useSearchFolder) {
			delete this.restriction.search;
		}

		this.load(options);
	},

	/**
	 * Function will be used to reset the {@Ext.data.store#lastOptions}.
	 */
	stopLiveScroll: function()
	{
		var restriction = this.lastOptions.params.restriction;

		// cancel all pending updatelist request
		if (this.isExecuting('updatelist')) {
			this.proxy.cancelRequests('updatelist');
		}

		// reset the action type in the last options, because
		// consecutive requests should use list action type
		Ext.apply(this.lastOptions, {
			actionType: Zarafa.core.Actions['list']
		});

		Ext.apply(restriction, {
			start: 0
		});

		if(Ext.isDefined(restriction)){
			delete restriction.limit;
		}
		delete this.lastOptions.add;
	},

	/**
	 * Function will be used to issue a search request to server to start searching,
	 * this will internally call {@link #load} method but with some different options.
	 * @param {Object} options options object that must contain restriction to apply for search.
	 */
	search: function(options)
	{
		if (!Ext.isObject(options)) {
			options = {};
		}

		if (!Ext.isObject(options.params)) {
			options.params = {};
		}

		// can't continue without a restriction
		if (Ext.isEmpty(options.searchRestriction)) {
			return;
		}

		// set search restriction
		this.setSearchRestriction(options.searchRestriction);

		// cancel pending load request when search is started
		if (this.isExecuting('list')) {
			this.proxy.cancelRequests('list');
		}

		this.setUseSearchFolder(options.useSearchFolder);
		this.setSubfolders(options.subfolders);

		if(this.useSearchFolder) {
			Ext.apply(options, {
				actionType: Zarafa.core.Actions['search']
			});
		}

		this.load(options);

		// set the flag to indicate that store contains search results
		// this should be only used when using search folders
		if(this.useSearchFolder) {
			this.hasSearchResults = true;
		}

		this.fireEvent('search', this, options);
	},

	/**
	 * Function will be used to issue a updatesearch request to server,
	 * this will internally call {@link #load} method but with some different options.
	 * @param {Object} options options object that must contain entryid of search folder.
	 */
	updateSearch: function(options)
	{
		// We are not searching...
		if (!this.hasSearchResults ) {
			return;
		}

		if(!Ext.isObject(options)) {
			options = {};
		}

		Ext.apply(options, {
			actionType: Zarafa.core.Actions['updatesearch']
		});

		this.load(options);

		this.fireEvent('updatesearch', this, options);

		// reset the action type in the last options, because updatesearch is not persistent action type
		// and consecutive requests should use search action type, if its not specifically done using updateSearch method
		Ext.apply(this.lastOptions, {
			actionType: Zarafa.core.Actions['search'],
			// Add a property to see that this was originally an updatesearch action
			originalActionType: Zarafa.core.Actions.updatesearch
		});
	},

	/**
	 * Function will be used to issue a stopsearch request to server,
	 * this will internally call {@link #load} method but with some different options.
	 * @param {Object} options options object that must contain entryid of search folder.
	 */
	stopSearch: function(options)
	{
		// remove search restriction
		this.setSearchRestriction({});

		// We are not searching
		if (!this.hasSearchResults ) {
			return;
		}

		if(!Ext.isObject(options)) {
			options = {};
		}

		if (!Ext.isObject(options.params)) {
			options.params = {};
		}


		// cancel all pending updatesearch requests
		if (this.isExecuting(Zarafa.core.Actions['updatesearch']) || this.isExecuting(Zarafa.core.Actions['search'])) {
			this.proxy.cancelRequests(Zarafa.core.Actions['updatesearch']);
			this.proxy.cancelRequests(Zarafa.core.Actions['search']);
		}

		// stop firing updatesearch requests if it is queued
		if(this.searchUpdateTimer) {
			clearTimeout(this.searchUpdateTimer);
			delete this.searchUpdateTimer;
		}

		Ext.apply(options, {
			actionType: Zarafa.core.Actions['stopsearch']
		});

		Ext.apply(options.params, {
			store_entryid: this.storeEntryId,
			search_folder_entryid: this.searchFolderEntryId
		});

		// Send a destroy request -- we are destroying the search folder on the server
		this.proxy.request(Ext.data.Api.actions['destroy'], null, options.params, this.reader, Ext.emptyFn, this, options);

		// Clear the search data
		this.setSearchEntryId(undefined);
		this.hasSearchResults = false;

		this.fireEvent('stopsearch', this, options);

		// reset the action type in the last options, because stopsearch is not persistent action type
		// and consecutive requests should use list action type, if its not specifically done using search method
		Ext.apply(this.lastOptions, {
			actionType: Zarafa.core.Actions['list']
		});
	},

	/**
	 * Function is used as a callback for 'read' action, we have overridden it to
	 * support search also using same 'read' action instead of creating new action.
	 * this will check that if action type is list then will do normal processing and
	 * add {@link Zarafa.core.data.IPMRecords[] records} to {@link Zarafa.core.data.ListModuleStore store}
	 * and if action type is search then it will call {@link #updateSearchInfo} as a callback function.
	 * @param {Object} data data that is returned by the proxy after processing it. will contain
	 * {@link Zarafa.core.data.IPMRecords records}.
	 * @param {Object} options options that are paased through {@link #load} event.
	 * @param {Boolean} success success status of request.
	 * @param {Object} metaData extra information that is received with response data.
	 */
	loadRecords: function(data, options, success, metaData)
	{
		if(success !== false) {
			var restriction;
			if (Ext.isDefined(options.params) && Ext.isDefined(options.params.restriction)) {
				restriction = options.params.restriction;
			}

			var pageSize = container.getSettingsModel().get('zarafa/v1/main/page_size');
			// update total loaded record
			if (restriction) {
				this.totalLoadedRecord = restriction.start ? restriction.start + restriction.limit : pageSize;

				// If store data is synchronized then update start and delete limit
				if (this.syncStore) {

					// Pagination and Infinite Scroll handle page information based on start and limit
					restriction.start = this.totalLoadedRecord - pageSize;
					delete restriction.limit;
					delete this.lastOptions.add;
					this.syncStore= false;
				}
			}
			if(metaData) {
				if(metaData.search_meta) {
					this.updateSearchInfo(metaData.search_meta, metaData.page);
				}

				// if paging information is provided then update total count
				if(metaData.page) {
					this.totalLength = metaData.page.totalrowcount;
				}

				// if folder information is provided, then update the folder
				if (metaData.folder && options.folder && options.folder.length === 1) {
					var folder = options.folder[0];

					if (Ext.isDefined(metaData.folder.content_unread)) {
						folder.set('content_unread', metaData.folder.content_unread);
					}
					if (Ext.isDefined(metaData.folder.content_count)) {
						folder.set('content_count', metaData.folder.content_count);
					}
				}

				// If the response contained an error, set it on the store
				// Note: This is not a PHP error. The response was a normal status 200
				// But the request could not be fulfilled and an error message was
				// sent with the response (listexceed error).
				if (metaData.error) {
					this.error = metaData.error;
				} else {
					this.error = false;
				}
			}
		}

		Zarafa.core.data.ListModuleStore.superclass.loadRecords.apply(this, arguments);
	},

	/**
	 * Function will be called when search response is received by {@link Zarafa.core.data.IPMProxy proxy},
	 * This will check if search is running or not on server based on search status and call {@link #updateSearch}
	 * after timeout of 500ms.
	 * If search is active then search status will be SEARCH_RUNNING (0x1) and if search is locating its messages
	 * then it will return SEARCH_RUNNING | SEARCH_REBUILD (0x3), and if subfolder option is selected then
	 * these two values can be combined with SEARCH_RECURSIVE (0x4), if user has stopped search then search status
	 * will be empty.
	 * @param {Object} searchResponse Object will contain search folder's entryid and its status of search.
	 * @param {Object} page Object will contain information for pagination.
	 */
	updateSearchInfo: function(searchResponse, page)
	{

		// do some data conversion on received data
		var searchData = {
			entryId: searchResponse.searchfolder_entryid,
			searchState: parseInt(searchResponse.searchstate, 10),
			results: parseInt(searchResponse.results, 10)
		};

		// paging info is not always returned
		if(!Ext.isEmpty(page)) {
			Ext.apply(searchData, {
				page: {
					start: parseInt(page.start, 10),
					rowCount: parseInt(page.rowcount, 10),
					totalRowCount: parseInt(page.totalrowcount, 10)
				}
			});
		}

		this.fireEvent('beforeupdatesearch', this, searchData);

		this.setSearchEntryId(searchData.entryId);
		this.setSearchStoreEntryId(searchResponse.search_store_entryid);

		if(Zarafa.core.mapi.Search.isSearchRunning(searchData.searchState)) {
			this.searchUpdateTimer = this.updateSearch.defer(
				container.getSettingsModel().get('zarafa/v1/contexts/search/updatesearch_timeout'),
				this
			);
		} else {
			delete this.searchUpdateTimer;
		}
	},

	/**
	 * Clear the {#hasFilterApplied} flag which used to indicate that filter in
	 * enabled or not.
	 */
	stopFilter: function()
	{
		this.hasFilterApplied = false;
	},

	/**
	 * If {@link #loadMask} is not undefined, this function will display the {@link #loadMask}.
	 * @protected
	 */
	showLoadMask: function ()
	{
		if (!this.loadMask) {
			var contentPanel = container.getContentPanel();
			if (Ext.isFunction(contentPanel.getGridPanel)) {
				var grid = contentPanel.getGridPanel();
				this.loadMask = grid.loadMask;
				this.loadMask.show();
			}
		}
	},

	/**
	 * This will provide the restriction based on the given {@link Zarafa.common.data.Filters.UNREAD Filter} or
	 * {@link Zarafa.task.data.DataModes datemode}.
	 */
	getFilterRestriction: Ext.emptyFn,

	/**
	 * Helper function which used to get the total number of records are loaded
	 * currently in Ext cache.
	 *
	 * @return {Number} The number of items in {@link Ext.data.Store Store}.
	 */
	getStoreLength: function()
	{
		return this.getRange().length;
	}
});

Ext.reg('zarafa.listmodulestore', Zarafa.core.data.ListModuleStore);
/*
 * #dependsFile client/zarafa/core/mapi/Restrictions.js
 */
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.RestrictionFactory
 *
 * Static methods for creating restrictions. Prop tags used for this restrictions
 * are actual constants used in php, eg. for display name property constant in php is PR_DISPLAY_NAME,
 * so we use 'PR_DISPLAY_NAME' as string in restrictions and that string is converted to actual constant
 * in php. for named constants we don't have any constants defined in php so for that we have to use
 * its hex value in string, like '0x80B5001E'. if we somehow dump all the prop tags of mapi to javascript
 * then we can use its hex value directly instead of using its string value.
 *
 * @singleton
 */
Zarafa.core.data.RestrictionFactory = {
	/**
	 * creates RES_AND restriction which applies bitwise AND operation between restrictions.
	 * @param {Array} restrictionsArray array of restriction that should be combined using
	 * RES_AND restriction.
	 * @return {Array} RES_AND restriction.
	 */
	createResAnd: function(restrictionsArray) {
		return [
			Zarafa.core.mapi.Restrictions.RES_AND,
			restrictionsArray.clone()
		];
	},

	/**
	 * creates RES_OR restriction which applies bitwise OR operation between restrictions.
	 * @param {Array} restrictionsArray array of restriction that should be combined using
	 * RES_OR restriction.
	 * @return {Array} RES_OR restriction.
	 */
	createResOr: function(restrictionsArray) {
		return [
			Zarafa.core.mapi.Restrictions.RES_OR,
			restrictionsArray.clone()
		];
	},

	/**
	 * creates RES_NOT restriction which applies logical NOT operation on a sub restriction.
	 * @param {Array} subRestriction restriction whose values should be inverted.
	 * @return {Object} RES_NOT restriction.
	 */
	createResNot: function(subRestriction) {
		return [
			Zarafa.core.mapi.Restrictions.RES_NOT,
			subRestriction
		];
	},

	/**
	 * creates RES_COMPAREPROPS restriction which compares two property values.
	 * @param {Number} propTag1 property tag of first property that should be compared.
	 * @param {Number} propTag2 property tag of second property that should be compared.
	 * @return {Array} RES_COMPAREPROPS restriction.
	 */
	createResCompareProps: function(propTag1, propTag2) {
		var res = [],
			resCondition = Zarafa.core.mapi.Restrictions.RES_COMPAREPROPS,
			resValues = {};

		resValues[Zarafa.core.mapi.Restrictions.ULPROPTAG1] = propTag1;
		resValues[Zarafa.core.mapi.Restrictions.ULPROPTAG2] = propTag2;

		res.push(resCondition);
		res.push(resValues);

		return res;
	},

	/**
	 * creates RES_SUBRESTRICTION restriction which is used for placing a restriction on properties
	 * that holds object pointers for attachment and recipient tables.
	 * @param {Number} propTag property tag of property (can be PR_MESSAGE_ATTACHMENTS or PR_MESSAGE_RECIPIENTS).
	 * @param {Array} subRestriction sub restriction that will be applied to property of table indicated by propTag.
	 * @return {Array} RES_SUBRESTRICTION restriction.
	 */
	createResSubRestriction: function(propTag, subRestriction) {
		var res = [],
			resCondition = Zarafa.core.mapi.Restrictions.RES_SUBRESTRICTION,
			resValues = {};

		resValues[Zarafa.core.mapi.Restrictions.ULPROPTAG] = propTag;
		resValues[Zarafa.core.mapi.Restrictions.RESTRICTION] = subRestriction;

		res.push(resCondition);
		res.push(resValues);

		return res;
	},

	/**
	 * creates a property structure to use in RES_COMMENT restriction (similar to SPropValue structure in mapi).
	 * For multi valued properties, propTag and propTagForValue will be different propTag will contain property tag
	 * of multi valued property and propTagForValue will contain single valued counter part of that propTag.
	 * for categories multi valued property tag is 0x8502101E and single valued property tag is 0x8502001E.
	 * @param {String} propTag property tag of the property.
	 * @param {Mixed} propValue value of the property.
	 * @param {String} propTagForValue single valued counter part of multi valued properties.
	 * @return {Array} property structure.
	 */
	createPropertyObject: function(propTag, propValue, propTagForValue) {
		var prop = [];

		if(Ext.isEmpty(propTagForValue)) {
			propTagForValue = propTag;
		}

		prop[Zarafa.core.mapi.Restrictions.ULPROPTAG] = propTag;
		prop[Zarafa.core.mapi.Restrictions.VALUE] = {};
		prop[Zarafa.core.mapi.Restrictions.VALUE][propTagForValue] = propValue;

		return prop;
	},

	/**
	 * creates RES_COMMENT restriction which is used for adding comments to restrictions which is ignored
	 * by mapi functions. in a special case this can be used in RES_SUBRESTRICTION to hold RES_PROPERTY restriction
	 * that will be applied on given property list.
	 * @param {Array} subRestriction sub restriction that will be applied to RES_SUBRESTRICTION.
	 * @param {Object} propertiesArray array of property list structure created by {@link #createPropertyObject}.
	 * @return {Array} RES_SUBRESTRICTION restriction.
	 */
	dataResComment: function(subRestriction, propertiesArray) {
		var res = [];
		var resCondition = Zarafa.core.mapi.Restrictions.RES_COMMENT;
		var resValues = {};

		resValues[Zarafa.core.mapi.Restrictions.RESTRICTION] = subRestriction;
		resValues[Zarafa.core.mapi.Restrictions.PROPS] = propertiesArray;

		res.push(resCondition);
		res.push(resValues);

		return res;
	},

	/**
	 * creates RES_PROPERTY restriction which is used to check value of properties using logical operators.
	 * For multi valued properties, propTag and propTagForValue will be different propTag will contain property tag
	 * of multi valued property and propTagForValue will contain single valued counter part of that propTag.
	 * for categories multi valued property tag is 0x8502101E and single valued property tag is 0x8502001E.
	 * @param {String} propTag property tag of property whose value should be checked.
	 * @param {Number} relOp relational operator that will be used for comparison.
	 * @param {Mixed} propValue value that should be used for checking.
	 * @param {String} propTagForValue property tag that should be used in value, only for multi valued properties.
	 * @return {Array} RES_PROPERTY restriction.
	 */
	dataResProperty: function(propTag, relOp, propValue, propTagForValue) {
		var res = [],
			resCondition = Zarafa.core.mapi.Restrictions.RES_PROPERTY,
			resValues = {};

		if(Ext.isEmpty(propTagForValue)) {
			propTagForValue = propTag;
		}

		resValues[Zarafa.core.mapi.Restrictions.RELOP] = relOp;
		resValues[Zarafa.core.mapi.Restrictions.ULPROPTAG] = propTag;
		resValues[Zarafa.core.mapi.Restrictions.VALUE] = {};
		resValues[Zarafa.core.mapi.Restrictions.VALUE][propTagForValue] = propValue;

		res.push(resCondition);
		res.push(resValues);

		return res;
	},

	/**
	 * creates RES_EXIST restriction which is used to check existence of a property.
	 * @param {String} propTag property tag of property whose existence should be checked.
	 * @return {Array} RES_EXIST restriction.
	 */
	dataResExist: function(propTag) {
		var res = [],
			resCondition = Zarafa.core.mapi.Restrictions.RES_EXIST,
			resValues = {};

		resValues[Zarafa.core.mapi.Restrictions.ULPROPTAG] = propTag;

		res.push(resCondition);
		res.push(resValues);

		return res;
	},

	/**
	 * creates RES_SIZE restriction which is used to check size of property's value.
	 * @param {String} propTag property tag of property whose size of value should be checked.
	 * @param {Number} relOp relational operator for comparing sizes.
	 * @param {Number} sizeValue size of value which will be used for comparison.
	 * @return {Array} RES_SIZE restriction.
	 */
	dataResSize: function(propTag, relOp, sizeValue) {
		var res = [],
			resCondition = Zarafa.core.mapi.Restrictions.RES_SIZE,
			resValues = {};

		resValues[Zarafa.core.mapi.Restrictions.ULPROPTAG] = propTag;
		resValues[Zarafa.core.mapi.Restrictions.RELOP] = relOp;
		resValues[Zarafa.core.mapi.Restrictions.CB] = sizeValue;

		res.push(resCondition);
		res.push(resValues);

		return res;
	},

	/**
	 * creates RES_BITMASK restriction which is used to check property's value after applying bitmask.
	 * @param {String} propTag property tag of property whose size of value should be checked.
	 * @param {Number} bitMaskType type of bitmask to be applied.
	 * @param {Number} bitMaskValue value that should be compared after applying bitmask to original value.
	 * @return {Array} RES_BITMASK restriction.
	 */
	dataResBitmask: function(propTag, bitMaskType, bitMaskValue) {
		var res = [],
			resCondition = Zarafa.core.mapi.Restrictions.RES_BITMASK,
			resValues = {};

		resValues[Zarafa.core.mapi.Restrictions.ULPROPTAG] = propTag;
		resValues[Zarafa.core.mapi.Restrictions.ULTYPE] = bitMaskType;
		resValues[Zarafa.core.mapi.Restrictions.ULMASK] = bitMaskValue;

		res.push(resCondition);
		res.push(resValues);

		return res;
	},

	/**
	 * creates RES_CONTENT restriction which is used to fuzzy search a property's value.
	 * For multi valued properties, propTag and propTagForValue will be different propTag will contain property tag
	 * of multi valued property and propTagForValue will contain single valued counter part of that propTag.
	 * for categories multi valued property tag is 0x8502101E and single valued property tag is 0x8502001E.
	 * @param {String} propTag property tag of property whose value should be checked.
	 * @param {Number} fuzzyLevel type of fuzzy search that should be applied.
	 * @param {Mixed} propValue value that should be used for checking.
	 * @param {String} propTagForValue property tag that should be used in value, only for multi valued properties.
	 * @return {Array} RES_CONTENT restriction.
	 */
	dataResContent: function(propTag, fuzzyLevel, propValue, propTagForValue) {
		var res = [],
			resCondition = Zarafa.core.mapi.Restrictions.RES_CONTENT,
			resValues = {};

		if(Ext.isEmpty(propTagForValue)) {
			propTagForValue = propTag;
		}

		// default is FL_FULLSTRING
		resValues[Zarafa.core.mapi.Restrictions.FUZZYLEVEL] = Zarafa.core.mapi.Restrictions.FL_FULLSTRING;
		if(Ext.isObject(fuzzyLevel)) {
			Ext.each(fuzzyLevel, function(fuzzyFlag) {
				resValues[Zarafa.core.mapi.Restrictions.FUZZYLEVEL] += fuzzyFlag;
			});
		} else {
			resValues[Zarafa.core.mapi.Restrictions.FUZZYLEVEL] = fuzzyLevel;
		}
		resValues[Zarafa.core.mapi.Restrictions.ULPROPTAG] = propTag;
		resValues[Zarafa.core.mapi.Restrictions.VALUE] = {};
		resValues[Zarafa.core.mapi.Restrictions.VALUE][propTagForValue] = propValue;

		res.push(resCondition);
		res.push(resValues);

		return res;
	}
};
Ext.namespace('Zarafa.core.ui');

/**
 * @class Zarafa.core.ui.MessageContentPanel
 * @extends Zarafa.core.ui.RecordContentPanel
 * @xtype zarafa.messagecontentpanel
 *
 * This extends the normal {@link Zarafa.core.ui.RecordContentPanel RecordContentPanel} which is
 * used for displaying and editing {@link Zarafa.core.data.IPMRecord records}. This
 * class is used for Messages which can be {@link #sendRecord send} as well.
 *
 * FIXME: Provide default buttons which are common for all content panels with messages
 */
Zarafa.core.ui.MessageContentPanel = Ext.extend(Zarafa.core.ui.RecordContentPanel, {

	/**
	 * The queue containing callback functions which can be used to validate the
	 * {@link Zarafa.core.data.IPMRecord} which is about to be send. If the queue
	 * completes successfully, the message can be send, otherwise it will be cancelled.
	 * @property
	 * @type Zarafa.core.data.CallbackQueue
	 * @protected
	 */
	sendValidationQueue: undefined,

	/**
	 * @cfg {String/Object} sendingText When {@link #showInfoMask} is true, this text
	 * will be shown when the message is being sent. When an object is provided which contains
	 * the 'msg' fields respectively.
	 */
	sendingText: { msg: _('Sending…') },

	/**
	 * @cfg {String/Object} sendingDoneText When {@link #showInfoMask} is true, this text
	 * will be shown when the message has been sent. When an object is provided which contains
	 * the 'msg' fields respectively.
	 */
	sendingDoneText:{ msg: '<b>'+ _('Sent successfully')+'</b>' },

	/**
	 * Indicates if the panel is currently busy sending data to the server.
	 * @property
	 * @type Boolean
	 */
	isSending: false,

	/**
	 * @cfg {Boolean} closeOnSend Config option to close the panel when client receives confirmation of message is sent.
	 */
	closeOnSend: false,

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

		config.plugins = Ext.value(config.plugins, []);
		config.plugins.push({
			ptype: 'zarafa.markasreadplugin',
			ignoreReadFlagTimer: true
		});


		this.addEvents(
			/**
			 * @event beforesendrecord
			 * Fires when a record is about to be {@link #sendRecord send}.
			 * This will allow Subclasses modify the {@link Zarafa.core.data.IPMRecord record} before
			 * it is being send. This event will be followed by the {@link #beforesaverecord} and
			 * {@link #saverecord} (which triggers the send action on the server) and ends with the
			 * {@link #sendrecord} event when everything has been completed.
			 * @param {Zarafa.core.ui.RecordContentPanel} contentpanel The contentpanel from where the record is send
			 * @param {Zarafa.core.data.IPMRecord} record The record which is going to be send
			 * @return {Boolean} false if the record should not be send.
			 */
			'beforesendrecord',
			/**
			 * @event sendrecord
			 * Fires after the record has been {@link #sendRecord send}.
			 * This follows the {@link #saverecord} event which triggers the send action on the server.
			 * @param {Zarafa.core.ui.RecordContentPanel} contentpanel The contentpanel from where the record is send
			 * @param {Zarafa.core.data.IPMRecord} record The record which has been send
			 */
			'sendrecord',
			/**
			 * @event aftersendrecord
			 * Fires after the record has been sent successfully.
			 * This follows the {@link #updaterecord} event when the server responded to the send action
			 * @param {Zarafa.core.ui.RecordContentPanel} contentpanel The contentpanel from where the record is send
			 * @param {Zarafa.core.data.IPMRecord} record The record which has been send
			 */
			'aftersendrecord'
		);

		Zarafa.core.ui.MessageContentPanel.superclass.constructor.call(this, config);

		// Initialize the Send Validation queue
		// which is used for validating the message before sending
		this.createSendValidationQueue();

		if (Ext.isString(this.sendingText)) {
			this.sendingText = { title: '', msg: this.sendingText };
		}
		if (Ext.isString(this.sendingDoneText)) {
			this.sendingDoneText = { title: '', msg: this.sendingDoneText };
		}

		if(this.record) {
			var store = this.record.getStore();
			if (store) {
				store.on('update', this.syncUpdatesToShadowStore, this);
			}
		}
	},

	/**
	 * Event handler which is triggered when a {@link Zarafa.core.data.IPMRecord record} has been
	 * updated from the server.
	 * When any record which belongs to {@link Zarafa.core.data.IPMStore} gets updated, respective
	 * {@link Zarafa.core.data.ShadowStore ShadowStore} record needs to be updated, if any.
	 *
	 * @param {Zarafa.core.data.IPMStore} store The store which performs the update
	 * @param {Zarafa.core.data.IPMRecord} record The Record which has been updated
	 * @param {String} operation The update operation being performed.
	 * ({@link Ext.data.Record#EDIT}, {@link Ext.data.Record#REJECT}, {@link Ext.data.Record#COMMIT}).
	 * @private
	 */
	syncUpdatesToShadowStore: function(store, record, operation)
	{
		if (Zarafa.core.EntryId.compareEntryIds(record.get('entryid'), this.record.get('entryid'))) {
			if(operation === Ext.data.Record.COMMIT){
				var receivedFlags = record.get('message_flags');
				var categories = record.get('categories');
				var commit = false;

				// Stop modification-tracking to prevent dirty mark
				this.record.setUpdateModificationsTracking(false);

				// As of now, message_flags and categories property is being processed only because some properties
				// seems to be missing in MailStore-record as compare to ShadowStore-record result into some weird
				// behavior with mail-formatting and meeting-request-accept functionalities.
				this.record.beginEdit();
				if (this.record.get('message_flags') !== receivedFlags) {
					this.record.set('message_flags', receivedFlags);
					commit = true;
				}
				if (this.record.get('categories') !== categories) {
					this.record.set('categories', categories);
					commit = true;
				}
				this.record.endEdit();
				if ( commit ){
					this.record.commit();
				}

				// Start modification-tracking back for future user changes
				this.record.setUpdateModificationsTracking(true);
			}
		}
	},

	/**
	 * Create and initialize the {@link #sendValidationQueue}. This will add various
	 * validation steps which must be executed to determine if the message can be send.
	 * @protected
	 */
	createSendValidationQueue: function()
	{
		// Create a callback queue to validate the record before sending
		this.sendValidationQueue = new Zarafa.core.data.CallbackQueue();

		// Add a validation step to determine if there are recipients
		this.sendValidationQueue.add(this.validateEmptyRecipients, this);
		// Add a validation step to determine if a subject was provided
		this.sendValidationQueue.add(this.validateEmptySubject, this);
		// Add a validation step to warn about missing attachments when keywords are found
		this.sendValidationQueue.add(this.validateMissingAttachment, this);
		// Add a validation step to determine if all recipients are resolved
		this.sendValidationQueue.add(this.validateResolvedRecipients, this);
		// Add a validation step to determine if all attachments are uploaded
		this.sendValidationQueue.add(this.validateAttachmentUpload, this);
	},

	/**
	 * If {@link showInfoMask} is enabled, this will display the {@link #savingText} to the user.
	 * @protected
	 * @overridden
	 */
	displayInfoMask: function()
	{
		if (this.showInfoMask === false) {
			return;
		}

		if (this.record.hasMessageAction('send') || this.record.getMessageAction('sendResponse')) {
			container.getNotifier().notify('info.sending', this.sendingText.title, this.sendingText.msg, {
				container: this.getEl()
			});
		} else {
			Zarafa.core.ui.MessageContentPanel.superclass.displayInfoMask.apply(this, arguments);
		}

	},

	/**
	 * If {@link #showInfoMask} is enabled, and {@link #displayInfoMask} has been called, this
	 * will remove the notification again. When saving has been successful, a new notification
	 * will be shown to display the {@link #savingDoneText}.
	 * @param {Boolean} success false to disable the display of {@link #savingDoneText}.
	 * @protected
	 * @overridden
	 */
	hideInfoMask: function(success)
	{
		if (this.showInfoMask === false) {
			return;
		}

		if (this.record.hasMessageAction('send') || this.record.getMessageAction('sendResponse')) {
			if (success !== false) {
				container.getNotifier().notify('info.sent', this.sendingDoneText.title, this.sendingDoneText.msg);
			}
		} else {
			Zarafa.core.ui.MessageContentPanel.superclass.hideInfoMask.apply(this, arguments);
		}
	},

	/**
	 * Event handler which is fired when the the {@link Ext.data.Store store} for the {@link #record}
	 * fires the {@link Ext.data.Store#beforesave} event. This will check if the event was really regarding
	 * {@link #record} and will update the {@link #isSaving} or {@link #isSending} property and
	 * {@link #displayInfoMask display the infobox}.
	 * @param {Ext.data.Store} store The store which fired the event
	 * @param {Object} data The object data which is being saved to the server
	 * @private
	 */
	onBeforeSaveRecord: function(store, data)
	{
		if (data &&
		  ((data.update && data.update.indexOf(this.record) >= 0) ||
		   (data.create && data.create.indexOf(this.record) >= 0))) {
			this.isSending = this.record.hasMessageAction('send') || this.record.hasMessageAction('sendResponse');
		}
		Zarafa.core.ui.MessageContentPanel.superclass.onBeforeSaveRecord.apply(this, arguments);
	},

	/**
	 * Fired when the {@link #updaterecord} event has been fired. If {@link #showSavingMask} is enabled,
	 * this will display the {@link #savingText to indicate the saving is in progress.
	 *
	 * @param {Zarafa.core.ui.RecordContentPanel} contentpanel The contentpanel which fired the event
	 * @param {String} action write Action that occurred. Can be one of
	 * {@link Ext.data.Record.EDIT EDIT}, {@link Ext.data.Record.REJECT REJECT} or
	 * {@link Ext.data.Record.COMMIT COMMIT}
	 * @param {Zarafa.core.data.IPMRecord} record The record which was updated
	 * @private
	 * @overridden
	 */
	onUpdateRecord: function(contentpanel, action, record)
	{
		Zarafa.core.ui.MessageContentPanel.superclass.onUpdateRecord.apply(this, arguments);

		var isSending = this.isSending;

		/**
		 * Close panel when it message has been sent or saved at serverside
		 * and here we have received confirmation
		 */
		if (action == Ext.data.Record.COMMIT) {
			var sendAction = record.hasMessageAction('send') || this.record.hasMessageAction('sendResponse');
			if (sendAction) {
				this.fireEvent('aftersendrecord', this, this.record);
				this.isSending = false;
			}

			if (isSending && this.closeOnSend && sendAction) {
				if (this.recordComponentPlugin) {
					this.recordComponentPlugin.resetUserChangeTracker();
				}
				this.close();
				// We closed the panel, stop the event propagation as there is
				// no longer an UI that can be updated.
				return false;
			}
		}
	},

	/**
	 * Fired when the {@link #exceptionrecord} event has been fired. Will reset {@link #isSending}.
	 *
	 * @param {misc} misc See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception}
	 * for description.
	 * @private
	 * @overridden
	 */
	onExceptionRecord: function(proxy, type, action, options, response, args)
	{
		Zarafa.core.ui.MessageContentPanel.superclass.onExceptionRecord.apply(this, arguments);

		if (type !== "open") {
			this.isSending = false;
		}
	},

	/**
	 * Save all changes made to the {@link #record} and send
	 * the message to the specified recipients.
	 */
	sendRecord: function()
	{
		// If record is saving then wait till saving is done. register aftersaverecord event
		// on message content panel.
		if(this.isSaving === true) {
			this.on('aftersaverecord', this.onAfterSaveRecord, this, {single: true});
			return;
		}

		if (this.recordComponentPlugin.allowWrite === false || this.isSending === true) {
			return;
		}

		if (this.fireEvent('beforesendrecord', this, this.record) === false) {
			return;
		}

		this.isSending = true;

		// Start the validation queue to determine if the record can be
		// send to the recipients correctly. If successful, onCompleteValidateSendRecord
		// is called to send the actual record.
		this.sendValidationQueue.run(this.onCompleteValidateSendRecord, this);
	},

	/**
	 * Event handler which is called when send and save button is clicked
	 * immediately one after another. Function registers {@link Zarafa.core.data.MAPIStore#write}
	 * event which will call {@link #sendRecord} function when saving has been completed.
	 *
	 * This function is use to solve below problem.
	 *
	 * If we call {@link #sendRecord} straight away after {@link Zarafa.core.ui.RecordContentPanel #aftersaverecord} event then
	 * {@link Ext.data.Store #write} event is triggered on {@link Zarafa.core.data.ShadowStore ShadowStore} for this record,
	 * It will call {@link Zarafa.core.data.MAPIRecord.clearMessageActions clearMessageActions}(i.e. send=true),
	 * so when we get response of send request there will be no reference of the send request in record.
	 * So we will not be able to perform {@link #aftersendrecord} event functionalities like,
	 * Closing the dialog, Showing the 'Sent Successfully' notification, hiding the 'Sending…' notification.
	 *
	 * @param {Zarafa.core.ui.RecordContentPanel} contentpanel The contentpanel from where the record is saved
	 * @param {Zarafa.core.data.IPMRecord} record The record which has been saved
	 */
	onAfterSaveRecord: function(contentPanel, record)
	{
		var store = record.getStore();
		this.mon(store, 'write', this.sendRecord, this, {single: true});
	},

	/**
	 * Validation function for the {@link #sendValidationQueue} to check if the Message
	 * can be send to the recipients.
	 *
	 * This validates if recipients were provided in the message. If not, then the
	 * message cannot be send
	 *
	 * @param {Function} callback The callback to call to continue in the queue
	 * @private
	 */
	validateEmptyRecipients: function(callback)
	{
		// Check if recipients have been provided
		var recipientStore = this.record.getRecipientStore();
		if (!Ext.isDefined(recipientStore) || recipientStore.getCount() === 0) {
			Ext.MessageBox.show({
				title: _('No recipient'),
				msg: _('Please specify at least one recipient'),
				buttons: Ext.MessageBox.OK
			});

			// The message cannot be send, cancel the callbacks
			callback(false);
		} else {
			// Check if the recipient_type is correct, there should at least be 1 TO/CC/BCC recipient
			var invalid = true;

			recipientStore.each(function(recip) {
				if (recip.get('recipient_type') !== Zarafa.core.mapi.RecipientType.MAPI_ORIG) {
					invalid = false;
					return false;
				}
			}, this);

			if (invalid) {
				Ext.MessageBox.show({
					title: _('No recipient'),
					msg: _('Please specify a recipient'),
					buttons: Ext.MessageBox.OK
				});

				// The message cannot be send, cancel the callbacks
				callback(false);
			} else {
				// The message can be send, the next callback can be called.
				callback(true);
			}
		}
	},

	/**
	 * Validation function for the {@link #sendValidationQueue} to check if the Message
	 * can be send to the recipients.
	 *
	 * This validates if the attachment is uploading while sending mail, if yes then the user is
	 * asked if he still wants to send the message without current uploading attachment.
	 *
	 * @param {Function} callback The callback to call to continue in the queue
	 * @private
	 */
	validateAttachmentUpload: function(callback)
	{
		var attachmentStore = this.record.getAttachmentStore();
		var isAllAttachUploaded = true;
		attachmentStore.each(function(attach) {
			if(!attach.isUploaded()) {
				isAllAttachUploaded = false;
				// stop further iterations
				return false;
			}
		}, this);

		// Check if the attachment has been uploaded
		if (!isAllAttachUploaded) {
			var message = _('The attached files are not uploaded yet.');
			message += '<br/>';
			message += _('Do you want to send this message without attachments?');

			Zarafa.common.dialogs.MessageBox.addCustomButtons({
				title: _('Attachments not uploaded yet'),
				cls: Ext.MessageBox.WARNING_CLS,
				msg: message,
				fn: function(button) {
					callback(button === 'sendanyway');
				},
				customButton: [{
					text: _('Don\'t Send'),
					name: 'dontsend'
				}, {
					text: _('Send Anyway'),
					name: 'sendanyway'
				}]
			});
		} else {
			callback(true);
		}
	},

	/**
	 * Validation function for the {@link #sendValidationQueue} to check if the Message
	 * body contains attachment-related keywords but no files are attached. If the user
	 * has enabled the attachment reminder setting and a keyword match is found, a
	 * confirmation dialog is shown before sending.
	 *
	 * @param {Function} callback The callback to call to continue in the queue
	 * @private
	 */
	validateMissingAttachment: function(callback)
	{
		// Only run when the user has opted in
		if (container.getSettingsModel().get('zarafa/v1/contexts/mail/attachment_reminder_enable') !== true) {
			callback(true);
			return;
		}

		// Count non-hidden attachments (inline images DO count as real attachments)
		var attachmentStore = this.record.getAttachmentStore();
		var realAttachments = 0;
		attachmentStore.each(function(attach) {
			if (!attach.isHidden()) {
				realAttachments++;
			}
		}, this);

		if (realAttachments > 0) {
			callback(true);
			return;
		}

		// Get plain-text body for keyword matching
		var body = this.record.getBody(false);
		if (Ext.isEmpty(body)) {
			callback(true);
			return;
		}

		var keywordsStr = container.getServerConfig().getAttachmentReminderKeywords();
		if (Ext.isEmpty(keywordsStr)) {
			callback(true);
			return;
		}

		var keywords = keywordsStr.split(',');
		var bodyLower = body.toLowerCase();
		var found = false;
		for (var i = 0; i < keywords.length; i++) {
			var kw = keywords[i].trim().toLowerCase();
			if (kw && bodyLower.indexOf(kw) !== -1) {
				found = true;
				break;
			}
		}

		if (found) {
			Ext.MessageBox.show({
				title: _('Missing attachment'),
				msg: _('Your message appears to reference an attachment but no file is attached. Send anyway?'),
				buttons: Ext.MessageBox.YESNO,
				fn: function(buttonClicked) {
					callback(buttonClicked === 'yes');
				},
				scope: this
			});
		} else {
			callback(true);
		}
	},

	/**
	 * Validation function for the {@link #sendValidationQueue} to check if the Message
	 * can be send to the recipients.
	 *
	 * This validates if the subject was provided in the message, if not then the user is
	 * asked if he still wants to send the message.
	 *
	 * @param {Function} callback The callback to call to continue in the queue
	 * @private
	 */
	validateEmptySubject: function(callback)
	{
		// Check if the subject has been provided
		if (Ext.isEmpty(this.record.get('subject'))){
			Ext.MessageBox.show({
				title: _('No subject'),
				msg: _('Send this message without a subject?'),
				buttons: Ext.MessageBox.YESNO,
				fn: function (buttonClicked) {
					// If the user clicked "yes" then the
					// callback queue can continue, otherwise
					// it will have to abort.
					callback(buttonClicked == 'yes');
				},
				scope: this,
			});
		} else {
			// The subject is provided, we can continue
			callback(true);
		}
	},

	/**
	 * Validation function for the {@link #sendValidationQueue} to check if the Message
	 * can be send to the recipients.
	 *
	 * This validates if all recipients were resolved in the message. If not, then
	 * the the recipientStore will resolve everything.
	 *
	 * @param {Function} callback The callback to call to continue in the queue
	 * @private
	 */
	validateResolvedRecipients: function(callback)
	{
		var recipientStore = this.record.getRecipientStore();
		var unresolved = recipientStore.getUnresolvedRecipients();
		var invalid = recipientStore.getInvalidRecipients();

		if (!Ext.isEmpty(invalid)) {
			// Invalid recipients were found, show error to the user
			// and cancel the callbacks as we cannot send the message
			Ext.MessageBox.show({
				title: _('Unresolved recipients'),
				msg: _('Not all recipients could be resolved'),
				cls: Ext.MessageBox.WARNING_CLS,
				buttons: Ext.MessageBox.OK
			});
			callback(false);
		} else if(!Ext.isEmpty(unresolved)) {
			// Unresolved recipients were found, try to resolve them, and have the event handler
			// call this function recursively so we can check again.
			this.mon(recipientStore, 'resolved', this.validateResolvedRecipients.createDelegate(this, [ callback ], false), this, { single: true });
			recipientStore.resolve(unresolved, { cancelPreviousRequest: true });
		} else {
			// No invalid or unresolved recipients, we can continue
			// the callback queue to send the message.
			callback(true);
		}
	},

	/**
	 * Callback function for the {@link #sendValidationQueue} which is called when the queue has been
	 * completed. If the queue ended successfully then the {@link #record} will get the
	 * {@link Zarafa.core.data.IPMRecord#addMessageAction 'send' message action} and will then
	 * {@link #saveRecord save the message} in order to send.
	 * @param {Boolean} success True if the queue ended successfully
	 * @private
	 */
	onCompleteValidateSendRecord: function(success)
	{
		if (success) {
			this.forceSendAsIdentityTransmission();
			this.record.addMessageAction('send', true);
			if (this.saveRecord() !== false) {
				this.fireEvent('sendrecord', this, this.record);
			}
		} else {
			this.isSending = false;
		}
	},

	/**
	 * Re-mark the sent_representing_* properties as modified just before the
	 * record is sent. Only modified fields are transmitted, so after an
	 * intermediate (auto)save a SendAs / on-behalf identity would be missing
	 * from the send request and the server would fall back to the logged-in
	 * user's own address.
	 * @private
	 */
	forceSendAsIdentityTransmission: function()
	{
		var record = this.record;
		if (Ext.isEmpty(record.get('sent_representing_email_address')) &&
			Ext.isEmpty(record.get('sent_representing_entryid'))) {
			return;
		}

		record.beginEdit();
		Ext.each(['sent_representing_name', 'sent_representing_email_address',
			'sent_representing_address_type', 'sent_representing_entryid',
			'sent_representing_search_key', 'sent_representing_smtp_address'], function(field) {
			var value = record.get(field);
			if (Ext.isDefined(value)) {
				record.set(field, value, true);
			}
		});
		record.endEdit();
	}
});

Ext.reg('zarafa.messagecontentpanel', Zarafa.core.ui.MessageContentPanel);
Ext.namespace('Zarafa.core.ui.notifier');

/**
 * @class Zarafa.core.ui.notifier.DropdownBoxPlugin
 * @extends Zarafa.core.ui.notifier.SliderNotifierPlugin
 *
 * Special {@link Zarafa.core.ui.notifier.SliderNotifierPlugin SliderPlugin} which sends all
 * messages to a nice dropdown box in the top-middle of the screen. This plugin
 * will be registered to the {@link Zarafa.core.ui.notifier.Notifier notifier} using
 * the name 'dropdown'.
 */
Zarafa.core.ui.notifier.DropdownBoxPlugin = Ext.extend(Zarafa.core.ui.notifier.SliderNotifierPlugin, {
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			itemCls: 'zarafa-notifier-dropdown-item',
			// Position the container at the top-center
			sliderContainerPosition: 't'
		});

		Zarafa.core.ui.notifier.DropdownBoxPlugin.superclass.constructor.call(this, config);
	}
});

Zarafa.onReady(function() {
	container.getNotifier().registerPlugin('dropdown', new Zarafa.core.ui.notifier.DropdownBoxPlugin());

	// Backwards compatibility
	container.getNotifier().registerPlugin('eventdropdown', new Zarafa.core.ui.notifier.DropdownBoxPlugin());
	container.getNotifier().registerPlugin('persistentmessageplugin', new Zarafa.core.ui.notifier.DropdownBoxPlugin());
	container.getNotifier().registerPlugin('dialognotifier', new Zarafa.core.ui.notifier.DropdownBoxPlugin());
});
Ext.namespace('Zarafa.core.ui.notifier');

/**
 * @class Zarafa.core.ui.notifier.LiveScrollSliderPlugin
 * @extends Zarafa.core.ui.notifier.PagingSliderPlugin
 *
 * Special {@link Zarafa.core.ui.notifier.LiveScrollSliderPlugin LiveScrollSliderPlugin} which shows the
 * a nice slider container which contains live scroll loading information into it.
 */
Zarafa.core.ui.notifier.LiveScrollSliderPlugin = Ext.extend(Zarafa.core.ui.notifier.PagingSliderPlugin, {
	/**
	 * @cfg {Boolean} pagingEnabled
	 * The pagingEnabled is true only when pagination is enabled.(Default to false)
	 */
	pagingEnabled: false,

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

		Ext.applyIf(config, {
			// Position the container at the bottom-center
			sliderContainerPosition: 'b',
			pagingEnabled: false,
			sliderDuration: 5000
		});

		Zarafa.core.ui.notifier.LiveScrollSliderPlugin.superclass.constructor.call(this, config);
	}
});

Zarafa.onReady(function() {
	container.getNotifier().registerPlugin('livescrollslider', new Zarafa.core.ui.notifier.LiveScrollSliderPlugin());
});Ext.namespace('Zarafa.core.ui.notifier');

/**
 * @class Zarafa.core.ui.notifier.PaginationSliderPlugin
 * @extends Zarafa.core.ui.notifier.PagingSliderPlugin
 *
 * Special {@link Zarafa.core.ui.notifier.PaginationSliderPlugin PaginationSliderPlugin} which shows the
 * a nice slider container which contains {@link Zarafa.common.ui.PagingToolbar PagingToolbar} into it.
 */
Zarafa.core.ui.notifier.PaginationSliderPlugin = Ext.extend(Zarafa.core.ui.notifier.PagingSliderPlugin, {

	/**
	 * @cfg {Boolean} pagingEnabled
	 * The pagingEnabled is true only when pagination is enabled.(Default to true)
	 */
	pagingEnabled: true,

	/**
	 * @cfg {Number} sliderDuration
	 * A specified time duration after that slide gets hide/remove the slide
	 * from DOM (defaults to <code>5</code> seconds).
	 */
	sliderDuration: 2000,

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

		Ext.applyIf(config, {
			// Position the container at the bottom-center
			sliderContainerPosition: 'b',
			pagingEnabled: true,
			sliderDuration: 2000
		});

		Zarafa.core.ui.notifier.PaginationSliderPlugin.superclass.constructor.call(this, config);
	},

	/**
	 * Notifier notifies only when number of pages are more than one page.
	 *
	 * @param {String} category The category which applies to the notification.
	 * @param {String} title The title which must be shown in the message.
	 * @param {String} message The message which should be displayed.
	 * @param {Object} config Configuration object which can be applied to the notifier
	 * This object can contain keys like:
	 * - parentEl: Which is the container to which the notifier should be restricted
	 * - destroy: Don't create new message, but destroy previous one
	 * - model: {@link Zarafa.core.ContextModel model} of the current context.
	 * - update: true to realign the slider.
	 * - slider: The slider container.
	 * @return {Ext.Element} The slider element which was created
	 */
	notify: function(category, title, message, config)
	{
		if (!config.destroy) {
			var total = config.store.totalLength;
			var pageSize = container.getSettingsModel().get('zarafa/v1/main/page_size');
			var pages = total < pageSize ? 1: Math.ceil(total / pageSize);
			if (pages == 1) {
				return;
			}
		}
		return Zarafa.core.ui.notifier.PaginationSliderPlugin.superclass.notify.apply(this, arguments);
	}
});

Zarafa.onReady(function() {
	container.getNotifier().registerPlugin('pagingslider', new Zarafa.core.ui.notifier.PaginationSliderPlugin());
});Ext.namespace('Zarafa.core.ui.notifier');

/**
 * @class Zarafa.core.ui.notifier.PopupBoxPlugin
 * @extends Zarafa.core.ui.notifier.SliderNotifierPlugin
 *
 * Special {@link Zarafa.core.ui.notifier.SliderNotifierPlugin SliderPlugin} which sends all
 * messages to a nice popup box in the lower-right corner of the screen. This plugin
 * will be registered to the {@link Zarafa.core.ui.notifier.Notifier notifier} using
 * the name 'popup'.
 */
Zarafa.core.ui.notifier.PopupBoxPlugin = Ext.extend(Zarafa.core.ui.notifier.SliderNotifierPlugin, {
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.apply(config, {
			itemCls: 'zarafa-notifier-popup-item',
			// Position the container at the bottom-right
			sliderContainerPosition: 'br',
			// We want to show the message for 5 seconds
			msgLifetime: 5
		});

		Zarafa.core.ui.notifier.PopupBoxPlugin.superclass.constructor.call(this, config);
	}
});

Zarafa.onReady(function() {
	container.getNotifier().registerPlugin('popup', new Zarafa.core.ui.notifier.PopupBoxPlugin());
});
Ext.namespace('Zarafa.addressbook');

/**
 * @class Zarafa.addressbook.AddressBookEmailAddressesSubStore
 * @extends Zarafa.core.data.MAPISubStore
 */
Zarafa.addressbook.AddressBookEmailAddressesSubStore = Ext.extend(Zarafa.core.data.MAPISubStore, {

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

		var recordType = Ext.data.Record.create([
			{ name: 'address', type: 'string' }
		]);

		Ext.applyIf(config, {
			// provide a default reader
			reader: new Zarafa.core.data.JsonReader({
				id: 'address',
				idProperty: 'address'
			}, recordType)
		});

		Zarafa.addressbook.AddressBookEmailAddressesSubStore.superclass.constructor.call(this, config);
	}
});
Ext.namespace('Zarafa.addressbook');

/**
 * @class Zarafa.addressbook.AddressBookHierarchyNotificationResponseHandler
 * @extends Zarafa.core.data.AbstractNotificationResponseHandler
 *
 * The default {@link Zarafa.core.data.AbstractNotificationResponseHandler ResponseHandler}
 * for AddressBook Hierarchy Notifications. This can handle addressbook updates.
 */
Zarafa.addressbook.AddressBookHierarchyNotificationResponseHandler = Ext.extend(Zarafa.core.data.AbstractNotificationResponseHandler, {

	/**
	 * Handle the 'addressbook' action.
	 * This will update the {@link Zarafa.addressbook.AddressBookHierarchyStore store}
	 */
	doAddressbook: function ()
	{
		this.addNotification(Zarafa.core.data.Notifications.objectModified, null, null);
	}
});
Ext.namespace('Zarafa.addressbook');

/**
 * @class Zarafa.addressbook.AddressBookHierarchyProxy
 * @extends Zarafa.core.data.IPMProxy
 *
 * proxy object which will communicate between store and server side code
 */
Zarafa.addressbook.AddressBookHierarchyProxy = Ext.extend(Zarafa.core.data.IPMProxy, {
	/**
	 * @constructor
	 * @param config Configuration structure
	 */
	constructor: function(config)
	{
		config = config || {};

		var module = Zarafa.core.ModuleNames.getModule('addressbook');

		Ext.applyIf(config, {
			listModuleName: module.list,
			itemModuleName: module.item
		});

		Zarafa.addressbook.AddressBookHierarchyProxy.superclass.constructor.call(this, config);
	},

	/**
	 * This will create a {@link Zarafa.core.data.ProxyResponseHandler ProxyResponseHandler} object
	 * which will be used by the {@link Zarafa.core.data.ResponseRouter ResponseRouter} when the
	 * response for the given request has returned.
	 *
	 * @param {String} modulename The modulename which is being accessed with this request
	 * @param {Zarafa.core.Actions} serverAction The action to perform on the server.
	 * @param {Ext.data.Api.action} action name of the action to perform.
	 * @param {Ext.data.Record[]} records list of records to operate on.
	 * @param {Object} parameters object containing user parameters such as range (pagination) information, sorting information, etc.
	 * @param {Ext.data.DataReader} reader data reader. Converts raw JavaScript objects (in our case) to instances of {@link Ext.data.Record}
	 * @param {Function} callback call back function to call when the request has finished successfully.
	 * @param {Object} scope scope for the call back function.
	 * @param {Object} args arguments object. This will be passed to the call back function on successful read.
	 * @return {Object} An instance of the {@link Zarafa.core.data.ProxyResponseHandler ProxyResponseHandler}
	 * which should be used for this request.
	 * @private
	 */
	getResponseHandlerForRequest: function(modulename, serverAction, action, records, parameters, reader, callback, scope, args)
	{
		return new Zarafa.addressbook.AddressBookResponseHandler({
			proxy: this,
			action: action,
			reader: reader,
			options: args,
			callback: callback,
			scope: scope
		});
	}
});
/*
 * #dependsFile client/zarafa/core/mapi/ObjectType.js
 * #dependsFile client/zarafa/core/data/RecordFactory.js
 */
Ext.namespace('Zarafa.addressbook');

/**
 * @class Zarafa.addressbook.AddressBookHierchyRecordFields
 * Array of {@link Ext.data.Field field} configurations for the
 * {@link Zarafa.core.data.IPMRecord IPMRecord} object.
 * These fields will be available in all
 * {@link Zarafa.core.mapi.ObjectType.MAPI_ABCONT MAPI_ABCONT} type messages.
 */
Zarafa.addressbook.AddressBookHierchyRecordFields = [
	{name: 'entryid'},
	{name: 'display_name'},
	{name: 'type'},
	{name: 'store_entryid'},
	{name: 'parent_entryid'},
	{name: 'depth', type: 'int', defaultValue: 0},
	{name: 'group_header', type: 'boolean', defaultValue: false}
];

Zarafa.core.data.RecordFactory.setBaseClassToObjectType(Zarafa.core.mapi.ObjectType.MAPI_ABCONT, Zarafa.core.data.MAPIRecord);
Zarafa.core.data.RecordFactory.addFieldToObjectType(Zarafa.core.mapi.ObjectType.MAPI_ABCONT, Zarafa.addressbook.AddressBookHierchyRecordFields);
Ext.namespace('Zarafa.addressbook');

/**
 * @class Zarafa.addressbook.AddressBookTelephoneNumberSubStore
 * @extends Zarafa.core.data.MAPISubStore
 */
Zarafa.addressbook.AddressBookTelephoneNumberSubStore = Ext.extend(Zarafa.core.data.MAPISubStore, {

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

		var recordType = Ext.data.Record.create([
			{ name: 'number', type: 'string' }
		]);

		Ext.applyIf(config, {
			// provide a default reader
			reader: new Zarafa.core.data.JsonReader({
				root: 'item',
				id: 'number',
				idProperty: 'number'
			}, recordType)
		});

		Zarafa.addressbook.AddressBookTelephoneNumberSubStore.superclass.constructor.call(this, config);
	}
});
Ext.namespace('Zarafa.addressbook.dialogs');

/**
 * @class Zarafa.addressbook.dialogs.ABGroupContentPanel
 * @extends Zarafa.core.ui.RecordContentPanel
 * @xtype zarafa.abgroupdetailcontentpanel
 *
 * This class will be used to create a content panel for showing userGroups/Company,
 */
Zarafa.addressbook.dialogs.ABGroupDetailContentPanel = Ext.extend(Zarafa.core.ui.RecordContentPanel, {

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

		Ext.applyIf(config, {
			xtype	: 'zarafa.abgroupdetailcontentpanel',
			layout	: 'fit',
			border	: false,
			title: _('Group details'),
			items: [{
				xtype: 'zarafa.abgroupdetailpanel'
			}]
		});

		Zarafa.addressbook.dialogs.ABGroupDetailContentPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Update the {@link Ext.Panel Panel} with the given {@link Zarafa.core.data.IPMRecord IPMRecord}
	 * @param {Zarafa.core.data.IPMRecord} record The record to update the panel with
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 */
	update: function(record, contentReset)
	{
		this.updateTitleFromRecord(this.record);
	},

	/**
	 * When record has been updated, title also has to be - for instance if we have the subject
	 * in the title and the subject changes
	 * Calls {@link #setTitle} this.setTitle in order to update
	 * @param {Zarafa.core.data.MAPIRecord} record The record that has been updated
	 */
	updateTitleFromRecord: function(record)
	{
		var display_name = record.get('display_name');
		if(!Ext.isEmpty(display_name)){
			this.setTitle(display_name);
		} else {
			this.setTitle(this.initialConfig.title);
		}
	}
});

// register content panel
Ext.reg('zarafa.abgroupdetailcontentpanel', Zarafa.addressbook.dialogs.ABGroupDetailContentPanel);
Ext.namespace('Zarafa.addressbook.dialogs');

/**
 * @class Zarafa.addressbook.dialogs.ABUserDetailContentPanel
 * @extends Zarafa.core.ui.RecordContentPanel
 * @xtype zarafa.zarafa.abuserdetailcontentpanel
 */
Zarafa.addressbook.dialogs.ABUserDetailContentPanel = Ext.extend(Zarafa.core.ui.RecordContentPanel, {

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

		Ext.applyIf(config, {
			xtype	: 'zarafa.abuserdetailcontentpanel',
			layout	: 'fit',
			border  : false,
			// Override from Zarafa.core.ui.RecordContentPanel
			title : _('User details'),
			items	: [{
				xtype		: 'zarafa.abuserdetailpanel'
			}]
		});

		Zarafa.addressbook.dialogs.ABUserDetailContentPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Update the {@link Ext.Panel Panel} with the given {@link Zarafa.core.data.IPMRecord IPMRecord}
	 * @param {Zarafa.core.data.IPMRecord} record The record to update the panel with
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 */
	update : function(record, contentReset)
	{
		this.updateTitleFromRecord(this.record);
		if(contentReset){
			this.updateIconFromRecord(this.record);
		}
	},

	/**
	 * Update this panel's icon class from the record that it contains
	 * First obtains the icon class from a mapping, then calls {@link #setIcon}
	 *
	 * @param {Zarafa.core.data.MAPIRecord} record The record bound to this component
	 * @private
	 */
	updateIconFromRecord : function(record)
	{
		//TODO: create a new icon mapping for tabs
		var iconCls = Zarafa.common.ui.IconClass.getIconClass(record);
		this.setIcon(iconCls);
	},

	/**
	 * When record has been updated, title also has to be - for instance if we have the subject
	 * in the title and the subject changes
	 * Calls {@link #setTitle} this.setTitle in order to update
	 * @param {Zarafa.core.data.MAPIRecord} record The record that has been updated
	 */
	updateTitleFromRecord : function(record)
	{
		var display_name = record.get('display_name');
		if(!Ext.isEmpty(display_name)){
			this.setTitle(display_name);
		} else {
			this.setTitle(this.initialConfig.title);
		}
	}
});

Ext.reg('zarafa.abuserdetailcontentpanel', Zarafa.addressbook.dialogs.ABUserDetailContentPanel);
Ext.namespace('Zarafa.addressbook.dialogs');

/**
 * @class Zarafa.addressbook.dialogs.ABUserSelectionContentPanel
 * @extends Zarafa.core.ui.RecordContentPanel
 * @xtype zarafa.abuserselectioncontentpanel
 */
Zarafa.addressbook.dialogs.ABUserSelectionContentPanel = Ext.extend(Zarafa.core.ui.RecordContentPanel, {
	/**
	 * @cfg {Function} callback the callback function to return to after selecting user from AdressBook.
	 */
	callback: Ext.emptyFn,

	/**
	 * @cfg {Boolean} singleSelect false to allow multiple selections(defaults to true allowing selection of only one row at a time)
	 */
	singleSelect: true,

	/**
	 * @cfg {Object} scope The scope in which the {@link #callback} will be called
	 */
	scope: undefined,

	/**
	 * @cfg (Boolean) Set to true to hide contacts folders in the address book
	 * hierarchy dropdown.
	 */
	hideContactsFolders: false,

	/**
	 * @cfg {Object} listRestriction The default restriction which
	 * must be send to the server side when obtaining a fresh list
	 * from the server. This can be used to restrict the visibility
	 * of users, groups, companies etc.
	 */
	listRestriction: undefined,

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

		config = Ext.applyIf(config, {
			xtype: 'zarafa.abuserselectioncontentpanel',
			layout: 'fit',
			title: _('Address Book'),
			width: 1000,
			items: [{
				xtype: 'zarafa.abuserselectionpanel',
				hideContactsFolders: config.hideContactsFolders,
				listRestriction: config.listRestriction,
				callback: config.callback,
				singleSelect: Ext.isDefined(config.singleSelect) ? config.singleSelect : this.singleSelect,
				scope: config.scope
			}]
		});

		// Call parent constructor
		Zarafa.addressbook.dialogs.ABUserSelectionContentPanel.superclass.constructor.call(this, config);
	}
});

Ext.reg('zarafa.abuserselectioncontentpanel', Zarafa.addressbook.dialogs.ABUserSelectionContentPanel);
Ext.namespace('Zarafa.addressbook.dialogs');

/**
 * @class Zarafa.addressbook.dialogs.AddressBookContentPanel
 * @extends Zarafa.core.ui.RecordContentPanel
 * @xtype addressbook.addressbookcontentpanel
 */
Zarafa.addressbook.dialogs.AddressBookContentPanel = Ext.extend(Zarafa.core.ui.ContentPanel, {
	/**
	 * @cfg (Boolean) Set to true to hide contacts folders in the address book
	 * hierarchy dropdown.
	 */
	hideContactsFolders: false,

	/**
	 * @cfg {Object} listRestriction The default restriction which
	 * must be send to the server side when obtaining a fresh list
	 * from the server. This can be used to restrict the visibility
	 * of users, groups, companies etc.
	 */
	listRestriction: undefined,

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

		Ext.applyIf(config, {
			xtype: 'zarafa.addressbookcontentpanel',
			layout: 'fit',
			cls: 'k-addressbookcontentpanel',
			title: _('Address Book'),
			width: 1000,
			items: [{
				xtype: 'zarafa.addressbookpanel',
				hideContactsFolders: config.hideContactsFolders,
				listRestriction: config.listRestriction
			}]
		});

		// Call parent constructor
		Zarafa.addressbook.dialogs.AddressBookContentPanel.superclass.constructor.call(this, config);
	}
});

Ext.reg('zarafa.addressbookcontentpanel', Zarafa.addressbook.dialogs.AddressBookContentPanel);
Ext.namespace('Zarafa.addressbook.ui');

/**
 * @class Zarafa.addressbook.ui.AddressBookBox
 * @extends Zarafa.common.recipientfield.ui.RecipientBox
 * @xtype zarafa.addressbookbox
 *
 * Special {@link Zarafa.common.ui.Box box} which is used
 * for displaying AddressBook records in the {@link Zarafa.addressbook.ui.AddressBookBoxField}.
 * This works similar to the {@link Zarafa.common.recipientfield.ui.RecipientBox} regarding
 * resolving, but it will mark any non-AddressBook user as invalid.
 */
Zarafa.addressbook.ui.AddressBookBox = Ext.extend(Zarafa.common.recipientfield.ui.RecipientBox, {

	/**
	 * @cfg {Zarafa.core.mapi.DisplayType} validDisplayType The displaytype which is considered
	 * valid as a box. This can be used to mark a particular AddressBook types as invalid.
	 */
	validDisplayType: undefined,


	/**
	 * Check if the given {@link Ext.data.Record record} is valid. This function can be
	 * overridden by the childclasses to indicate if the given record is valid.
	 *
	 * This class will check if the given record is {@link Zarafa.core.data.IPMRecipientRecord#isResolved resolved},
	 * and if the display_type is {@link #validDisplayType valid}.
	 *
	 * @param {Zarafa.core.data.IPMRecipientRecord} record The record to check
	 * @return {Boolean} True if the record is valid
	 * @protected
	 */
	isValidRecord: function(record)
	{
		return record.isResolved() && (!Ext.isDefined(this.validDisplayType) || record.get('display_type') === this.validDisplayType);
	}
});

Ext.reg('zarafa.addressbookbox', Zarafa.addressbook.ui.AddressBookBox);
Ext.namespace('Zarafa.advancesearch');

/**
 * @class Zarafa.advancesearch.AdvanceSearchContextModel
 * @extends Zarafa.core.ContextModel
 */
Zarafa.advancesearch.AdvanceSearchContextModel = Ext.extend(Zarafa.core.ContextModel, {

	/**
	 * @cfg {Object} stores The object which contains more then one {@link Zarafa.advancesearch.AdvanceSearchStore
	 * AdvanceStore} instances. Each {@link Zarafa.advancesearch.AdvanceSearchStore AdvanceStore} contains search
	 * results of respective search tab. When user creates new search tab it will create new
	 * {@link Zarafa.advancesearch.AdvanceSearchStore AdvanceStore} object for that particular search tab
	 * and push that object to {@link #stores} object. When user close search tab, after the removing of search
	 * folder from server side we remove {@link Zarafa.advancesearch.AdvanceSearchStore AdvanceStore} which mapped with that
	 * particular search tab from {@link #stores}. While switching between search tabs we find the relevant
	 * {@link Zarafa.advancesearch.AdvanceSearchStore AdvanceStore} from {@link #stores} and set it to store config of
	 * model using {@link #setActiveStore} so model can use that store for performing operation on that tab.
	 * @Private
	 */
	stores: {},

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

		if(!Ext.isDefined(config.store)) {
			config.store = new Zarafa.advancesearch.AdvanceSearchStore();
		}

		Ext.applyIf(config, {
			statefulRecordSelection: true,
			current_data_mode: Zarafa.common.data.DataModes.ALL,
			stateful: false
		});

		Zarafa.advancesearch.AdvanceSearchContextModel.superclass.constructor.call(this, config);
		container.on('folderselect', this.onFolderSelect, this);
		container.getHierarchyStore().on('removeFolder', this.onHierarchyRemoveFolder, this);
	},

	/**
	 * Create a new {@link Zarafa.core.data.IPFRecord SearchFolder} record.
	 *
	 * @param {String} folderName display name of folder which we are going to create.
	 * @return {Zarafa.core.data.IPFRecord} The new {@link Zarafa.core.data.IPFRecord IPFRecord}.
	 */
	createSearchFolderRecord: function (folderName)
	{
		var hierarchyStore = container.getHierarchyStore();
		var defaultStore = hierarchyStore.getDefaultStore();
		var folder = defaultStore.getFavoritesRootFolder();
		var entryId = '';
		if (defaultStore.getFavoritesStore().findExact('entryid',this.store.searchFolderEntryId) === -1){
			entryId = this.store.searchFolderEntryId;
		}
		var record = Zarafa.core.data.RecordFactory.createRecordObjectByObjectType(Zarafa.core.mapi.ObjectType.MAPI_FOLDER, {
			entryid: entryId,
			parent_entryid: folder.get('entryid'),
			store_entryid: folder.get('store_entryid'),
			display_name: folderName,
			folder_type: Zarafa.core.mapi.MAPIFolderType.FOLDER_SEARCH
		});

		return record;
	},

	/**
	 * Set given {@link Zarafa.advancesearch.AdvanceSearchStore AdvanceSearchStore} instance to active store for the model.
	 * @param {Zarafa.advancesearch.AdvanceSearchStore} store the newly created store.
	 */
	setActiveStore: function(store)
	{
		this.store = store;
	},

	/**
	 * Provide active {@link Zarafa.advancesearch.AdvanceSearchStore AdvanceSearchStore}
	 * @return {Zarafa.advancesearch.AdvanceSearchStore} return active {@link Zarafa.advancesearch.AdvanceSearchStore AdvanceSearchStore}
	 * if found from {@link #stores} object else return undefined.
	 */
	getActiveStore: function()
	{
		return this.store;
	},

	/**
	 * Pushes the newly create {@link Zarafa.advancesearch.AdvanceSearchStore AdvanceSearchStore} in to {@link #stores} object.
	 *
	 * @param {String} searchTabId the newly created {@link Zarafa.advancesearch.dialogs.SearchContentPanel SearchContentPanel} name.
	 * @param {Zarafa.advancesearch.AdvanceSearchStore} store the newly created {@link Zarafa.advancesearch.AdvanceSearchStore AdvanceSearchStore}.
	 */
	pushStore: function(searchTabId, store)
	{
		this.stores[searchTabId] = store;
	},

	/**
	 * Discards the {@link Zarafa.advancesearch.AdvanceSearchStore AdvanceSearchStore} object from {@link #stores} object.
	 *
	 * @param {String} searchTabId is {@link Zarafa.advancesearch.dialogs.SearchContentPanel search content panel} name,
	 * which is used to find the mapped {@link Zarafa.advancesearch.AdvanceSearchStore search store} from {@link #stores} object.
	 */
	discardStore: function(searchTabId)
	{
		delete this.stores[searchTabId];
	},

	/**
	 * Creates new instance of {@link Zarafa.advancesearch.AdvanceSearchStore AdvanceSearchStore} object for the newly
	 * created search tab and push that {@link Zarafa.advancesearch.AdvanceSearchStore AdvanceSearchStore} object in to {@link #stores}
	 * object for future use.
	 *
	 * @param {object} config the configuration object for the {@link Zarafa.advancesearch.AdvanceSearchStore AdvanceSearchStore}
	 * @return {Zarafa.advancesearch.AdvanceSearchStore} return {@link Zarafa.advancesearch.AdvanceSearchStore AdvanceSearchStore}
	 */
	createNewSearchStore: function (config)
	{
		var store = new Zarafa.advancesearch.AdvanceSearchStore({
			searchStoreUniqueId: config.searchTabId
		});
		this.setActiveStore(store);
		this.pushStore(config.searchTabId , store);
		return store;
	},

	/**
	 * Function is call the {@link Zarafa.mail.MailContextModel#createResponseRecord} to create
	 * a new {@link Zarafa.core.data.IPMRecord IPMRecord} for responding to an original
	 * {@link Zarafa.core.data.IPMRecord IPMRecord}. This will also set subject, body, attachment, recipient
	 * properties based on {@link Zarafa.mail.data.ActionTypes ActionType} provided.
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The original {@link Zarafa.core.data.IPMRecord IPMRecord}.
	 * @param {String} actionType The action type for the given {@link Zarafa.core.data.IPMRecord record}.
	 * Can be any of the values of {@link Zarafa.mail.data.ActionTypes ActionTypes}.
	 * @param {Zarafa.core.data.IPMRecord} responseRecord The new {@link Zarafa.core.data.IPMRecord IPMRecord}.
	 * @return {Zarafa.core.data.IPMRecord} return the response record.
	 * @private
	 */
	createResponseRecord: function(record, actionType, responseRecord)
	{
		var mailContextModel = container.getContextByName('mail').getModel();
		return mailContextModel.createResponseRecord(record, actionType, responseRecord);
	},

	/**
	 * Event handler triggers when context or folder gets change.
	 * it will set the newly selected folder to default folder for the
	 * advance search.
	 *
	 * @param {Zarafa.core.data.IPFRecord} folders.
	 */
	onFolderSelect: function(folders)
	{
		var folder;
		if(Array.isArray(folders)) {
			folder = folders[0];
		} else {
			folder = folders;
		}

		// Check if we can use this folder
		if ( !( folder instanceof Zarafa.core.data.IPFRecord && Ext.isDefined(folder.getMAPIStore()) ) ){
			// Don't change the default folder
			return;
		}

		if(Ext.isDefined(folder) && !folder.isIPMSubTree()) {
			var defaultFolder = this.getDefaultFolder();
			var isSameFolder = folder.equals(defaultFolder);
			if(!isSameFolder) {
				this.setFolders(folders);
			}
		}
	},

	/**
	 * Event handler triggers when folder was delete from hierarchy. function was
	 * responsible to clear/delete the search criteria from settings.
	 *
	 * @param {Zarafa.hierarchy.data.HierarchyStore} store The store which fired the event
	 * @param {Zarafa.hierarchy.data.MAPIStoreRecord} mapiStore mapi store in which folders are deleted.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder which was deleted from hierarchy.
	 * @private
	 */
	onHierarchyRemoveFolder: function (store, mapiStore, folder)
	{
		if (folder.isSearchFolder()) {
			container.getSettingsModel().remove('zarafa/v1/contexts/search/search_criteria/'+folder.get('entryid'));
		}
	}
});
Ext.namespace('Zarafa.advancesearch.dialogs');

/**
 * @class Zarafa.advancesearch.dialogs.CreateSearchFolderContentPanel
 * @extends Zarafa.core.ui.RecordContentPanel
 * @xtype zarafa.createsearchfoldercontentpanel
 *
 * Content panel for users to create the search folder.
 */
Zarafa.advancesearch.dialogs.CreateSearchFolderContentPanel = Ext.extend(Zarafa.core.ui.RecordContentPanel, {

	/**
	 * @cfg {String} searchStoreEntryId Entryid of the store to which the search folder belongs.
	 */
	searchStoreEntryId: undefined,

	/**
	 * @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, {
			xtype: 'zarafa.createsearchfoldercontentpanel',
			title: _('Add to favorites'),
			recordComponentPluginConfig: Ext.applyIf(config.recordComponentPluginConfig || {}, {
				allowWrite: true,
				enableOpenLoadTask: false
			}),
			layout: 'fit',
			width: 250,
			height: 150,
			stateful: false,
			items: [{
				xtype: 'zarafa.createsearchfolderpanel',
				searchText: config.searchText,
				buttons: [{
					text: _('Add'),
					handler: this.onAddFolder,
					scope: this
				},{
					text: _('Cancel'),
					handler: this.onCancel,
					scope: this
				}]
			}]
		});

		Zarafa.advancesearch.dialogs.CreateSearchFolderContentPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Update the given {@link Zarafa.core.data.IPFRecord record} with
	 * the values from this {@link Ext.Panel panel}.
	 *
	 * @param {Zarafa.core.data.IPFRecord} record The record to update
	 */
	updateRecord: function(record)
	{
		record.beginEdit();
		record.set('display_name', this.searchFolderTextField.getValue());
		record.endEdit();
	},

	/**
	 * Update the components with the given record.
	 *
	 * @param {Zarafa.core.data.IPFRecord} record The record to update in this component
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 */
	update: function(record, contentReset)
	{
		this.searchFolderTextField.setValue(record.get('display_name'));
	},

	/**
	 * Event handler triggers when add button is press. function is add
	 * the respective message actions which used to create search folder.
	 */
	onAddFolder: function()
	{
		var newFolderName = this.searchFolderTextField.getValue().trim();
		if (Ext.isEmpty(newFolderName)) {
			Ext.MessageBox.show({
				title: _('Empty folder name'),
				msg: _("You must specify a name."),
				buttons: Ext.MessageBox.OK
			});

			return;
		}

		this.record.addToFavorites(this.searchStoreEntryId);
		if (Ext.isEmpty(this.record.get('entryid'))) {
			this.record.addMessageAction('search_folder_entryid', this.searchFolderEntryId);
			this.record.getStore().setBaseParam('subfolders', this.includeSubFolder);
		}
		Zarafa.advancesearch.dialogs.CreateSearchFolderContentPanel.superclass.onOk.apply(this, arguments);
	}
});

Ext.reg('zarafa.createsearchfoldercontentpanel', Zarafa.advancesearch.dialogs.CreateSearchFolderContentPanel);
Ext.namespace('Zarafa.advancesearch.ui');

/**
 * @class Zarafa.advancesearch.ui.SearchGrid
 * @extends Zarafa.common.ui.grid.MapiMessageGrid
 * @xtype zarafa.searchgrid
 */
Zarafa.advancesearch.ui.SearchGrid = Ext.extend(Zarafa.common.ui.grid.MapiMessageGrid, {
	/**
	 * @cfg {Zarafa.advancesearch.AdvanceSearchContext} searchContext The searchContext to which this panel belongs
	 */
	searchContext: undefined,

	/**
	 * The {@link Zarafa.advancesearch.AdvanceSearchContextModel} which is obtained from the {@link #context}.
	 * @property
	 * @type Zarafa.advancesearch.AdvanceSearchContextModel
	 */
	model: undefined,

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

		if (!Ext.isDefined(config.model) && Ext.isDefined(config.searchContext)) {
			config.model = config.searchContext.getModel();
		}

		if (!Ext.isDefined(config.store) && Ext.isDefined(config.model)) {
			/**
			 * For the first search tab we have already created store instance in model, which is created at the time of the
			 * search context model initialization so we use that store for the first search tab and for the second search tab
			 * we create the new instance of the search store and so on.
			 */
			if(!Ext.isDefined(config.model.store.getSearchStoreUniqueId())){
				var model = config.model;
				model.store.searchStoreUniqueId = config.searchTabId;
				config.store = model.store;
				model.pushStore(config.searchTabId , model.store);
			} else {
				config.store = config.model.createNewSearchStore({searchTabId: config.searchTabId});
			}
		}

		config.store = Ext.StoreMgr.lookup(config.store);

		var searchFolder = config.searchCenterPanel.searchPanel.searchFolder;
		if(Ext.isDefined(searchFolder)) {
			config.store.setSearchStoreEntryId(searchFolder.get('store_entryid'));
			config.store.setSearchEntryId(searchFolder.get('entryid'));
		}

		Ext.applyIf(config, {
			xtype: 'zarafa.searchgrid',
			cls: 'zarafa-searchgrid',
			ariaLabel: _('Search results'),
			border: false,
			// Don't make the search grid stateful. By default we always want Gromox to sort new search results on relevance.
			stateful: false,
			statefulRelativeDimensions: false,
			loadMask: this.initLoadMask(),
			sm: this.initSelectionModel(),
			cm: new Zarafa.advancesearch.ui.SearchGridColumnModel({
				grid: this,
				folder: config.model.getDefaultFolder()
			}),
			enableDragDrop: true,
			ddGroup: 'dd.mapiitem',
			viewConfig: this.initViewConfig(),
			enableColumnHide: false,
			enableColumnMove: false,
			enableColumnResize: false,
			enableHdMenu: false,
			supportLiveScroll: true,
			autoExpandMin: 200,
			sortableRecordsMax: 1000
		});

		Zarafa.advancesearch.ui.SearchGrid.superclass.constructor.call(this, config);
	},

	/**
	 * Initialize event handlers
	 * @private
	 */
	initEvents: function()
	{
		Zarafa.advancesearch.ui.SearchGrid.superclass.initEvents.call(this);

		this.on({
			'headerclick': this.onHeaderClick,
			'cellclick': this.onCellClick,
			'rowclick': this.onRowClick,
			'rowcontextmenu': this.onRowContextMenu,
			'rowdblclick': this.onRowDblClick,
			scope: this
		});

		this.mon(this.getView(), 'livescrollstart', this.onLiveScrollStart, this);
		this.mon(this.getView(), 'beforesort', this.onBeforeSort, this);

		// Add a buffer to the following 2 event handlers. These are influenced by Extjs when a record
		// is removed from the store. However removing of records isn't performed in batches. This means
		// that we need to offload the event handlers attached to removing of records in case that
		// a large batch of records is being removed.
		this.mon(this.getSelectionModel(), 'rowselect', this.onRowSelect, this, { buffer: 1 });
		this.mon(this.getSelectionModel(), 'selectionchange', this.onSelectionChange, this, { buffer: 1 });

		this.mon(this.model, 'searchstop', this.onSearchStop, this);

		this.mon(this.searchContext, 'viewchange', this.onContextViewChange, this);

		this.store.on('beforeupdatesearch', this.onBeforeUpdateSearch, this);

		this.mon(this.searchContext, 'viewmodechange', this.onContextViewModeChange, this);
		this.onContextViewModeChange(this.searchContext, this.searchContext.getCurrentViewMode());
	},

	/**
	 * Initialize the {@link Ext.grid.GridPanel.loadMask} field
	 *
	 * @return {Ext.LoadMask} The configuration object for {@link Ext.LoadMask}
	 * @private
	 */
	initLoadMask: function()
	{
		return {
			msg: _('Loading Search results') + '...'
		};
	},

	/**
	 * Initialize the {@link Ext.grid.GridPanel.sm SelectionModel} field
	 *
	 * @return {Ext.grid.RowSelectionModel} The subclass of {@link Ext.grid.AbstractSelectionModel}
	 * @private
	 */
	initSelectionModel: function()
	{
		return new Zarafa.advancesearch.ui.AdvanceSearchRowSelectionModel({
			singleSelect: false
		});
	},

	/**
	 * Initialize the {@link Ext.grid.GridPanel#viewConfig} field
	 *
	 * @return {Ext.grid.GridView} The configuration object for {@link Ext.grid.GridView}
	 * @private
	 */
	initViewConfig: function()
	{
		return {
			getRowClass: this.viewConfigGetRowClass,
			enableRowBody: true,
			getBodyCell: this.getBodyCell,
			getSubjectCell: this.getSubjectCell,
			// We need a rowselector depth of 15 because of the nested
			// table in the rowBody.
			rowSelectorDepth: 15
		};
	},

	/**
	 * Apply custom style and content for the row body. This will always
	 * apply the Read/Unread style to the entire row.
	 *
	 * @param {Ext.data.Record} record The {@link Ext.data.Record Record} corresponding to the current row.
	 * @param {Number} rowIndex The row index
	 * @param {Object} rowParams A config object that is passed to the row template during
	 * rendering that allows customization of various aspects of a grid row.
	 * If enableRowBody is configured true, then the following properties may be set by this function,
	 * and will be used to render a full-width expansion row below each grid row.
	 * @param {Ext.data.Store} store The Ext.data.Store this grid is bound to
	 * @return {String} a CSS class name to add to the row
	 * @private
	 */
	viewConfigGetRowClass: function (record, rowIndex, rowParams, store)
	{
		var cssClass = (Ext.isFunction(record.isRead) && !record.isRead() ? 'mail_unread' : 'mail_read');

		// Client-side fallback: hide records that don't match the unread filter.
		// Uses CSS hiding instead of store removal to avoid breaking live scroll.
		var unreadState = this.grid.getUnreadFilterState();
		if (unreadState && Ext.isFunction(record.isRead)) {
			if (unreadState === 'unread' && record.isRead()) {
				cssClass += ' k-search-filtered-out';
			} else if (unreadState === 'read' && !record.isRead()) {
				cssClass += ' k-search-filtered-out';
			}
		}

		if (this.enableRowBody) {
			rowParams.body = '<div class="zarafa-grid-body-container">';

			// Render the folder location
			var folder = container.getHierarchyStore().getFolder(record.get('parent_entryid'));
			var folderName = folder?.getDisplayName() || '';

			var folderLocation= String.format('<div class="k-folder-location-container">' +
				'<div class="k-folder-location" ext:qtip="{0}" ext:qwidth="100%">' +
					'<span class="k-folder-name">{1}</span>' +
				'</div></div>', folderName, Ext.util.Format.ellipsis(folderName, 17));

			cssClass += ' with-categories';
			var categories = Zarafa.common.categories.Util.getCategories(record);
			var categoriesHtml = Zarafa.common.categories.Util.getCategoriesHtml(categories);
			var category = '<div class="k-category"><div class="k-category-add-container"><span class="k-category-add"></span></div><div class="k-category-container">' + categoriesHtml + '</div></div>';

			// Get the subject cell content
			var subject = this.getSubjectCell(record);

			// Get the body cell content
			var body = this.getBodyCell(record);

			// Render subject, category
			if (!Ext.isEmpty(subject)) {
				rowParams.body += folderLocation + subject + category;
			} else {
				rowParams.body += '<div class="k-right-column"><div class="k-folder-location-row">' + folderLocation + '</div>' + category + '</div>';
				cssClass += ' with-no-subject';
			}

			// Render body
			rowParams.body += body + '</div>';
			return 'x-grid3-row-expanded ' + cssClass;
		}

		return 'x-grid3-row-collapsed ' + cssClass;
	},

	/**
	 * Apply custom style and content for the body cell
	 *
	 * @param {Ext.data.Record} record The {@link Ext.data.Record Record} corresponding to the current row.
	 * @returns {string} an html content string
	 */
	getBodyCell: function (record)
	{
		var meta = {};
		var body = '';
		var messageClass = record.get('message_class');
		switch (messageClass) {
			case 'IPM.Contact':
				body = Ext.util.Format.htmlEncode(record.get('email_address_1'));
				break;
			case 'IPM.Task':
			case 'IPM.TaskRequest':
				body = Zarafa.common.ui.grid.Renderers.percentage(record.get('percent_complete'), meta, record);
				break;
			default:
				body = Ext.util.Format.htmlEncode(record.get('body'));
		}

		return String.format('<div class="grid_compact grid_compact_left grid_compact_body {0}">{1}</div>', meta.css, body);
	},

	/**
	 * Apply custom style and content for the subject cell
	 *
	 * @param {Ext.data.Record} record The {@link Ext.data.Record Record} corresponding to the current row.
	 * @returns {string} an html content string
	 */
	getSubjectCell: function (record)
	{
		var subject = '';
		var meta = {};
		if (record.isMessageClass('IPM.Contact')) {
			subject = Zarafa.advancesearch.ui.SearchGridRenderers.phoneNumber(record.get('home_telephone_number'), meta, record);
		} else if (!record.isMessageClass('IPM.StickyNote')) {
			subject = Zarafa.common.ui.grid.Renderers.subject(record.get('subject'), meta, record);
		}

		if (!Ext.isEmpty(subject)) {
			subject = String.format('<div class="grid_compact grid_compact_left grid_compact_subject {0}">{1}</div>', meta.css, subject);
		}

		return subject;
	},

	/**
	 * Event handler which is fired when the currently active view inside the {@link #context}
	 * has been updated. This will update the call
	 * {@link #viewPanel}#{@link Zarafa.core.ui.SwitchViewContentContainer#switchView}
	 * to make the requested view active.
	 *
	 * @param {Zarafa.core.Context} context The context which fired the event.
	 * @param {Zarafa.common.data.Views} newView The ID of the selected view.
	 * @param {Zarafa.common.data.Views} oldView The ID of the previously selected view.
	 */
	onContextViewChange: function(context, newView, oldView)
	{
		if(oldView === Zarafa.common.data.Views.LIVESCROLL) {
			this.getView().resetScroll();
		}
	},

	/**
	 * Event handler for a click on a column header. If a search suggestion is shown
	 * it will trigger a new search with the suggestion.
	 * @param {Zarafa.advancesearch.ui.SearchGrid} grid The search grid
	 * @param {Number} columnIndex The index of the column of which the header was clicked
	 * @param {Event} event The click event
	 */
	onHeaderClick: function(grid, columnIndex, event)
	{
		var suggestion = this.store.suggestion;
		// Don't do anything if there is no suggestion or if the click was not on the suggestion text
		if ( Ext.isEmpty(suggestion) || event.target.className!=='zarafa-search-suggestion' ){
			return;
		}

		var searchToolbarPanel = this.searchCenterPanel.searchPanel.searchToolbar;
		var searchField = searchToolbarPanel.getAdvanceSearchField();
		searchField.setValue(suggestion);
		searchField.onTriggerClick();
	},

	/**
	 * Raw click event handler for the entire grid.
	 * Toggles the unread/read status when a user clicks on the mail icon of a message.
	 * Toggles the flag menu when a user clicks on the flag icon of a message.
	 *
	 * @param {Zarafa.advancesearch.ui.SearchGrid} grid the grid
	 * @param {Number} rowIndex The index of the clicked row
	 * @param {Number} columnIndex The column index of the clicked row
	 * @param {Ext.EventObject} e The click eventobject
	 */
	onCellClick: function(grid, rowIndex, columnIndex, e)
	{
		var record = this.store.getAt(rowIndex);
		if (!Ext.isDefined(record)) {
			return;
		}

		var cm = this.getColumnModel();
		var dataIndex = cm.getDataIndex(columnIndex);
		if (dataIndex === 'icon_index') {
			Zarafa.common.Actions.markAsRead(record, !record.isRead());
		} else if (dataIndex === 'flag_due_by') {
			Zarafa.common.Actions.openFlagsMenu(record, e.getXY(), false);
		}
	},

	/**
	 * Event handler that opens the categories dialog when the user clicks on the
	 * add category icon.
	 *
	 * @param {Zarafa.mail.ui.MailGrid} grid The grid which was right clicked
	 * @param {Number} rowIndex The index number of the row which was right clicked
	 * @param {Ext.EventObject} event The event structure
	 * @private
	 */
	onRowClick: function(grid, rowIndex, event)
	{
		if ( Ext.get(event.target).hasClass('k-category-add') ){
			// Get the record from the rowIndex
			var record = this.store.getAt(rowIndex);

			Zarafa.common.Actions.openCategoriesMenu([record], event.getXY());
		}
	},

	/**
	 * Event handler which is triggered when the user opens the context menu.
	 *
	 * There are some selection rules regarding the context menu. If no rows where
	 * selected, the row on which the context menu was requested will be marked
	 * as selected. If there have been rows selected, but the context menu was
	 * requested on a different row, then the old selection is lost, and the new
	 * row will be selected. If the row on which the context menu was selected is
	 * part of the previously selected rows, then the context menu will be applied
	 * to all selected rows.
	 *
	 * @param {Zarafa.advancesearch.ui.SearchGrid} grid The grid which was right clicked
	 * @param {Number} rowIndex The index number of the row which was right clicked
	 * @param {Ext.EventObject} event The event structure
	 * @private
	 */
	onRowContextMenu: function(grid, rowIndex, event)
	{
		var sm = this.getSelectionModel();

		if (sm.hasSelection()) {
			// Some records were selected...
			if (!sm.isSelected(rowIndex)) {
				// But none of them was the record on which the
				// context menu was invoked. Reset selection.
				sm.clearSelections();
				sm.selectRow(rowIndex);
			}
		} else {
			// No records were selected,
			// select row on which context menu was invoked
			sm.selectRow(rowIndex);
		}

		var records = sm.getSelections();

		Zarafa.core.data.UIFactory.openDefaultContextMenu(records, { position: event.getXY(), context: this.searchContext, shadowEdit: false });
	},

	/**
	 * Event handler which is triggered when the user double-clicks on a particular item in the
	 * grid. This will open a contentpanel which contains the selected item.
	 *
	 * @param {Zarafa.advancesearch.ui.SearchGrid} grid The Grid on which the user double-clicked
	 * @param {Number} rowIndex The Row number on which was double-clicked.
	 * @param {Ext.EventObject} e The event object
	 * @private
	 */
	onRowDblClick: function(grid, rowIndex, e)
	{
		this.cancelPreviewRecordTask();
		Zarafa.common.Actions.openMessageContent(this.getSelectionModel().getSelected());
	},

	/**
	 * Event handler which is trigggerd when the user selects a row from the {@link Ext.grid.GridPanel}.
	 * This will updates the {@link Zarafa.advancesearch.AdvanceSearchContextModel AdvanceSearchContextModel} with the record which
	 * was selected in the grid for preview
	 *
	 * @param {Ext.grid.RowSelectionModel} selectionModel The selection model used by the grid.
	 * @param {Integer} rowNumber The row number which is selected in the selection model
	 * @param {Ext.data.Record} record The record which is selected for preview.
	 * @private
	 */
	onRowSelect: function(selectionModel, rowNumber, record)
	{
		var count = selectionModel.getCount();

		if (count === 0) {
			this.cancelPreviewRecordTask();
			this.model.setPreviewRecord(undefined);
		} else if (count == 1 && selectionModel.getSelected() === record) {
			this.queuePreviewRecord(record);
		}
	},

	/**
	 * Event handler which is triggered when the {@link Zarafa.advancesearch.ui.SearchGrid grid}
	 * {@link Zarafa.core.data.IPMRecord record} selection is changed. This will inform
	 * the {@link Zarafa.advancesearch.AdvanceSearchContextModel contextmodel} about the change.
	 *
	 * @param {Ext.grid.RowSelectionModel} selectionModel The selection model used by the grid.
	 * @private
	 */
	onSelectionChange: function(selectionModel)
	{
		var selections = selectionModel.getSelections();

		this.model.setSelectedRecords(selections);
		if (Ext.isEmpty(selections)) {
			this.cancelPreviewRecordTask();
			this.model.setPreviewRecord(undefined);
		}
	},

	/**
	 * Lazily create and return the delayed task used for preview updates.
	 * @return {Ext.util.DelayedTask}
	 * @private
	 */
	getPreviewRecordTask: function()
	{
		if (!this.previewRecordTask) {
			this.previewRecordTask = new Ext.util.DelayedTask(this.applyPreviewRecord, this);
		}
		return this.previewRecordTask;
	},

	/**
	 * Queue updating the preview panel for the provided record.
	 * @param {Ext.data.Record} record The record to preview.
	 * @private
	 */
	queuePreviewRecord: function(record)
	{
		var task = this.getPreviewRecordTask();
		task.cancel();
		task.delay(50, this.applyPreviewRecord, this, [record]);
	},

	/**
	 * Cancel the delayed preview update task when no longer needed.
	 * @private
	 */
	cancelPreviewRecordTask: function()
	{
		if (this.previewRecordTask) {
			this.previewRecordTask.cancel();
		}
	},

	/**
	 * Apply the record to the preview panel.
	 * @param {Ext.data.Record} record The record to preview.
	 * @private
	 */
	applyPreviewRecord: function(record)
	{
		this.model.setPreviewRecord(record);
	},

	/**
	 * Event handler which triggered when scrollbar gets scrolled more then 90% of it`s height.
	 * it will be used to start live scroll on {@link Zarafa.core.data.ListModuleStore ListModuleStore}.
	 * also it will register event on {@link Zarafa.core.data.ListModuleStore ListModuleStore} to get
	 * updated batch of search result status.
	 *
	 * @param {Number} cursor the cursor contains the last index of record in grid.
	 * @private
	 */
	onLiveScrollStart: function(cursor)
	{
		this.model.startLiveScroll(cursor);
	},

	/**
	 * Event handler which triggered when header of grid was clicked to apply the sorting
	 * on {@link Zarafa.advancesearch.ui.SearchGrid search grid}. it will first stop the
	 * {@link Zarafa.core.ContextModel#stopLiveScroll live scroll} and then apply the sorting.
	 * @param {Ext.grid.GridView} gridView The GridView of the grid panel
	 * @private
	 */
	onBeforeSort: function(gridView)
	{
		var cm = this.getColumnModel();
		var dataIndex = cm.getDataIndex(gridView.activeHdIndex);
		// Apply sorting only on search date column, Because other columns doesn't support sorting.
		if (dataIndex === 'searchdate') {

			// Check if the total number of results is less then the maximum for which we will enable sorting
			var store = this.getStore();
			var recordTotalCount = store.getTotalCount();
			if ( recordTotalCount >= this.sortableRecordsMax ){
				Zarafa.common.dialogs.MessageBox.alert(
					_('Sorting tip'),
					_('You can sort by date if you narrow down your search results. Enable filters or adjust your input.'));
				return false;
			}

			// Stop the infinite scrolling if it is running
			this.model.stopLiveScroll();

			// Because we are sorting on the searchdate property and this property is added in the frontend
			// we cannot do remote sorting and will change to local sorting
			store.remoteSort = false;

			var loadedRecordCount = store.getCount();

			// If necessary we must first load all records from the store before we can do the sorting
			if (loadedRecordCount < recordTotalCount){

				// cancel pending load request when sorting is started.
				// NOTE: 'updatelist' is already handled when we stop infinite scroll
				if (store.isExecuting('list')) {
					store.proxy.cancelRequests('list');
				}

				// Set the options for the load request
				var options = {
					actionType: Zarafa.core.Actions['list'],
					folder: this.model.getFolders()[0],
					params: {
						restriction: {
							start: 0,
							limit: recordTotalCount
						}
					},
					callback: function(){
						// Do the sorting again, because Ext tried to do the sorting before we reloaded
						store.sort(store.sortInfo.field, store.sortInfo.direction);
					}
				};
				store.load(options);
			}
		}
	},

	/**
	 * Checks the unread filter state in the search tool box.
	 * @return {String|false} 'unread' if filtering for unread, 'read' if negated
	 * (NOT Unread = show read only), or false if no unread filter is active.
	 * @private
	 */
	getUnreadFilterState: function()
	{
		var searchPanel = this.searchCenterPanel && this.searchCenterPanel.searchPanel;
		var toolBox = searchPanel && searchPanel.searchToolBox;
		if (!toolBox || !toolBox.searchCriteria) {
			return false;
		}
		// Check for normal unread filter (checkbox checked)
		if (Ext.isArray(toolBox.searchCriteria['extra_fields']) &&
			toolBox.searchCriteria['extra_fields'].indexOf('message_flags') !== -1) {
			return 'unread';
		}
		// Check for negated unread filter (NOT Unread:Yes)
		if (Ext.isArray(toolBox.searchCriteria['negated_extra_fields']) &&
			toolBox.searchCriteria['negated_extra_fields'].indexOf('unread') !== -1) {
			return 'read';
		}
		return false;
	},

	/**
	 * Event handler for the {@link Zarafa.core.ContextModel#searchstop searchstop} event.
	 * This will {@link Zarafa.common.ui.LoadMask#hide hide} the {@link Zarafa.common.ui.LoadMask loadmask}, if any.
	 * @param {Zarafa.core.ContextModel} model The model which fired the event
	 * @private
	 */
	onSearchStop: function(model)
	{
		this.loadMask.hide();
		// Clear the grid when search is stopped
		var store = this.getStore();
		if (store && store.getCount() > 0) {
			store.removeAll();
		}
	},

	/**
	 * Handler for the beforeupdatesearch event of the search store of the grid
	 * @param {Zarafa.advancesearch.AdvanceSearchStore} store The store that holds the data
	 * of this grid.
	 * @param {Object} searchMeta The returned meta data of the search that was performed
	 */
	onBeforeUpdateSearch: function(store, searchMeta)
	{
		// Don't update for the updatesearch action because the suggestion is not send
		// with the response
		if ( Ext.isDefined(store.lastOptions) && store.lastOptions.originalActionType!==Zarafa.core.Actions.updatesearch ){
			var cm = this.getColumnModel();
			if ( Ext.isEmpty(store.suggestion) ){
				cm.setColumnHeader(0, '');
				cm.setColumnTooltip(0, '');
			} else {
				var encodedSuggestion = Ext.util.Format.htmlEncode(store.suggestion);
				cm.setColumnHeader(0, _('Did you mean') +' <span class="zarafa-search-suggestion">' + encodedSuggestion + '</span> ?');
				cm.setColumnTooltip(0, _('Did you mean') + ' ' + encodedSuggestion);
			}
		}
	},

	/**
	 * Event handler which is fired when the {@link Zarafa.core.Context} fires the
	 * {@link Zarafa.core.Context#viewmodechange viewmodechange} event. This will check
	 * where the preview panel is located, and if needed change the
	 * {@link Ext.grid.Column columns} inside the {@link Ext.grid.ColumnModel ColumnModel}
	 * of the {@link Zarafa.mail.ui.MailGrid MailGrid}. Either use the extended (and more flexible)
	 * set or the more compact set.
	 *
	 * @param {Zarafa.core.Context} context The context which fired the event
	 * @param {Zarafa.mail.data.ViewModes} newViewMode The new active mode
	 * @param {Zarafa.mail.data.ViewModes} oldViewMode The previous mode
	 * @private
	 */
	onContextViewModeChange: function(context, newViewMode, oldViewMode)
	{
		var compact = newViewMode === Zarafa.mail.data.ViewModes.RIGHT_PREVIEW;
		this.getView().enableRowBody = compact;
		this.getColumnModel().setCompactView(compact);
	},

	/**
	 * Cleanup any pending tasks before destroying the grid.
	 * @private
	 */
	beforeDestroy: function()
	{
		this.cancelPreviewRecordTask();
		Zarafa.advancesearch.ui.SearchGrid.superclass.beforeDestroy.apply(this, arguments);
	}
});

Ext.reg('zarafa.searchgrid', Zarafa.advancesearch.ui.SearchGrid);
/*
 * #dependsFile client/zarafa/core/mapi/BusyStatus.js
 */
Ext.namespace('Zarafa.calendar.data');

/**
 * @class Zarafa.calendar.data.BusyStatus
 * @singleton
 */
Zarafa.calendar.data.BusyStatus = [{
	value: Zarafa.core.mapi.BusyStatus['FREE'],
	name: Zarafa.core.mapi.BusyStatus.getDisplayName(Zarafa.core.mapi.BusyStatus['FREE'])
},{
	value: Zarafa.core.mapi.BusyStatus['TENTATIVE'],
	name: Zarafa.core.mapi.BusyStatus.getDisplayName(Zarafa.core.mapi.BusyStatus['TENTATIVE'])
},{
	value: Zarafa.core.mapi.BusyStatus['BUSY'],
	name: Zarafa.core.mapi.BusyStatus.getDisplayName(Zarafa.core.mapi.BusyStatus['BUSY'])
},{
	value: Zarafa.core.mapi.BusyStatus['OUTOFOFFICE'],
	name: Zarafa.core.mapi.BusyStatus.getDisplayName(Zarafa.core.mapi.BusyStatus['OUTOFOFFICE'])
},{
	value: Zarafa.core.mapi.BusyStatus['WORKINGELSEWHERE'],
	name: Zarafa.core.mapi.BusyStatus.getDisplayName(Zarafa.core.mapi.BusyStatus['WORKINGELSEWHERE'])
}];
/*
 * #dependsFile client/zarafa/core/mapi/BusyStatus.js
 */
Ext.namespace('Zarafa.calendar.data.busytime');

/**
 * @class Zarafa.calendar.data.busytime.BusyTimeRecordFields
 *
 * Array of {@link Ext.data.Field field} configurations for the
 * {@link Zarafa.calendar.data.busytime.BusyTimeRecord BusyTimeRecord} object.
 */
Zarafa.calendar.data.busytime.BusyTimeRecordFields = [
	{name: 'startdate', type: 'date', dateFormat: 'timestamp'},
	{name: 'duedate', type: 'date', dateFormat: 'timestamp'},
	{name: 'busystatus', type: 'int', defaultValue: Zarafa.core.mapi.BusyStatus.FREE}
];

/**
 * @class Zarafa.calendar.data.busytime.BusyTimeRecord
 * A record with minimal data, used for determining days in the month that have appointments
 */
Zarafa.calendar.data.busytime.BusyTimeRecord = Ext.data.Record.create(Zarafa.calendar.data.busytime.BusyTimeRecordFields);
Ext.namespace('Zarafa.calendar.data.busytime');

/**
 * @class Zarafa.calendar.data.busytime.BusyTimeResponseHandler
 * @extends Zarafa.core.data.ProxyResponseHandler
 *
 * Small extension to the main {@link Zarafa.core.data.ProxyResponseHandler ProxyResponseHandler},
 * to add support for the special {@link #doList 'list'} action.
 */
Zarafa.calendar.data.busytime.BusyTimeResponseHandler = Ext.extend(Zarafa.core.data.ProxyResponseHandler, {
	/**
	 * Handles the 'list' response. Gathers the records from the response data,
	 * converts each entry into a records and pushes them into the {@link #receivedRecords records list}.
	 * @param {Object} response The response object belonging to the given command.
	 * @return {Boolean} False when action could not be handled successfully. This will
	 * not cancel the transaction itself, but rather causes the 'success' argument for the
	 * {@link #done} function.
	 */
	doList: function(response)
	{
		this.receivedRecords = this.readRecordsFromResponse(response, 'item');
	}
});
Ext.namespace('Zarafa.calendar.dialogs');

/**
 * @class Zarafa.calendar.dialogs.AppointmentToolbar
 * @extends Zarafa.core.ui.ContentPanelToolbar
 * @xtype zarafa.appointmenttoolbar
 */
Zarafa.calendar.dialogs.AppointmentToolbar = Ext.extend(Zarafa.core.ui.ContentPanelToolbar, {
	// Insertion points for this class, insertion points are registered in {Zarafa.core.ui.Toolbar}
	/**
	 * @insert context.calendar.appointmentcontentpanel.toolbar.actions
	 * Insertion point for the Actions buttons in the Appointment ContentPanel Toolbar
	 * @param {Zarafa.calendar.dialogs.AppointmentToolbar} toolbar This toolbar
	 */
	/**
	 * @insert context.calendar.appointmentcontentpanel.toolbar.options
	 * Insertion point for the Options button in the Appointment Content Panel Toolbar
	 * @param {Zarafa.calendar.dialogs.AppointmentToolbar} toolbar This toolbar
	 */

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

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

		Ext.applyIf(config, {
			insertionPointBase: 'context.calendar.appointmentcontentpanel',
			actionItems: this.createActionButtons(),
			optionItems: this.createOptionButtons()
		});

		Zarafa.calendar.dialogs.AppointmentToolbar.superclass.constructor.call(this, config);
		this.mon(this.saveMeeting,'beforeshow',this.onSaveButtonBeforeShow,this);
	},

	/**
	 * Create all buttons which should be added by default the the `Actions` buttons.
	 * These buttons are used to send, save and add attachments to the message. And it contains
	 * also buttons to check the recipient names or add a signature.
	 *
	 * @return {Array} The {@link Ext.Button Button} elements which should be added
	 * in the Actions section of the {@link Ext.Toolbar Toolbar}.
	 * @private
	 */
	createActionButtons: function()
	{
		return [{
			xtype: 'zarafa.meetingrequestbutton',
			name: Zarafa.calendar.data.MeetingRequestButtonNames.REMOVEFROMCALENDAR,
			text: _('Remove From Calendar'),
			overflowText: _('Remove From Calendar'),
			tooltip: {
				title: _('Remove From Calendar'),
				text: _('Remove From Calendar')
			},
			cls: 'tb-calendar-btn-remove zarafa-action'
		}, {
			xtype: 'zarafa.meetingrequestbutton',
			name: Zarafa.calendar.data.MeetingRequestButtonNames.NORESPONSE,
			text: _('No Response Required'),
			overflowText: _('No Response Required'),
			tooltip: {
				title: _('No Response Required'),
				text: _('No Response Required')
			},
			cls: 'tb-calendar-btn-no-response-required',
			iconCls: 'icon_no_response_required'
		}, {
			xtype: 'zarafa.meetingrequestbutton',
			name: Zarafa.calendar.data.MeetingRequestButtonNames.ACCEPT,
			text: _('Accept'),
			overflowText: _('Accept'),
			parentScope: this,
			tooltip: {
				title: _('Accept'),
				text: _('Accept Meeting Request')
			},
			cls: 'zarafa-action',
			iconCls: 'icon_calendar_appt_accept',
			responseStatus: Zarafa.core.mapi.ResponseStatus.RESPONSE_ACCEPTED
		}, {
			xtype: 'zarafa.meetingrequestbutton',
			name: Zarafa.calendar.data.MeetingRequestButtonNames.TENTATIVE,
			text: _('Tentative'),
			overflowText: _('Tentative'),
			tooltip: {
				title: _('Tentative'),
				text: _('Tentatively Accept Meeting Request')
			},
			iconCls: 'icon_calendar_appt_tentative',
			responseStatus: Zarafa.core.mapi.ResponseStatus.RESPONSE_TENTATIVE
		}, {
			xtype: 'zarafa.meetingrequestbutton',
			name: Zarafa.calendar.data.MeetingRequestButtonNames.DECLINE,
			text: _('Decline'),
			overflowText: _('Decline'),
			tooltip: {
				title: _('Decline'),
				text: _('Decline Meeting Request')
			},
			iconCls: 'icon_calendar_appt_cancelled',
			responseStatus: Zarafa.core.mapi.ResponseStatus.RESPONSE_DECLINED
		}, {
			xtype: 'zarafa.meetingrequestbutton',
			name: Zarafa.calendar.data.MeetingRequestButtonNames.PROPOSENEWTIME,
			text: _('Propose New Time'),
			overflowText: _('Propose New Time'),
			tooltip: {
				title: _('Propose New Time'),
				text: _('Propose New Time for Meeting Request')
			},
			iconCls: 'icon_calendar_appt_newtime'
		}, {
			xtype: 'zarafa.meetingrequestbutton',
			name: Zarafa.calendar.data.MeetingRequestButtonNames.VIEWPROPOSALS,
			text: _('View All Proposals'),
			overflowText: _('View All Proposals'),
			tooltip: {
				title: _('View All Proposals'),
				text: _('View All Proposals')
			},
			iconCls: 'icon_calendar_appt_proposals'
		}, {
			xtype: 'zarafa.meetingrequestbutton',
			name: Zarafa.calendar.data.MeetingRequestButtonNames.ACCEPTPROPOSAL,
			text: _('Accept Proposal'),
			overflowText: _('Accept Proposal'),
			tooltip: {
				title: _('Accept Proposal'),
				text: _('Accept Proposed Time')
			},
			iconCls: 'icon_calendar_appt_accept'
		}, {
			xtype: 'zarafa.meetingrequestbutton',
			name: Zarafa.calendar.data.MeetingRequestButtonNames.CALENDAR,
			ref: 'calendarButton',
			text: _('View in calendar'),
			overflowText: _('View in calendar'),
			tooltip: {
				title: _('View in calendar'),
				text: _('View in calendar')
			},
			cls: 'tb-calendar-btn-calendar zarafa-action',
			iconCls: 'icon_calendar'
		},{
			xtype: 'button',
			overflowText: _('Send'),
			text: _('Send'),
			tooltip: _('Send') + Zarafa.core.KeyMapMgr.formatShortcutHint('Ctrl + Enter', true),
			iconCls: 'icon_send_white',
			cls: 'button_sendMail zarafa-action',
			ref: 'sendInvitation',
			handler: this.onSendButton,
			scope: this
		},{
			xtype: 'button',
			text: _('Save')+' & '+_('Close'),
			overflowText: _('Save')+' & '+_('Close'),
			tooltip: _('Save')+' & '+_('Close') + Zarafa.core.KeyMapMgr.formatShortcutHint('Ctrl + S', true),
			cls: 'zarafa-action',
			iconCls: 'icon_save_white',
			ref: 'saveAppointment',
			handler: this.onSaveButton,
			scope: this
		},{
			xtype: 'button',
			overflowText: _('Save'),
			tooltip: _('Save') + Zarafa.core.KeyMapMgr.formatShortcutHint('Ctrl + S', true),
			cls: 'tb-calendar-btn-save',
			iconCls: 'icon_floppy',
			ref: 'saveMeeting',
			handler: this.onSaveButton,
			scope: this
		},{
			xtype: 'button',
			overflowText: _('Delete'),
			tooltip: _('Delete'),
			cls: 'tb-calendar-btn-delete',
			iconCls: 'icon_delete',
			ref: 'deleteAppointment',
			handler: this.onDeleteButton,
			scope: this
		},{
			xtype: 'zarafa.attachmentbutton',
			plugins: [ 'zarafa.recordcomponentupdaterplugin' ],
			ref: 'normalAttachmentsButton', // FIXME: Remove after WA-4880 is implemented
			overflowText: _('Add attachment'),
			tooltip: _('Add attachment'),
			cls: 'tb-calendar-btn-add-attachment',
			iconCls: 'icon_paperclip',
			// Add a listener to the component added event to set use the correct update function when the toolbar overflows
			// (i.e. is too wide for the panel) and Ext moves the button to a menuitem.
			listeners: {
				added: this.onAttachmentButtonAdded,
				scope: this
			}
		},{
			// FIXME: Remove after WA-4880 is implemented
			xtype: 'button',
			disabled: true,
			ref: 'occurrenceAttachmentsButton',
			overflowText: _('Add attachment'),
			tooltip: _('Attachments cannot be modified for a single occurrence'),
			cls: 'tb-calendar-btn-occurrence-attachment',
			iconCls: 'icon_paperclip'
		},{
			xtype: 'button',
			overflowText: _('Print'),
			tooltip: _('Print'),
			cls: 'tb-calendar-btn-print',
			iconCls: 'icon_print',
			handler: function() {
				Zarafa.common.Actions.openPrintDialog(this.record);
			},
			scope: this
		},{
			xtype: 'button',
			overflowText: _('Reply'),
			tooltip: _('Reply to the meeting organizer'),
			cls: 'tb-calendar-btn-reply',
			iconCls: 'icon_reply',
			ref: 'replyMeeting',
			handler: this.onReplyMeeting.createDelegate(this, [false]),
			scope: this
		},{
			xtype: 'button',
			overflowText: _('Reply All'),
			tooltip: _('Reply to all attendees'),
			cls: 'tb-calendar-btn-reply-all',
			iconCls: 'icon_reply_all',
			ref: 'replyAllMeeting',
			handler: this.onReplyMeeting.createDelegate(this, [true]),
			scope: this
		},{
			xtype: 'button',
			overflowText: _('Forward'),
			tooltip: _('Forward meeting request to additional recipients'),
			cls: 'tb-calendar-btn-forward',
			iconCls: 'icon_forward',
			ref: 'forwardMeeting',
			handler: this.onForwardMeeting,
			scope: this
		},{
			xtype: 'button',
			overflowText: _('Check names'),
			tooltip: _('Check names'),
			cls: 'tb-calendar-btn-checknames',
			iconCls: 'icon_checknames',
			ref: 'checkNames',
			handler: this.onCheckNamesButton,
			scope: this
		},{
			xtype: 'button',
			overflowText: _('Addressbook'),
			tooltip: _('Open addressbook'),
			iconCls: 'icon_small_addressbook',
			ref: 'addressbookBtn',
			handler: function() {
				Zarafa.calendar.Actions.openRecipientSelectionContent(this.record, {
					defaultRecipientType: Zarafa.core.mapi.RecipientType.MAPI_TO
				});
			},
			scope: this
		}];
	},

	/**
	 * Event listener for the added event of the {@link Zarafa.common.attachment.ui.AttachmentButton attachmentButton}
	 * Adds the update function to the item when Ext converts the button to a menu item
	 * (which happens when the toolbar overflows, i.e. is too wide for the containing panel)
	 *
	 * @param {Ext.Component} item The item that was added. This can be a {@link Zarafa.common.attachment.ui.AttachmentButton}
	 * or a {@link Ext.menu.Item}
	 */
	onAttachmentButtonAdded: function(item)
	{
		if ( item.isXType('menuitem') ){
			// Set the update function to the update function of the original button
			// otherwise the Ext.Component.update function would be called by the recordcomponentupdaterplugin
			item.update = Zarafa.common.attachment.ui.AttachmentButton.prototype.update.createDelegate(this.normalAttachmentsButton);
		}
	},

	/**
	 * Create all buttons which should be added by default the the `Options` buttons.
	 * This contains the buttons to set the message options like priority and read receipt.
	 *
	 * @return {Array} The {@link Ext.Button Button} elements which should be
	 * added in the Options section of the {@link Ext.Toolbar Toolbar}.
	 * @private
	 */
	createOptionButtons: function()
	{
		return [{
			xtype: 'button',
			text: _('Recurrence'),
			overflowText: _('Recurrence'),
			cls: 'tb-calendar-btn-recurrence',
			iconCls: 'icon_recurrence',
			ref: 'recurrence',
			handler: this.onSetRecurrence,
			scope: this
		},{
			xtype: 'button',
			text: _('Invite attendees'),
			overflowText: _('Invite attendees'),
			cls: 'tb-calendar-btn-ivite-attendees',
			iconCls: 'icon_invite_attendees',
			ref: 'inviteAttendees',
			handler: this.onSetMeetingRequest,
			scope: this
		},{
			xtype: 'button',
			text: _('Cancel invitation'),
			overflowText: _('Cancel invitation'),
			cls: 'tb-calendar-btn-cancel',
			iconCls: 'icon_calendar_appt_cancelled',
			ref: 'cancelInvitation',
			handler: this.onCancelMeetingRequest,
			scope: this
		},{
			xtype: 'button',
			overflowText: _('High priority'),
			tooltip: _('Mark this appointment as high priority'),
			cls: 'tb-calendar-btn-high-priority',
			iconCls: 'icon_priority_high',
			ref: 'highPriority',
			toggleGroup: 'priorityGroup',
			importance: Zarafa.core.mapi.Importance.URGENT,
			enableToggle: true,
			toggleHandler: this.onPriorityGroupToggle,
			scope: this
		},{
			xtype: 'button',
			overflowText: _('Low priority'),
			tooltip: _('Mark this appointment as low priority'),
			cls: 'tb-calendar-btn-low-priority',
			iconCls: 'icon_priority_low',
			ref: 'lowPriority',
			toggleGroup: 'priorityGroup',
			importance: Zarafa.core.mapi.Importance.NONURGENT,
			enableToggle: true,
			toggleHandler: this.onPriorityGroupToggle,
			scope: this
		},{
			xtype: 'button',
			overflowText: _('Categories'),
			tooltip: _('Open the categories dialog'),
			cls: 'tb-calendar-btn-categories',
			iconCls: 'icon_categories',
			ref: 'categoriesButton',
			handler: this.onCategories,
			scope: this
		},{
			xtype: 'button',
			overflowText: _('Private'),
			tooltip: _('Mark this appointment as private'),
			cls: 'tb-calendar-btn-private',
			iconCls: 'icon_private',
			ref: 'setPrivate',
			enableToggle: true,
			toggleHandler: this.onPrivateGroupToggle,
			scope: this
		}];
	},

	/**
	 * Event handler which is called when the "Recurrence" button has been pressed.
	 * This will open the {@link Zarafa.common.recurrence.dialogs.RecurrenceContentPanel RecurrenceContentPanel}.
	 * @private
	 */
	onSetRecurrence: function()
	{
		Zarafa.common.Actions.openRecurrenceContent(this.record, { autoSave: false });
	},

	/**
	 * Event handler which is called when the "Invite Attendees" button has
	 * been pressed. This will update the "meeting" state of the record which
	 * will trigger the update of all UI components.
	 * @private
	 */
	onSetMeetingRequest: function()
	{
		this.record.convertToMeeting();
	},

	/**
	 * Event handler which is called when "Reply" or "Reply All" is pressed.
	 * Builds a mail reply from the meeting's properties and opens it.
	 * @param {Boolean} isReplyAll True for Reply All, false for Reply
	 * @private
	 */
	onReplyMeeting: function(isReplyAll)
	{
		var record = this.record;
		if (!record) {
			return;
		}

		var iAmOrganizer = record.isMeetingOrganized && record.isMeetingOrganized();

		if (!isReplyAll && iAmOrganizer) {
			container.getNotifier().notify('info.meeting',
				pgettext('calendar.toolbar', 'Use Reply All to reply to all attendees of your meeting.'));
			return;
		}

		var buildReply = function(rec) {
			var mailModel = container.getContextByName('mail').getModel();
			var responseRecord = mailModel.createRecord();

			var prefix = container.getSettingsModel().get('zarafa/v1/contexts/mail/use_english_abbreviations') ?
				'Re' : _('Re');
			var subject = rec.get('normalized_subject') || rec.get('subject') || '';
			responseRecord.set('subject', prefix + ': ' + subject);

			var recipientStore = responseRecord.getRecipientStore();
			var loggedInEntryId = container.getUser().getEntryId();

			var addRecipient = function(name, email, addrType, entryId, type) {
				if (!email) {
					return;
				}
				if (loggedInEntryId && entryId &&
					Zarafa.core.EntryId.compareABEntryIds(entryId, loggedInEntryId)) {
					return;
				}
				recipientStore.add(Zarafa.core.data.RecordFactory.createRecordObjectByCustomType(
					Zarafa.core.data.RecordCustomObjectType.ZARAFA_RECIPIENT, {
						display_name: name || '',
						smtp_address: email,
						address_type: addrType || 'SMTP',
						entryid: entryId || '',
						recipient_type: type
					}));
			};

			if (iAmOrganizer) {
				var apptRecipientStore = rec.getRecipientStore();
				if (apptRecipientStore) {
					apptRecipientStore.each(function(r) {
						if (r.isMeetingOrganizer()) {
							return;
						}
						addRecipient(
							r.get('display_name'),
							r.get('smtp_address') || r.get('email_address'),
							r.get('address_type'),
							r.get('entryid'),
							Zarafa.core.mapi.RecipientType.MAPI_TO
						);
					});
				}
			} else {
				addRecipient(
					rec.get('sent_representing_name') || rec.get('sender_name'),
					rec.get('sent_representing_email_address') || rec.get('sender_email_address'),
					rec.get('sent_representing_address_type') || rec.get('sender_address_type'),
					rec.get('sent_representing_entryid') || rec.get('sender_entryid'),
					Zarafa.core.mapi.RecipientType.MAPI_TO
				);

				if (isReplyAll) {
					var apptRecipientStore = rec.getRecipientStore();
					if (apptRecipientStore) {
						apptRecipientStore.each(function(r) {
							if (r.isMeetingOrganizer()) {
								return;
							}
							addRecipient(
								r.get('display_name'),
								r.get('smtp_address') || r.get('email_address'),
								r.get('address_type'),
								r.get('entryid'),
								Zarafa.core.mapi.RecipientType.MAPI_CC
							);
						});
					}
				}
			}

			Zarafa.core.data.UIFactory.openCreateRecord(responseRecord);
		};

		if (isReplyAll && !(record.getRecipientStore && record.getRecipientStore())) {
			var store = record.getStore();
			var openHandler = function(s, r) {
				if (record !== r) {
					return;
				}
				store.un('open', openHandler, record);
				buildReply(r);
			};
			store.on('open', openHandler, record);
			record.open();
		} else {
			buildReply(record);
		}
	},

	/**
	 * Event handler which is called when the "Forward" button has been pressed.
	 * Opens a recipient selection dialog and forwards the meeting request.
	 * @private
	 */
	onForwardMeeting: function()
	{
		Zarafa.calendar.Actions.openForwardMeetingRequestContent(this.record);
	},

	/**
	 * Event handler which is called when the "Cancel invitation" button has
	 * been pressed. This will update the "meeting" state of the record which
	 * will trigger the update of all UI components.
	 * @private
	 */
	onCancelMeetingRequest: function()
	{
		if (this.record.isMeetingSent()) {
			if (this.record.isAppointmentInPast()) {
				this.dialog.deleteRecord();
			} else {
				Zarafa.calendar.Actions.openSendCancellationContent(this.record);
			}
		} else {
			this.record.convertToAppointment();
		}
	},

	/**
	 * Event handler which is called when one of the PriorityGroup buttons
	 * have been toggled. If this is the case, the importance is updated,
	 * if the button is untoggled, then all buttons in the prioritygroup
	 * are untoggled and the normal importance is applied. Otherwise the
	 * importance which belongs to the button is applied.
	 *
	 * @param {Ext.Button} button The button from the PriorityGroup which was pressed
	 * @private
	 */
	onPriorityGroupToggle: function(button)
	{
		if (button.pressed) {
			this.record.set('importance', button.importance);
		} else {
			this.record.set('importance', Zarafa.core.mapi.Importance.NORMAL);
		}
	},

	/**
	 * Event handler which is called when the user pressed the 'Categories' button.
	 * This will open the {@link Zarafa.common.categories.dialogs.CategoriesContentPanel CategoriesContentPanel}.
	 * @private
	 */
	onCategories: function()
	{
		Zarafa.common.Actions.openCategoriesContent(this.record, {autoSave: false});
	},

	/**
	 * Event handler which is called when the PrivateGroup button
	 * has been toggled. If this is the case 'private' is updated.
	 *
	 * @param {Ext.Button} button The button which was toggled
	 * @private
	 */
	onPrivateGroupToggle: function(button)
	{
		this.record.beginEdit();
		this.record.set('private', button.pressed);
		if (button.pressed) {
			this.record.set('sensitivity', Zarafa.core.mapi.Sensitivity['PRIVATE']);
		} else {
			this.record.set('sensitivity', Zarafa.core.mapi.Sensitivity['NONE']);
		}
		this.record.endEdit();
	},

	/**
	 * Event handler when the "Check Names" button has been pressed.
	 * This will {@link Zarafa.core.data.IPMRecipient#resolve resolve} all recipients.
	 *
	 * @param {Ext.Button} button The button which has been pressed
	 * @private
	 */
	onCheckNamesButton: function(button)
	{
		this.record.getRecipientStore().resolve(undefined, { cancelPreviousRequest: true });
	},

	/**
	 * Event handler when the "Send" button has been pressed.
	 * This will {@link Zarafa.core.data.MessageContentPanel#sendRecord send} the given record.
	 *
	 * @param {Ext.Button} button The button which has been pressed
	 * @private
	 */
	onSendButton: function(button)
	{
		this.dialog.sendRecord();
	},

	/**
	 * Event handler when the "Save" button has been pressed.
	 * This will {@link Zarafa.core.data.RecordContentPanel#saveRecord save} the given record.
	 * it also checks whether the message is a meeting, and if to send the changes to attendees as well
	 * @param {Ext.Button} button The button which has been pressed
	 * @private
	 */
	onSaveButton: function(button)
	{
		this.dialog.saveRecord();
	},

	/**
	 * Event handler when the "Delete" button has been pressed.
	 * This will {@link Zarafa.core.data.RecordContentPanel#deleteRecord delete} the given record.
	 *
	 * @param {Ext.Button} button The button which has been pressed
	 * @private
	 */
	onDeleteButton: function(button)
	{
		this.dialog.deleteRecord();
	},


	/**
	 * Event handler which called when the 'Save' button is ready to show.
	 * This will update the tooltip if the record is received meeting request
	 * @param {Ext.Button} button The button which has been pressed
	*/
	onSaveButtonBeforeShow: function(button)
	{
		if(this.record.isMeetingReceived()) {
			button.setTooltip(_('Save') + Zarafa.core.KeyMapMgr.formatShortcutHint('Ctrl + S', true));
		}
	},

	/**
	 * Updates the toolbar by updating the Toolbar buttons based on the settings
	 * from the {@link Zarafa.core.data.IPMRecord record}.
	 *
	 * @param {Zarafa.core.data.IPMRecord} 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)
	{
		var layout = false;

		this.record = record;

		if(record.isSubMessage()) {
			// hide all buttons which is used to save changes
			this.sendInvitation.setVisible(false);
			this.saveAppointment.setVisible(false);
			this.saveMeeting.setVisible(false);
			this.checkNames.setVisible(false);
			this.addressbookBtn.setVisible(false);
			this.deleteAppointment.setVisible(false);
			this.inviteAttendees.setVisible(false);
			this.cancelInvitation.setVisible(false);
			this.replyMeeting.setVisible(false);
			this.replyAllMeeting.setVisible(false);
			this.forwardMeeting.setVisible(false);
			this.setPrivate.setVisible(false);
			this.highPriority.setVisible(false);
			this.lowPriority.setVisible(false);

			layout = true;
		} else {
			if (record.isModifiedSinceLastUpdate('store_entryid')) {
				var store = container.getHierarchyStore().getById(record.get('store_entryid'));
				if ((store.isSharedStore() || store.isPublicStore()) && this.setPrivate.pressed) {
					this.setPrivate.toggle(!this.setPrivate.pressed, true);
					this.onPrivateGroupToggle(this.setPrivate);
				}
				this.setPrivate.setDisabled(store.isSharedStore() || store.isPublicStore());	
			} else {
				var store = container.getHierarchyStore().getById(record.get('store_entryid'));
				this.setPrivate.setDisabled(store.isSharedStore() || store.isPublicStore());
				this.setPrivate.setVisible(true);
			}

			this.highPriority.setVisible(true);
			this.lowPriority.setVisible(true);

			// Only enable delete button when it is not a phantom and the user
			// actually has delete rights on the item.
			this.deleteAppointment.setDisabled(record.phantom === true || !record.hasDeleteAccess());

			if (contentReset === true || record.isModifiedSinceLastUpdate('recurring')) {
				if (Ext.isEmpty(record.get('basedate'))) {
					this.recurrence.setVisible(true);
				} else {
					this.recurrence.setVisible(false);
				}
				layout = true;
			}

			if (contentReset === true || record.isModifiedSinceLastUpdate('meeting')) {
				switch (record.get('meeting')) {
					case Zarafa.core.mapi.MeetingStatus.NONMEETING:
					/* falls through */
					default:
						this.sendInvitation.setVisible(false);
						this.saveAppointment.setVisible(true);
						this.saveMeeting.setVisible(false);
						this.deleteAppointment.setVisible(true);
						this.checkNames.setVisible(false);
						this.addressbookBtn.setVisible(false);
						this.inviteAttendees.setVisible(true);
						this.cancelInvitation.setVisible(false);
						this.replyMeeting.setVisible(false);
						this.replyAllMeeting.setVisible(false);
						this.forwardMeeting.setVisible(false);
						break;
					case Zarafa.core.mapi.MeetingStatus.MEETING:
						this.sendInvitation.setVisible(true);
						this.saveAppointment.setVisible(false);
						this.saveMeeting.setVisible(true);
						this.deleteAppointment.setVisible(false);
						this.checkNames.setVisible(true);
						this.addressbookBtn.setVisible(true);
						this.inviteAttendees.setVisible(false);
						this.cancelInvitation.setVisible(true);
						this.replyMeeting.setVisible(true);
						this.replyAllMeeting.setVisible(true);
						this.forwardMeeting.setVisible(true);
						break;
					case Zarafa.core.mapi.MeetingStatus.MEETING_RECEIVED:
						this.sendInvitation.setVisible(false);
						this.saveAppointment.setVisible(false);
						this.saveMeeting.setVisible(true);
						this.deleteAppointment.setVisible(true);
						this.checkNames.setVisible(false);
						this.addressbookBtn.setVisible(false);
						this.inviteAttendees.setVisible(false);
						this.cancelInvitation.setVisible(false);
						this.replyMeeting.setVisible(true);
						this.replyAllMeeting.setVisible(true);
						this.forwardMeeting.setVisible(true);
						break;
					case Zarafa.core.mapi.MeetingStatus.MEETING_CANCELED:
					case Zarafa.core.mapi.MeetingStatus.MEETING_RECEIVED_AND_CANCELED:
						this.sendInvitation.setVisible(false);
						this.saveAppointment.setVisible(false);
						this.saveMeeting.setVisible(true);
						this.deleteAppointment.setVisible(true);
						this.checkNames.setVisible(false);
						this.addressbookBtn.setVisible(false);
						this.inviteAttendees.setVisible(false);
						this.cancelInvitation.setVisible(false);
						this.replyMeeting.setVisible(false);
						this.replyAllMeeting.setVisible(false);
						this.forwardMeeting.setVisible(false);
						break;
				}

				layout = true;
			}

			if (contentReset === true || record.isModifiedSinceLastUpdate('importance')) {
				switch (record.get('importance')) {
				case Zarafa.core.mapi.Importance.URGENT:
					this.highPriority.toggle(true, true);
					break;
				case Zarafa.core.mapi.Importance.NONURGENT:
					this.lowPriority.toggle(true, true);
					break;
				default:
					break;
				}
			}

			if (contentReset === true || record.isModifiedSinceLastUpdate('private')) {
				this.setPrivate.toggle(record.get('private'), true);
			}
		}

		// FIXME: Remove after WA-4880 is implemented
		if (contentReset === true) {
			if (record.isRecurringOccurrence()) {
				this.occurrenceAttachmentsButton.setVisible(true);
				this.normalAttachmentsButton.setVisible(false);
			} else {
				this.occurrenceAttachmentsButton.setVisible(false);
				this.normalAttachmentsButton.setVisible(true);
			}

			layout = true;
		}

		if (layout === true) {
			this.doLayout();
		}

		// Runs last so it wins over the meeting-status visibility switch and the
		// priority/private toggles above.
		this.applyReadOnlyState(record);
	},

	/**
	 * Disable every button which modifies the record when the given record is
	 * read-only (see {@link Zarafa.core.data.MAPIRecord#isReadOnlyRecord}).
	 * Read-only actions (print, reply, forward, view in calendar) stay enabled,
	 * and the delete button keeps its own delete-rights based state. It is a
	 * no-op for records the user is allowed to modify.
	 * @param {Zarafa.core.data.IPMRecord} record The record to check
	 * @private
	 */
	applyReadOnlyState: function(record)
	{
		// Only decide once the record is opened: a not-yet-opened record may
		// still carry the default (read) access instead of its authoritative
		// PR_ACCESS, which would otherwise wrongly freeze dialogs whose record
		// is built client-side (e.g. accepting a proposed meeting time).
		if (!record.isOpened() || !record.isReadOnlyRecord()) {
			return;
		}

		var writeButtons = [
			this.sendInvitation, this.saveAppointment, this.saveMeeting,
			this.checkNames, this.addressbookBtn, this.inviteAttendees,
			this.cancelInvitation, this.recurrence, this.highPriority,
			this.lowPriority, this.setPrivate, this.categoriesButton,
			this.normalAttachmentsButton
		];
		Ext.each(writeButtons, function(button) {
			if (button) {
				button.setDisabled(true);
			}
		});

		// Disable the meeting-response actions (Accept/Tentative/Decline/...)
		// which would write to the calendar, but keep the navigation-only
		// "View in calendar" button usable.
		Ext.each(this.findByType('zarafa.meetingrequestbutton'), function(button) {
			if (button !== this.calendarButton) {
				button.setDisabled(true);
			}
		}, this);
	}
});

Ext.reg('zarafa.appointmenttoolbar', Zarafa.calendar.dialogs.AppointmentToolbar);
Ext.namespace('Zarafa.calendar.dialogs');

/**
 * @class Zarafa.calendar.dialogs.CalendarOptionsContentPanel
 * @extends Zarafa.core.ui.RecordContentPanel
 * @xtype zarafa.calendaroptionscontentpanel
 */
Zarafa.calendar.dialogs.CalendarOptionsContentPanel = Ext.extend(Zarafa.core.ui.RecordContentPanel, {
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

		config = Ext.applyIf(config, {
			xtype: 'zarafa.calendaroptionscontentpanel',
			layout: 'fit',
			title: _('Message Options'),
			recordComponentPluginConfig: Ext.applyIf(config.recordComponentPluginConfig || {}, {
				allowWrite: true
			}),
			autoSave: config.modal ? false : true,
			width: 360,
			height: 220,
			items: [{
				xtype: 'zarafa.calendaroptionspanel',
				buttons: [{
					text: _('Ok'),
					handler: this.onOk,
					scope: this
				}]
			}]
		});

		Zarafa.calendar.dialogs.CalendarOptionsContentPanel.superclass.constructor.call(this, config);
	}
});

Ext.reg('zarafa.calendaroptionscontentpanel', Zarafa.calendar.dialogs.CalendarOptionsContentPanel);
/*
 * #dependsFile client/zarafa/core/mapi/ResponseStatus.js
 */
Ext.namespace('Zarafa.calendar.dialogs');

/**
 * @class Zarafa.calendar.dialogs.ProposeNewTimePanel
 * @extends Ext.Panel
 * @xtype zarafa.proposenewtimepanel
 */
Zarafa.calendar.dialogs.ProposeNewTimePanel = Ext.extend(Ext.Panel, {
	/**
	 * @cfg {Zarafa.core.data.IPMRecord} record The record for which
	 * propose new time dialog is opened
	 */
	record: undefined,
	/**
	 * @cfg {Zarafa.core.mapi.ResponseStatus} responseType tentative accept/decline
	 * As a default accept tentatively and propose new time.
	 */
	responseType: Zarafa.core.mapi.ResponseStatus.RESPONSE_TENTATIVE,
	/**
	 * @constructor
	 * @param config Configuration structure
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			// Override from Ext.Component
			xtype: 'zarafa.proposenewtimepanel',
			layout: {
				type: 'vbox',
				align: 'stretch'
			},
			border: false,
			defaults: {
				border: false,
				bodyStyle: 'padding-bottom: 5px; background-color: inherit;'
			},
			items: this.createProposeTimePanel()
		});

		Zarafa.calendar.dialogs.ProposeNewTimePanel.superclass.constructor.call(this, config);

		// set the record values in UI
		this.update(this.record, true);
	},

	/**
	 * Create the {@link Ext.Panel Panel} containing the
	 * {@link Zarafa.common.ui.DateTimePeriodField DateTimePeriodField}.
	 * @return {Object} Configuration object for the panel with time selection fields
	 * @private
	 */
	createProposeTimePanel: function()
	{
		return [{
				xtype: 'zarafa.datetimeperiodfield',
				ref: 'datetimePeriod',
				defaultPeriod: 30,
				width: 300,
				startFieldConfig: {
					name: 'startdate',
					fieldLabel: _('Start time'),
					labelWidth: 100,
					listeners: {
						change: this.onFieldChange,
						scope: this
					}
				},
				endFieldConfig: {
					name: 'duedate',
					fieldLabel: _('End time'),
					labelWidth: 100,
					listeners: {
						change: this.onFieldChange,
						scope: this
					}
				}
			},{
				layout: {
					type: 'hbox',
					pack: 'start',
					align: 'stretch'
				},
				items: [{
						xtype: 'label',
						text: _('Comment') + ': ',
						width: 105
					},{
						xtype: 'textarea',
						ref: '../comment',
						fieldLabel: _('Comment'),
						flex:1
					}],
				flex:1
			}];
	},

	/**
	 * Update the components with the given record.
	 *
	 * @param {Zarafa.core.data.MAPIRecord} record The record to update in this component
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 */
	update: function(record, contentReset)
	{
		var startDate;
		var dueDate;

		if (record.isMessageClass('IPM.Schedule.Meeting.Request', true)) {
			startDate = record.get('appointment_startdate');
			dueDate = record.get('appointment_duedate');
		} else {
			startDate = record.get('startdate');
			dueDate = record.get('duedate');
		}

		if (startDate && dueDate) {
			this.datetimePeriod.getValue().set(startDate, dueDate);
		}
	},

	/**
	 * Update the {@link Zarafa.core.data.IPMRecord IPMRecord} with the data from the {@link Ext.Panel Panel}.
	 * @param {Zarafa.core.data.IPMRecord} record The record which has to be updated
	 */
	updateRecord: function(record)
	{
		record.proposeNewTimeToMeetingRequest(this.responseType, this.comment.getValue(), this.datetimePeriod.getValue().startDate, this.datetimePeriod.getValue().dueDate);
	}
});

Ext.reg('zarafa.proposenewtimepanel', Zarafa.calendar.dialogs.ProposeNewTimePanel);
// -*- coding: utf-8; indent-tabs-mode: nil -*-
Ext.namespace('Zarafa.calendar.printer');

/**
 * @class Zarafa.calendar.printer.DaysViewRenderer
 * @extends Zarafa.calendar.printer.AbstractViewRenderer
 *
 * Prints single day calendar overview.
 * Also it serves as baseclass to be used to print other calendar appointments.
 */
Zarafa.calendar.printer.DaysViewRenderer = Ext.extend(Zarafa.calendar.printer.AbstractViewRenderer, {

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

		Ext.applyIf(config, {
			timeStyle : 'width:10%;'
		});

		Zarafa.calendar.printer.DaysViewRenderer.superclass.constructor.call(this, config);
	},

	/**
	 * Function which is used to prepare the svg image based on the an appointment status.
	 *
	 * @param {Zarafa.core.mapi.BusyStatus} busyStatus the {@link Zarafa.core.mapi.BusyStatus busyStatus} of an appointment.
	 * @return {String} return the svg icon which used to indicate the status of an appointment.
	 */
	getAppointmentStatus: function(busyStatus)
	{
		switch (busyStatus) {
			case Zarafa.core.mapi.BusyStatus.FREE:
				return '<svg width="7" height="20" class="k-appointment-status">'
					+ '<rect x="0" y="0" rx="1" ry="1" width="7" height="20" style="fill:none; stroke:#FFF; stroke-width:1;" /> ' +
					+'</svg>';
			case Zarafa.core.mapi.BusyStatus.BUSY:
				return '<svg width="7" height="20" class="k-appointment-status">'
					+ '<rect x="0" y="0" rx="5" ry="1" width="7" height="20" style="fill:#0000FF; stroke:#0000FF; stroke-width:1;" /> ' +
					+'</svg>';
			case Zarafa.core.mapi.BusyStatus.OUTOFOFFICE:
				return '<svg width="7" height="20" class="k-appointment-status">'
					+ '<rect x="0" y="0" rx="5" ry="1" width="7" height="20" style="fill:#912787; stroke:#912787; stroke-width:0;" /> ' +
					+'</svg>';
			case Zarafa.core.mapi.BusyStatus.WORKINGELSEWHERE:
				return '<svg width="7" height="20" class="k-appointment-status">'
					+ '<rect x="0" y="0" rx="5" ry="1" width="7" height="20" style="fill:#5b9bd5; stroke:#5b9bd5; stroke-width:0;" /> ' +
					+'</svg>';
			case Zarafa.core.mapi.BusyStatus.TENTATIVE:
				return '<svg width="7" height="20" class="k-appointment-status">'
					+ '<defs>'
					+   '<pattern id="tentative" patternUnits="userSpaceOnUse" width="3" height="1" patternTransform="rotate(30)">'
					+	'<rect width="1" height="20" fill="#0000FF" style="stroke-width:0;"/>'
					+   '</pattern>'
					+ '</defs>'
					+ '<rect style="fill: url(#tentative);" x="0" y="0" rx="1" ry="1" width="7" height="20" ></rect>'
					+ '</svg>';
		}
	},
	/**
	 * Prepares data suitable for use in an XTemplate from the component
	 * @param {Ext.Component} component The component to acquire data from
	 * @return {Array} An empty array (override this to prepare your own data)
	 */
	prepareData: function(context)
	{
		var data = {
			fullname: Ext.util.Format.htmlEncode(container.getUser().getDisplayName())
		};
		var model = context.getModel();
		var daterange = model.dateRange;
		var numDays = daterange.getDuration(Date.DAY);

		var startDate = daterange.getStartDate().clone();
		var dueDate = daterange.getDueDate().clone();

		// Obtain the calendar name
		var folders = model.getFolders();
		var foldernames = [];
		for (var i = 0; i < folders.length; i++) {
			if (folders[i].getMAPIStore().get('display_name') === container.getUser().getDisplayName()) {
				foldernames.push(folders[i].get('display_name'));
			} else {
				foldernames.push(folders[i].get('display_name') + ' ' + _('of') + ' ' + folders[i].getMAPIStore().get('display_name'));
			}
		}
		data['foldernames'] = foldernames.join(', ');

		// Set the startDate to 12:00 to be able to call Date.add(Date.DAY, ..)
		// safely for DST switches at 00:00 in Brasil.
		startDate.setHours(12);
		dueDate.setHours(12);

		// Obtain the daterange information, subtract 1 day from duedate
		// as it is set to 00:00 of the day _after_ the duedate.
		data['currenttime'] = new Date();
		data['startdate'] = startDate.clearTime();
		data['duedate'] = dueDate.add(Date.DAY, -1).clearTime();

		for (var i = 0, len = numDays; i < len; i++) {
			var key = 'date' + (i + 1);
			data[key] = startDate.add(Date.DAY, i).clearTime();
			data[key + '_table_data'] = '';
		}

		var items = model.getStore().getRange();

		// If there are no items, no point in continuing, so return the data.
		if (items.length === 0) {
			return data;
		}

		var offset = startDate.getDay();
		for (var i = 0, len = items.length; i < len; i++) {
			var start = items[i].get('commonstart');
			var end = items[i].get('commonend');
			if (!Ext.isDate(start) || !Ext.isDate(end)) {
				continue;
			}

			// Check if the appointment fits into the range,
			// if it doesn't bind the limits to the printed range.
			var showStart = start;
			if (start < startDate) {
				showStart = startDate.clone();
			}
			var showEnd = end;
			if (end > dueDate) {
				showEnd = dueDate.clone();
			}

			// Obtain the subject
			var subject = items[i].get('subject');
			subject = Ext.isString(subject) && !Ext.isEmpty(subject) ? subject : ' ';

			// Obtain the folderlocation
			var folderLocation = '';
			// If only 1 folder is printed, we do not show the folderlocation
			// else we do print the location of the item
			if (foldernames.length > 1) {

				var store = container.getHierarchyStore();
				var calendarFolder = store.getFolder(items[i].get('parent_entryid')).get('display_name');
				var storeName = store.getFolder(items[i].get('parent_entryid')).getMAPIStore().get('display_name');
				if (storeName != container.getUser().getDisplayName()) {
					folderLocation = '- '+ calendarFolder + ' ' + _('of') + ' ' + storeName;
				}
			}

			// Obtain the location of the appointment/meeting
			var location = items[i].get('location');
			location = Ext.isString(location) && !Ext.isEmpty(location) ? ' ' + _('Location: ') + location : ' ';

			var showDays = Math.floor(Date.diff(Date.DAY, showEnd, showStart));
			var allday = items[i].get('alldayevent');
			if (allday) {
				// allday events end on midnight the next day, so deduct
				// one, since we only deal in days here.
				if (showEnd === end) {
					showDays--;
				}
			}

			// Set allday or normal class
			var append = '<tr class="calendar-'+ (allday ? 'allday' : 'normal') + '">';

			// Add the time of the appointment if it's not an all day event
			if (!allday) {

				// l = Monday
				// jS = 1rd
				// F = January M = Jan
				// Y
				// {0] 12/24 hour

				var multiDay = start.getDayOfYear() !== end.getDayOfYear();
				var multiYear = start.getYear() !== end.getYear();

				// Multi day and multi year format
				var timeFormat = container.settingsModel.get('zarafa/v1/main/datetime_time_format');
				var multiTimeFormat;

				// Todo check if multiday can be shown in different days (works for multi year)
				if (multiDay && !multiYear) {
					multiTimeFormat = _('j M {0}'); // TODO check correct dateformat for English
				} else if (multiYear) {
					multiTimeFormat = _('l jS M Y {0}');
				} else {
					multiTimeFormat = timeFormat;
				}

				append += '<td width=7 valign="center">' + this.getAppointmentStatus(items[i].get('busystatus')) + '</td>'
					+ '<td class="nowrap" style="' + this.timeStyle + '">'
					+ start.formatDefaultTime(multiTimeFormat) + ' - ' + end.formatDefaultTime(multiTimeFormat) + '</td>';
			}

			// Add string before all day appointment
			// to indicate appointment last an entire day
			if (allday) {
				subject = _('All day: ') + subject;
			}
			append += '<td class="calendar-item" colspan='+ (allday ? '3' : '2') +'>'
				+ Ext.util.Format.htmlEncode(subject) + Ext.util.Format.htmlEncode(location) + '<i class="folder-location">'+Ext.util.Format.htmlEncode(folderLocation) + '</i></td></tr>';

			var startday = showStart.getDay();
			if (startday < offset) {
				startday += 7;
			}
			startday -= (offset - 1);
			for (var n = 0; n <= showDays && n < numDays; n++) {
				data['date' + (startday + n) + '_table_data'] += append;
			}
		}

		return data;
	},

	/**
	 * Returns the HTML that will be placed into the <body> part of the print window.
	 * @return {String} The HTML fragment to place inside the print window's <body> element
	 */
	generateBodyTemplate: function()
	{
		/* +---------------------------------------------------------------+
		 * | Overview of {fullname}     | datepicker_left datepicker_right |
		 * | {foldernames}              | datepicker_left datepicker_right |
		 * | Day                        |                                  |
		 * +---------------------------------------------------------------+
		 * +---------------------------------------------------------------+
		 * |                              day                              |
		 * +---------------------------------------------------------------+
		 * |                                                               |
		 * |                                                               |
		 * |                                                               |
		 * +---------------------------------------------------------------+
		 *
		 * Printed by {fullname} at {date}
		 */

		return '<table class="print-calendar" cellpadding=0 cellspacing=0>\n'
			+	'<tr style="height:10%;"><td colspan=2>'
			+		'<table id="top">\n'
			+			'<tr><td align="center">' +_('An overview of') + ' {fullname} {foldernames}</td>'
			+			'<td align="center" rowspan="2"valign="top" width="10%"><div id="datepicker_left"></div></td>'
			+			'<td align="center" rowspan="2" valign="top" width="10%"><div id="datepicker_right"></div></td></tr>\n'
			+			'<tr><td align="left" valign="top" style="font-size: large;">{startdate:date("' + _("l jS F Y") + '")}</td></tr>\n'
			+		'</table>\n'
			+ '</td></tr>\n'
			+	'<tr style="height:40px;">'
				// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for formatting instructions
			+		'<th class="date-header-center">{date1:date("' + _("l jS F") + '")}</th>'
				// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for formatting instructions
			+	'</tr>\n'
			+	'<tr style="height:90%;">'
			+		'<td valign="top"><table id="date1">{date1_table_data}</table></td>'
			+	'</tr>\n'
			+	'</tr>\n</table>\n'
			// Bottom name and print date
			+ '<table class="bottom">'
			+		'<tr>'
						// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for formatting instructions
			+			'<td>'+_('Printed by') + ' {fullname} ' + _('at') + ' {currenttime:formatDefaultTimeString("' + _("l jS F Y {0}") + '")}</td>'
			+		'</tr>'
			+ '</table>';
	}
});
// -*- coding: utf-8; indent-tabs-mode: nil -*-
Ext.namespace('Zarafa.calendar.printer');

/**
 * @class Zarafa.calendar.printer.MeetingRenderer
 * @extends Zarafa.common.printer.renderers.RecordRenderer
 *
 * A printer for appointments and meeting requests using the same layout as for emails
 *
 * Prints a single meetingrequest email or calendar item
 */
Zarafa.calendar.printer.MeetingRenderer = Ext.extend(Zarafa.common.printer.renderers.RecordRenderer, {
	/**
	 * Generates a template on which prepareData() will be applied to create the HTML body.
	 * @param {zarafa.core.data.MAPIRecord} record the email to print
	 * @return {String} The HTML for the XTemplate to print
	 */
	generateBodyTemplate: function(record) {
		var html = '';

		html += '<b>{fullname}</b>\n';
		html += '<hr>\n';
		html += '<table>\n';
		html += this.addRow(_('Subject'), '{subject}');
		html += this.optionalRow(_('Location'), 'location', '{location}');
		html += this.addRow('', '');   // separator
		// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
		html += this.addRow(_('Start'), '{startdate:formatDefaultTimeString("' + _("l jS F Y {0}") + '")}');
		// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
		html += this.addRow(_('End'), '{enddate:formatDefaultTimeString("' + _("l jS F Y {0}") + '")}');
		// this.optionalRow(_('Show Time As', record, '?');
		html += this.addRow('', '');   // separator
		// outlook always prints Recurrence: (none) .. no clue what it means, and why.
		html += this.optionalRow(_('Recurrence pattern'), 'recurring_pattern', '{recurring_pattern}');
		html += this.optionalRow('', 'recurring_pattern', '');
		html += this.optionalRow(_('Organiser'), 'sender_name', '{sender_name}');
		html += this.optionalRow(_('Meeting status'), 'responsestatus', '{responsestatus:responseStatusString}');
		html += '<tpl if="!Ext.isEmpty(values.sender_name) || !Ext.isEmpty(values.responsestatus)">';
		html += this.addRow('', '');   // separator
		html += '</tpl>';
		// following depends on state of item
		html += this.optionalRow(_('Required Attendees'), 'display_to', '{display_to}');
		html += this.optionalRow(_('Optional Attendees'), 'display_cc', '{display_cc}');
		html += this.optionalRow(_('Resources'), 'display_bcc', '{display_bcc}');
		html += this.addRow('', '');   // separator
		html += this.optionalRow(_('Sensitivity'), 'sensitivity', '{sensitivity:sensitivityString}');
		html += this.optionalRow(_('Importance'), 'importance', '{importance:importanceString}');
		html += '<tpl if="!Ext.isEmpty(values.sensitivity) || !Ext.isEmpty(values.importance)">';
		html += this.addRow('', '');   // separator
		html += '</tpl>';
		html += this.optionalRow(_('Attachments'), 'attachment_names', '{attachment_names}');
		html += '</table><br><p>\n';
		// outlook strips until the ~*~*~... marker.. that's a bit of a hassle
		html += record.getBody(true);
		html += '</p>\n';
		return html;
	},

	/**
	 * Prepares data for any record for use in the XTemplate
	 * @param {Zarafa.core.data.MAPIRecord} record The mapi record to print
	 * @return {Array} Data suitable for use in the XTemplate
	 */
	prepareData: function(record) {
		var data = Zarafa.calendar.printer.MeetingRenderer.superclass.prepareData.apply(this, arguments);
		if (record.isMessageClass('IPM.Schedule', true)) {
			// meeting request has other names for the same properties
			data['startdate'] = data['appointment_startdate'];
			data['enddate'] = data['appointment_duedate'];
			data['location'] = data['appointment_location'];
			data['recurring_pattern'] = data['appointment_recurring_pattern'];
		} else {
			// normal calendar item
			data['startdate'] = data['commonstart'];
			data['enddate'] = data['commonend'];
		}
		return data;
	}
});

Ext.namespace('Zarafa.calendar.printer');

/**
 * @class Zarafa.calendar.printer.DaysViewRenderer
 * @extends Zarafa.calendar.printer.AbstractViewRenderer
 *
 * Prints single day calendar overview.
 * Also it serves as baseclass to be used to print other calendar appointments.
 */
Zarafa.calendar.printer.MonthViewRenderer = Ext.extend(Zarafa.calendar.printer.AbstractViewRenderer, {

	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};
		Zarafa.calendar.printer.MonthViewRenderer.superclass.constructor.call(this, config);
	},

	/**
	 * Prepares data suitable for use in an XTemplate from the component
	 * @param {Ext.Component} component The component to acquire data from
	 * @return {Array} An empty array (override this to prepare your own data)
	 */
	prepareData: function(context)
	{
		var data = {
			fullname: Ext.util.Format.htmlEncode(container.getUser().getDisplayName())
		};
		var model = context.getModel();
		var dateRange = model.dateRange;
		var numDays = dateRange.getDuration(Date.DAY);
		var startDate = dateRange.getStartDate().clone();

		// Obtain the calendar name
		var folders = model.getFolders();
		var foldersData = [];
		var folderColor;
		for (var i = 0; i < folders.length; ++i) {
			folderColor = model.getColorScheme(folders[i].get('entryid')).base;
			if (folders[i].getMAPIStore().get('display_name') === container.getUser().getDisplayName()) {
				foldersData.push({folderName : folders[i].get('display_name'), folderColor : folderColor});
			} else {
				foldersData.push({folderName : folders[i].get('display_name') + ' ' + _('of') + ' ' + folders[i].getMAPIStore().get('display_name'), folderColor : folderColor});
			}
		}
		data['folderList'] = foldersData;

		var days = [];
		for (var i = 0; i < 7; ++i) {
			days.push({"day": startDate.add(Date.DAY,i).format("l")});
		}

		data["days"] = days;
		data["weeks"] = this.prepareCalendarDays(startDate, numDays, model);
		data["current_month"] = model.getMonthRangeText();
		data["currenttime"] = new Date();
		return data;
	},

	/**
	 * Helper function prepare the nested array which contains the
	 * weeks and days information which we need to render in calendar.
	 * @param {Date} startDate The start date of {@link Zarafa.core.DateRange DateRange}.
	 * @param {Number} numDays The number of days belongs to selected {@link Zarafa.core.DateRange DateRange}.
	 * @param {Zarafa.calendar.CalendarContextModel} model The {@link Zarafa.calendar.CalendarContextModel CalendarContextModel}.
	 * @return {Array} nested array which contains the weeks and days information.
	 */
	prepareCalendarDays: function(startDate, numDays, model)
	{
		var weeks = [];
		var appointments = this.prepareAppointments(model);

		var weekStartDate = startDate;
		for(var i = 0; i < numDays / 7; ++i) {
			var week = [];
			var maxDayAppointments = 0;
			for(var j = 0; j < 7; ++j) {
				var date = weekStartDate.add(Date.DAY, j);
				var result = this.findAppointmentsByDate(appointments, date);
				// push day into week array.
				week.push({
					"date": this.getFormattedDate(date, startDate),
					"appointments": result.items,
				});
				if (result.items.length > maxDayAppointments) {
					maxDayAppointments = result.items.length;
				}
			}

			weekStartDate = date.add(Date.DAY, 1);
			weeks.push({
				"week": week,
				"maxDayAppointments": Math.max(100, (maxDayAppointments + 1) * 25)
			});
		}

		return weeks;
	},

	/**
	 * Function which prepare the an array of appointments which used in {@link #findAppointmentsByDate} to prepare the
	 * appointment bounds.
	 * @param {Zarafa.calendar.CalendarContextModel} model The {@link Zarafa.calendar.CalendarContextModel CalendarContextModel}.
	 * @return {Array} an appointments of selected month.
	 */
	prepareAppointments: function(model)
	{
		var appointments = [];
		var dateRange = new Zarafa.core.DateRange();
		var records = model.getStore().getRange();

		records.forEach(function (record) {
			var startDate = record.get('startdate').clone();
			var status = record.get('busystatus');

			dateRange = dateRange.set( startDate,  record.get('duedate').clone(), true, true);
			var text = dateRange.isAllDay() ? record.get("subject") : record.get('startdate').formatDefaultTime() + " " + record.get("subject");

			var location = record.get('location');
			if (!Ext.isEmpty(location)) {
				text += " (" + location + ")";
			}

			var entryID = record.get('parent_entryid');
			var colorScheme = model.getColorScheme(entryID);
			var folderColor = colorScheme.base;

			if (dateRange.getNumDays() > 1) {
				for(var i = 0; i < dateRange.getNumDays(); ++i) {
					var date = startDate.add(Date.DAY, i).clearTime(true).getTime();
					appointments.push({startDate: date, color: folderColor, text: text, busyStatus: status});
				}
			}
			else {
				appointments.push({startDate: startDate.clearTime(true).getTime(), color: folderColor, text: text, busyStatus: status});
			}
		}, this);

		return appointments;
	},

	/**
	 * Helper function which is used to find and prepare an appointment bounds which help to
	 * render the appointment in calendar.
	 *
	 * @param {Zarafa.calendar.AppointmentRecord} records The {@link Zarafa.calendar.AppointmentRecord AppointmentRecord}
	 * which are going to render in calendar.
	 * @param {Date} date The Date of the month for which we have to prepare the appointment bounds.
	 * @return {Object} an object contains an appointment bounds which belongs to selected date.
	 */
	findAppointmentsByDate: function(records, date)
	{
		var items = [];
		var appointments = records.filter(function (record) {
			return record.startDate === date.getTime();
		}, this);

		if (!Ext.isEmpty(appointments)) {
			var topPosition = 25;
			var maxAppointment = 50;

			for (var i = 0; i < appointments.length; ++i) {
				// Stop the loop when maximum amount of appointments is reached.
				// In practice this will never be the case, due to high maximum and resizable print output.
				if (i > maxAppointment - 1) {
					break;
				}

				var item = {};
				item["appointment"] = {
					text: appointments[i].text,
					top: topPosition * (i + 1),
					color: appointments[i].color,
					busyStatus: this.getAppointmentStatus(appointments[i].busyStatus)
				};
				items.push(item);
			}
		}
		return {"items": items};
	},

	/**
	 * Function which is used to prepare the svg image based on the an appointment status.
	 *
	 * @param {Zarafa.core.mapi.BusyStatus} busyStatus the {@link Zarafa.core.mapi.BusyStatus busyStatus} of an appointment.
	 * @return {String} return the svg icon which used to indicate the status of an appointment.
	 */
	getAppointmentStatus: function(busyStatus)
	{
		switch (busyStatus) {
			case Zarafa.core.mapi.BusyStatus.FREE:
				return '<svg width="7" height="20" class="k-appointment-status">'
					+ '<rect x="0" y="0" rx="1" ry="1" width="7" height="20" style="fill:none; stroke:#FFF; stroke-width:1;" /> ' +
					+'</svg>';
			case Zarafa.core.mapi.BusyStatus.BUSY:
				return '<svg width="7" height="20" class="k-appointment-status">'
					+ '<rect x="0" y="0" rx="5" ry="1" width="7" height="20" style="fill:#0000FF; stroke:#0000FF; stroke-width:1;" /> ' +
					+'</svg>';
			case Zarafa.core.mapi.BusyStatus.OUTOFOFFICE:
				return '<svg width="7" height="20" class="k-appointment-status">'
					+ '<rect x="0" y="0" rx="5" ry="1" width="7" height="20" style="fill:#912787; stroke:#912787; stroke-width:0;" /> ' +
					+'</svg>';
			case Zarafa.core.mapi.BusyStatus.WORKINGELSEWHERE:
				return '<svg width="7" height="20" class="k-appointment-status">'
					+ '<rect x="0" y="0" rx="5" ry="1" width="7" height="20" style="fill:#5b9bd5; stroke:#5b9bd5; stroke-width:0;" /> ' +
					+'</svg>';
			case Zarafa.core.mapi.BusyStatus.TENTATIVE:
				return '<svg width="7" height="20" class="k-appointment-status">'
					+ '<defs>'
					+   '<pattern id="tentative" patternUnits="userSpaceOnUse" width="3" height="1" patternTransform="rotate(30)">'
					+	'<rect width="1" height="20" fill="#0000FF" style="stroke-width:0;"/>'
					+   '</pattern>'
					+ '</defs>'
					+ '<rect style="fill: url(#tentative);" x="0" y="0" rx="1" ry="1" width="7" height="20" ></rect>'
					+ '</svg>';
		}
	},

	/**
	 * Helper function which used to show the formatted date in calendar.
	 *
	 * @param {Date} date The Date which belongs to month.
	 * @param {Date} startDate The start date of date range.
	 * @return {String} return formatted date.
	 */
	getFormattedDate: function(date, startDate)
	{
		if (date.getTime() === startDate.getTime() || date.getTime() === date.getFirstDateOfMonth().getTime()) {
			return date.format(_('M j'));
		}
		return date.format(_('j'));
	},

	/**
	 * Returns the HTML that will be placed into the <body> part of the print window.
	 * @return {String} The HTML fragment to place inside the print window's <body> element
	 */
	generateBodyTemplate: function()
	{
		return '<table class="k-calendar-header" cellpadding=0 cellspacing=0>\n'
			+		'<tr><td align="left" style="font-size: large;">{current_month}</td>'
			+		'<td align="center">' +_('An overview of') + ' {fullname} '
			+ 		'<tpl for="folderList">'
			+				'<span class="circle" style="background-color: {values.folderColor};"></span>'
			+				'<span>{values.folderName}</span>'
			+			'</tpl>'
			+ 	'</td>'
			+		'<td align="center" valign="top" width="10%"><div id="datepicker_left"></div></td>'
			+		'<td align="center" valign="top" width="10%"><div id="datepicker_right"></div></td></tr>\n'
			+	'</table>\n'

			+'<table class="k-calendar-days">'
			+	'<tr style="height:5px;">'
			// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
			+		'<tpl for="days"><th class="date-header-days">{values.day}</th></tpl>'
			+	'</tr>'
			+ '<tpl for="weeks">'
			+	'<tr height="{values.maxDayAppointments}">'
			+		'<tpl for="values.week">'
			+			'<td style="position: relative;">'
			+				'<span class="k-day">'
			+					'{values.date}'
			+				'</span>'
			+				'<tpl for="values.appointments">'
			+					'<div class="k-appointment" style="top:{values.appointment.top}px; border:1px solid {values.appointment.color};">'
			+						'{values.appointment.busyStatus}'
			+						'<p style="padding-left: 10px; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; ">'
			+							'{values.appointment.text}'
			+						'</p>'
			+					'</div>'
			+				'</tpl>'
			+			'</td>'
			+		'</tpl>'
			+	'</tr>'
			+ '</tpl>'
			+ '</table>\n'

			// skipping page nr for now
			+'<table id="bottom">'
			+	'<tr>'
			+		'<td class="nowrap" align="left">{fullname}</td>'
			// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
			+		'<td class="nowrap" align="right">{currenttime:formatDefaultTimeString("' + _("l jS F Y {0}") + '")}</td>'
			+	'</tr>'
			+'</table>\n';
	}
});
Ext.namespace('Zarafa.calendar.ui');

/**
 * @class Zarafa.calendar.ui.AbstractCalendarBoxView
 * @extends Zarafa.calendar.ui.AbstractCalendarView
 *
 * The main calendar showing appointments as boxed (see {@link Zarafa.calendar.ui.AbstractCalendarBoxView BoxView}.
 * A {@link Zarafa.calendar.ui.CalendarPanel CalendarPanel} may show several individual calendars,
 * each represented by a subclass of {@link Zarafa.calendar.ui.AbstractCalendarBoxView AbstractCalendarBoxView}.
 *
 * The BoxView is used to display multiple weeks in a single view (e.g. a month). For displaying only a few days,
 * it is recommended to use the {@link Zarafa.calendar.ui.AbstractCalendarDaysView DaysView} instead.
 * The days which have been loaded will not always match the days which are visible in the view. This is because
 * the view will only display whole weeks, while in a monthview, the first day of the month might be in the middle
 * of the week. Hence some extra days will be rendered into the view which are marked as inactive. These days do not
 * contain any appointments, and will be displayed differently than active days, to clearly show their special status.
 */
Zarafa.calendar.ui.AbstractCalendarBoxView = Ext.extend(Zarafa.calendar.ui.AbstractCalendarView, {
	/**
	 * Array of {@link Zarafa.calendar.data.DayBoxConfiguration configuration} objects for the
	 * Day positions within this view.
	 * @property
	 * @type Array
	 */
	dayBoxConfigurations: undefined,

	/**
	 * @cfg {Number} firstDayOfWeek The day number of the first day of the week (0: sunday, 1: monday, ...)
	 * This day will be shown as the left column.
	 */
	firstDayOfWeek: 1,

	/**
	 * @cfg {Number} dayHeaderHeight The height of the Box Header (containing the date)
	 */
	dayHeaderHeight: 24,

	/**
	 * @cfg {Number} headerHeight The height of the Calendar Header (containing the column title).
	 */
	headerHeight: 30,

	/**
	 * @cfg {Number} appointmentHeight The height of an appointment within the calendar view.
	 */
	appointmentHeight: 26,

	/**
	 * @cfg {Number} expandThreshold The threshold in pixels which must remain empty at the bottom of
	 * box. If there are more appointments that should be visible in that box, they will not be rendered.
	 */
	expandThreshold: 5,

	/**
	 * The number of days per week.
	 * @property
	 * @type Number
	 */
	numDaysInWeek: 7,

	/**
	 * The format which must be passed to the {@link Date#format} function when
	 * the width of the dayHeader is smaller then the configured
	 * {@link Zarafa.calendar.ui.CalendarMultiView#minHeaderDayTextWidth minHeaderDayTextWidth}.
	 * If there is sufficient width, then {@link #longDayHeaderFormat} will be used.
	 * @property
	 * @type String
	 */
	// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
	shortDayHeaderFormat: _('l'),

	/**
	 * The format which must be passed to the {@link Date#format} function when
	 * the width of the dayHeader is greater then the configured
	 * {@link Zarafa.calendar.ui.CalendarMultiView#minHeaderDayTextWidth minHeaderDayTextWidth}.
	 * If there is not sufficient width, then {@link #shortDayHeaderFormat} will be used.
	 * @property
	 * @type String
	 */
	// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
	longDayHeaderFormat: _('l'),

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

		// If not explicitly configured, the firstDayOfWeek is determined
		// by the Settings option.
		if (!Ext.isDefined(config.firstDayOfWeek)) {
			config.firstDayOfWeek = container.getSettingsModel().get('zarafa/v1/main/week_start');
		}

		// If Drag & Drop is enabled by the enableDD option, we must split it up
		// to only enable Drag & Drop for the body element, as we have no functional
		// Drag & Drop support for the header of this view.
		if (config.enableDD === true) {
			Ext.apply(config, {
				enableDD: false,
				enableBodyDD: true
			});
		}

		Zarafa.calendar.ui.AbstractCalendarBoxView.superclass.constructor.call(this, config);
	},

	/**
	 * The {@link Zarafa.calendar.ui.CalendarMultiView CalendarMultiView} view has a header area that automatically
	 * resizes when its child views require more space. In the days view for instance, appointments that span
	 * more than 24 hours are laid out in the header.
	 * @return {Number} height in pixels the calendar view needs to properly lay out its header.
	 */
	getDesiredHeaderHeight: function()
	{
		return this.headerHeight;
	},

	/**
	 * Determine the Header Text which must be displayed for the given column.
	 * This will apply the {@link #dayHeaderFormat}.
	 * @param {Date} date The date for which the header title is requested
	 * @param {Number} width The available width for the header
	 * @private
	 */
	getDayHeaderTitle: function(date, width)
	{
		var dateFormat;

		if (width >= this.parentView.minHeaderDayTextWidth) {
			dateFormat = this.longDayHeaderFormat;
		} else {
			dateFormat = this.shortDayHeaderFormat;
		}

		return date.format(dateFormat);
	},

	/**
	 * Converts a location in page coordinates to a corresponding date.
	 * @param {Number} x horizontal component of the location
	 * @param {Number} y vertical component of the location
	 * @return {Date} a Date object that corresponds to the given location
	 */
	screenLocationToDate: function(x, y)
	{
		var bodyX = x - this.body.getLeft();
		var bodyY = y - this.body.getTop();

		for (var i=0, configuration; configuration=this.dayBoxConfigurations[i]; i++) {
			if (bodyX>configuration.left && bodyX<=configuration.right && bodyY>configuration.top && bodyY<=configuration.bottom)
			{
				// Check if the location is in the left or right half of the box. If it's in the right half
				// the location is actually closer to the date of the next box (next day), so we'll return
				// the date plus one day instead.
				// return (configuration.right-bodyX < bodyX-configuration.left) ? configuration.date.add(Date.DAY, 1) : configuration.date;
				return configuration.date;
			}
		}

		return null;
	},

	/**
	 * Converts a location in page coordinates to a corresponding daterange.
	 * @param {Number} x horizontal component of the location
	 * @param {Number} y vertical component of the location
	 * @return {Zarafa.core.DateRange} A DateRange object that corresponds to the given location
	 */
	screenLocationToDateRange: function(x, y)
	{
		// Both the start and dueDate need a clearTime() call, this
		// is in case of DST switches for Brasil where 00:00 doesn't exist
		// and clearTime() will return 01:00.
		var date = this.screenLocationToDate(x, y);

		// check for the valid date object
		if (Ext.isDate(date)) {
			date.setHours(12);
			var dueDate = date.add(Date.DAY, 1);

			return new Zarafa.core.DateRange({ startDate: date.clearTime(), dueDate: dueDate.clearTime() });
		}

		return null;
	},

	/**
	 * Converts a date range ([startDate, dueDate>) to zero or more (left, right, top, bottom) bounds objects.
	 * This method is used to lay out appointments (and proxies) on the calendar body.
	 * @param {Zarafa.core.DateRange} dateRange date range
	 * @param {Number} slot (optional) when several appointments are overlapping a column may be assigned
	 */
	dateRangeToBodyBounds: function(dateRange, slot)
	{
		var visible = this.getVisibleDateRange();
		var visibleWeeks = this.getVisibleWeekCount(visible);
		var fill = slot === undefined; // fill will be true for the selection box
		slot = slot || 0;

		var weekStart = visible.getStartDate();
		var startDate = dateRange.getStartDate();
		var dueDate  = dateRange.getDueDate();

		// To determine the number of days between the 2 dates.
		// To improve the result we should force both dates to be
		// around the same time. This prevents problems when the
		// start of the date variable is on a DST day where (in case
		// of Brasil) the DST switch occurs on midnight. Causing
		// a off-by-one error in the Math.floor(Date.diff()) line.
		weekStart = weekStart.clone();
		weekStart.setHours(12);
		startDate = startDate.clone();
		startDate.setHours(12);
		dueDate = dueDate.clone();
		dueDate.setHours(12);

		var ret = [];

		for (var i=0; i<visibleWeeks; i++) {
			var weekDue = weekStart.add(Date.DAY, this.numDaysInWeek);
			weekDue.setHours(12);

			var startDay = Math.floor(Date.diff(Date.DAY, startDate, weekStart));
			var dueDay = Math.floor(Date.diff(Date.DAY, dueDate, weekStart));

			// Handles a corner case where the appointment ends at exactly 00:00 the next day,
			// which means that the appointment is actually the end of the previous day.
			var origDue = dateRange.getDueDate();
			if (origDue.getTime() == origDue.clearTime(true).getTime()) {
				// Here it will check that appointment is not 0 minute appointment.
				if(!dateRange.isZeroMinuteRange()) {
					dueDay--;
				}
			}

			startDay = Math.max(startDay, 0);
			dueDay = Math.min(dueDay, 6);

			if (startDay<7 && dueDay>=0) {
				var startdayBoxConfigurations = this.dayBoxConfigurations[i*7 + startDay];
				var duedayBoxConfigurations = this.dayBoxConfigurations[i*7 + dueDay];

				var left = startdayBoxConfigurations.left + 2;
				var right = duedayBoxConfigurations.right - 2;
				var top = startdayBoxConfigurations.top + this.dayHeaderHeight + this.appointmentHeight * slot;
				if ( !fill ) {
					top += 2; // 2 pixels margin at the top
				}
				var bottom = fill ? startdayBoxConfigurations.bottom -1 : top + this.appointmentHeight - 1;

				if (fill || bottom <= startdayBoxConfigurations.bottom - this.expandThreshold) {
					ret.push({
						left: left, //startdayBoxConfigurations.left /*+ leftMargin*/ + 2,
						right: right, //duedayBoxConfigurations.right /*- rightMargin*/ - 2,
						top: top,
						bottom: bottom
					});
				}
			}

			weekStart = weekDue;

			if (weekStart > dueDate) {
				break;
			}
		}

		return ret;
	},

	/**
	 * Find all {@link Zarafa.calendar.ui.AppointmentView appointments} which
	 * fall within the given {@link Zarafa.core.DateRange dateRange}.
	 * @param {Zarafa.core.DateRange} The daterange for which the appointments are sought.
	 * @return {Array} The array of {@link Zarafa.calendar.ui.AppointmentView appointments}.
	 */
	findAppointmentsByRange: function(dateRange)
	{
		var ret = [];
		for (var i=0, appointment; appointment=this.appointments[i]; i++) {
			if (dateRange.inside(appointment.getDateRange())) {
				ret.push(appointment);
			}
		}

		return ret;
	},

	/**
	 * Find the {@link Zarafa.calendar.ui.AppointmentView appointment} with the highest
	 * {@link Zarafa.calendar.ui.AppointmentView#slot slot} value. within the given
	 * {@link Zarafa.core.DateRange dateRange}.
	 * @param {Zarafa.core.DateRange} The daterange for which the appointments are sought.
	 * @return {Number} The highest slot value
	 */
	maxSlotInRange: function(dateRange)
	{
		var ret = 0;

		for (var i=0, appointment; appointment=this.appointments[i]; i++) {
			if (dateRange.overlaps(appointment.getDateRange())) {
				ret = Math.max(ret, appointment.slot);
			}
		}

		return ret;
	},

	/**
	 * Return the {@link Zarafa.core.DateRange dateRange} of the period
	 * which is visible within this calendar view. This might be a larger
	 * number of days, as the loaded dateRange might start (or end) in the middle
	 * of the week. But the BoxView could show the entire week instead, meaning
	 * that extra boxes will have been rendered.
	 * @return {Zarafa.core.DateRange} The visible daterange
	 */
	getVisibleDateRange: function()
	{
		// Detect the Date of the first day of the week for the start date of the loaded range
		var visibleStart = this.getStartDate().getPreviousWeekDay(this.firstDayOfWeek);
		// Detect the Date of the last day of the week for the due date of the loaded range
		var visibleDue = this.getDueDate().getNextWeekDay(this.firstDayOfWeek);
		return new Zarafa.core.DateRange({ startDate: visibleStart, dueDate: visibleDue });
	},

	/**
	 * Return the number of visible weeks (See {@link #getVisibleDateRange}.
	 * @param {Zarafa.core.DateRange} range (optional) The daterange for which the number of weeks must be calculated,
	 * if not provided, then {@link #getVisibleDateRange} will be used.
	 * @return {Number} The number of visible weeks
	 */
	getVisibleWeekCount: function(range)
	{
		range = range || this.getVisibleDateRange();
		return Math.round(Date.diff(Date.DAY, range.getDueDate(), range.getStartDate()) / 7);
	},

	/**
	 * Initializes and returns the {@link #dayBoxConfigurations} array.
	 * @param {Number} height The height over which all the dayboxes must be spread
	 * @return {Array} The {@link Zarafa.calendar.data.DayBoxConfiguration DayBox} Configuration objects
	 */
	calculateDayBoxConfigurations: function(height)
	{
		if (!height) {
			height = this.body.getHeight();
		}

		var visibleRange = this.getVisibleDateRange();
		var visibleWeeks = this.getVisibleWeekCount(visibleRange);
		this.dayBoxConfigurations = [];
		var dayWidth = (this.body.getWidth()-1) / this.numDaysInWeek;
		var dayHeight = (height-1) / visibleWeeks;

		// Set the hours to 12 so we can safely use Date.add(Date.DAY, ...)
		// for cases where the DST switch occurs at 00:00 (like in Brasil).
		var firstVisibleDay = visibleRange.getStartDate().clone();
		firstVisibleDay.setHours(12);
		var todayTime = (new Date()).clearTime().getTime();

		for (var i=0; i<visibleWeeks; i++) {
			for (var j=0; j<this.numDaysInWeek; j++)
			{
				var busyStatus = 0;
				var date = firstVisibleDay.add(Date.DAY, (i * this.numDaysInWeek) + j);
				var dueDate = date.add(Date.DAY, 1);

				date.clearTime();
				dueDate.clearTime();

				var dayRange = new Zarafa.core.DateRange({ startDate: date, dueDate: dueDate });
				var workingDay = (this.parentView.workingDays.indexOf(date.getDay()) >= 0);

				// Find all appointments that cover the complete dayRange. We can use those
				// to change the background of the dayBox to reflect the busyStatus.
				var appointments = this.findAppointmentsByRange(dayRange);
				appointments.forEach(function(appointment) {
					var appointmentStatus = appointment.getRecord().get('busystatus');
					busyStatus = Math.max(busyStatus, appointmentStatus);
				});

				//var boxHeight = Math.round((i+1) * dayHeight) + 1 - Math.round(i * dayHeight);
				var boxHeight = dayHeight;
				var slotCount = Math.floor((boxHeight - this.dayHeaderHeight - this.expandThreshold - 2) / this.appointmentHeight);

				// Here it will get the first and last date of selected month.
				var activeDateRange = this.contextModel.getActiveDateRange();

				this.dayBoxConfigurations.push(new Zarafa.calendar.data.DayBoxConfiguration({
					left: j * dayWidth,
					right: (j+1) * dayWidth - 1,
					top: i * dayHeight,
					bottom: (i+1) * dayHeight - 1,
					date: date,
					today: todayTime == date.getTime(),
					workingDay: workingDay,
					busyStatus: busyStatus,
					overflow: this.maxSlotInRange(dayRange) + 1 > slotCount,
					active: date.getTime() >= activeDateRange.getStartDate().getTime() && date.getTime() < activeDateRange.getDueDate().getTime()
				}));
			}
		}

		return this.dayBoxConfigurations;
	}
});
Ext.namespace('Zarafa.calendar.ui');

/**
 * @class Zarafa.calendar.ui.AbstractCalendarDaysView
 * @extends Zarafa.calendar.ui.AbstractCalendarView
 *
 * The DaysView is used to display one or more days in columns next to each other. This is usually limited to 7 days,
 * although more days are supported. For an overview of a large number of days, the {@link Zarafa.calendar.ui.AbstractCalendarBoxView BoxView}
 * is however more recommended
 */
Zarafa.calendar.ui.AbstractCalendarDaysView = Ext.extend(Zarafa.calendar.ui.AbstractCalendarView, {
	/**
	 * Array of configuration {@link Zarafa.calendar.data.DayLayoutPosition DayLayoutPositions} within this view.
	 * @property
	 * @type Array
	 */
	dayLayoutPositions: [],

	/**
	 * The number of rows which are needed to display all all-day appointments
	 * for the given day.
	 * This property is calculated in {@link #calculateHeaderOverlaps}  (called by {@link #onBeforeLayout}) and used in
	 * {@link getDesiredHeaderHeight} to calculate the number of rows inside the {@link #header}.
	 * @property
	 * @type Number
	 */
	rowCount: 1,

	/**
	 * The format which must be passed to the {@link Date#format} function when
	 * the width of the dayHeader is smaller then the configured
	 * {@link Zarafa.calendar.ui.CalendarMultiView#minHeaderDayTextWidth minHeaderDayTextWidth}.
	 * If there is sufficient width, then {@link #longDayHeaderFormat} will be used.
	 * @property
	 * @type String
	 */
	// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
	shortDayHeaderFormat: _('jS'),

	/**
	 * The format which must be passed to the {@link Date#format} function when
	 * the width of the dayHeader is greater then the configured
	 * {@link Zarafa.calendar.ui.CalendarMultiView#minHeaderDayTextWidth minHeaderDayTextWidth}.
	 * If there is not sufficient width, then {@link #shortDayHeaderFormat} will be used.
	 * @property
	 * @type String
	 */
	// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
	longDayHeaderFormat: _('l jS F'),

	/**
	 * It will apply a right margin to the appointment box to prevent them from filling up the entire width of a day box.
	 * default is 10px.
	 * @property
	 * @type Number
	 */
	appointmentBodyRightMargin: 10,

	/**
	 * It will apply the left margin to the appointment box to prevents the appointment to overlap with the border.
	 *
	 * @property
	 * @type Number
	 */
	appointmentBodyLeftMargin: 1,

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

		Ext.applyIf(config, {
			// Add 1 pixel to the left margin, this prevents the appointment
			// to overlap with the border.
			appointmentBodyLeftMargin: config.appointmentBodyLeftMargin || this.appointmentBodyLeftMargin,
			appointmentBodyRightMargin: config.appointmentBodyRightMargin || this.appointmentBodyRightMargin
		});

		Zarafa.calendar.ui.AbstractCalendarDaysView.superclass.constructor.call(this, config);
	},

	/**
	 * The {@link Zarafa.calendar.ui.CalendarMultiView CalendarMultiView} view has a header area that automatically
	 * resizes when its child views require more space.
	 * @return {Number} height in pixels the calendar view needs to properly lay out its header.
	 */
	getDesiredHeaderHeight: function()
	{
		// this.rowCount has been calculated in onBeforeLayout, so by the time this function is called it
		// should be up to date
		return Math.max(this.rowCount || 1, 1) * this.parentView.headerItemHeight + this.parentView.headerTextHeight;
	},

	/**
	 * Determines the height of the calendar header in which appointments can be positioned.
	 * @return {Number} height in pixels of the appointment header area
	 */
	getAppointmentHeaderheight: function()
	{
		return this.header.getHeight() - this.parentView.headerTextHeight;
	},

	/**
	 * Tests if the date range should be laid out in the header, which is when a range spans 24 hours or more.
	 * @return {Boolean} true if the date range represents 24 hours or more, false otherwise.
	 */
	isHeaderRange: function(dateRange)
	{
		return dateRange.getDuration(Date.DAY) >= 1;
	},

	/**
	 * Determine the Header Text which must be displayed for the given {@link Date}. If the
	 * available width for the header is sufficiently large (The width is greater then
	 * {@link Zarafa.calendar.ui.CalendarMultiView#minHeaderDayTextWidth minHeaderDayTextWidth})
	 * then the {@link #longDayHeaderFormat} will be used for formatting the date. Otherwise the
	 * {@link #shortDayHeaderFormat} will be used.
	 * @param {Date} date The date for which the header title is requested
	 * @param {Number} width The available width for the header
	 * @private
	 */
	getDayHeaderTitle: function(date, width)
	{
		var dateFormat;

		if (width >= this.parentView.minHeaderDayTextWidth) {
			dateFormat = this.longDayHeaderFormat;
		} else {
			dateFormat = this.shortDayHeaderFormat;
		}

		return date.format(dateFormat);
	},

	/**
	 * This calculates the positions of each day which must be rendered into this view.
	 * This initializes the {@link #dayLayoutPositions} array containing all the positioning information.
	 * @return {Array} The array of {@link Zarafa.calendar.data.DayLayoutPosition dayLayoutPositions}
	 * @private
	 */
	calculateDayLayoutPositions: function()
	{
		var todayTime = (new Date()).clearTime().getTime();
		var firstDay = this.getDateRange().getStartDate();
		var numDays = this.getDateRange().getNumDays();

		var headerWidth = (this.body.getWidth()-1) / numDays;

		// Because of DST switches at 00:00 in Brasil, ensure
		// we are using a safe time to perform the "add" action
		firstDay = firstDay.clone();
		firstDay.setHours(12);

		// calculate header positions (left, right)
		this.dayLayoutPositions = [];
		for (var i = 0; i < numDays; i++) {
			var date = firstDay.add(Date.DAY, i).clearTime();
			var workingDay = (this.parentView.workingDays.indexOf(date.getDay()) >= 0);

			this.dayLayoutPositions.push(new Zarafa.calendar.data.DayLayoutPosition({
				left: Math.round(i * headerWidth),
				right: Math.round((i+1) * headerWidth) + 1,
				date: date, // Make sure we select the start of the day
				today: (todayTime == date.getTime()),
				workingDay: workingDay
			}));
		}

		return this.dayLayoutPositions;
	},

	/**
	 * Calculate the {@link Date date} within this view based on the
	 * X & Y coordinates. This uses the {@link #dayLayoutPositions} to
	 * determine which day & time matches the XY coordinates.
	 *
	 * @param {Number} x horizontal component of the location
	 * @param {Number} y vertical component of the location
	 * @return {Date} a Date object that corresponds to the given location
	 * @private
	 */
	locationToDate: function(x, y)
	{
		var numDays = this.getDateRange().getNumDays();
		var stripHeight = this.parentView.numHours * this.parentView.getHourHeight();

		// determine the day they mouse is over (horizontal position)
		var day;
		if (x < this.dayLayoutPositions[0].left) {
			day = 0;
		} else if (x > this.dayLayoutPositions[numDays - 1].right) {
			day = numDays - 1;
		} else {
			for (var i = 0; i < this.dayLayoutPositions.length; i++) {
				if (x >= this.dayLayoutPositions[i].left && x<=this.dayLayoutPositions[i].right) {
					day = i;
					break;
				}
			}
		}

		// Get the start date of the visible range
		var startDate = this.getDateRange().getStartDate();

		// Our first action is to move the date to the correct day,
		// we do this at 12:00 so we don't need to worry about the
		// DST switch at 00:00
		var date = startDate.clone();
		date.setHours(12);
		date = date.add(Date.DAY, day).clearTime();

		// determine time in milliseconds (vertical position)
		var time = (y / stripHeight) * Date.dayInMillis;

		// In some cases, the day might not start at 00:00, this could
		// occur in Brasil for example where the DST switch occurs at
		// midnight. At that moment 00:00 -> 01:00 doesn't exist. We
		// must compensate for this alternative start of the day by reducing
		// the time offset with the minutes which we skipped at midnight.
		if (time > 0) {
			time -= ((date.getHours() * 60 * 60) + (date.getMinutes() * 60)) * 1000;
		}

		// return a new Date object with the appropriate time
		return date.add(Date.MILLI, time);
	},

	/**
	 * Converts a location in page coordinates to a corresponding date.
	 * @param {Number} x horizontal component of the location
	 * @param {Number} y vertical component of the location
	 * @return {Date} a Date object that corresponds to the given location
	 */
	screenLocationToDate: function(x, y)
	{
		// get height / width of header's parent as that container is scrollable and
		// will give proper viewable height / width for comparison
		if (Zarafa.core.Util.inside(this.header.parent().getBox(), x, y)) {
			return this.locationToDate(x - this.header.getX(), 0);
		} else {
			return this.locationToDate(x - this.body.getX(), y - this.body.getY());
		}
	},

	/**
	 * Converts a location in page coordinates to a corresponding daterange.
	 * @param {Number} x horizontal component of the location
	 * @param {Number} y vertical component of the location
	 * @return {Zarafa.core.DateRange} A DateRange object that corresponds to the given location
	 */
	screenLocationToDateRange: function(x, y)
	{
		// get height / width of header's parent as that container is scrollable and
		// will give proper viewable height / width for comparison
		var date;
		if (Zarafa.core.Util.inside(this.header.parent().getBox(), x, y)) {
			// Both the start and dueDate need a clearTime() call, this
			// is in case of DST switches for Brasil where 00:00 doesn't exist
			// and clearTime() will return 01:00.
			date = this.locationToDate(x - this.header.getX(), 0).clearTime();
			var dueDate = date.clone();
			dueDate.setHours(12);
			dueDate = dueDate.add(Date.DAY, 1).clearTime();

			return new Zarafa.core.DateRange({ startDate: date, dueDate: dueDate });
		} else {
			date = this.locationToDate(x - this.body.getX(), y - this.body.getY());
			var snapSize = this.getZoomLevel() * 60 * 1000;
			var snap = date.getTime() - (date.getTime() % snapSize);

			return new Zarafa.core.DateRange({ startDate: new Date(snap), dueDate: new Date(snap + snapSize) });
		}
	},

	/**
	 * Convenience method that maps a Date to a number representing a day column
	 * on the current calendar (ranging from [0..numdays-1]).
	 * @param {Date} date date
	 * @param {Number} The day number in this view.
	 * @private
	 */
	getDayColumn: function(date)
	{
		var start = this.getDateRange().getStartDate();

		// We use the Ext clearTime() extension of Date() to account for
		// differences in DST. We pass true to get a clone of the passed
		// date, so we don't overwrite the original date.
		start = start.clearTime(true);
		date = date.clearTime(true);

		return Math.floor(Date.diff(Date.DAY, date, start));
	},

	/**
	 * Calculate the vertical position of the provided date on a given day column.
	 * @param {Date} date The date to calculate
	 * @private
	 */
	getDateVerticalPosition: function(date)
	{
		var timeZoneOffset = date.getTimezoneOffset() * 60 * 1000;
		return ((date.getTime() - timeZoneOffset) % Date.dayInMillis) / Date.dayInMillis;
	},

	/**
	 * Calculate the height of a time duration on a day column
	 * @param {Number} time The duration
	 * @private
	 */
	getRangeVerticalHeight: function(daterange)
	{
		// This is DST safe, as we render 24 hours regardless of the DST which might
		// occur on this particular day.
		var stripHeight = this.parentView.numHours * this.parentView.getHourHeight();
		var duration = daterange.getDuration();
		duration += Date.getDSTDiff(daterange.getStartDate(), daterange.getDueDate());
		return (duration * stripHeight) / Date.dayInMillis;
	},

	/**
	 * Converts a date range ([startDate, dueDate>) to zero or more (left, right, top, bottom) bounds objects.
	 * This method is used to lay out appointments (and proxies) on the calendar body.
	 * @param {Zarafa.core.DateRange} dateRange date range
	 * @param {Number} column (optional) when several appointments are overlapping a column may be assigned
	 * @param {Number} columnCount (optional) the number of overlapping appointments in an overlap dependency graph
	 * @param {Boolean} useMargin (optional) True to apply a right margin to the appointments to prevent them from
	 * filling up the entire width of a daybox.
	 * @return {Array} Array of {@link Zarafa.calendar.data.AppointmentBounds Bounds} which are used
	 * to position all the body elements.
	 */
	dateRangeToBodyBounds: function(dateRange, column, columnCount, useMargin)
	{
		var ret = [];
		var numDays = this.dayLayoutPositions.length;
		var stripHeight = this.parentView.numHours * this.parentView.getHourHeight();
		var leftMargin = useMargin ? this.appointmentBodyLeftMargin : 0;
		var rightMargin = useMargin ? this.appointmentBodyRightMargin : 0;

		// TODO
		var startDate = dateRange.getStartDate();
		var dueDate = dateRange.getDueDate();

		// Apply default values
		column = column || 0;
		columnCount = columnCount || 1;

		// if dueDate>=startDate, return false. We don't swap the dates here to force a valid
		// bounds array because that should be handled in the caller
		if (dueDate.getTime() <= startDate.getTime()) {
			return [];
		}

		var startDay = this.getDayColumn(startDate);
		var dueDay = this.getDayColumn(dueDate);

		// the date is outside the calendar range, return empty bounds array.
		// IMPORTANT: This checks only if _both_ start and due date are out of bounds.
		// It could still happen that the startDate is out of bounds, while the dueDate
		// is positioned within the view (or vice versa). In both cases we will create
		// a bound for the appointment, we will check what the exact case is later
		// during the construction of the Zarafa.calendar.data.AppointmentBounds objects.
		if (dueDay < 0 || startDay >= numDays) {
			return [];
		}

		var startDatePos = Math.floor(this.getDateVerticalPosition(startDate) * stripHeight);
		var dueDatePos = Math.floor(this.getDateVerticalPosition(dueDate) * stripHeight);

		// move the top of each box one pixel up. This is because we want both the top line and bottom line of
		// each appointment box to line up over the horizontal hour lines
		startDatePos--;

		if (startDay == dueDay)	{
			// The startDay is the same as the dueDay, we already checked that
			// both values fall within the current view boundaries, so all that
			// is left to be done is creating a single Zarafa.calendar.data.AppointmentBounds
			// object which is both our first, as well as the lastBox.

			// Calculate the columnWidth, this is the total available space within the dayColumn
			// which is usable for this appointment. The width depends on a few factors:
			//  1) The width of the dayColumn itself
			//  2) The margins which are applied to the dayColumn (both left, and right).
			//  3) The number of appointments which overlap, each appointment will then be positioned
			//     into a subcolumn inside the dayColumn. The number of these columns is indicated by
			//     the columnCount.
			var columnWidth = (this.dayLayoutPositions[startDay].right - this.dayLayoutPositions[startDay].left - 1 - rightMargin - leftMargin) / columnCount;

			// Calculate the left position of the appointment, this is the position
			// of the dayColumn plus the leftMargin, and the offset depending on the number
			// of subcolumns.
			var left = this.dayLayoutPositions[startDay].left + (columnWidth * column) + leftMargin + 1;

			ret.push(new Zarafa.calendar.data.AppointmentBounds({
				left: Math.round(left),
				right: Math.round(left + columnWidth),
				top: startDatePos,
				bottom: dueDatePos,
				firstBox: true,
				lastBox: true
			}));
		} else {
			// add a bounds object for the first day.
			if (startDay >= 0) {
				columnWidth = (this.dayLayoutPositions[startDay].right - this.dayLayoutPositions[startDay].left - 1 - rightMargin - leftMargin) / columnCount;
				left = this.dayLayoutPositions[startDay].left + (columnWidth * column) + leftMargin;
				ret.push(new Zarafa.calendar.data.AppointmentBounds({
					left: Math.round(left),
					right: Math.round(left + columnWidth),
					top: startDatePos,
					bottom: stripHeight,
					firstBox: true
				}));
			}

			// for every day that is spanned completely by the appointment, return a bounds object that spans
			// that day strip
			for (var i = Math.max(startDay + 1, 0); i <= Math.min(dueDay - 1, numDays - 1); i++) {
				columnWidth = (this.dayLayoutPositions[i].right - this.dayLayoutPositions[i].left - 1 - rightMargin - leftMargin) / columnCount;
				left = this.dayLayoutPositions[i].left + (columnWidth * column) + leftMargin;
				ret.push(new Zarafa.calendar.data.AppointmentBounds({
					left: Math.round(left),
					right: Math.round(left + columnWidth),
					top: -1,
					bottom: stripHeight
				}));
			}

			// add a bounds object for the last day. If the appointment starts and ends on the same day
			// this will be the only item in the bounds array
			// The check 'dueDatePos>0' handles a corner case where an appointment is exactly 24 hours long
			// (even though this shouldn't be displayed in the body anyway).
			if (dueDay < numDays && dueDatePos > 0)	{
				var columnWidth = (this.dayLayoutPositions[dueDay].right - this.dayLayoutPositions[dueDay].left - 1 - rightMargin - leftMargin) / columnCount;
				var left = this.dayLayoutPositions[dueDay].left + (columnWidth * column) + leftMargin;

				// This is the last bound for the appointment which represents the
				// due date of the appointment. Note that this is not automatically the
				// case since the appointments due date might fall _after_ the last visible
				// date in our view.
				ret.push(new Zarafa.calendar.data.AppointmentBounds({
					left: Math.round(left),
					right: Math.round(left + columnWidth),
					top: -1,
					bottom: dueDatePos,
					lastBox: true
				}));
			}

		}

		return ret;
	},

	/**
	 * Converts a date range ([startDate, dueDate>) a (left, right, top, bottom) box.
	 * This method is used to lay out appointments (and proxies) on the calendar header.
	 * @param {Zarafa.core.DateRange} dateRange date range
	 * @param {Number} column (optional) when several appointments are overlapping a column may be assigned
	 * @param {Number} columnCount (optional) the number of overlapping appointments in an overlap dependency graph
	 * @param {Boolean} useMargin (optional) True to apply margins to the appointments to prevent them
	 * from filling up the entire width of the header (This applies {@link #appointmentHeaderLeftMargin} and
	 * {@link #appointmentHeaderRightMargin}).
	 * @return {Array} Array of {@link Zarafa.calendar.data.AppointmentBounds Bounds} which are used
	 * to position the header element.
	 */
	dateRangeToHeaderBounds: function(dateRange, row, rowCount, useMargin)
	{
		var numDays = this.dayLayoutPositions.length;
		var leftMargin = useMargin ? this.appointmentHeaderLeftMargin : 0;
		var rightMargin = useMargin ? this.appointmentHeaderRightMargin : 0;
		var top = this.parentView.headerTextHeight;

		var startDate = dateRange.getStartDate();
		var dueDate = dateRange.getDueDate();

		// TODO
		var startDay = this.getDayColumn(startDate);
		var dueDay = this.getDayColumn(dueDate);

		// Apply default values
		row = row || 0;
		rowCount = rowCount || this.rowCount;

		// Handles a corner case where the appointment ends at exactly 00:00 the next day,
		// which means that the appointment is actually the end of the previous day. Note that
		// we cannot use clearTime() here in case of DST switches in Brasil where 00:00 doesn't
		// exist and clearTime() will return 01:00
		if (dueDate.clearTime(true).getTime() === dueDate.getTime()) {
			dueDay--;
		}

		// the date is outside the calendar range, return empty bounds array.
		if (dueDay < 0 || startDay >= numDays) {
			return [];
		}

		// We now have 4 possible cases:
		// 1) The start date does not fall within the visible range,
		//    which means that this isn't the firstBox, and the leftMargin
		//    should not be applied.
		// 2) The start and due date fall within the range, so the box
		//    is the firstBox and lastBox, and all margins must be applied.
		// 3) The due date does not fall within the visible range,
		//    which means that this isn't the lastBox, and the right
		//    Margin should not be applied.
		// 4) The start and due data both fall outside of the range, so the box
		//    is actually a middleBox, and none of the margins must be applied.
		if (startDay < 0 && dueDay >= numDays) {
			// case (4)
			// This is a middleBox which is drawn, both the firstBox as lastBox fall outside of the range.
			// Our left position is the most-left position (the first dayColumn), the right position is the
			// most-right position (the last dayColumn). This will ensure that the appointment will stretch
			// the entire length of all columns.
			return new Zarafa.calendar.data.AppointmentBounds({
				left: this.dayLayoutPositions[0].left,
				right: this.dayLayoutPositions[numDays - 1].right,
				top: top + row * this.parentView.headerItemHeight,
				bottom: top + (row + rowCount) * this.parentView.headerItemHeight
			});
		} else if (startDay < 0) {
			// case (1)
			// This is the lastBox which is drawn, but not the firstBox (the firstBox is out of range).
			// Our left position is the most-left position (the first dayColumn). This will ensure that
			// the appointment will stretch from the first visible day until the due date.
			return new Zarafa.calendar.data.AppointmentBounds({
				left: this.dayLayoutPositions[0].left,
				right: this.dayLayoutPositions[dueDay].right - rightMargin,
				top: top + row * this.parentView.headerItemHeight,
				bottom: top + (row + rowCount) * this.parentView.headerItemHeight,
				lastBox: true
			});
		} else if (dueDay >= numDays) {
			// case (3)
			// This is the firstBox which is drawn, but not the lastBox (the lastBox is out of range).
			// Our right position is the most-right position (the last dayColumn). This will ensure that
			// the appointment will stretch from the start date to the last visible day.
			return new Zarafa.calendar.data.AppointmentBounds({
				left: this.dayLayoutPositions[startDay].left + leftMargin,
				right: this.dayLayoutPositions[numDays - 1].right,
				top: top + row * this.parentView.headerItemHeight,
				bottom: top + (row + rowCount) * this.parentView.headerItemHeight,
				firstBox: true
			});
		} else {
			// case (2)
			// This is both the firstBox as well as the lastBox (everythhing is within the range).
			// The left and right position is read from the positions from the correct dayColumns
			// with padding applied.
			return new Zarafa.calendar.data.AppointmentBounds({
				left: this.dayLayoutPositions[startDay].left + leftMargin,
				right: this.dayLayoutPositions[dueDay].right - rightMargin,
				top: top + row * this.parentView.headerItemHeight,
				bottom: top + (row + rowCount) * this.parentView.headerItemHeight,
				firstBox: true,
				lastBox: true
			});
		}
	},

	/**
	 * Splits a set of {@link Zarafa.calendar.ui.Appointment Appointment} objects into overlapping clusters.
	 * @param {Zarafa.calendar.ui.Appointment|Array} appointment The appointment list
	 * @return {Zarafa.calendar.ui.Appointment|Array} clusters The matrix of appointments
	 */
	getAppointmentClusters: function(appointments)
	{
		// sort appointments by start date
		appointments.sort(this.appointmentCompare);

		// find connected graphs (clusters) of appointments and determine the maximum column number among them
		// TODO clean up this code
		var cluster = [], clusters = [];
		var clusterDueDate = 0;
		for (var i=0, appointment; appointment=appointments[i]; i++) {
			var startDate = appointment.getDateRange().getStartTime();
			var dueDate = appointment.getAdjustedDateRange().getDueTime();

			if (cluster.length === 0) {
				clusterDueDate = dueDate;
				cluster.push(appointment);
			} else if (startDate < clusterDueDate) {
				clusterDueDate = Math.max(dueDate, clusterDueDate);
				cluster.push(appointment);
			} else {
				clusters.push(cluster);

				// start new cluster
				cluster = [ appointment ];
				clusterDueDate = dueDate;
			}
		}
		if (cluster.length > 0) {
			clusters.push(cluster);
		}

		return clusters;
	},

	/**
	 * Appointments that span less than 24 hours are laid out in the body of the view. This method calculates
	 * how these appointments overlap in time and assigns slots (columns) to each appointment.
	 * <p>
	 * The appointments are first split up into clusters of overlapping appointments. Each cluster is then
	 * treated individually, using a simple greedy coloring algorithm. The number of required slots for each
	 * cluster is added to each appointment in the form of a 'slotCount' property. This property is then used
	 * to scale the appointments properly in the layout phase.
	 *
	 * @return {Number} the number of appointment rows needed to accommodate all appointments
	 * @private
	 */
	calculateBodyOverlaps: function()
	{
		// Make a list of appointments that are displayed on the calendar body
		var bodyAppointments = [];
		Ext.each(this.appointments, function(appointment) {
			if (!this.isHeaderRange(appointment.getDateRange())) {
				bodyAppointments.push(appointment);
			}
		}, this);

		var clusters = this.getAppointmentClusters(bodyAppointments);

		clusters.forEach(function(cluster) {
			this.doGreedyColoring(cluster, false);
		}, this);
	},

	/**
	 * Appointments that span over 24 hours are laid out in the header of the view. This method calculates
	 * how these appointments overlap in time and assigns slots (rows) to each appointment. The same greedy
	 * coloring algorithm is used as in calculateBodyOverlaps().
	 * @private
	 */
	calculateHeaderOverlaps: function()
	{
		// Make a list of appointments that are displayed in the calendar header area
		var headerAppointments = [];
		Ext.each(this.appointments, function(appointment) {
			if (this.isHeaderRange(appointment.getDateRange())) {
				headerAppointments.push(appointment);
			}
		}, this);

		this.doGreedyColoring(headerAppointments, true);

		this.rowCount = (headerAppointments.length>0) ? headerAppointments[0].slotCount : 0;
	},

	/**
	 * Calculate both body and header overlaps in a single pass over the appointments array.
	 * More efficient than calling calculateBodyOverlaps() and calculateHeaderOverlaps()
	 * separately, as appointments are partitioned in one iteration.
	 * @private
	 */
	calculateAllOverlaps: function()
	{
		var bodyAppointments = [];
		var headerAppointments = [];

		Ext.each(this.appointments, function(appointment) {
			if (this.isHeaderRange(appointment.getDateRange())) {
				headerAppointments.push(appointment);
			} else {
				bodyAppointments.push(appointment);
			}
		}, this);

		var clusters = this.getAppointmentClusters(bodyAppointments);
		clusters.forEach(function(cluster) {
			this.doGreedyColoring(cluster, false);
		}, this);

		this.doGreedyColoring(headerAppointments, true);
		this.rowCount = (headerAppointments.length > 0) ? headerAppointments[0].slotCount : 0;
	},

	/**
	 * Called by the {@link #parentView} when the {@link Zarafa.core.data.IPMStore#load load} event
	 * has been fired from the appointment {@link Zarafa.calendar.ui.CalendarMultiView#store store}.
	 * After all appointments have been added to the calendar, the {@link #calculateBodyOverlaps body overlaps}
	 * and {@link #calculateHeaderOverlaps header overlaps} will be recalculated.
	 * @param {Zarafa.core.data.IPMStore} store store that fired the event.
	 * @param {Zarafa.core.data.IPMRecord[]} records loaded record set
	 * @param {Object} options the options (parameters) with which the load was invoked.
	 */
	onAppointmentsLoad: function(store, records, options)
	{
		Zarafa.calendar.ui.AbstractCalendarDaysView.superclass.onAppointmentsLoad.apply(this, arguments);

		// Appointments have been loaded, recalculate all overlaps in a single pass
		this.calculateAllOverlaps();
	},

	/**
	 * Adds a new appointment to the view.
	 * Depending if it is an allday appointment, the {@link #calculateBodyOverlaps body overlaps}
	 * or {@link #calculateHeaderOverlaps header overlaps} will be recalculated.
	 * @param {Ext.data.Record} record a record with the appointment data
	 * @param {Boolean} layout (optional) if true layout() will be called after the appointment was added. Defaults to true.
	 * @return {Boolean} True if an appointment was added, false otherwise.
	 */
	addAppointment: function(record, layout)
	{
		var added = Zarafa.calendar.ui.AbstractCalendarDaysView.superclass.addAppointment.apply(this, arguments);
		if ( !added ){
			return false;
		}

		if (record.get('alldayevent')) {
			this.calculateHeaderOverlaps();
		} else {
			this.calculateBodyOverlaps();
		}

		return true;
	},

	/**
	 * Removes an appointment from the view.
	 * Depending if it is an allday appointment, the {@link #calculateBodyOverlaps body overlaps}
	 * or {@link #calculateHeaderOverlaps header overlaps} will be recalculated.
	 * @param {Ext.data.Record} record appointment record
	 * @param {Boolean} layout (optional) if true layout() will be called after the appointment was added. Defaults to true.
	 * @return {Boolean} true if an appointment was removed, false otherwise (the appointment was not found in this view)
	 */
	removeAppointment: function(record, layout)
	{
		Zarafa.calendar.ui.AbstractCalendarDaysView.superclass.removeAppointment.apply(this, arguments);
		if (record.get('alldayevent')) {
			this.calculateHeaderOverlaps();
		} else {
			this.calculateBodyOverlaps();
		}
	},

	/**
	 * Called before the calendar will be laid out.
	 * This will recalculate the {@link #calculateBodyOverlaps body} and {@link #calculateHeaderOverlaps header overlaps}.
	 * @protected.
	 */
	onBeforeLayout: function()
	{
		Zarafa.calendar.ui.AbstractCalendarDaysView.superclass.onBeforeLayout.apply(this, arguments);
	}
});
Ext.namespace('Zarafa.calendar.ui');

/**
 * @class Zarafa.calendar.ui.AppointmentView
 * @extends Zarafa.calendar.ui.AbstractDateRangeView
 *
 * This view represents a {@link Zarafa.core.data.IPMRecord record} within the
 * {@link Zarafa.calendar.ui.AbstractCalendarView view}.
 */
Zarafa.calendar.ui.AppointmentView = Ext.extend(Zarafa.calendar.ui.AbstractDateRangeView, {
	/**
	 * @cfg {String} timeFormat The time format which must be applied to appointments
	 * when displaying the time period (see {@link Date.format format}).
	 */
	// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
	timeFormat: _('G:i'),

	/**
	 * @cfg {Boolean} stripVisible Enables the BusyStatus strip on the left side of
	 * the appointment. The &lt;div&gt; which is being rendered for this strip contains
	 * the styling to correctly mark the appointment with the given BusyStatus.
	 */
	stripVisible: true,

	/**
	 * @cfg {Number} stripWidth The width of the strip which contains the CSS styling for
	 * marking an appointment with a particular BusyStatus (free, tentative, out of office).
	 * This width is only applied when {@link #stripVisible} is true.
	 */
	stripWidth: 6,

	/**
	 * @cfg {Zarafa.core.data.IPMRecord} The record which is being displayed by this {@link Zarafa.core.ui.View view}.
	 */
	record: undefined,

	/**
	 * Indicates if the this appointment has been selected by the user. This can be
	 * set using {@link #setSelected} and requested using {@link #isSelected}.
	 * @property
	 * @type Boolean
	 */
	selected: false,

	/**
	 * Indicates if the folder in which this appointment lives has been selected by the
	 * user. This indicates that the entire folder (and thus this appointment) is active.
	 * This can be set using {@link #setActive} and requested using
	 * {@link #isActive}.
	 * @property
	 * @type Boolean
	 */
	active: false,

	/**
	 * The adjusted daterange object. This is an alternative to the {@link #dateRange} object.
	 * If the duration of an appointment is short, the height of the appointment view may less
	 * than the minimal height at which we can render appointment views comfortably.
	 * Therefore the 'adjusted date range' of an appointment is defined such that the duration
	 * of the appointment is always at least the parent calendar view's current {@link #zoomLevel}
	 * (which is defined as a time range, for instance 30 minutes).
	 * @property
	 * @type Zarafa.core.DateRange
	 */
	adjustedDateRange: undefined,

	/**
	 * The color scheme set by the {@link Zarafa.calendar.ui.AbstractCalendarView parent calendar View}.
	 * @property
	 * @type Object
	 */
	calendarColorScheme: undefined,

	/**
	 * The element which indicates that the selectionrange is currently focussed by the user.
	 * As long as this element is focussed, the focus on this selection is assumed.
	 * @property
	 * @type Ext.Element
	 */
	focusEl: undefined,

	/**
	 * Initialises the AppointmentView.
	 */
	init: function()
	{
		Zarafa.calendar.ui.AppointmentView.superclass.init.call(this);

		this.timeFormat = container.settingsModel.get('zarafa/v1/main/datetime_time_format');
		if (this.record) {
			this.updateDateRange(this.record);
		}
	},

	/**
	 * Renders the view.
	 * @param {Ext.Element} container The Ext.Element into which the view must be rendered.
	 */
	render: function(container)
	{
		Zarafa.calendar.ui.AppointmentView.superclass.render.apply(this, arguments);

		// Create the focusElement and relay the important events to this.
		this.create({
			tag: 'a',
			href: '#',
			// Disable tab-index, and position it somewhere where it cannot be seen
			// by the user. This will make the element completely invisible for the
			// user while we can benefit from the focus capabilities.
			tabindex: -1,
			style: 'position: absolute; left:-10000px; top:-10000px;'
		}, this.container, 'focusEl');
	},

	/**
	 * Focuses on the {@link #focusEl}.
	 */
	focus: function()
	{
		if(this.focusEl) {
			this.focusEl.focus();
		}
	},

	/**
	 * Updates the {@link #dateRange} and {@link #adjustedDateRange} objects attached to this
	 * {@link Zarafa.calendar.ui.AbstractDateRangeView view}.
	 * This must be called whenever the {@link #record} has been updated.
	 * @param {Zarafa.core.data.IPMRecord} record The record which must be used to update the {@link #dateRange}.
	 */
	updateDateRange: function(record)
	{
		var range = this.getDateRange();
		var adjustedRange = this.getAdjustedDateRange();
		var start = record.get('startdate');
		var end = record.get('duedate');
		var adjustment = 0;

		if (range) {
			range.set(start, end);
		} else {
			range = new Zarafa.core.DateRange({ startDate: start, dueDate: end });
			this.setDateRange(range);
		}

		adjustment = Math.max(range.getDuration(Date.MINUTE), this.parentView.getZoomLevel());
		end = start.add(Date.MINUTE, adjustment);

		if (adjustedRange) {
			adjustedRange.set(start, end);
		} else {
			adjustedRange = new Zarafa.core.DateRange({ startDate: start, dueDate: end });
			this.setAdjustedDateRange(adjustedRange);
		}
	},

	/**
	 * Sets {@link #adjustedDateRange}. This method does not auto-update.
	 * @param {Zarafa.core.DateRange} dateRange
	 */
	setAdjustedDateRange: function(dateRange)
	{
		this.adjustedDateRange = dateRange;
	},

	/**
	 * Returns the current {@link #adjustedDateRange}.
	 * @return {Zarafa.core.DateRange} dateRange
	 */
	getAdjustedDateRange: function()
	{
		return this.adjustedDateRange;
	},

	/**
	 * Sets whether the appointment is selected. When set to true the appointment will appear on screen with a thick
	 * black border around it. This method does not auto-update, so layout must be called explicitly after setting
	 * this value.
	 * @param {Boolean} selected true iff the appointment is selected.
	 */
	setSelected: function(selected)
	{
		this.selected = selected;
	},

	/**
	 * @return {Boolean} true iff the appointment is selected.
	 */
	isSelected: function()
	{
		return this.selected;
	},

	/**
	 * Sets whether the appointment is active. An active appointment means that the parent folder of the appointment is selected by
	 * the user.  When multiple folders are shown in a single calendar view, only one of them is selected at any given time.
	 * Appointments from that folder are rendered differently to show the distinction.
	 * @param {Boolean} parentFolderSelected true iff the appointment parent folder is selected.
	 */
	setActive: function(active)
	{
		this.active = active;
	},

	/**
	 * @return {Boolean} true iff the appointment is active (parent folder is selected).
	 */
	isActive: function()
	{
		return this.active;
	},

	/**
	 * Icon renderer. This will return an array of icon CSS names which must be rendered
	 * for this appointment. The icons which will be added if the appointment is recurring,
	 * if it it is an meeting request or if a reminder has been set.
	 * @return {Array} The array of strings containing the CSS names for the icons
	 * @private
	 */
	iconRenderer: function()
	{
		var record = this.getRecord();
		var icons = [];

		if (record.get('private') === true) {
			icons.push('private');
		}

		if (record.get('importance') === Zarafa.core.mapi.Importance.URGENT) {
			icons.push('importance');
		}

		if (record.get('importance') === Zarafa.core.mapi.Importance.NONURGENT) {
			icons.push('importance_low');
		}

		if (record.isRecurringOccurrence() === true) {
			if (record.isRecurringException() === true) {
				icons.push('exception');
			} else {
				icons.push('recurring');
			}
		}

		return icons;
	},

	/**
	 * Body main text renderer. This will return the string which must be displayed most
	 * prominently in the appointment when it has been rendered in the main contents
	 * section of the calendar. This applies to non-all-day appointments.
	 * @return {String} The body-header text
	 * @private
	 */
	mainTextRenderer: function()
	{
		return Ext.util.Format.htmlEncode(this.record.get('subject'));
	},

	/**
	 * Body sub text renderer. This will return the string which must be displayed less
	 * prominently in the appointment when it has been rendered in the main contents
	 * section of the calendar. This applies to non-all-day appointments.
	 * @return {String} The body text
	 * @private
	 */
	subTextRenderer: function()
	{
		var location = this.record.get('location');
		if(!Ext.isEmpty(location)) {
			location = '(' + location + ')';
		}

		return Ext.util.Format.htmlEncode(location);
	},

	/**
	 * @return {String} returns the appointment ID.
	 */
	getId: function()
	{
		return this.record.id;
	},

	/**
	 * @return {Ext.data.Record} the appointment record this view presents.
	 */
	getRecord: function()
	{
		return this.record;
	},

	/**
	 * Gets the busy status of the appointment. Is a value from the {@link Zarafa.core.mapi.BusyStatus BusyStatus}
	 * enumeration to indicate the status as free, tentative, busy, and out of office respectively.
	 * @return {Zarafa.core.mapi.BusyStatus} the busy status of the appointment.
	 */
	getBusyStatus: function()
	{
		return this.record.get('busystatus');
	},

	/**
	 * Gets the busy status name of the appointment. This is a lowercase string, which
	 * indicates the status of the appointment as 'free', 'tentative', 'busy' and 'outofoffice'.
	 * @return {String} The lowercase string of the busystatus
	 *
	 */
	getBusyStatusName: function()
	{
		return Zarafa.core.mapi.BusyStatus.getName(this.getBusyStatus()).toLowerCase();
	},

	/**
	 * Gets the label of the appointment.
	 * @return {String} the label of the appointment.
	 */
	getLabel: function()
	{
		return this.record.get('label');
	},

	/**
	 * Indicates if this appointment is an all-day event
	 * @return {Boolean} True if this is an allday event
	 */
	isAllDay: function()
	{
		return this.getDateRange().isAllDay();
	},

	/**
	 * Obtain the stripWidth (See {@link #stripWidth}). The width depends on the
	 * {@link #stripVisible} and {@link #stripWidth} values.
	 * @return {Number} The width of the strip
	 */
	getStripWidth: function()
	{
		return (this.stripVisible) ? this.stripWidth : 0;
	},

	/**
	 * Return the color based on the activity and the categories of the appointment. When the
	 * appointment belongs to a non-active calendar, it will return the color scheme of the
	 * calendar. This color scheme is set in the {@link #calendarColorScheme calendarColorScheme}
	 * property. When the appointment belongs to an active calendar it will return the color of
	 * the last added category. In the case that the appointment has no label the color scheme
	 * of the calendar view it belongs to is used.
	 * @return {Object} The color scheme object
	 */
	getAppointmentColor: function()
	{
		var categories = Zarafa.common.categories.Util.getCategories(this.record);

		// Note: If a label has been set, then it will have been added as the last
		// category by the getCategories method
		if ( this.isActive() &&  !Ext.isEmpty(categories) ){
			return Zarafa.common.categories.Util.getCategoryColor(categories.pop());
		}

		// If no category is set then default back to the color scheme of the calendar
		return this.calendarColorScheme.base;
	}
});
Ext.namespace('Zarafa.calendar.ui');

/**
 * @class Zarafa.calendar.ui.CalendarGridView
 * @extends Zarafa.common.ui.grid.MapiMessageGrid
 * @xtype zarafa.calendargrid
 *
 * This view is used to list all the appointments as a list view
 */
Zarafa.calendar.ui.CalendarGridView = Ext.extend(Zarafa.common.ui.grid.MapiMessageGrid, {
	/**
	 * @cfg {Zarafa.calendar.CalendarContext} context The context to which this panel belongs
	 */
	context: undefined,

	/**
	 * The {@link Zarafa.calendar.CalendarContextModel} which is obtained from
	 * the {@link #context}.
	 *
	 * @property
	 * @type Zarafa.calendar.CalendarContextModel
	 */
	model: undefined,

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

		if (!Ext.isDefined(config.model) && Ext.isDefined(config.context)) {
			config.model = config.context.getModel();
		}
		if (!Ext.isDefined(config.store) && Ext.isDefined(config.model)) {
			config.store = config.model.getStore();
		}
		config.store = Ext.StoreMgr.lookup(config.store);

		config = Ext.applyIf(config, {
			xtype: 'zarafa.calendargrid',
			ariaLabel: _('Appointments list'),
			overCls: 'zarafa-calendar-grid-row-over',
			border: false,
			stateful: true,
			statefulRelativeDimensions: false,
			loadMask: this.initLoadMask(),
			viewConfig: this.initViewConfig(),
			selModel: this.initSelectionModel(),
			colModel: this.initColumnModel(),
			enableDragDrop: true,
			ddGroup: 'dd.mapiitem'
		});

		Zarafa.calendar.ui.CalendarGridView.superclass.constructor.call(this, config);
	},

	/**
	 * @return {Object} view config object
	 * @private
	 */
	initViewConfig: function()
	{
		return {
			forceFit: false,
			getRowClass: this.getRowClass.createDelegate(this)
		};
	},

	/**
	 * @return {Object} loas mask config object
	 * @private
	 */
	initLoadMask: function()
	{
		return {
			msg: _('Loading appointments') + '...'
		};
	},

	/**
	 * creates and returns a column model object, used in {@link Ext.grid.EditorGridPanel.colModel colModel} config
	 * @return {Ext.grid.ColumnModel} column model object
	 * @private
	 */
	initColumnModel: function()
	{
		return new Zarafa.calendar.ui.CalendarGridColumnModel();
	},

	/**
	 * creates and returns a selection model object, used in {@link Ext.grid.GridPanel.selModel selModel} config
	 * @return {Ext.grid.RowSelectionModel} selection model object
	 * @private
	 */
	initSelectionModel: function()
	{
		return new Ext.grid.RowSelectionModel({
			singleSelect: false
		});
	},

	/**
	 * initialize events for the grid panel
	 * @private
	 */
	initEvents: function()
	{
		Zarafa.calendar.ui.CalendarGridView.superclass.initEvents.call(this);

		this.mon(this.model, 'foldermergestatechanged', this.onFolderMergestateChanged, this);
		this.on('rowdblclick', this.onRowDblClick, this);
		this.mon(this.getSelectionModel(), 'selectionchange', this.onSelectionChange, this);
	},

	/**
	 * Event handler which is triggered when the mergeState has
	 * changed for the folders. This will either merge the contents
	 * of all folders into a single Grid, or apply grouping to
	 * clearly differentiate between the different folders
	 * @param {Zarafa.core.ContextModel} model The model which raised the event
	 * @param {Boolean} mergeState The current merge state
	 */
	onFolderMergestateChanged: function(model, mergeState)
	{
		if (mergeState) {
			model.clearGrouping();
		} else {
			model.groupBy('parent_entryid');
		}
	},

	/**
	 * Event handler which is triggered when user double clicks on a row.
	 * @param {Ext.grid.GridPanel} grid grid panel object
	 * @param {Number} rowIndex index of row
	 * @param {Ext.event} eventObj object of the event
	 * @private
	 */
	onRowDblClick: function(grid, rowIndex, eventObj)
	{
		Zarafa.calendar.Actions.openAppointmentContent(grid.getSelectionModel().getSelections());
	},

	/**
	 * Event handler which is triggered when the {@link Zarafa.mail.ui.MailGrid grid}
	 * {@link Zarafa.core.data.IPMRecord record} selection is changed. This will inform
	 * the {@link Zarafa.mail.MailContextModel contextmodel} about the change.
	 *
	 * @param {Ext.grid.RowSelectionModel} selectionModel The selection model used by the grid.
	 * @private
	 */
	onSelectionChange: function(selectionModel)
	{
		this.model.setSelectedRecords(selectionModel.getSelections());
	},


	/**
	 * Modify the row class and CSS style based on the folder to which the
	 * given {@link Zarafa.core.data.IPMRecord record} belongs.
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record which is loaded in this row
	 * @param {Number} The index number of this record in the given row
	 * @param {Object} rowParams Row parameters (CSS style, body contents/style)
	 * @param {Zarafa.core.data.IPMStore} store The store to which the record belongs
	 * @return {String} a CSS class name to add to the row
	 */
	getRowClass: function(record, index, rowParams, store)
	{
		return 'zarafa-calendar-grid-row';
	}
});

Ext.reg('zarafa.calendargrid', Zarafa.calendar.ui.CalendarGridView);
/*
 * #dependsFile client/zarafa/calendar/data/SnapModes.js
 * #dependsFile client/zarafa/calendar/data/DragStates.js
 */
Ext.namespace('Zarafa.calendar.ui');

/**
 * @class Zarafa.calendar.ui.CalendarViewDropZone
 * @extends Ext.dd.DropZone
 *
 * A special DropZone which supports dragging appointments over the calendar.
 * The calendar is represented using a special {@link Zarafa.calendar.ui.AbstractDateRangeView}
 * which can visualize the daterange which is selected or will be occupied by the selected
 * appointment.
 */
Zarafa.calendar.ui.CalendarViewDropZone = Ext.extend(Ext.dd.DropZone, {
	/**
	 * @cfg {String} The CSS class returned to the drag source when drop is allowed
	 * while the user has the Ctrl-key pressed (defaults to "x-dd-drop-ok-add").
	 */
	dropAllowedAdd: 'x-dd-drop-ok-add',

	/**
	 * @cfg {Boolean} headerMode True of this DropZone is installed on the header of
	 * the calendar, or in the body. This determines if the {@link Zarafa.calendar.ui.AbstractCalendarView#header}
	 * or {@link Zarafa.calendar.ui.AbstractCalendarView#body} will be used to connect the event handlers.
	 */
	headerMode: false,

	/**
	 * @cfg {Zarafa.calendar.data.SnapModes} selectingSnapMode The snapmode for selections and resizing.
	 * If this is {@link Zarafa.calendar.data.SnapModes#ZOOMLEVEL} then appointments or selections that
	 * are resized are snapped to the time which matches the
	 * {@link Zarafa.calendar.ui.AbstractCalendarView#getZoomLevel zoomlevel}. When this option is
	 * {@link Zarafa.calendar.data.SnapModes#DAY} the time is snapped to the entire day.
	 */
	selectingSnapMode: Zarafa.calendar.data.SnapModes.ZOOMLEVEL,

	/**
	 * @cfg {Zarafa.calendar.data.SnapModes} draggingSnapMode The snapmode for appointments when dragging.
	 * If this is {@link Zarafa.calendar.data.SnapModes#ZOOMLEVEL} then appointments are snapped to the
	 * time which matches the {@link Zarafa.calendar.ui.AbstractCalendarView#getZoomLevel zoomlevel}.
	 * When this option is {@link Zarafa.calendar.data.SnapModes#DAY} the appointments are snapped
	 * to the entire day.
	 */
	draggingSnapMode: Zarafa.calendar.data.SnapModes.ZOOMLEVEL,

	/**
	 * The proxy which must be used to display the selected range.
	 * @property
	 * @type Zarafa.calendar.ui.AbstractDateRangeView
	 * @private
	 */
	proxy: undefined,

	/**
	 * The current Drag & Drop State. This influences the way how the dragged appointment
	 * is being treated (dragging, resizing, selecting).
	 * @property
	 * @type Zarafa.calendar.data.DragStates
	 * @private
	 */
	state: Zarafa.calendar.data.DragStates.NONE,

	/**
	 * The daterange which reflects the size of the selected range. This equals
	 * the DateRange in the {@link #proxy}.
	 * @property
	 * @type Zarafa.core.DateRange
	 * @private
	 */
	dateRange: undefined,

	/**
	 * The {@link Date} object which represents the exact start date on which the user
	 * {@link #onNodeEnter entered} this region.
	 * @property
	 * @type Date
	 * @private
	 */
	initDate: undefined,

	/**
	 * The {@link Zarafa.core.DateRange DateRange} of the selection area on which the
	 * user {@link #onNodeEnter entered} this region.
	 * @property
	 * @type Zarafa.core.DateRange
	 * @private
	 */
	initDateRange: undefined,

	/**
	 * @constructor
	 * @param {Zarafa.calendar.ui.AbstractCalendarView} calendar The calendar on which this Dropzone is installed
	 * @param {Object} config Configuration object
	 */
	constructor: function(calendar, config)
	{
		config = config || {};

		this.calendar = calendar;

		var element = this.calendar.body;
		if (config.headerMode === true) {
			element = this.calendar.header;
		}

		Ext.applyIf(config, {
			ddGroup: 'AppointmentDD'
		});

		Zarafa.calendar.ui.CalendarViewDropZone.superclass.constructor.call(this, element, config);
	},

	/**
	 * Returns a custom data object associated with the DOM node that is the target of the event.
	 * This will return the element on which this DropZone has been installed.
	 * @param {Ext.EventObject} The event
	 * @returns {Object} The custom data
	 * @protected
	 */
	getTargetFromEvent: function(e)
	{
		// Check if the cursor is over the visible part of the
		// element on which this dropzone is installed. For the
		// header this is simple as there is no scrollbar active.
		// For the body, we must check if the cursor is over the
		// hidden part of the body (in which case, the body is
		// not considered the target).
		var element = this.el;
		if (this.headerMode !== true) {
			var parentElement = this.el.parent();

			if (e.getPageY() < parentElement.getTop() || parentElement.getBottom() < e.getPageY()) {
				element = undefined;
			}
		}

		return element;
	},

	/**
	 * Called when the DropZone determines that a {@link Ext.dd.DragSource} has entered a drop node
	 * that has either been registered or detected by a configured implementation of
	 * {@link #getTargetFromEvent}.
	 * This function will disable the {@link Ext.dd.DragZone#proxy} and will let the
	 * local {@link #proxy} handle the visualization.
	 * @param {Ext.Element} target The custom data associated with the drop node (as returned from {@link #getTargetFromEvent}.
	 * @param {Ext.dd.DragSource} The drag source that was dragged over this drop zone
	 * @param {Ext.EventObject} e The event
	 * @param {Object} data An object containing arbitrary data supplied by the drag source
	 * @protected
	 */
	onNodeEnter: function(target, dd, e, data)
	{
		var appointment = data.selections[0];
		var DragStates = Zarafa.calendar.data.DragStates;

		// If the appointment was being resized, but this dropzone is
		// on a different calendar then the dragzone, then we fallback
		// to dragging (as we can't resize from one calendar to another).
		this.state = data.state;
		if (this.state === DragStates.RESIZING_START || this.state === DragStates.RESIZING_DUE || this.state === DragStates.SELECTING) {
			if (this.calendar !== dd.calendar) {
				this.state = DragStates.DRAGGING;
			}
		}

		if (appointment) {
			// We are currently working with an appointment which is either being dragged or resized.
			// Initialize the initDate based on the appointment. Note that in this case we don't need
			// to be very accurate about the date.
			if (data.state === DragStates.RESIZING_START || data.state === DragStates.DRAGGING) {
				this.initDate = appointment.get('startdate');
			} else {
				this.initDate = appointment.get('duedate');
			}

			// The selected range will always be the entire appointment.
			this.initDateRange = new Zarafa.core.DateRange({ startDate: appointment.get('startdate'), dueDate: appointment.get('duedate') });
		} else {
			// We are not working with an appointment, and thus are selecting a daterange.
			// Determine what the basic selection should be based on the current zoomLevel.
			var zoomLevel = this.calendar.getZoomLevel();

			// The initial date is the exact date on which this events starts. Note that
			// for the header events, this date will be moved to the start of the selected
			// day.
			this.initDate = this.calendar.screenLocationToDate(e.getPageX(), e.getPageY());

			var dueDate, startDate;

			// Now we start detecting the range which should be selected by default.
			if (this.snapMode === Zarafa.calendar.data.SnapModes.DAY) {
				// For the snapMode DAY, the initDate will have been rounded to
				// the start of the day (but for safety we ensure that it will be anyway),
				// the dueDate will always be exactly 1 day after the start. This way
				// we select a single day by default.
				startDate = this.initDate.clearTime(true);
				dueDate = startDate.add(Date.DAY, 1);
			} else {
				// For the snapMode ZOOMLEVEL we must select a region the size of the zoomLevel.
				// This is easiest down by using floor() for the initDate to obtain the start,
				// and then add the zoomLevel to it to obtain the dueDate.
				startDate = this.initDate.clone().floor(Date.MINUTE, zoomLevel);
				dueDate = startDate.add(Date.MINUTE, zoomLevel);
			}

			this.initDateRange = new Zarafa.core.DateRange({ startDate: startDate, dueDate: dueDate });
		}

		// Activate the initial range
		this.dateRange = this.initDateRange.clone();

		// Change the function from the prototype to update the scope of the function.
		// This is needed to ensure we can call removeEventListener again with the correct
		// function reference later.
		this.onDragKeyDown = Zarafa.calendar.ui.CalendarViewDropZone.prototype.onDragKeyDown.createDelegate({ dd: dd, dz: this });
		this.onDragKeyUp = Zarafa.calendar.ui.CalendarViewDropZone.prototype.onDragKeyUp.createDelegate({ dd: dd, dz: this });

		// During dragging (onNodeOver) we either apply dropAllowed or dropAllowedAdd based on the Ctrl-key,
		// if the user didn't drag but just presses the button we must also update the icon. For that we
		// have these 2 event handlers.
		Ext.EventManager.on(Ext.getDoc(), 'keydown', this.onDragKeyDown, this);
		Ext.EventManager.on(Ext.getDoc(), 'keyup', this.onDragKeyUp, this);

		// Update the proxy
		this.proxy.setShowTime(this.selectingSnapMode === Zarafa.calendar.data.SnapModes.ZOOMLEVEL);
		this.proxy.setDateRange(this.dateRange);
		this.updateProxy(e.getXY(), data.selections);

		// DragZone is placed on the same calendar or different calendar DropZone,
		// we will disable the default proxy of the DragZone and activate
		// our own as replacement.
        dd.proxy.hide();
        this.proxy.setVisible(true);
	},

	/**
	 * Called while the DropZone determines that a {@link Ext.dd.DragSource} is over a drop node that
	 * has either been registered or detected by a configured implementation of {@link #getTargetFromEvent}.
	 * This function will call {@link #updateProxy} to update the selected range.
	 * @param {Ext.Element} target The custom data associated with the drop node (as returned from {@link #getTargetFromEvent}.
	 * @param {Ext.dd.DragSource} The drag source that was dragged over this drop zone
	 * @param {Ext.EventObject} e The event
	 * @param {Object} data An object containing arbitrary data supplied by the drag source
	 * @protected
	 */
	onNodeOver: function(target, dd, e, data)
	{
		this.updateProxy(e.getXY(), data.selections);

		// If the Crl-key is pressed, return dropAllowedAdd to
		// have the correct icon displayed which represents a copy
		// rather then a move.
		return e.ctrlKey ? this.dropAllowedAdd : this.dropAllowed;
	},

	/**
	 * Called when the DropZone determines that a {@link Ext.dd.DragSource} has been dragged
	 * out of the drop node without dropping.
	 * This function will disable the local {@link #proxy} and hand back the responsibility
	 * of the visualization back to the {@link Ext.dd.DragZone#proxy}.
	 * @param {Ext.Element} target The custom data associated with the drop node (as returned from {@link #getTargetFromEvent}.
	 * @param {Ext.dd.DragSource} The drag source that was dragged over this drop zone
	 * @param {Ext.EventObject} e The event
	 * @param {Object} data An object containing arbitrary data supplied by the drag source
	 * @protected
	 */
	onNodeOut: function(target, dd, e, data)
	{
		// Clear event handlers again
		Ext.EventManager.un(Ext.getDoc(), 'keydown', this.onDragKeyDown, this);
		Ext.EventManager.un(Ext.getDoc(), 'keyup', this.onDragKeyUp, this);

		delete this.initDate;
		delete this.initDateRange;

		this.proxy.setVisible(false);
		dd.proxy.show();
	},

	/**
	 * Event handler which is fired when the user presses a key, this is registered during {@link #onNodeEnter}
	 * and will be released in {@link #onNodeOut} and {@link #onNodeDrop}. When the user presses the Ctrl-key
	 * and the user is allowed to drop the item on this DropZone, we update the icon to {@link #dropAllowedAdd}
	 * to visualize the action will be a copy rather then a move.
	 *
	 * NOTE: This function is called using a special scope. 'this' is an object containing 2 fields,
	 * 'dd' which is the DragZone from where the item is dragged and 'dz' which is the DragZone over which
	 * the item is hovering.
	 *
	 * @param {Ext.EventObject} e The event
	 * @private
	 */
	onDragKeyDown: function(e)
	{
		if (e.ctrlKey || e.keyCode === Ext.EventObject.CONTROL) {
			if (this.dd.proxy.dropStatus === this.dz.dropAllowed) {
				this.dd.proxy.setStatus(this.dz.dropAllowedAdd);
			}
		}
	},

	/**
	 * Event handler which is fired when the user releases a key, this is registered during {@link #onNodeEnter}
	 * and will be released in {@link #onNodeOut} and {@link #onNodeDrop}. When the user releases the Ctrl-key
	 * and the current dropStatus is {@link #dropAllowedAdd} we change it back to {@link #dropAllowed} to visualize
	 * that the action will be a moved.
	 *
	 * NOTE: This function is called using a special scope. 'this' is an object containing 2 fields,
	 * 'dd' which is the DragZone from where the item is dragged and 'dz' which is the DragZone over which
	 * the item is hovering.
	 *
	 * @param {Ext.EventObject} e The event
	 * @private
	 */
	onDragKeyUp: function(e)
	{
		if (e.ctrlKey || e.keyCode === Ext.EventObject.CONTROL) {
			if (this.dd.proxy.dropStatus === this.dz.dropAllowedAdd) {
				this.dd.proxy.setStatus(this.dz.dropAllowed);
			}
		}
	},

	/**
	 * Called when the DropZone determines that a item has been dropped.
	 * This will determine what action has occurred and call the appropriate
	 * callback function on the {@link #calendar}.
	 * @param {Ext.Element} target The custom data associated with the drop node (as returned from {@link #getTargetFromEvent}.
	 * @param {Ext.dd.DragSource} The drag source that was dragged over this drop zone
	 * @param {Ext.EventObject} e The event
	 * @param {Object} data An object containing arbitrary data supplied by the drag source
	 * @protected
	 */
	onNodeDrop: function(target, dd, e, data)
	{
		var DragStates = Zarafa.calendar.data.DragStates;
		switch (data.state) {
			case DragStates.SELECTING:
				this.calendar.onSelect(e, this.dateRange);
				break;
			case DragStates.DRAGGING:
			       var record = data.target.record;
			       var isCopyAction = e.ctrlKey === true;
			       if ( record.isMeeting() && record.isMeetingReceived() ) {
				       var action = (isCopyAction || dd.calendar !== this.calendar) ? "drop" : "move";
				       this.showWarningMessageBox(e, dd, data.target, action);
			       } else if (isCopyAction || dd.calendar !== this.calendar) {
				       this.calendar.onDrop(e, dd.calendar, data.target, this.dateRange);
			       } else {
				       this.calendar.onMove(e, data.target, this.dateRange);
			       }
			       break;
			case DragStates.RESIZING_START:
			case DragStates.RESIZING_DUE:
				var record = data.target.record;
				if (record.isMeeting() && record.isMeetingReceived() ) {
					this.showWarningMessageBox(e, dd, data.target, "resize");
				} else {
					this.calendar.onResize(e, data.target, this.dateRange);
				}
				break;
		}
	},

	/**
	 * Helper function which show {@link Zarafa.common.dialogs.MessageBox MessageBox}
	 * when attendee is trying to move the meeting.
	 *
	 * @param {Ext.EventObject} event The event
	 * @param {Ext.dd.DragSource} dd The drag source that was dragged over this drop zone
	 * @param {Zarafa.calendar.ui.AppointmentView} appointment The appointment which was dropped
	 * @param {String} action The action can be anything from "move", "drop" or "resize".
	 */
	showWarningMessageBox: function(event, dd, appointment, action)
	{
		var dateRange = this.dateRange;
		Zarafa.common.dialogs.MessageBox.addCustomButtons({
			width: 400,
			title: _('grommunio Web'),
			msg: _('Please note that any changes you make will be overwritten when this meeting request is updated by the organizer. Would you like to move this meeting?'),
			cls: Ext.MessageBox.WARNING_CLS,
			fn: function(buttonName) {
				if (buttonName === 'move') {
					switch (action) {
						case "move":
							this.calendar.onMove(event, appointment, dateRange);
							break;
						case "drop":
							this.calendar.onDrop(event, dd.calendar, appointment, dateRange);
							break;
						case "resize":
							this.calendar.onResize(event, appointment, dateRange);
							break;
					}
				}
			},
			customButton: [{
				name: 'move',
				text: _('Move')
			}, {
				name: 'cancel',
				text: _('Don\'t move')
			}],
			scope: this
		});
	},

	/**
	 * Recalculate the {@link Zarafa.core.DateRange date values} for the {@link #proxy}. This
	 * will check at which position the mouse is currently hovering, and will update the
	 * start and the dueDate respectively.
	 *
	 * @param {Number|Array} xy The X and Y Coordinates of the cursor position
	 * @param {Zarafa.calendar.AppointmentRecord|Array} The array of currently selected appointments
	 * @private
	 */
	updateProxy: function(xy, selections)
	{
		var selection = !Ext.isEmpty(selections) ? selections[0] : undefined;
		var overDate = this.calendar.screenLocationToDate(xy[0], xy[1]);
		var zoomLevel = this.calendar.getZoomLevel();
		var DragStates = Zarafa.calendar.data.DragStates;
		var moveStart = (this.state === DragStates.SELECTING ||
						 this.state === DragStates.DRAGGING ||
						 this.state === DragStates.RESIZING_START);
		var moveDue = (this.state === DragStates.SELECTING ||
					   this.state === DragStates.DRAGGING ||
					   this.state === DragStates.RESIZING_DUE);
		var startDate = this.dateRange.getStartDate();
		var dueDate = this.dateRange.getDueDate();
		var duration = this.dateRange.getDuration(Date.MINUTE);

		var snapMode;
		// Determine what snapMode we should use to determine what changes
		// need to be made to the start and due date.
		if (this.state === DragStates.DRAGGING){
			snapMode = this.draggingSnapMode;
		} else {
			snapMode = this.selectingSnapMode;
		}

		if(Ext.isDate(overDate)){

			if (snapMode === Zarafa.calendar.data.SnapModes.DAY ) {
				// We must snap the appointment to an entire day
				if (moveStart) {
					// Always floor the startDate to the start of the day
					startDate = overDate.clearTime();

					if (moveDue) {
						// We are moving both the start as well as the dueDate
						// of the selection. In other words we are dragging our selection
						if (!selection) {
							// If we don't have an appointment selected we ensure that the
							// current duration is maintained (without an appointment,
							// the duration is always a single day).
							dueDate = startDate.add(Date.DAY, 1);
						} else if (selection.get('alldayevent')) {
							// We are dragging an allday appointment over the header,
							// simply maintain the duration of the appointment by always
							// updating the dueDate based on the appointment duration.
							dueDate = startDate.add(Date.MINUTE, selection.get('duration'));
						} else {
							// If there is an appointment, but it is an non-allday appointment,
							// we have to resize it to the entire day, as dropping this
							// appointment on the body container will imply a resize of the
							// appointment.
							dueDate = startDate.add(Date.DAY, 1);
						}
					}
				} else {
					// We are only moving the dueDate of the selection. The overDate will always
					// point to the start of the day over which we are hovering, while the dueDate
					// should represent the end of that same day. Hence we always do Date.add().
					dueDate = overDate.add(Date.DAY, 1);
					if (dueDate === startDate) {
						dueDate = dueDate.add(Date.DAY, 1);
					}
				}
			} else if (snapMode === Zarafa.calendar.data.SnapModes.ZOOMLEVEL) {
				// We must snap the appointment to the current zoomLevel
				if (moveStart) {
					// Always floor the startDate to the upper zoomLevel boundary.
					startDate = overDate.floor(Date.MINUTE, zoomLevel);

					if (moveDue) {
						// We are moving both the start as well as the dueDate
						// of the selection. In other words we are dragging our selection
						if (!selection) {
							// If we don't have an appointment selected we ensure that the
							// current duration is maintained (without an appointment,
							// the duration is always the zoomLevel).
							dueDate = startDate.add(Date.MINUTE, zoomLevel);
						} else if (selection.get('alldayevent')) {
							// If there is an appointment, but it is an allday appointment,
							// we have to resize it to the zoomLevel as well, as dropping
							// this appointment on the body container will imply a resize
							// of the appointment.
							dueDate = startDate.add(Date.MINUTE, zoomLevel);
						} else {
							// We are dragging a normal appointment around, we maintain
							// the duration of the appointment by always updating the dueDate
							// based on the appointment duration.
							dueDate = startDate.add(Date.MINUTE, selection.get('duration'));
						}
					}
				} else {
					// We are only moving the dueDate of the selection,
					// round the selection up to the upper limit of the zoomLevel.
					// Note that this will not change the dueDate when the date is
					// an exact multiple of the zoomLevel. This doesn't matter as
					// long as the startDate and dueDate are not equal, as the user
					// will simply have to move an extra pixel to select the next
					// block.
					dueDate = overDate.ceil(Date.MINUTE, zoomLevel);
					if (dueDate === startDate) {
						dueDate = dueDate.add(Date.MINUTE, zoomLevel);
					}
				}
			} else {
				// Get number of minutes since start of day
				var minSinceStartOfDay = startDate.getHours()*60 + startDate.getMinutes();

				// We check whether the overDate is actually at 0:00. If not the DST change happens at
				// midnight. The DST diff fix later on will not work then as it will search between the
				// start of the day and the appointment time. Because 0:00 will become 01:00 it will not
				// detect the DST change between 01:00 and for example 11:00. By taking the number of
				// minutes that were supposed to be between 0:00 and 01:00 and subtract from
				// minSinceStartOfDay, the Date object will calculate the Date correctly. An example of
				// a DST change at 0:00 is the Brazilian DST change.
				var minCorrectionForDSTChangeAtMidNight = overDate.getHours()*60 + overDate.getMinutes();
				// Prevent negative numbers when the DST changes makes 0:15 go back to 23:15
				minSinceStartOfDay = Math.max(0, minSinceStartOfDay - minCorrectionForDSTChangeAtMidNight);

				// Set the startdate to the start of the day we are dragging it to
				startDate = overDate.clone();
				// Add the number of minutes since the start of the day
				startDate = startDate.add(Date.MINUTE, minSinceStartOfDay);
				// Check if there is an DST diff between the start of this new day and time of the
				// appointment on that day. If so we need to add the DST diff, otherwise it will be an
				// hour off on DST change days. If there is no DST diff it will just add 0.
				startDate = startDate.add( Date.MILLI, Date.getDSTDiff(startDate, overDate) );

				// Finally we set the due date based on the start date and duration
				dueDate = startDate.add(Date.MINUTE, duration);
			}
		}

		switch (this.state) {
			// When selecting it is unclear if the user is changing the start or
			// the dueDate of the selection. Here we check if the updated dates
			// gives us a clear picture on the direction where the user is dragging
			// towards. As soon as we have established that we will be using the
			// resize dueDate and startDate handlers.
			case DragStates.SELECTING:
				if (startDate > this.initDate) {
					this.dateRange.set(this.initDateRange.getStartDate(), dueDate);
					this.state = DragStates.RESIZING_DUE;
				} else if (dueDate < this.initDate || snapMode === Zarafa.calendar.data.SnapModes.DAY) {
					this.state = DragStates.RESIZING_START;
					this.dateRange.set(startDate, this.initDateRange.getDueDate());
				}
				break;
			// While dragging we always update the start and dueDate to preserve
			// the duration of the selected appointment.
			case DragStates.DRAGGING:
				this.dateRange.set(startDate, dueDate);
				break;
			// When resizing the startDate of the selection, beware that the user
			// could also be moving his cursor to beyond the dueDate. If this
			// happens, we swap the working state so during next round we will
			// be moving the dueDate.
			case DragStates.RESIZING_START:
				if (startDate >= dueDate) {
					this.state = DragStates.RESIZING_DUE;
					this.dateRange.set(this.initDateRange.getStartDate(), startDate);
				} else {
					this.dateRange.setStartDate(startDate);
				}
				break;
			// When resizing the dueDate of the selection, beware that the user
			// could also be moving his cursor to before the startDate. If this
			// happens, we swap the working state so during next round we will
			// be moving the startDate.
			case DragStates.RESIZING_DUE:
				if (startDate >= dueDate) {
					this.state = DragStates.RESIZING_START;
					this.dateRange.set(dueDate, this.initDateRange.getDueDate());
				} else {
					this.dateRange.setDueDate(dueDate);
				}
				break;
		}
	}
});
Ext.namespace('Zarafa.calendar.widget');

/**
 * @class Zarafa.calendar.widget.AppointmentsWidget
 * @extends Zarafa.core.ui.widget.AbstractFolderWidget
 *
 * Widget that displays the appointments for today, from the default
 * calendar.  It only displays appointments that occur on or after the
 * current time, so outdated information is never shown.
 *
 * Reload time is configurable per instance of the
 * widget (keys: 'reloadinterval', default 5 minutes).  These values are in
 * saved in milliseconds but displayed in seconds. The reload
 * interval is how often the calendar is fully reloaded from the
 * server, to show records that were added to the folder
 * outside of grommunio Web.
 */
Zarafa.calendar.widget.AppointmentsWidget = Ext.extend(Zarafa.core.ui.widget.AbstractFolderWidget, {
	/**
	 * The folder which was selected by the user
	 * @property
	 * @type Zarafa.hierarchy.data.MAPIFolderRecord
	 */
	folder: undefined,

	/**
	 * 'entryid' of folder which was selected by the user
	 * @property
	 * @type String
	 */
	folderId: undefined,

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

		var store = new Zarafa.calendar.AppointmentStore();

		Ext.applyIf(config, {
			autoScroll: true,
			layout: 'fit',
			folderType: 'calendar',
			store: store,
			items: [{
				xtype: 'zarafa.gridpanel',
				cls: 'k-appointmentwidget',
				store: store,
				hideHeaders: true,
				ref: 'appointmentsGrid',
				loadMask: {
					msg: _('Loading appointments…')
				},
				sm: new Zarafa.common.ui.grid.RowSelectionModel({
					singleSelect: true
				}),
				viewConfig: {
					deferEmptyText: false,
					emptyText: '<div class="emptytext">' + _("Please select a calendar via widget settings") + '</div>',
					forceFit: true,
					enableRowBody: true,
					rowSelectorDepth: 15,
					getRowClass: this.viewConfigGetRowClass,
					scope: this
				},
				colModel: new Ext.grid.ColumnModel({
					columns: [{
						header: _('Time'),
						dataIndex: 'startdate',
						renderer: this.timeRenderer,
						scope: this
					}]
				}),
				listeners: {
					'rowcontextmenu': this.onRowContextMenu,
					'rowdblclick': this.onRowDblClick,
					scope: this
				}
			}]
		});

		// Customize the configuration of the settings window
		this.configurationConfig = {
			height: 180
		};

		Zarafa.calendar.widget.AppointmentsWidget.superclass.constructor.call(this, config);
		this.folderId = this.get('folderId');
	},

	/**
	 * Adds a field to configure the number of days for which appointments will be shown
	 * @return {array} An array of configuration objects for {@link Ext.Component components}
	 */
	getConfigurationItems: function()
	{
		return [{
			xtype: 'zarafa.spinnerfield',
			fieldLabel: _('Number of days'),
			name: 'numdays',
			boxLabel: _('day(s)'),
			width: 60,
			minValue: 1, // 1 day
			maxValue: 365, // 1 year
			incrementValue: 1, // 1 day
			defaultValue: this.get('numdays') || 5,
			listeners: {
				'change': this.onNumDaysChange,
				scope: this
			},
			plugins: ['zarafa.numberspinner']
		},{
			xtype: 'zarafa.calendarfolderselectionlink',
			folder: this.folder,
			fieldLabel: _('Calendar'),
			name: 'Calendar',
			listeners: {
				'folderupdate': this.onUpdateFolder,
				scope: this
			}
		}];
	},

	/**
	 * Initialize the event handlers for the {@link #store} and {@link Zarafa.hierarchy.data.HierarchyStore HierarchyStore}.
	 * @protected
	 */
	initEvents: function () {
		Zarafa.calendar.widget.AppointmentsWidget.superclass.initEvents.apply(this, arguments);

		// set handlers to update widget state on remove of folder from hierarchy.
		var hierarchyStore = container.getHierarchyStore();
		this.mon(hierarchyStore, {
			'remove': this.onSharedStoreRemove,
			'removeFolder': this.onSharedStoreRemove,
			scope: this
		});

		// Wait for the store to be loaded, so we can activate
		// the filter task and set empty text if no appointments available.
		this.mon(this.store, 'load', this.onStoreLoad, this, { single: true });
		this.mon(this.store, 'exception', this.onStoreException, this);
	},

	/**
	 * Handler for {@link #store} for {@link Ext.data.Store#load load} event.
	 * This will call {@link #startFilterTask} and {@link #setEmptytext} after the {@link #store} load.
	 * @private
	 */
	onStoreLoad: function()
	{
		this.startFilterTask();
		this.setEmptytext();
	},

	/**
	 * Handler for {@link #store} for {@link Ext.data.Store#exception exception} event.
	 * This will remove all records from store and set empty text as error message received from php side.
	 *
	 * @param {Misc} misc See {@link Ext.data.DataProxy}#{@link Ext.data.DataProxy#exception exception}
	 * for description.
	 * @private
	 */
	onStoreException: function(proxy, type, action, options, response, args)
	{
		var emptyText;
		if (response && response.error) {
			var errObj = response.error;

			if (errObj.info && errObj.info.display_message) {
				emptyText = String.format(_("{0}"), errObj.info.display_message);
			}
		}

		// Empty store so that we can show error message.
		this.appointmentsGrid.getStore().removeAll();
		this.setEmptytext(emptyText);
	},

	/**
	 * Load the stored calendar folder and retrieve the records.
	 * @param {Zarafa.hierarchy.data.HierarchyStore} hierarchyStore The store which fired the event
	 * @private
	 */
	onHierarchyLoad: function (hierarchyStore)
	{
		if (Ext.isEmpty(this.folderId)) {
			return;
		}

		this.folder = hierarchyStore.getFolder(this.folderId);
		var folderReadRight = Zarafa.core.mapi.Rights.RIGHTS_READ_ANY;
		if (Ext.isEmpty(this.folder) ||
			(this.folder.get('rights') & folderReadRight) !== folderReadRight) {
			// Reset when there is folderId and we can't find folder
			// or folder has not 'RIGHTS_FOLDER_VISIBLE' rights.
			// So, we need to reset the state.
			this.resetWidget();
			return;
		}

		this.setWidgetTitle(this.folder);
		this.reloadStore();
	},

	/**
	 * Reset when previously selected folder is not found (e.g. saved states will be cleared on reset settings of webapp).
	 * This will be called from {@link Zarafa.widgets.folderwidgets.AbstractFolderWidget#onHierarchyLoad onHierarchyLoad}
	 * when folder is not found and when the 'remove' or 'removeFolder' event fired for hierarchy store.
	 */
	resetWidget: function()
	{
		this.folder = undefined;
		this.folderId = undefined;
		this.set("folderId", undefined);
		this.appointmentsGrid.getStore().removeAll();
	},

	/**
	 * This method will set name of this widget.
	 * If folder is not given, it will set default title otherwise it will prepare
	 * title for widget as per folder given and apply it.
	 *
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder folder which is selected to show appointments from.
	 * @private
	 */
	setWidgetTitle: function(folder)
	{
		var title = _('Upcoming Appointments');

		if (folder) {
			var folderStore = folder.getMAPIStore();
			var isOwnStore = folderStore.isDefaultStore();
			var isDefaultFolder = folder.isDefaultFolder();

			var ownerName = folderStore.get('display_name');
			var folderName = folder.get('display_name');

			// Do not show ownername for own store folder.
			// And don't change title for default calendar folder of own store.
			if (!(isOwnStore && isDefaultFolder)) {
				if (isOwnStore) {
					title = String.format(_('Appointments of {0}'), folderName);
				} else {
					title = String.format(_('Appointments of {0} - {1}'), folderName, ownerName);
				}
			}
		}

		this.setTitle(title);
	},

	/**
	 * This method will set empty text message of this widget.
	 * If the emptyText param is not provided then it will set 'No appointments' message
	 * when no appointments are available.
	 * Or if {@link #folder} has no value (no folder is selected currently) then
	 * this will display a message to select folder.
	 *
	 * @param {String} emptyText which needs to be set as empty text of grid view.
	 */
	setEmptytext: function(emptyText)
	{
		var gridView = this.appointmentsGrid.getView();

		if (Ext.isEmpty(emptyText)) {
			emptyText = this.folder ? _('No appointments') : _("Please select a calendar via widget settings");
		}

		gridView.emptyText = '<div class="emptytext">'+ emptyText +'</div>';
		gridView.refresh();
	},

	/**
	 * Event handler for 'remove' and 'removeFolder' events of {@link Zarafa.hierarchy.data.HierarchyStore Hierarchy}
	 * This will reset widget if state folder of this widget gets deleted.
	 *
	 * @param {Zarafa.hierarchy.data.HierarchyStore} store hierarchy store after removing record.
	 * @param {Zarafa.hierarchy.data.MAPIStoreRecord} record The record which is removed
	 * @param {Number} totalRecords total number of records in store.
	 */
	onSharedStoreRemove: function(store, record, totalRecords)
	{
		var stateFolderId = this.get('folderId');
		if (!Ext.isEmpty(stateFolderId)) {
			var stateFolder = store.getFolder(stateFolderId);

			// If deleted folder is state folder or closed shared store includes
			// state folder then reset this widget.
			if (Ext.isEmpty(stateFolder)) {
				this.resetWidget();
				this.setEmptytext();
			}
		}
	},

	/**
	 * Event handler for 'folderupdate' event.
	 * This will update the {@link #folder} , save the folder entryid in state,
	 * set title of this widget and reload the state with updated folder.
	 *
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder folder which is selected to show appointments from.
	 * @private
	 */
	onUpdateFolder: function (folder)
	{
		this.folder = folder;
		this.folderId = folder.get('entryid');
		this.set("folderId", this.folderId);
		this.setWidgetTitle(folder);
		this.reloadStore();
		this.setEmptytext();
	},

	/**
	 * Event handler which is fired when 'Number of Days' field in the Configuration dialog
	 * has been changed. This will update the corresponding option in the settings.
	 * @param {Ext.form.Field} field The field which fired the event
	 * @param {Mixed} newValue The new value which was applied
	 * @param {Mixed} oldValue The old value which was applied
	 * @private
	 */
	onNumDaysChange: function (field, newValue, oldValue)
	{
		this.set(field.getName(), newValue);
		this.reloadStore();
	},

	/**
	* Starts a filter task for the store. Will make sure that the filter is updated at
	* every 30 seconds so old appointments will be filtered out.
	* @private
	*/
	startFilterTask: function ()
	{
		Ext.TaskMgr.start({
			run: this.updateFilter,
			interval: 30000,
			scope: this
		});
	},

	/**
	 * This will {@link Ext.data.Store#load load} the {@link #store} using
	 * a restriction which only allows todays appointments.
	 * @private
	 */
	reloadStore: function ()
	{
		if (!this.folder) {
			return;
		}

		var numDays = this.get('numdays') || 5;

		var startdate = new Date().clearTime().getTime() / 1000;
		var duedate = new Date().clearTime().add(Date.DAY, numDays).getTime() / 1000;

		this.store.load({
			folder: this.folder,
			params: {
				suppress_exception: true,
				restriction: {
					startdate: startdate,
					duedate: duedate
				}
			}
		});
	},

	/**
	 * Update the filter with the current time. Items that end
	 * before now are removed.
	 * @private
	 */
	updateFilter: function ()
	{
		var now = new Date().getTime() / 1000;
		this.store.filterBy(function (record) {
			var dueDate = record.get('duedate').getTime() / 1000;
			return !dueDate || dueDate >= now;
		}, this);
	},

	/**
	 * Renderer for the time column. Adds a recurrence icon and a private icon if applicable
	 *
	 * @param {Mixed} value The subject of the appointment
	 * @param {Object} metaData Used to set style information to gray out appointments that occur now
	 * @param {Ext.data.Record} record The record being displayed, used to retrieve the start and end times
	 * @private
	 */
	timeRenderer: function (value, metaData, record)
	{
		var recurringIcon = '';
		var recurringPattern = record.get('recurring_pattern');
		if (recurringPattern) {
			if (record.get('exception') === true) {
				recurringIcon =
					'&nbsp;<span ext:qwidth="300" ext:qtip="' + recurringPattern + '">' +
						'<img src="' + Zarafa.calendar.ui.IconCache.getExceptionIcon().src + '" alt="' + Ext.util.Format.htmlEncode(_('Exception')) + '"/>' +
					'</span>';
			} else {
				recurringIcon =
					'&nbsp;<span ext:qwidth="300" ext:qtip="' + recurringPattern + '">' +
						'<img src="' + Zarafa.calendar.ui.IconCache.getRecurringIcon().src + '" alt="' + Ext.util.Format.htmlEncode(_('Recurring')) + '"/>' +
					'</span>';
			}
		}

		var privateIcon = '';
		if (record.get('private') === true) {
			privateIcon = '&nbsp;<img src="' + Zarafa.calendar.ui.IconCache.getPrivateIcon().src + '" alt="' + Ext.util.Format.htmlEncode(_('Private')) + '"/>';
		}

		var dayStart = new Date().clearTime();
		var dayEnd = new Date().add(Date.DAY, 1).clearTime();
		var start = value;
		var end = record.get('duedate');
		var allDayEvent = record.get('alldayevent');

		if ( start >= dayStart && end <= dayEnd ) {
			if ( !allDayEvent ) {
				return String.format(_('Today {0} - {1}'), start.formatDefaultTime(), end.formatDefaultTime()) + recurringIcon + privateIcon;
			}

			return _('Today (all day)') + recurringIcon + privateIcon;
		}

		if ( !allDayEvent ) {
			return String.format(_('{0} - {1}'), start.formatDefaultTime(_('d/m/y {0}')), end.formatDefaultTime(_('d/m/y {0}'))) + recurringIcon + privateIcon;
		}

		return String.format(_('{0} - {1} (all day)'), start.format(_('d/m/y')), end.format(_('d/m/y'))) + recurringIcon + privateIcon;
	},

	/**
	 * Event handler which is triggered when user opens context menu
	 * @param {Ext.grid.GridPanel} grid grid panel object
	 * @param {Number} rowIndex index of row
	 * @param {Ext.EventObject} event event object of the event
	 * @private
	 */
	onRowContextMenu: function (grid, rowIndex, event)
	{
		// check row is already selected or not, if its not selected then select it first
		var selectionModel = grid.getSelectionModel();
		if (!selectionModel.isSelected(rowIndex)) {
			selectionModel.selectRow(rowIndex);
		}

		// The ContextMenu needs the ContextModel for cases where we want to reply the mail.
		var model;
		if (this.folder) {
			var context = container.getContextByFolder(this.folder);
			if (context) {
				model = context.getModel();
				Zarafa.core.data.UIFactory.openDefaultContextMenu(selectionModel.getSelections(), {
					position: event.getXY(),
					model: model
				});
			}
		}
	},

	/**
	 * Called when the user double-clicks on an appointment.
	 * @param {Ext.grid.GridPanel} grid The grid which fired the event
	 * @param {Number} rowIndex The row which was double clicked
	 * @param {Ext.EventObject} event The event object
	 * @private
	 */
	onRowDblClick: function (grid, rowIndex, event)
	{
		var record = grid.getSelectionModel().getSelected();
		if (!Ext.isEmpty(record)) {
			// FIXME what about recurring series records ?
			if (record.isRecurringOccurrence()) {
				record = record.convertToOccurrenceRecord();
			}
		}
		Zarafa.core.data.UIFactory.openViewRecord(record);
	}
});

Zarafa.onReady(function () {
	container.registerWidget(new Zarafa.core.ui.widget.WidgetMetaData({
		name: 'appointments',
		iconCls: "icon_widget_calendar",
		displayName: _('Upcoming Appointments'),
		widgetConstructor: Zarafa.calendar.widget.AppointmentsWidget
	}));
});
Ext.namespace('Zarafa.calendar.widget');

/**
 * @class Zarafa.calendar.widget.QuickAppointmentWidget
 * @extends Zarafa.core.ui.widget.AbstractQuickItemWidget
 *
 * Widget for creating an Appointment quickly with a minimum set of
 * input fields
 */
Zarafa.calendar.widget.QuickAppointmentWidget = Ext.extend(Zarafa.core.ui.widget.AbstractQuickItemWidget, {

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

		Ext.applyIf(config, {
			wrapCfg: {
				recordComponentPluginConfig: Ext.applyIf(config.recordComponentPluginConfig || {}, {
					allowWrite: true
				}),
				layout: 'fit',
				items: [{
					xtype: 'form',
					ref: 'formPanel',
					layout: {
						type: 'vbox',
						align: 'stretch'
					},
					border: false,
					bodyStyle: 'background-color: inherit; padding: 5px;',
					defaults: {
						border: false,
						labelLength: 100,
						style: 'padding-bottom: 2px'
					},
					items: [{
						xtype: 'zarafa.compositefield',
						hideLabel: true,
						anchor: '100%',
						items: [{
							xtype: 'textfield',
							flex: 1,
							name: 'subject',
							emptyText: _('Subject') + ':',
							listeners: {
								change: this.onChange,
								scope: this
							}
						}]
					},{
						xtype: 'zarafa.datetimeperiodfield',
						ref: '../datetimePeriod',
						defaultPeriod: container.getSettingsModel().get('zarafa/v1/contexts/calendar/default_appointment_period'),
						timeIncrement: container.getSettingsModel().get('zarafa/v1/contexts/calendar/default_zoom_level'),
						listeners: {
							change: this.onDateRangeFieldChange,
							scope: this
						},
						startFieldConfig: {
							minValue: new Date().clearTime()
						},
						endFieldConfig: {
							minValue: new Date().clearTime()
						}
					},{
						xtype: 'zarafa.compositefield',
						hideLabel: true,
						anchor: '100%',
						items: [{
							xtype: 'checkbox',
							name: 'alldayevent',
							ref: '../../alldayCheck',
							hideLabel: false,
							boxLabel: _('All Day Event'),
							height: 15,
							handler: this.onToggleAllDay,
							scope: this
						}]
					},{
						xtype: 'zarafa.editorfield',
						ref: '../editorField',
						htmlName: 'html_body',
						plaintextName: 'body',
						hideLabel: true,
						flex: 1,
						useHtml: true,
						defaultValue: '',
						listeners: {
							change: this.onBodyChange,
							scope: this
						}
					}]
				}]
			},
			buttons: [{
				text: _('Save'),
				cls: 'zarafa-action',
				style: 'padding-bottom: 5px',
				handler: this.onSave,
				scope: this
			},{
				text: _('Discard'),
				style: 'padding-bottom: 5px',
				handler: this.onDiscard,
				scope: this
			}]
		});

		Zarafa.calendar.widget.QuickAppointmentWidget.superclass.constructor.call(this, config);
	},

	/**
	 * Event handler which is fired when the {@link Zarafa.common.ui.DateRangeField} has been changed.
	 * This will update the start and due date inside the {@link #record} accordingly.
	 * @param {Ext.form.Field} field The field which has changed
	 * @param {Mixed} newValue The new value for the field
	 * @param {Mixed} oldValue The original value for the field
	 * @private
	 */
	onDateRangeFieldChange: function(field, newRange, oldRange)
	{
		this.updateStartDueDate(this.wrap.record, newRange);
	},

	/**
	 * A function called when the checked value changes for the
	 * all day event checkbox.
	 * @param {Ext.form.Checkbox} checkbox The Checkbox being toggled.
	 * @param {Boolean} checked The new checked state of the checkbox.
	 * @private
	 */
	onToggleAllDay: function(checkbox, checked)
	{
		if (this.wrap.record.get('alldayevent') !== checked) {
			var settingsModel = container.getSettingsModel();

			this.wrap.record.beginEdit();
			this.wrap.record.set('alldayevent', checked);
			if (checked) {
				this.updateStartDueDate(this.wrap.record, this.wrap.datetimePeriod.getValue());

				this.wrap.record.set('reminder_minutes', settingsModel.get('zarafa/v1/contexts/calendar/default_allday_reminder_time'));
				this.wrap.record.set('busystatus', Zarafa.core.mapi.BusyStatus.FREE);
			} else {
				var zoomLevel = settingsModel.get('zarafa/v1/contexts/calendar/default_zoom_level');
				var defaultPeriod = settingsModel.get('zarafa/v1/contexts/calendar/default_appointment_period');

				var startDate = new Date();
				if(this.wrap.record.get('startdate')) {
					// use existing date if it is set
					startDate = this.wrap.record.get('startdate').clearTime(true);
				}

				startDate = startDate.ceil(Date.MINUTE, zoomLevel);
				var dueDate = startDate.add(Date.MINUTE, defaultPeriod);

				this.wrap.record.set('reminder_minutes', settingsModel.get('zarafa/v1/contexts/calendar/default_reminder_time'));
				this.wrap.record.set('busystatus', Zarafa.core.mapi.BusyStatus.BUSY);
				this.wrap.record.set('startdate', startDate);
				this.wrap.record.set('duedate', dueDate);
				this.wrap.record.set('commonstart', startDate);
				this.wrap.record.set('commonend', dueDate);
				this.wrap.record.set('duration', (dueDate - startDate) / (60 * 1000));
			}
			this.wrap.record.endEdit();
		}
	},

	/**
	 * Update the 'startdate' and 'duedate' in the given record from
	 * the given daterange. When the appointment is an allday event, then
	 * the times are always set to midnight. However when selecting
	 * the duedate the user selects on which day the appointment
	 * ends, so in reality the appointment ends on 00:00 hours on
	 * the following day.
	 * @param {Zarafa.core.data.MAPIRecord} record the Record to update
	 * @param {Zarafa.core.DateRange} daterange the Daterange to apply
	 * @private
	 */
	updateStartDueDate: function(record, daterange)
	{
		var startDate = daterange.getStartDate().clone();
		var dueDate = daterange.getDueDate().clone();

		if (record.get('alldayevent') === true) {
			startDate = startDate.clearTime();
			dueDate = dueDate.add(Date.DAY, 1).clearTime();
		}

		record.beginEdit();
		record.set('startdate', startDate);
		record.set('duedate', dueDate);
		record.set('commonstart', startDate);
		record.set('commonend', dueDate);
		record.set('duration', (dueDate - startDate) / (60 * 1000));
		record.endEdit();
	},

	/**
	 * Create a new record which must be edited by this widget.
	 * @return {Ext.data.Record} record The record to load into the {@link #wrap}
	 * @protected
	 */
	createRecord: function()
	{
		var folder = container.getHierarchyStore().getDefaultFolder('calendar');
		var context = container.getContextByName('calendar');
		var model = context.getModel();

		return model.createRecord(undefined, folder);
	},

	/**
	 * Updates the widget by loading data from the record into the {@link #wrap}.
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record update the panel with.
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 * @protected
	 */
	update: function(record, contentReset)
	{
		if (contentReset === true || record.isModifiedSinceLastUpdate('alldayevent')) {
			if (record.get('alldayevent')) {
				this.wrap.datetimePeriod.setEnabledTimeSelection(false);
			} else {
				this.wrap.datetimePeriod.setEnabledTimeSelection(true);
			}
		}

		this.wrap.formPanel.getForm().loadRecord(record);

		var startDate = record.get('startdate');
		var startDateUpdate = false;
		if (Ext.isDate(startDate)) {
			startDate = startDate.clone();
			startDateUpdate = contentReset || record.isModifiedSinceLastUpdate('startdate');
		}
		var dueDate = record.get('duedate');
		var dueDateUpdate = false;
		if (Ext.isDate(dueDate)) {
			dueDate = dueDate.clone();
			dueDateUpdate = contentReset || record.isModifiedSinceLastUpdate('duedate');
		}

		// For all day events we store the due date as 00:00 on the day after
		// it ends. For the UI, this means we have to subtract 1 day to get
		// the date on which the appointment actually ends for the user.
		if (record.get('alldayevent')) {
			dueDate = dueDate.add(Date.DAY, -1);
		}

		if (startDateUpdate || dueDateUpdate) {
			this.wrap.datetimePeriod.getValue().set(startDate, dueDate);
		}
	},

	/**
	 * Updates the widget by loading data from the record into the {@link #wrap}.
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record to update
	 * @protected
	 */
	updateRecord: function(record)
	{
		record.beginEdit();
		this.wrap.formPanel.getForm().updateRecord(record);
		this.updateStartDueDate(record, this.wrap.datetimePeriod.getValue());
		this.onToggleAllDay(this.wrap.alldayCheck, this.wrap.alldayCheck.getValue());
		this.onBodyChange(this.wrap.editorField, this.wrap.editorField.getValue());
		record.endEdit();
	}
});

Zarafa.onReady(function() {
	container.registerWidget(new Zarafa.core.ui.widget.WidgetMetaData({
		name: 'quickappt',
		iconCls: 'icon_widget_new_appointment',
		displayName: _('Quick Appointment'),
		widgetConstructor: Zarafa.calendar.widget.QuickAppointmentWidget
	}));
});
Ext.namespace('Zarafa.common.attachment.dialogs');

/**
 * @class Zarafa.common.attachment.dialogs.ImportToFolderContentPanel
 * @extends Zarafa.common.dialogs.CopyMoveContentPanel
 * @xtype zarafa.importtofoldercontentpanel
 *
 * This will display a {@link Zarafa.core.ui.ContentPanel contentpanel}
 * for importing {@link Zarafa.core.data.IPMAttachmentRecord records}
 * to {@link Zarafa.hierarchy.data.MAPIFolderRecord folder}.
 */
Zarafa.common.attachment.dialogs.ImportToFolderContentPanel = Ext.extend(Zarafa.common.dialogs.CopyMoveContentPanel, {
	/**
	 * @constructor
	 * @param {Object} config Configuration structure
	 */
	constructor: function(config)
	{
		config = config || {};

		if (config.record && !Array.isArray(config.record)) {
			config.record = [ config.record ];
		}

		Ext.applyIf(config, {
			// Override from Ext.Component
			xtype: 'zarafa.importtofoldercontentpanel',
			title: _('Import to'),
			items: [{
				xtype: 'zarafa.importtofolderpanel',
				// A bit ugly, but the Zarafa.common.dialogs.CopyMoveContentPanel uses this ref
				ref: 'copyMovePanel',
				record: config.record
			}]
		});

		Zarafa.common.attachment.dialogs.ImportToFolderContentPanel.superclass.constructor.call(this, config);
	}
});

Ext.reg('zarafa.importtofoldercontentpanel', Zarafa.common.attachment.dialogs.ImportToFolderContentPanel);
/*
 * #dependsFile client/zarafa/core/mapi/Importance.js
 */
Ext.namespace('Zarafa.common.data');

/**
 * @class Zarafa.common.data.ImportanceFlags
 * @singleton
 * This class can be used by any context to set the important/priority of the items
 */
Zarafa.common.data.ImportanceFlags = {
	flags: [{
		value: Zarafa.core.mapi.Importance['NONURGENT'],
		name: Zarafa.core.mapi.Importance.getDisplayName(Zarafa.core.mapi.Importance['NONURGENT']),
		iconCls: Zarafa.core.mapi.Importance.getClassName(Zarafa.core.mapi.Importance['NONURGENT'])
	},{
		value: Zarafa.core.mapi.Importance['NORMAL'],
		name: Zarafa.core.mapi.Importance.getDisplayName(Zarafa.core.mapi.Importance['NORMAL']),
		iconCls: Zarafa.core.mapi.Importance.getClassName(Zarafa.core.mapi.Importance['NORMAL'])
	},{
		value: Zarafa.core.mapi.Importance['URGENT'],
		name: Zarafa.core.mapi.Importance.getDisplayName(Zarafa.core.mapi.Importance['URGENT']),
		iconCls: Zarafa.core.mapi.Importance.getClassName(Zarafa.core.mapi.Importance['URGENT'])
	}]
};
/*
 * #dependsFile client/zarafa/core/mapi/Sensitivity.js
 */
Ext.namespace('Zarafa.common.data');

/**
 * @class Zarafa.common.data.SensitivityFlags
 * @singleton
 * This class is used by rules to display the sensitivity.
 */
Zarafa.common.data.SensitivityFlags = {
	flags: [{
		value: Zarafa.core.mapi.Sensitivity['NONE'],
		name: Zarafa.core.mapi.Sensitivity.getDisplayName(Zarafa.core.mapi.Sensitivity['NONE'])
	},{
		value: Zarafa.core.mapi.Sensitivity['PERSONAL'],
		name: Zarafa.core.mapi.Sensitivity.getDisplayName(Zarafa.core.mapi.Sensitivity['PERSONAL'])
	},{
		value: Zarafa.core.mapi.Sensitivity['PRIVATE'],
		name: Zarafa.core.mapi.Sensitivity.getDisplayName(Zarafa.core.mapi.Sensitivity['PRIVATE'])
	},{
		value: Zarafa.core.mapi.Sensitivity['COMPANY_CONFIDENTIAL'],
		name: Zarafa.core.mapi.Sensitivity.getDisplayName(Zarafa.core.mapi.Sensitivity['COMPANY_CONFIDENTIAL'])
	}]
};
/*
 * #dependsFile client/zarafa/core/mapi/Rights.js
 */
Ext.namespace('Zarafa.common.delegates.data');

/**
 * @class Zarafa.common.delegates.data.DelegatePermissionProfiles
 * @singleton
 */
Zarafa.common.delegates.data.DelegatePermissionProfiles = [{
	value: Zarafa.core.mapi.Rights.RIGHTS_OWNER,
	name: _('Owner')
},{
	value: Zarafa.core.mapi.Rights.RIGHTS_SECRETARY,
	name: _('Secretary')
},{
	value: Zarafa.core.mapi.Rights.RIGHTS_READONLY,
	name: _('Only read')
},{
	value: Zarafa.core.mapi.Rights.RIGHTS_NONE,
	name: _('None')
}];/*
 * #dependsFile client/zarafa/core/data/RecordFactory.js
 * #dependsFile client/zarafa/core/data/RecordCustomObjectType.js
 */
Ext.namespace('Zarafa.common.delegates.data');

Zarafa.common.delegates.data.DelegateRecordFields = [
	{name: 'entryid', type: 'string'},
	{name: 'display_name', type: 'string'},
	{name: 'can_see_private', type: 'boolean', defaultValue: false},
	{name: 'has_meeting_rule', type: 'boolean', defaultValue: false},

	{name: 'rights_calendar', type: 'int'},
	{name: 'rights_tasks', type: 'int'},
	{name: 'rights_inbox', type: 'int'},
	{name: 'rights_contacts', type: 'int'},
	{name: 'rights_notes', type: 'int'},
	{name: 'rights_journal', type: 'int'}
];

/**
 * @class Zarafa.common.delegates.data.DelegateRecord
 * @extends Zarafa.core.data.MAPIRecord
 *
 * Record will hold information about delegates.
 */
Zarafa.common.delegates.data.DelegateRecord = Ext.extend(Zarafa.core.data.MAPIRecord, {
	/**
	 * The base array of ID properties which is copied to the {@link #idProperties}
	 * when the record is being created.
	 * @property
	 * @type Array
	 * @private
	 */
	baseIdProperties: [ 'entryid' ],

	/**
	 * Copy the {@link Zarafa.common.delegates.data.DelegateRecord DelegateRecord} to a new instance
	 * @param {String} newId (optional) A new Record id, defaults to the id of the record being copied. See id.
	 * @return {Zarafa.common.delegates.data.DelegateRecord} The copy of the record.
	 */
	copy: function(newId)
	{
		var copy = Zarafa.core.data.RecordFactory.createRecordObjectByCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_DELEGATE, this.data, newId || this.id);

		copy.idProperties = this.idProperties.clone();
		copy.phantom = this.phantom;

		return copy.applyData(this);
	},

	/**
	 * Applies all data from an {@link Zarafa.common.delegates.data.DelegateRecord DelegateRecord}
	 * to this instance. This will update all data.
	 *
	 * @param {Zarafa.common.delegates.data.DelegateRecord} record The record to apply to this
	 * @return {Zarafa.common.delegates.data.DelegateRecord} this
	 */
	applyData: function(record)
	{
		this.beginEdit();

		Ext.apply(this.data, record.data);
		Ext.apply(this.modified, record.modified);

		this.dirty = record.dirty;

		this.endEdit();

		return this;
	},

	/**
	 * Compare this {@link Zarafa.common.delegates.data.DelegateRecord DelegateRecord} instance
	 * with another one to see if they are same.
	 *
	 * @param {Zarafa.common.delegates.data.DelegateRecord} record The Record to compare with
	 * @return {Boolean} True if the records are same.
	 */
	equals: function(record)
	{
		// Simplest case, do we have the same object...
		if (this === record) {
			return true;
		}

		return Zarafa.core.EntryId.compareABEntryIds(this.get('entryid'), record.get('entryid'));
	}
});

// Register a custom type to be used by the Record Factory
Zarafa.core.data.RecordCustomObjectType.addProperty('ZARAFA_DELEGATE');

Zarafa.core.data.RecordFactory.setBaseClassToCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_DELEGATE, Zarafa.common.delegates.data.DelegateRecord);
Zarafa.core.data.RecordFactory.addFieldToCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_DELEGATE, Zarafa.common.delegates.data.DelegateRecordFields);
Ext.namespace('Zarafa.common.delegates.dialogs');

/**
 * @class Zarafa.common.delegates.dialogs.DelegatePermissionContentPanel
 * @extends Zarafa.core.ui.RecordContentPanel
 * @xtype zarafa.delegatepermissioncontentpanel
 *
 * {@link Zarafa.common.delegates.dialogs.DelegatePermissionContentPanel DelegatePermissionContentPanel} will be used to display
 * permissions of a specific delegate user.
 */
Zarafa.common.delegates.dialogs.DelegatePermissionContentPanel = Ext.extend(Zarafa.core.ui.RecordContentPanel, {
	/**
	 * @constructor
	 * @param config Configuration structure
	 */
	constructor: function(config)
	{
		config = config || {};

		// Add in some standard configuration data.
		Ext.applyIf(config, {
			// Override from Ext.Component
			xtype: 'zarafa.delegatepermissioncontentpanel',
			// Override from Ext.Component
			layout: 'fit',
			modal: true,
			recordComponentPluginConfig: Ext.applyIf(config.recordComponentPluginConfig || {}, {
				allowWrite: true,
				useShadowStore: true
			}),
			autoSave: false,
			width: 500,
			height: 325,
			title: _('Delegate Permissions'),
			items: [{
				xtype: 'zarafa.delegatepermissionpanel',
				buttons: [{
					text: _('Ok'),
					handler: this.onOk,
					scope: this
				},{
					text: _('Cancel'),
					handler: this.onCancel,
					scope: this
				}]
			}]
		});

		Zarafa.common.delegates.dialogs.DelegatePermissionContentPanel.superclass.constructor.call(this, config);
	}
});

Ext.reg('zarafa.delegatepermissioncontentpanel', Zarafa.common.delegates.dialogs.DelegatePermissionContentPanel);
Ext.namespace('Zarafa.common.freebusy.data');

/**
 * @class Zarafa.common.freebusy.data.FreebusyResponseHandler
 * @extends Zarafa.core.data.ProxyResponseHandler
 *
 * Small extension to the main {@link Zarafa.core.data.ProxyResponseHandler ProxyResponseHandler},
 * to add support for the special {@link #doList 'list'} action for Freebusy blocks.
 */
Zarafa.common.freebusy.data.FreebusyResponseHandler = Ext.extend(Zarafa.core.data.ProxyResponseHandler, {
	/**
	 * Handles the 'list' response. Gathers the records from the response data,
	 * converts each entry into a records and pushes them into the {@link #receivedRecords records list}.
	 * @param {Object} data The response object belonging to the given command.
	 * @return {Boolean} False when action could not be handled successfully. This will
	 * not cancel the transaction itself, but rather causes the 'success' argument for the
	 * {@link #done} function.
	 */
	doList: function(response)
	{
		var items = [];
		Ext.each(response.users, function(userData) {
			Ext.each(userData.items, function(userItem) {
				items.push(Ext.applyIf(userItem, {
					userid: userData.userid
				}));
			});
		});

		this.receivedRecords = this.reader.readRecords({
			count: items.length,
			item: items
		});
	}
});
Ext.namespace('Zarafa.common.freebusy.ui');

/**
 * @class Zarafa.common.freebusy.ui.UserlistBox
 * @extends Zarafa.common.recipientfield.ui.RecipientBox
 * @xtype zarafa.userlistbox
 *
 * Extension of the {@link Zarafa.common.recipientfield.ui.RecipientBox RecipientBox}
 * which must be used in the {@link Zarafa.common.freebusy.ui.UserlistView} where
 * the users are listed
 */
Zarafa.common.freebusy.ui.UserlistBox = Ext.extend(Zarafa.common.recipientfield.ui.RecipientBox, {

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

		Ext.applyIf(config, {
			autoHeight: true,
			btnCls: 'x-freebusy-userrow-remove',
			btnHoverCls: 'x-freebusy-userrow-remove-hover'
		});

		Zarafa.common.freebusy.ui.UserlistBox.superclass.constructor.call(this, config);
	},

	/**
	 * Function which can be overridden to provide custom icon rendering for the given {@link Ext.data.Record}
	 * to the {@link #iconEl} element. The string returned here is the CSS class which will be set on the
	 * {@link #iconEl}.
	 * @param {Ext.data.Record} record The record which is going to be rendered
	 * @return {String} The CSS class which must be applied to the {@link #iconEl}.
	 * @private
	 */
	prepareIcon: function(record)
	{
		switch (record.get('recipient_type')) {
			case Zarafa.core.mapi.RecipientType.MAPI_CC:
				return 'icon_meetingrequest_optionalattendee';
			case Zarafa.core.mapi.RecipientType.MAPI_BCC:
				return 'icon_meetingrequest_resource';
			case Zarafa.core.mapi.RecipientType.MAPI_TO:
			/*fall through*/
			default:
				if(record.isMeetingOrganizer()) {
					return 'icon_meetingrequest_organizer';
				}
				return 'icon_meetingrequest_requiredattendee';
		}
	},

	/**
	 * Function called after the {@link #render rendering} of this component.
	 * This will hide the {@link #delBtnEl} when the {@link #editable} flag is false
	 * @param {Ext.Container} ct The container in which the component is being rendered.
	 * @private.
	 */
	afterRender: function(ct)
	{
		Zarafa.common.freebusy.ui.UserlistBox.superclass.afterRender.call(this, ct);
		this.delBtnEl.setVisible(this.editable);
	},

	/**
	 * Update the {@link #textEl inner HTML} of this component using the {@link #textTpl template}.
	 * @param {Ext.data.Record} record The Ext.data.Record which data must be applied to the template
	 */
	update: function(record)
	{
		if (record.isMeetingOrganizer()) {
			this.setEditable(false);
		}

		// className is contains css classes for different type of attendee's icon element.
		var className = ['icon_meetingrequest_requiredattendee',
			'icon_meetingrequest_optionalattendee',
			'icon_meetingrequest_resource'];

		// Clear the css class of attendee's icon element which is defined attendee's type.
		this.iconEl.removeClass(className);

		Zarafa.common.freebusy.ui.UserlistBox.superclass.update.apply(this, arguments);

		// When record gets updated by resolve request, it may be possible that it is a DistList.
		// Resize the expand button only if the recipient is distribution list.
		if(this.record.get('object_type') == Zarafa.core.mapi.ObjectType.MAPI_DISTLIST) {
			if (this.enableButtons === true && Ext.isDefined(this.expandBtnEl)) {
				// Box needs to be resized manually because resizing (and all other post rendering activities)
				// was completed at the time when box gets updated after resolve-response arrived.
				this.onResize(this.width, this.height, this.width, this.height);
			}
		}
	}
});

Ext.reg('zarafa.userlistbox', Zarafa.common.freebusy.ui.UserlistBox);
Ext.namespace('Zarafa.common.manageCc.dialogs');

/**
 * @class Zarafa.common.manageCc.dialogs.ManageCcEditContentPanel
 * @extends Zarafa.common.recipientfield.ui.EditRecipientContentPanel
 * @xtype zarafa.managecceditcontentpanel
 *
 * Panel will be used to add/edit {@link Zarafa.common.manageCc.data.IPMCcRecipientRecord IPMCcRecipientRecord} address.
 */
Zarafa.common.manageCc.dialogs.ManageCcEditContentPanel = Ext.extend(Zarafa.common.recipientfield.ui.EditRecipientContentPanel, {
	/**
	 * @cfg {Boolean} removeOnCancel Remove the {@link Zarafa.common.manageCc.data.IPMCcRecipientRecord record} from store
	 * while user press "cancel" button.
	 */
	removeOnCancel: true,

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

		Ext.applyIf(config, {
			xtype: 'zarafa.managecceditcontentpanel',
			title: _('Add/Edit Cc recipient')
		});

		Zarafa.common.manageCc.dialogs.ManageCcEditContentPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Event handler which is raised when the user clicks the "Ok" {@link Ext.Button button}
	 * @private
	 */
	onOk: function()
	{
		var editForm = this.formPanel.getForm();

		if (!editForm.isValid()) {
			return;
		}

		var record = this.record;
		// Update the record as per the form data.
		editForm.updateRecord(record);

		// return if no change is made by the user.
		if (record.dirty === false) {
			this.close();
			return;
		}

		var store = record.store;
		// If email address is already there then show the message box.
		if (store.isRecipientExists(record)) {
			record.reject();
			Ext.Msg.alert(_('Duplicate recipient'), _('Recipient already exists.'));
			return;
		}

		this.setDialogBusy(true);
		record.store.resolveRecipientByEmailAddress(record, this.onRecipientResolved, this);
	},

	/**
	 * Finalize the save after the typed SMTP address has been checked against the address book.
	 *
	 * @param {Zarafa.core.data.IPMRecipientRecord} record The recipient being saved.
	 * @private
	 */
	onRecipientResolved: function(record)
	{
		this.setDialogBusy(false);

		if (!record) {
			return;
		}

		if (record.store.isRecipientExists(record)) {
			record.reject();
			Ext.Msg.alert(_('Duplicate recipient'), _('Recipient already exists.'));
			return;
		}

		if (!record.isResolved() || record.isOneOff()) {
			record.generateOneOffEntryId();
		}
		record.commit();
		this.close();
	},

	/**
	 * Toggle a temporary busy state while checking if the recipient exists in the address book.
	 *
	 * @param {Boolean} busy True to disable dialog interaction, false otherwise.
	 * @private
	 */
	setDialogBusy: function(busy)
	{
		if (this.formPanel && this.formPanel.el) {
			this.formPanel.el[busy ? 'mask' : 'unmask'](_('Resolving recipient') + '...');
		}

		Ext.each(this.formPanel.buttons || [], function(button) {
			button.setDisabled(busy);
		});
	},
	/**
	 * Function will be called when user clicks on close tool on the {@link Ext.Window}
	 * and should remove phantom record if needed.
	 * @protected
	 */
	closeWrap: function()
	{
		this.removePhantomRecord();
		Zarafa.common.manageCc.dialogs.ManageCcEditContentPanel.superclass.closeWrap.apply(this, arguments);
	},

	/**
	 * Event handler which is raised when the user clicks the "Cancel" {@link Ext.Button button}
	 * @private
	 */
	onCancel: function()
	{
		this.removePhantomRecord();
		Zarafa.common.manageCc.dialogs.ManageCcEditContentPanel.superclass.onCancel.call(this);
	},

	/**
	 * Function is used to remove {@link Zarafa.common.manageCc.data.IPMCcRecipientRecord IPMCcRecipientRecord}
	 * from {Zarafa.core.data.IPMRecipientStore} when {@link #removeOnCancel}
	 * is true and user has closed the dialog without saving it.
	 * @private
	 */
	removePhantomRecord: function()
	{
		if (this.removeOnCancel === true && this.record.phantom) {
			this.record.store.remove(this.record);
		}
	}
});

Ext.reg('zarafa.managecceditcontentpanel', Zarafa.common.manageCc.dialogs.ManageCcEditContentPanel);
/*
 * #dependsFile client/zarafa/core/mapi/ObjectType.js
 * #dependsFile client/zarafa/core/data/RecordCustomObjectType.js
 * #dependsFile client/zarafa/core/data/RecordFactory.js
 */
Ext.namespace('Zarafa.common.outofoffice.data');

/**
 * @class Zarafa.common.outofoffice.data.OofRecord
 *
 * Array of default fields for the {@link Zarafa.common.outofoffice.data.OofRecord} object.
 * These fields will always be added, regardless of the exact type of
 * {@link Zarafa.common.outofoffice.data.OofRecord record}.
 */
Zarafa.common.outofoffice.data.OofRecordFields = [
  // Here 'entryid' is user's entryid whereas 'store_entryid' is shared user's store's entry id.
  {name: 'entryid'},
  {name: 'store_entryid'},
  {name: 'from', type: 'int'},
  {name: 'internal_reply'},
  {name: 'set'},
  {name: 'internal_subject'},
  {name: 'until', type: 'int'},
  {name: 'allow_external'},
  {name: 'external_audience'},
  {name: 'external_reply'},
  {name: 'external_subject'},
];

/**
 * @class Zarafa.common.outofoffice.data.OofRecord
 * @extends Zarafa.core.data.MAPIRecord
 */
Zarafa.common.outofoffice.data.OofRecord = Ext.extend(Zarafa.core.data.MAPIRecord, {
  /**
   * The base array of ID properties which is copied to the {@link #idProperties}
   * when the record is being created.
   * @property
   * @type Array
   * @private
   */
  baseIdProperties: ['store_entryid'],
});

// Register a custom type to be used by the Record Factory
Zarafa.core.data.RecordCustomObjectType.addProperty('ZARAFA_OOF_SETTINGS');

Zarafa.core.data.RecordFactory.setBaseClassToCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_OOF_SETTINGS, Zarafa.common.outofoffice.data.OofRecord);
Zarafa.core.data.RecordFactory.addFieldToCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_OOF_SETTINGS , Zarafa.common.outofoffice.data.OofRecordFields);
Ext.namespace('Zarafa.common.plugins');

/**
 * @class Zarafa.common.plugins.ManualPlugin
 * @extends Zarafa.core.Plugin
 *
 * The Manual Plugin, which inserts a 'Help' button in the Top Toolbar,
 * from where the user can open the Manual in a new page.
 */
Zarafa.common.plugins.ManualPlugin = Ext.extend(Zarafa.core.Plugin, {

	/**
	 * List of Contexts name for which a shortcut exists inside the manual.
	 * @property
	 * @type Array
	 */
	manualShortcuts: {
		'mail': 'mail.html',
		'calendar': 'calendar.html',
		'contact': 'contacts.html',
		'task': 'tasks.html',
		'note': 'notes.html',
		'settings': 'settings.html'
	},

	/**
	 * Called after constructor.
	 * Registers insertion points in Top Toolbar
	 * @protected
	 */
	initPlugin: function()
	{
		Zarafa.common.plugins.ManualPlugin.superclass.initPlugin.apply(this, arguments);

		// First of all check if webapp manual plugin setting available else check main settings.
		if (container.getSettingsModel().getOneOf('zarafa/v1/plugins/webappmanual/enable', 'zarafa/v1/main/help_manual/show') === true) {
			this.registerInsertionPoint('main.maintabbar.right', this.createManualMainTab, this);
		}
	},

	/**
	 * Adds a button to the top tab bar for the manual
	 * @return {Object} The button for the top tabbar
	 * @private
	 */
	createManualMainTab: function()
	{
		return {
			text: _('Help'),
			tabOrderIndex: 0,
			handler: this.onHelpButton,
			scope: this
		};
	},

	/**
	 * Event handler which is called when the button in the Top toolbar is pressed.
	 * This will check what the {@link Zarafa.core.Container#getCurrentContext current context}
	 * is, and if a {@link #manualShortcuts shortcut} exists for that context.
	 * It will then open a new Browser window with the correct page of the manual.
	 * @private
	 */
	onHelpButton: function()
	{
		var context = container.getCurrentContext();
		var shortcut = this.manualShortcuts[context.getName()];
		var url = container.getServerConfig().getWebappManualUrl();

		if (!Ext.isEmpty(shortcut)) {
			url += '/' + shortcut;
		}

		window.open(url, 'webapp_manual');
	}
});

Zarafa.onReady(function() {
	container.registerPlugin(new Zarafa.core.PluginMetaData({
		name: 'webappmanual',
		displayName: _('Web Manual'),
		allowUserDisable: false,
		allowUserVisible: false,
		pluginConstructor: Zarafa.common.plugins.ManualPlugin
	}));
});
Ext.ns('Zarafa.common.plugins');

/**
 * @class Zarafa.common.plugins.PercentSpinner
 * @extends Zarafa.common.plugins.NumberSpinner
 * @ptype zarafa.percentspinner
 *
 * Extension to the {@link Zarafa.common.plugins.NumberSpinner} object,
 * to correctly parse percentages. This is used by the {@link Zarafa.common.ui.SpinnerField SpinnerField}.
 *
 * We inherit from the {@link Zarafa.common.plugins.NumberSpinner} class because
 * percentages are represented as Floats, and only our raw DOM value is a string
 * where the % sign is postfixed.
 */
Zarafa.common.plugins.PercentSpinner = Ext.extend(Zarafa.common.plugins.NumberSpinner, {
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			allowDecimals: true,
			decimalPrecision: 4
		});

		Zarafa.common.plugins.PercentSpinner.superclass.constructor.call(this, config);
	},

	/**
	 * Initializes the {@link Zarafa.common.ui.SpinnerField SpinnerField} to which
	 * this plugin has been hooked.
	 * @param {Zarafa.common.ui.SpinnerField} The parent field to which this component is connected
	 */
	init: function(field)
	{
		// The superClass will call parseFloat() on the defaultValue, minValue and
		// maxValue when those values are written in Strings. We however expect a string
		// to be formatted with a '%' as postfix. Although we don't really care for that
		// case (parseFloat will still work as expected), we must divide the value by 100
		// to get a value between 0 and 1.
		if (Ext.isDefined(field.defaultValue) && Ext.isString(field.defaultValue)) {
			field.defaultValue = this.stringToFloat(field.defaultValue);
		}
		if (!Ext.isDefined(field.minValue)) {
			field.minValue = 0;
		} else if (Ext.isString(field.minValue)) {
			field.minValue = this.stringToFloat(field.minValue);
		}
		if (!Ext.isDefined(field.maxValue)) {
			field.maxValue = 1;
		} else if (Ext.isString(field.maxValue)) {
			field.maxValue = this.stringToFloat(field.maxValue);
		}

		Zarafa.common.plugins.PercentSpinner.superclass.init.call(this, field);
	},

	/**
	 * Convert a String into a Float. If the String contains the '%' character,
	 * the value is converted as percentage (and thus divided by 100), otherwise
	 * it is assumed to be the literal stringified version of the float.
	 * @param {String} value The string to convert to a float
	 * @return {Float} The float value representing the string
	 * @protected
	 */
	stringToFloat: function(value)
	{
		if (Ext.isEmpty(value)) {
			return;
		}

		var isPercent = value.indexOf('%');

		value = parseFloat(value);
		if (isPercent) {
			value /= 100;
		}

		return value;
	},

	/**
	 * Convert a Float to a String representing the Float as percentage.
	 * @param {Float} value The float value which must be converted
	 * @return {String} The stringified value represented as percentage
	 * @protected
	 */
	floatToString: function(value)
	{
		if (Ext.isDefined(value)) {
			return (value * 100) + '%';
		}
	},

	/**
	 * Sets a data value into the field and validates it.
	 * To set the value directly without validation see {@link Ext.form.Field#setRawValue}.
	 * @param {Number/String} value The value to set
	 * @private
	 */
	setValue: function(value)
	{
		if (!Ext.isEmpty(value) && Ext.isString(value)) {
			value = parseFloat(value) / 100;
		}
		Zarafa.common.plugins.PercentSpinner.superclass.setValue.call(this, value);
	}
});

// Register as plugin, so that it can be used using lazy loading
// with the Zarafa.common.ui.SpinnerField
Ext.preg('zarafa.percentspinner', Zarafa.common.plugins.PercentSpinner);
Ext.namespace('Zarafa.common.plugins');

/**
 * @class Zarafa.common.plugins.ZDeveloperPlugin
 * @extends Zarafa.core.Plugin
 *
 * Shows possible insertion points inside application.
 */
Zarafa.common.plugins.ZDeveloperPlugin = Ext.extend(Zarafa.core.Plugin, {

	/**
	 * Initialize the plugin by calling {@link #registerInsertionPoint}.
	 * @protected
	 */
	initPlugin: function()
	{
		Zarafa.common.plugins.ZDeveloperPlugin.superclass.initPlugin.apply(this, arguments);

		var isAdvancedSettingsEnabled = container.getServerConfig().isAdvancedSettingsEnabled();
		if (container.getSettingsModel().get("zarafa/v1/main/kdeveloper_tool/kdeveloper") === true && isAdvancedSettingsEnabled === true)  {
			this.registerInsertionPoint(/(.*?)/, this.putMessageBox, this);
		}

		// Register the item data button when the setting is enabled and when advanced settings is enabled
		if (container.getSettingsModel().get('zarafa/v1/main/kdeveloper_tool/itemdata') === true && isAdvancedSettingsEnabled === true) {
			this.registerInsertionPoint('context.mail.contextmenu.options', this.itemdatabutton, this);
		}
	},

	/**
	 * Create item data button
	 */
	itemdatabutton: function () {
		return [{
			xtype: 'zarafa.conditionalitem',
			text: _('Item data'),
			iconCls: 'icon_cogwheel',
			singleSelectOnly: true,
			handler: this.onClickItemDataHandler,
			scope: this
		}];
	},

	/**
	 * Event handler which is called when the user clicks the 'Item data'
	 * item in the context menu. This will open a new window with item details.
	 * @private
	 */
	onClickItemDataHandler: function (btn) {
		var popUp = window.open("", "",'directories=no,titlebar=no,toolbar=no,location=no,status=no,menubar=no,scrollbars=yes,resizable=yes,width=1000,height=900');
		popUp.document.write("<pre style=word-wrap:break-word;white-space:pre-line;>"+JSON.stringify(btn.parentMenu.records[0].data, undefined, 2)+"</pre>");
	},

	/**
	 * Return the message box with the name of insertion point.
	 *
	 * WARNING: The objects returned by this function do not represent the actual
	 * objects which are expected by the callers of the insertion point. The returned
	 * objects only ensure that "Something" is shown at the correct location as reference
	 * to the plugin developers where the particular insertion point might appear.
	 * For returning the correct object for an insertion point, please refer to the API
	 * documentation for that particular insertion point.
	 *
	 * @param context
	 * @return {Object}
	 */
	putMessageBox: function(context)
	{
		if (context === 'main.categories') {
			return _('INSERT') + ': ' + context;
		} else if (context.match(/(.*?).tabs/)) {
			return {
				xtype: 'panel',
				title: context,
				listeners: {
					'added': function() {
						Ext.get(this.tabEl.lastChild).addClass('k-developer-insertion-point');
					},
					delay: 1,
					single: true
				}
			};
		} else if (context === 'navigation.center') {
			return {
				xtype: 'zarafa.contextnavigation',
				items: [{
						xtype: 'box',
						autoEl: {
							tag: 'span',
							html: context,
							cls: 'k-developer-insertion-point'
						}
					}
				],
				context: container.getCurrentContext()
			};
		} else if (context === 'context.settings.categories') {
			return {
				xtype: 'zarafa.settingscategory',
				title: context,
				iconCls: 'k-developer-insertion-point',
				items: []
			};
		} else if (context.match(/(.*?)toolbar(.*?)/) && context !== 'previewpanel.toolbar.detaillinks') {
			return {
				xtype: 'button',
				text: context,
				cls: 'k-developer-insertion-point',
				listeners: {
					'render': function() {
						if (!this.text) {
							// In case the view buttons are expanded
							// rather then placed in a dropdown menu.
							this.setText(Ext.isObject(this.tooltip) ? this.tooltip.title : this.tooltip);
						}
					}
				}
			};
		} else if (context === 'context.mail.gridrow') {
			return String.format('<td style="width: 112px"><div class="{0}">{1}</div></td>', 'grid_compact k-developer-insertion-point', 'context.mail.gridrow');
		} else if (context === 'context.mail.griddefaultcolumn' || context === 'context.addressbook.gridpanel') {
			return {
				header: '<p class="k-developer-insertion-point">context.mail.griddefaultcolumn</p>',
				width: 200,
				renderer: function(value, p, record) {
					return 'context.mail.griddefaultcolumn';
				},
				tooltip: _('Sort by: context.mail.griddefaultcolumn')
			};
		}
		else if(context !== 'main.content') {
			return {
				xtype: 'box',
				autoEl: {
					tag: 'div',
					html: context,
					cls: 'k-developer-insertion-point'
				}
			};
		}
	}

});

Zarafa.onReady(function() {
	container.registerPlugin(new Zarafa.core.PluginMetaData({
		name: 'zdeveloper',
		allowUserDisable: false,
		allowUserVisible: false,
		displayName: _('ZDeveloper'),
		pluginConstructor: Zarafa.common.plugins.ZDeveloperPlugin
	}));
});
/*
 * #dependsFile client/zarafa/core/mapi/ObjectType.js
 */
Ext.namespace('Zarafa.common.recipientfield.data');

/**
 * @class Zarafa.common.recipientfield.data.SuggestionListRecord
 * @extends Ext.data.Record
 *
 * Contains a description of what a single RecipientField Suggestion item looks like.
 * Is used by the JSON reader in the {@link Zarafa.common.recipientfield.ui.SuggestionListProxy proxy}.
 */
Zarafa.common.recipientfield.data.SuggestionListRecord = Ext.data.Record.create([
	{ name: 'display_name' },
	{ name: 'smtp_address' },
	{ name: 'email_address' },
	{ name: 'address_type' },
	{ name: 'count', type: 'int' },
	{ name: 'last_used', type: 'date', dateFormat:'timestamp' },
	{ name: 'object_type', type: 'int', defaultValue: Zarafa.core.mapi.ObjectType.MAPI_MAILUSER }
]);

Zarafa.common.recipientfield.data.SuggestionListRecord = Ext.extend(Zarafa.common.recipientfield.data.SuggestionListRecord, {
	/**
	 * Convert this suggestion record into a {@link Zarafa.core.data.IPMRecipientRecord recipient}
	 * which can be used for composing news mails.
	 *
	 * @param {Zarafa.core.mapi.RecipientType} recipientType (optional) The recipient type which should
	 * be applied to this recipient. Defaults to {@link Zarafa.core.mapi.RecipientType#MAPI_TO}.
	 * @return {Zarafa.core.data.IPMRecipientRecord} The recipientRecord for this addressbook item
	 */
	convertToRecipient: function(recipientType)
	{
		var recipientRecord = Zarafa.core.data.RecordFactory.createRecordObjectByCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_RECIPIENT, {
			object_type: this.get('object_type'),
			display_name: this.get('display_name'),
			email_address: this.get('email_address'),
			smtp_address: this.get('smtp_address'),
			address_type: this.get('address_type'),
			recipient_type: recipientType || Zarafa.core.mapi.RecipientType.MAPI_TO
		});

		return recipientRecord;
	}
});
Ext.namespace('Zarafa.common.recipientfield.data');

/**
 * @class Zarafa.common.recipientfield.data.SuggestionListResponseHandler
 * @extends Zarafa.core.data.ProxyResponseHandler
 *
 * Small extension to the main {@link Zarafa.core.data.ProxyResponseHandler ProxyResponseHandler},
 * to add support for the special {@link #doList 'list'} action (which requires different handling
 * compared to the 'list' action frion {@link Zarafa.core.data.IPMResponseHandler IPMResponseHandler}.
 */
Zarafa.common.recipientfield.data.SuggestionListResponseHandler = Ext.extend(Zarafa.core.data.ProxyResponseHandler, {
	/**
	 * Handles the 'list' response. Gathers the records from the response data,
	 * converts each entry into a records and pushes them into the {@link #receivedRecords records list}.
	 * @param {Object} data The response object belonging to the given command.
	 * @return {Boolean} False when action could not be handled successfully. This will
	 * not cancel the transaction itself, but rather causes the 'success' argument for the
	 */
	doList: function(response)
	{
		var results = response.results || [];

		// Force results to be an array.
		if (!Array.isArray(results)) {
			results = [ results ];
		}

		this.receivedRecords = this.reader.readRecords({
			count: results.length,
			result: results
		});
	}
});
/*
 * #dependsFile client/zarafa/core/mapi/RecipientType.js
 */
Ext.namespace('Zarafa.common.recipientfield.ui');

/**
 * @class Zarafa.common.recipientfield.ui.RecipientField
 * @extends Zarafa.common.ui.BoxField
 * @xtype zarafa.recipientfield
 *
 * Extension to the default {@link Zarafa.common.ui.BoxField} which
 * handles recipients as for rendering. It uses a
 * {@link Zarafa.core.data.IPMRecipientStore IPMRecipientStore} for storing
 * and resolving {@link Zarafa.core.data.IPMRecipientsRecord Recipients}.
 *
 * If the {@link Zarafa.core.plugins.RecordComponentUpdaterPlugin} is installed
 * in the {@link #plugins} array of this component, this component will automatically
 * load the {@link Zarafa.core.data.IPMRecipientStore RecipientStore} into the component.
 * Otherwise the user of this component needs to call {@link #setRecipientStore}.
 */
Zarafa.common.recipientfield.ui.RecipientField = Ext.extend(Zarafa.common.ui.BoxField, {
	/**
	 * @cfg {Zarafa.core.mapi.RecipientType} defaultRecipientType The default recipient type value which should be used for the field
	 * defaultRecipientType takes the value of filterRecipientType properties if its defined.
	 */
	defaultRecipientType: Zarafa.core.mapi.RecipientType.MAPI_TO,

	/**
	 * @cfg {Zarafa.core.mapi.RecipientType} filterRecipientType The recipient type value which is used for this Input field
	 * This field also helps us to make a decision on which recipients should visible into the field and if set to undefined
	 * it will allow to display multiple recipient types into that field.
	 */
	filterRecipientType: undefined,

	/**
	 * @cfg {String} delimiterCharacters The characters which is used to split input up into multiple
	 * recipients. This allows the user to type in multiple recipients before they are converted into
	 * boxes.
	 */
	delimiterCharacters: [';',','],

	/**
	 * @cfg {Boolean} enableDrag true to enable just drag
	 */
	enableDrag: true,

	/**
	 * @cfg {Boolean} enableDrop true to enable just drop
	 */
	enableDrop: true,

	/**
	 * @cfg {Boolean} enableDragDrop true to enable drag and drop
	 */
	enableDragDrop: true,

	/**
	 * The dragZone used by this field if drag is enabled (see {@link #enableDrag})
	 * @property
	 * @type Zarafa.common.recipientfield.ui.RecipientDragZone
	 */
	dragZone: undefined,

	/**
	 * The dropZone used by this tree if drop is enabled (see {@link #enableDrop})
	 * @property
	 * @type Zarafa.common.recipientfield.ui.RecipientDropZone
	 */
	dropZone: undefined,

	/**
	 * @cfg {Number} The length of time in milliseconds to delay between
	 * the start of typing and sending the query to filter the dropdown
	 * list
	 */
	queryDelay: 250,

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

		if (!Ext.isDefined(config.store)) {
			config.store = new Zarafa.common.recipientfield.data.SuggestionListStore();
		}

		Ext.applyIf(config, {
			boxType: 'zarafa.recipientbox',
			itemSelector: 'div.x-zarafa-boxfield-suggestion-item',
			extraItemSelector: 'a.x-zarafa-boxfield-suggestion-cross',
			border: false,
			minChars: 1,
			listEmptyText: _('No suggestions available'),
			boxMinHeight: 24,
			boxMaxHeight: 66,
			height: 24,
			tpl: new Ext.XTemplate(
				'<tpl for=".">',
					'<div class="x-zarafa-boxfield-suggestion-item x-combo-list-item">',
						'<a class="x-zarafa-boxfield-suggestion-cross" ext:qtip="'+ _('Delete recipient from suggestion list') +'" ext:qwidth="100%"></a>',
						'{values.display_name:htmlEncode} &lt;',
							'<tpl if="!Ext.isEmpty(values.smtp_address)">{values.smtp_address:htmlEncode}</tpl>',
							'<tpl if="Ext.isEmpty(values.smtp_address)">{values.email_address:htmlEncode}</tpl>',
						'&gt;',
					'</div>',
				'</tpl>'
			)
		});

		Zarafa.common.recipientfield.ui.RecipientField.superclass.constructor.call(this, config);

		this.on('boxdblclick', this.onBoxDblClick, this);
		this.on('boxcontextmenu', this.onBoxContextMenu, this);

		// For some reason Ext explicitly sets the width if the size of a boxComponent is set before it is rendered
		// This means it will never be resized again when the parents components size changes
		// So we make sure it will be undefined again and then it is resized as we want it to.
		// (it took me weeks to figure this out, so be careful when you change this!!!)
		this.on('afterrender', function(){
			if ( this.width ){
				this.width = undefined;
			}
		});
	},

	/**
	 * Called to handle the input when the user presses the handleInputKey or another trigger makes
	 * this component need to handle the input. Has to be overwritten to implement the desired
	 * behavior and the creation of the correct type of record.
	 *
	 * It will split up the input string if it has multiple email addresses or valid delimeteres.
	 * It will split following types of multiple email addresses string
	 *   1. Email addresses with valid delimiters , and ; and spaces
	 *   2. Email addresses with user name where email address is enclosed in < >
	 *   3. User name with delimiters , and ;
	 * @param {String} value The value from the input field
	 */
	handleInput: function(value)
	{
		// FIXME: Disallow typing in HTML formatting...

		// fetch the email addresses from string.
		var emailAddresses = value.match(Zarafa.reMultipleEmailAddresses);
		var splitted = [];

		// If string doesn't have email address and
		// it has only user name with delimiter then split the string with that delimiter
		if (Ext.isEmpty(emailAddresses)) {
			splitted = value.split(new RegExp(this.delimiterCharacters.join('|'), 'g'));

			if(Ext.isEmpty(splitted)) {
				splitted.push(value);
			}
		} else {
			splitted = emailAddresses;
		}

		// Remove duplicate email addresses.
		splitted = splitted.filter(function (emailAddress, index, self) {
			self[index] = self[index].trim();
			return index === self.indexOf(emailAddress.trim());
		});

		var newRecords = splitted.map(function (emailAddress) {
			return this.boxStore.parseRecipient(emailAddress.trim(), this.defaultRecipientType);
		}, this);

		if (newRecords.length > 0) {
			this.boxStore.add(newRecords);
		}
	},

	/**
	 * Called to handle a selection from the dropdown list. This function needs to
	 * convert the selected record into a record for the {@link #boxStore}.
	 * @param {Ext.data.Record} record The record which was selected from {@link #store}
	 * @protected
	 */
	handleSelection: function(record)
	{
		var recipient = record.convertToRecipient(this.defaultRecipientType);
		this.boxStore.add(recipient);
	},

	/**
	 * Set the store on this field. See {@link #setBoxStore}.
	 * @param {Zarafa.core.data.IPMRecipientStore} store The store to set on this field
	 */
	setRecipientStore: function(store)
	{
		return this.setBoxStore.apply(this, arguments);
	},

	/**
	 * Get the store attached to this field. See {@link #getBoxStore}.
	 * @return {Zarafa.core.data.IPMRecipientStore} store
	 */
	getRecipientStore: function()
	{
		return this.getBoxStore();
	},

	/**
	 * Update the components with the given record.
	 *
	 * @param {Zarafa.core.data.MAPIRecord} record The record to update in this component
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 */
	update: function(record, contentReset)
	{
		if (record && record instanceof Zarafa.core.data.MAPIRecord) {
			// In case the recordcomponentupdaterplugin is installed
			// we have a special action to update the component.
			if (contentReset && record.isOpened()) {
				this.setRecipientStore(record.getRecipientStore());
			}
		} else {
			// The recordcomponentupdaterplugin is not installed and the
			// caller really wants to perform the update() function. Probably
			// a bad move, but lets not disappoint the caller.
			Zarafa.common.recipientfield.ui.RecipientField.superclass.update.apply(this, arguments);
		}
	},

	/**
	 * Overwrites the function to set a box to add the resolved and beforeresolve event handlers to
	 * the store.
	 * @param {Ext.data.Store} boxStore The store that will be applied to this field.
	 * @param {Boolean} initial True if this function is called from the constructor.
	 */
	setBoxStore: function(boxStore, initial)
	{
		if (this.boxStore === boxStore && initial !== true) {
			return;
		}

		if (this.boxStore) {
			this.mun(this.boxStore, 'resolved', this.onBoxStoreResolved, this);
			this.mun(this.boxStore, 'beforeresolve', this.onBoxStoreBeforeResolve, this);
		}

		Zarafa.common.recipientfield.ui.RecipientField.superclass.setBoxStore.apply(this, arguments);

		if (this.boxStore) {
			this.mon(this.boxStore, 'resolved', this.onBoxStoreResolved, this);
			this.mon(this.boxStore, 'beforeresolve', this.onBoxStoreBeforeResolve, this);
		}
	},

	/**
	 * Event handler for when the server returns a resolve response. It is also fired when the request for
	 * resolving was not successful. If the latter is the case the success parameter is set to false.
	 * @param {Zarafa.core.data.IPMRecipientStore} store The store which fired the event
	 * @param {Zarafa.core.data.IPMRecipientRecord[]} records The records that were sent in the resolve request.
	 */
	onBoxStoreResolved: function(store, records)
	{
		records = this.filterRecords(this.getBoxStore(), records);

		for (var i = 0; i < records.length; i++) {
			var recipient = records[i];
			var recipientBox = this.getBoxForRecord(recipient);
			if (recipientBox) {
				recipientBox.update(recipient);
				this.hideSuggestionList();
			}
		}
	},

	/**
	 * Event handler for when a resolve request is made to the server. This can be used to mark the
	 * records that are concerned as pending.
	 * @param {Zarafa.core.data.IPMRecipientStore} store The store which fired the event
	 * @param {Zarafa.core.data.IPMRecipientRecord[]} records The records that are pending resolving
	 */
	onBoxStoreBeforeResolve: function(store, records)
	{
		records = this.filterRecords(this.getBoxStore(), records);

		for (var i = 0; i < records.length; i++) {
			var recipient = records[i];
			var recipientBox = this.getBoxForRecord(recipient);
			if (recipientBox) {
				recipientBox.update(recipient);
			}
		}
	},

	/**
	 * Called by {@link #filterRecords} to check if the given record
	 * must be visible in the field or not. For the recipientfield, we check
	 * the {@link filterRecipientType} property and prevent that the
	 * {@link Zarafa.core.data.IPMRecipientRecord#isMeetingOrganizer meeting organizer}
	 * are visible in the box.
	 * @param {Ext.data.Record} record The record to filter
	 * @return {Boolean} True if the record should be visible, false otherwise
	 * @protected
	 */
	filterRecord: function(record)
	{
		return !record.isMeetingOrganizer() &&
				(!Ext.isDefined(this.filterRecipientType) ||
				 record.get('recipient_type') === this.filterRecipientType);
	},

	/**
	 * Event handler when a Box has been double-clicked.
	 * @param {Zarafa.common.recipientfield.ui.RecipientField} field This field to which the box belongs
	 * @param {Zarafa.common.recipientfield.ui.RecipientBox} box The box for which was double-clicked
	 * @param {Zarafa.core.data.IPMRecipientRecord} record The record which is attached to the box
	 * @private
	 */
	onBoxDblClick: function(field, box, record)
	{
		if (record.isResolved()) {
			// Resolved recipients cannot be edited, so we open the "View" dialog for it.
			Zarafa.common.Actions.openViewRecipientContent(record, {
				modal: true
			});
		} else {
			// Unresolved recipients can be edited, so we open the "Create" dialog for it.
			Zarafa.core.data.UIFactory.openCreateRecord(record, {
				modal: true
			});
		}
	},

	/**
	 * Event handler when the contextmenu is requested for a Box.
	 * @param {Zarafa.common.recipientfield.ui.RecipientField} field This field to which the box belongs
	 * @param {Zarafa.common.recipientfield.ui.RecipientBox} box The box for which the contextmenu is requested
	 * @param {Zarafa.core.data.IPMRecipientRecord} record The record which is attached to the box
	 * @private
	 */
	onBoxContextMenu: function(field, box, record)
	{
		Zarafa.core.data.UIFactory.openDefaultContextMenu(record, {
			position: box.getEl().getXY(),
			editable: box.editable
		});
	},

	/**
	 * Function called during the {@link #render rendering} of this component.
	 * Override initializes drag and drop zones
	 * @param {Ext.Container} ct The container in which the component is being rendered.
	 * @param {NUmber} position The position within the container where the component will be rendered.
	 * @override
	 * @private
	 */
	initEvents: function()
	{
		Zarafa.common.recipientfield.ui.RecipientField.superclass.initEvents.apply(this, arguments);

		if ((this.enableDrag || this.enableDragDrop) && !this.dragZone) {
			this.dragZone = new Zarafa.common.ui.BoxFieldDragZone(this.container, {
				ddGroup: 'dd.mapiitem',
				field: this
			});
		}

		if ((this.enableDrop || this.enableDragDrop) && !this.dropZone) {
			this.dropZone = new Zarafa.common.recipientfield.ui.RecipientDropZone(this.container, {
				ddGroup: 'dd.mapiitem',
				field: this
			});
		}
	}
});

Ext.reg('zarafa.recipientfield', Zarafa.common.recipientfield.ui.RecipientField);
Ext.namespace('Zarafa.common.recurrence.dialogs');

/**
 * @class Zarafa.common.recurrence.dialogs.RecurrenceContentPanel
 * @extends Zarafa.core.ui.RecordContentPanel
 * @xtype zarafa.recurrencecontentpanel
 */
Zarafa.common.recurrence.dialogs.RecurrenceContentPanel = Ext.extend(Zarafa.core.ui.RecordContentPanel, {
	/**
	 * True when an existing recurrence is being edited by this content panel. When this is true,
	 * {@link #onOk} will request a confirmation if the existing exceptions can be deleted.
	 * @property
	 * @type Boolean
	 * @private
	 */
	editRecurrence: false,

	/**
	 * True when recurring item is being created by pasting. When this is true,
	 * {@link #onOk} will request to create new recurring message in calendar.
	 * default is false.
	 * @property
	 * @type Boolean
	 * @private
	 */
	pasteItem: false,

	/**
	 * @constructor
	 * @param config Configuration structure
	 */
	constructor: function(config)
	{
		// Add in some standard configuration data.
		config = config || {};

		config = Ext.applyIf(config, {
			// Override from Ext.Component
			xtype: 'zarafa.recurrencecontentpanel',
			cls: 'k-recurrence-dialog',
			// Override from Ext.Component
			layout: 'fit',
			title: _('Appointment Recurrence'),
			recordComponentPluginConfig: Ext.applyIf(config.recordComponentPluginConfig || {}, {
				allowWrite: true
			}),
			border: false,
			defaults: {
				bodyStyle: 'padding-top: 5px; padding-left: 6px; padding-right: 5px; background-color: inherit;',
				border: false
			},
			autoSave: false,
			width: 720,
			height: 500,
			items: [{
				xtype: 'zarafa.recurrencepanel',
				ref: 'recurrencePanel',
				buttons: [{
					text: config.pasteItem ? _('Create') : _('Ok'),
					handler: this.onOk,
					scope: this,
					ref: '../../okBtn'
				},{
					text: _('Cancel'),
					scope: this,
					handler: this.onCancel
				},{
					text: _('Remove Recurrence'),
					handler: this.onRemoveRecurrence,
					scope: this,
					ref: '../../removeReccurrenceBtn'
				}]
			}]
		});

		// Call parent constructor
		Zarafa.common.recurrence.dialogs.RecurrenceContentPanel.superclass.constructor.call(this, config);

		this.on('beforesetrecord', this.onBeforeSetRecord, this);
		this.on('saverecord', this.onSaveRecord, this);
	},

	/**
	 * When the record is about to be set on the {@link Zarafa.core.ui.RecordContentPanel RecordContentPanel}
	 * we must enable recurrence in the record. By default, opening the RecurrenceContentPanel implies
	 * enabled it.
	 *
	 * @param {Zarafa.core.ui.RecordContentPanel} contentpanel The content panel to which the record was set.
	 * @param {Zarafa.core.data.IPMRecord} record The record which must be used for initialization.
	 * @param {Zarafa.core.data.IPMRecord} oldrecord The oldrecord which was previously set
	 * @private
	 */
	onBeforeSetRecord: function(contentpanel, record, oldrecord)
	{
		if (!record.get('recurring')) {
			var startDate = record.get('startdate') || new Date();
			var dueDate = record.get('duedate');
			var startOcc;
			var endOcc;

			if (!Ext.isDate(dueDate)) {
				if (record.get('alldayevent')) {
					dueDate = startDate.add(Date.DAY, 1);
				} else {
					dueDate = startDate.add(Date.MINUTE, 30);
				}
			}

			// Small workaround for allday appointments, in Brasil the DST occurs at 00:00,
			// which means for allday events, that we might think the appointment should start
			// at 01:00, but that might be because that is the start of the actual day. Hence
			// we need to correct the startOcc here for that.
			if (record.get('alldayevent')) {
				var offset = 0;

				startOcc = (startDate.getHours() * 60) + startDate.getMinutes();
				if (startDate.clearTime(true).getTime() === startDate.getTime()) {
					offset = startOcc;
					startOcc = 0;
				}

				endOcc = startOcc + offset + Date.diff(Date.MINUTE, dueDate, startDate);
			} else {
				startOcc = (startDate.getHours() * 60) + startDate.getMinutes();
				endOcc = startOcc + Date.diff(Date.MINUTE, dueDate, startDate);
			}

			record.beginEdit();
			record.set('recurring', true);
			record.set('recurrence_type', Zarafa.common.recurrence.data.RecurrenceType.WEEKLY);
			record.set('recurrence_subtype', Zarafa.common.recurrence.data.RecurrenceSubtype.WEEKLY.type);
			record.set('recurrence_regen', Zarafa.common.recurrence.data.RecurrenceSubtype.WEEKLY.regen);
			record.set('recurrence_term', Zarafa.common.recurrence.data.RecurrenceEnd.NEVER);
			record.set('recurrence_start', startDate.clearTime(true).fromUTC());
			record.set('recurrence_startocc', startOcc);
			record.set('recurrence_endocc', endOcc);
			record.set('recurrence_everyn', 1);
			record.set('recurrence_weekdays', Math.pow(2, startDate.getDay()));
			record.endEdit();

			// We have applied a new recurrence to the record
			this.editRecurrence = false;
		} else {
			// We are editing an existing recurrence on the record
			this.editRecurrence = true;
		}
	},

	/**
	 * Action handler when the user presses the "Ok" button. Overridden from the superclass,
	 * in order to display a warning message that changing the recurrence will discard all
	 * previously created exceptions.
	 * This will call {@link #saveRecord} with the {@link #autoSave} argument.
	 */
	onOk: function()
	{
		// When this was already an existing recurring series, then we must warn the user that all previously
		// created exceptions will be discarded. When this is a new series, then we can directly start
		// creating the new recurrence.
		if (this.record.phantom !== true && this.editRecurrence === true && this.record.hasRecurringExceptions()) {
			Ext.MessageBox.show({
				title: _('Reset recurring'),
				msg: _('This will reset every exception. Do you want to continue?'),
				cls: Ext.MessageBox.WARNING_CLS,
				buttons: Ext.MessageBox.YESNO,
				fn: function(button) {
					if (button === 'yes') {
						Zarafa.common.recurrence.dialogs.RecurrenceContentPanel.superclass.onOk.apply(this, arguments);
					}
				},
				scope: this
			});
		} else {
			// If we are adding recurrence for the first time, we have set properties in onBeforeSetRecord(). We must
			// make sure these properties will be put in the modified property of the record or they will not be
			// applied to the original record when applyData is called. (the setRecord method has emptied the modified
			// property)
			if ( !this.editRecurrence ){
				this.record.modified['recurring'] = this.record.data['recurring'];
				this.record.modified['recurrence_type'] = this.record.data['recurrence_type'];
				this.record.modified['recurrence_subtype'] = this.record.data['recurrence_subtype'];
				this.record.modified['recurrence_regen'] = this.record.data['recurrence_regen'];
				this.record.modified['recurrence_term'] = this.record.data['recurrence_term'];
				this.record.modified['recurrence_start'] = this.record.data['recurrence_start'];
				this.record.modified['recurrence_startocc'] = this.record.data['recurrence_startocc'];
				this.record.modified['recurrence_endocc'] = this.record.data['recurrence_endocc'];
				this.record.modified['recurrence_everyn'] = this.record.data['recurrence_everyn'];
				this.record.modified['recurrence_weekdays'] = this.record.data['recurrence_weekdays'];

				// remove basedate from idProperties and update the
				// opened and openedSeries
				this.record.removeIdProp('basedate');
				this.modalRecord.removeIdProp('basedate');
				this.modalRecord.opened = !this.record.isRecurring();
				this.modalRecord.openedSeries = !this.modalRecord.opened;
			}

			if(this.pasteItem) {
				this.store.add(this.modalRecord);
			}

			Zarafa.common.recurrence.dialogs.RecurrenceContentPanel.superclass.onOk.apply(this, arguments);
		}
	},

	/**
	 * When the record is about to be saved on the {@link Zarafa.core.ui.RecordContentPanel RecordContentPanel}
	 * we must update the recurrence description, based on all the properties which have been filled in
	 * from the {@link Zarafa.common.recurrence.dialogs.RecurrencePanel RecurrencePanel}.
	 * @param {Zarafa.core.ui.RecordContentPanel} contentpanel The contentpanel which invoked the event
	 * @param {Zarafa.core.data.IPMRecord} record The record which is being saved.
	 * @private
	 */
	onSaveRecord: function(contentpanel, record)
	{
		// Do not set recurring_reset to true while pasting recurring meeting/appointment
		// because we are creating recurring item and not resetting recurring item.
		if(!this.pasteItem) {
			// Recurrence has been changed, inform the server
			// to reset the recurrence.
			record.set('recurring_reset', true);
		}

		// This is not a recurring message, clear the recurring pattern
		if (!record.get('recurring')) {
			record.set('recurring_pattern', '');
			return;
		}

		// When applying a recurrence, update the timezone information
		// to ensure that the PHP can convert everything correctly.
		record.updateTimezoneInformation();

		// generate recurring pattern and save in the record
		record.set('recurring_pattern', record.generateRecurringPattern());
	},

	/**
	 * Update the {@link Ext.Panel Panel} with the given {@link Zarafa.core.data.IPMRecord IPMRecord}
	 * @param {Zarafa.core.data.IPMRecord} record The record to update the panel with
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 */
	update: function(record, contentReset)
	{
		this.updateTitleFromRecord(record);

		// disable ok and remove recurrence buttons for sub messages
		var disabled = false;
		if(record && record.isSubMessage()) {
			disabled = true;
		}

		if(this.pasteItem) {
			this.removeReccurrenceBtn.setVisible(false);
		} else {
			this.removeReccurrenceBtn.setDisabled(disabled);
		}
		this.okBtn.setDisabled(disabled);
	},

	/**
	 * When record has been updated, title also has to be - for instance if we have the subject
	 * in the title and the subject changes
	 * Calls {@link #setTitle} this.setTitle in order to update
	 * @param {Zarafa.core.data.MAPIRecord} record The record that has been updated
	 */
	updateTitleFromRecord: function(record)
	{
		if (!Ext.isDefined(record)) {
			this.setTitle(this.initialConfig.title);
		} else if (record.isMessageClass('IPM.TaskRequest', true)) {
			this.setTitle(_('Task Recurrence'));
		} else {
			this.setTitle(_('Appointment Recurrence'));
		}
	},

	/**
	 * Override the onSetRecord function from {@link Zarafa.core.ui.RecordContentPanel RecordContentPanel}
	 * to set the title to the correct value depending on the Message Class.
	 * @param {Zarafa.core.ui.RecordContentPanel} contentpanel The content panel to which the record was set.
	 * @param {Zarafa.core.data.IPMRecord} record The record which must be used for initialization.
	 * @param {Zarafa.core.data.IPMRecord} oldrecord The oldrecord which was previously set
	 * @override
	 * @private
	 */
	onSetRecord: function(contentpanel, record, oldrecord)
	{
		Zarafa.common.recurrence.dialogs.RecurrenceContentPanel.superclass.onSetRecord.call(this, contentpanel, record, oldrecord);
		this.updateTitleFromRecord(record);
	},

	/**
	 * Event handler which is fired when the user pressed the "Remove Recurrence" button.
	 * This will delete any recurring information and closes the window.
	 * @private
	 */
	onRemoveRecurrence: function()
	{
		this.record.beginEdit();
		this.record.set('recurring', false);
		this.record.set('recurring_pattern', '');
		this.record.set('recurring_reset', true);
		this.record.set('recurrence_type', Zarafa.common.recurrence.data.RecurrenceType.NONE);
		this.record.endEdit();

		if (Ext.isDefined(this.modalRecord)) {
			this.modalRecord.applyData(this.record);
		}

		if (this.autoSave !== false) {
			var record = this.modalRecord || this.record;
			this.displayInfoMask();
			this.isSaving = true;
			record.save();
		} else {
			this.close();
		}
	}
});

Ext.reg('zarafa.recurrencecontentpanel', Zarafa.common.recurrence.dialogs.RecurrenceContentPanel);
/*
 * #dependsFile client/zarafa/core/mapi/ObjectType.js
 * #dependsFile client/zarafa/core/data/RecordCustomObjectType.js
 * #dependsFile client/zarafa/core/data/RecordFactory.js
 */
Ext.namespace('Zarafa.common.reminder.data');

/**
 * @class Zarafa.common.reminder.data.ReminderRecord
 *
 * Array of default fields for the {@link Zarafa.common.reminder.data.ReminderRecord} object.
 * These fields will always be added, regardless of the exact type of
 * {@link Zarafa.common.reminder.data.ReminderRecord record}.
 */
Zarafa.common.reminder.data.ReminderRecordFields = [
	{name: 'entryid'},
	{name: 'store_entryid'},
	{name: 'parent_entryid'},
	{name: 'message_flags'},
	{name: 'message_class'},
	{name: 'icon_index'},
	{name: 'subject'},
	{name: 'object_type', type: 'int', defaultValue: Zarafa.core.mapi.ObjectType.MAPI_MESSAGE},
	{name: 'location'},

	{name: 'reminder', type: 'boolean'},
	{name: 'reminder_minutes', type: 'int'},
	{name: 'reminder_time', type: 'date', dateFormat: 'timestamp', defaultValue: null},
	{name: 'flagdueby', type: 'date', dateFormat: 'timestamp', defaultValue: null},

	{name: 'task_duedate', type: 'date', dateFormat: 'timestamp', defaultValue: null},
	{name: 'task_startdate', type: 'date', dateFormat: 'timestamp', defaultValue: null},
	{name: 'task_resetreminder'},
	{name: 'task_recurring'},

	{name: 'appointment_recurring'},
	{name: 'appointment_startdate', type: 'date', dateFormat: 'timestamp', defaultValue: null},
	{name: 'appointment_enddate', type: 'date', dateFormat: 'timestamp', defaultValue: null},
	{name: 'appointment_startdate_recurring'},
	{name: 'appointment_enddate_recurring'}
];

/**
 * @class Zarafa.common.reminder.data.ReminderRecord
 * @extends Zarafa.core.data.MAPIRecord
 */
Zarafa.common.reminder.data.ReminderRecord = Ext.extend(Zarafa.core.data.MAPIRecord, {
	/**
	 * Create a new {@link Zarafa.core.data.IPMRecord IPMRecord}. This record can be used to get all the properties
	 * of appointment/task/mail associated with this {@link Zarafa.common.reminder.data.ReminderRecord ReminderRecord}.
	 * @return {Zarafa.core.data.IPMRecord} record which can be used to open appointment/task/mail dialogs.
	 */
	convertToIPMRecord: function()
	{
		var entryId = this.get('entryid');
		var messageClass = this.get('message_class');
		var props = {};

		if (Zarafa.core.MessageClass.isClass(messageClass, 'IPM.Appointment', true)) {
			props = {
				recurring: this.get('appointment_recurring'),
				startdate: this.get('appointment_startdate'),
				duedate: this.get('appointment_enddate'),
				startdate_recurring: this.get('appointment_startdate_recurring'),
				enddate_recurring: this.get('appointment_enddate_recurring')
			};
		} else if (Zarafa.core.MessageClass.isClass(messageClass, 'IPM.Task', true)) {
			props = {
				startdate: this.get('task_startdate'),
				duedate: this.get('task_duedate')
			};
		} else if (Zarafa.core.MessageClass.isClass(messageClass, 'IPM.Contact', true)) {
			Ext.MessageBox.alert(_('Info'), _('Not supported at the moment.'));
			return false;
		}

		// Copy all common properties
		Ext.applyIf(props, {
			entryid: entryId,
			parent_entryid: this.get('parent_entryid'),
			store_entryid: this.get('store_entryid'),
			object_type: this.get('object_type'),
			message_class: messageClass,

			message_flags: this.get('message_flags'),
			subject: this.get('subject'),
			location: this.get('location'),
			icon_index: this.get('icon_index'),

			reminder: this.get('reminder'),
			reminder_minutes: this.get('reminder_minutes'),
			reminder_time: this.get('reminder_time'),
			flagdueby: this.get('flagdueby')
		});

		var record = Zarafa.core.data.RecordFactory.createRecordObjectByRecordData(props, entryId);

		// For mail record it's require a store to perform mark as read while opening the record.
		if (Zarafa.core.MessageClass.isClass(messageClass, 'IPM.Note', true)) {
			Ext.copyTo(record, this, 'store');
		}

		return record;
	}
});

// Register a custom type to be used by the Record Factory
Zarafa.core.data.RecordCustomObjectType.addProperty('ZARAFA_REMINDER');

Zarafa.core.data.RecordFactory.setBaseClassToCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_REMINDER, Zarafa.common.reminder.data.ReminderRecord);
Zarafa.core.data.RecordFactory.addFieldToCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_REMINDER , Zarafa.common.reminder.data.ReminderRecordFields);
/*
 * #dependsFile client/zarafa/common/reminder/data/ReminderProxy.js
 */
Ext.namespace('Zarafa.common.reminder.data');

/**
 * @class Zarafa.common.reminder.data.ReminderStore
 * @extends Zarafa.core.data.MAPIStore
 * @xtype zarafa.reminderstore
 */
Zarafa.common.reminder.data.ReminderStore = Ext.extend(Zarafa.core.data.ListModuleStore, {
	/**
	 * @cfg {String} actionType type of action that should be used to send request to server,
	 * valid action types are defined in {@link Zarafa.core.Actions Actions}, default value is 'list'.
	 */
	actionType: Zarafa.core.Actions['list'],

	/**
	 * Checksum that will be generated from records that are received from server, this checksum will be
	 * used to check if server did sent us new data than the data shown in previous reminder dialog.
	 * @property
	 * @type Number
	 */
	lastChecksum: undefined,

	/**
	 * Flag is used to indicate that we should reload the data in {@link Zarafa.common.reminder.data.ReminderStore ReminderStore}
	 * after receiving response of snooze or dismiss actions, so we don't have to wait until new polling request is sent to get reminder data
	 * because it could happen that there are still some reminders pending for action and user has only selected some reminders
	 * for snoozing or dismissing.
	 * @property
	 * @type Boolean
	 */
	refreshStore: false,

	/**
	 * Flag is used to show reminder dialog or not. It is false when reminder button render as we have to show reminder dialog
	 * when webapp {@link #initializeReminderInterval polling} reminder store.
	 * @property
	 * @type Boolean
	 */
	showReminderDialog: true,

	/**
	 * The object with the configuration for the {@link Ext.TaskMgr TaskMgr} to start the polling for reminders.
	 * @property
	 * @type Object
	 */
	pollTask: null,

	/**
	 * The notifier plugin that is used to show 'error.json' notifications. If the {#link load loading} of reminders
	 * is failing because the backend produces an error, we will show the notification once using the originally
	 * registered notification plugin, and after that we will move the noficiations to the console to not bother the
	 * user with the same message notification over and over again. Once the backend returns valid responses again
	 * we will restore the original notifier plugin.
	 * @property
	 * @type Zarafa.core.ui.notifier.NotifyPlugin
	 */
	originalErrorNotifier: undefined,

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

		var recordType = Zarafa.core.data.RecordFactory.getRecordClassByCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_REMINDER);

		Ext.applyIf(config, {
			proxy: new Zarafa.common.reminder.data.ReminderProxy(),
			writer: new Zarafa.core.data.JsonWriter(),
			reader: new Zarafa.core.data.JsonReader({ dynamicRecord: false }, recordType),

			// @FIXME when batching the delete requests then there is some problem with response router and
			// it updates the grid view twice with the same data
			batch: false
		});

		Zarafa.common.reminder.data.ReminderStore.superclass.constructor.call(this, config);
	},

	/**
	 * Initialize events which Zarafa.common.reminder.data.ReminderStore ReminderStore} will listen to.
	 * @protected
	 */
	initEvents: function()
	{
		this.on('exception', this.onError, this);
		Zarafa.common.reminder.data.ReminderStore.superclass.initEvents.call(this);
	},

	/**
	 * <p>Loads the Record cache from the configured <tt>{@link #proxy}</tt> using the configured <tt>{@link #reader}</tt>.</p>
	 * <br> Function just adds 'list' as actionType in options and calls parent {@link Zarafa.core.data.IPFStore#load} method.
	 * <br> Check documentation of {@link Ext.data.Store#load} for more information.
	 */
	load: function(options)
	{
		if (!Ext.isObject(options)) {
			options = {};
		}

		if (!Ext.isObject(options.params)) {
			options.params = {};
		}

		// Load should always cancel the previous actions.
		if (!Ext.isDefined(options.cancelPreviousRequest)) {
			options.cancelPreviousRequest = true;
		}

		// ignore action type passed in options and instead use action type from config
		Ext.apply(options, {
			actionType: this.actionType
		});

		return Zarafa.common.reminder.data.ReminderStore.superclass.load.call(this, options);
	},

	/**
	 * Event handler will be called when an error/exception is occurred at server side,
	 * and error is returned. The notification method will be changed to avoid more errors.
	 * @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 received,
	 * '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
	 */
	onError: function(proxy, type, action, options, response, args)
	{
		// if any error occurs in getting reminders then we don't need to nag users by displaying error message
		// every time so we will show the error message once and then use the console notifier for subsequent
		// messages
		if(action === Ext.data.Api.actions.read && !this.originalErrorNotifier) {
			// Store the original notifier so we can restore it later
			this.originalErrorNotifier = container.getSettingsModel().get('zarafa/v1/main/notifier/error/value');
			container.getSettingsModel().set('zarafa/v1/main/notifier/error/value', 'console');

			// Restore the original notifier when everything works again
			this.on('load', function() {
				container.getSettingsModel().set('zarafa/v1/main/notifier/error/value', this.originalErrorNotifier);
				this.originalErrorNotifier = undefined;
			}, this, {single: true});
		}

		// clear the checksum, after getting error we should always show the reminder dialog even if no reminders are changed
		this.lastChecksum = undefined;
	},


	/**
	 * Initialize reminder requests to the server by {@link Ext.TaskMgr.start starting} a task.
	 */
	initializeReminderInterval: function()
	{
		var interval = container.getSettingsModel().get('zarafa/v1/main/reminder/polling_interval');
		this.pollTask = {
			run: this.sendReminderRequest,
			scope: this,
			interval: interval * 1000
		};

		Ext.TaskMgr.start(this.pollTask);
	},

	/**
	 * Function which is called periodically to send a reminder request to the server.
	 * @private
	 */
	sendReminderRequest: function()
	{
		if(!this.showReminderDialog) {
			this.showReminderDialog = true;
		}
		this.load();
	},

	/**
	 * Function will reset reminder request interval.
	 * @private
	 */
	resetReminderInterval: function()
	{
		this.clearReminderInterval();
		this.initializeReminderInterval();
	},

	/**
	 * Function will clear reminder request interval.
	 * @private
	 */
	clearReminderInterval: function()
	{
		Ext.TaskMgr.stop(this.pollTask);
	},

	/**
	 * Function is used as a callback for 'read' action, we have overridden it to
	 * support search also using same 'read' action instead of creating new action.
	 * this will check that if action type is list then will do normal processing and
	 * add {@link Zarafa.core.data.IPMRecords[] records} to {@link Zarafa.core.data.ListModuleStore store}
	 * and if action type is search then it will call {@link #updateSearchInfo} as a callback function.
	 * @param {Object} data data that is returned by the proxy after processing it. will contain
	 * {@link Zarafa.core.data.IPMRecords records}.
	 * @param {Object} options options that are paased through {@link #load} event.
	 * @param {Boolean} success success status of request.
	 */
	loadRecords: function(data, options, success)
	{
		Zarafa.common.reminder.data.ReminderStore.superclass.loadRecords.apply(this, arguments);

		if (success !== false) {
			var records = data.records;
			var newChecksum = Ext.util.JSON.encode(Ext.pluck(records, 'id'));

			// if checksum has been changed that means we should update the store with new data
			if(this.lastChecksum !== newChecksum) {
				var reminderEl = Ext.DomQuery.select('#mainmenu-button-reminder')[0];
				// Update reminder icon with counter.
				if (reminderEl) {
					this.updateReminderIcon(reminderEl, records.length);
				}
				if (this.showReminderDialog) {
					if ( records.length > 0 ) {
						// Emit a notification message. (used by the desktopnotifications plugin)
						var notificationMessage = String.format(ngettext('There is {0} reminder', 'There are {0} reminders', records.length), records.length);
						container.getNotifier().notify('info.reminder', _('Reminders'), notificationMessage);
					}

					Zarafa.common.Actions.openReminderContent(records);
					this.lastChecksum = newChecksum;
				}
			}
		}
	},

	/**
	 * Update the reminder icon when reminder store updated. It will hide the
	 * reminder button and set proper tooltip if reminder store does not contains
	 * any reminders.
	 *
	 * @param {Ext.Element} reminderEl The reminder button element.
	 * @param {Number} recordsLength The number of reminder in reminder store.
	 */
	updateReminderIcon: function(reminderEl, recordsLength)
	{
		var reminderBtn = Ext.get(reminderEl);
		reminderBtn.setStyle('backgroundImage', 'url(\'' + Zarafa.common.ui.IconClass.getReminderSvgIcon(recordsLength) + '\')');
		var reminder = container.getMainPanel().mainTabBar.reminder;
		var noReminder = recordsLength === 0;
		reminder.setDisabled(noReminder);
		reminder.setTooltip(noReminder ? _('There are no reminders') : '');
	},

	/**
	 * Function dismisses the reminder which are passed to the function.
	 * @param {Zarafa.common.reminder.data.ReminderRecord[]} reminderRecords
	 * reminder records which are going to be dismissed
	 * @param {Boolean} openAfterDismiss true if the record is to be opened after dismissal
	 */
	dismissReminders: function(reminderRecords, openAfterDismiss)
	{
		if(!Array.isArray(reminderRecords)){
			reminderRecords = [reminderRecords];
		}

		Ext.each(reminderRecords, function(reminderRecord) {
			reminderRecord.addMessageAction('action_type', 'dismiss');
			if (openAfterDismiss) {
				reminderRecord.addMessageAction('response_action', openAfterDismiss);
			}
		}, this);

		this.remove(reminderRecords);
		this.refreshStore = true;
		this.save(reminderRecords);
	},

	/**
	 * Function will snooze the reminder with the time passed to the function.
	 * @param {Zarafa.common.reminder.data.ReminderRecord[]} reminderRecords
	 * reminder records which are going to be snoozed.
	 * @param {Number} snoozeTime time in minutes, after which reminder will be pop-up again.
	 */
	snoozeReminders: function(reminderRecords, snoozeTime)
	{
		Ext.each(reminderRecords, function(reminderRecord) {
			reminderRecord.addMessageAction('action_type', 'snooze');
			reminderRecord.addMessageAction('snoozeTime', snoozeTime);
		}, this);

		this.remove(reminderRecords);
		this.refreshStore = true;
		this.save(reminderRecords);
	},

	/**
	 * Event handler which is raised when the {@link #write} event has been fired. It will convert the
	 * {@link Zarafa.common.reminder.ReminderRecord ReminderRecord} to an
	 * {@link Zarafa.core.data.IPMRecord IPMRecord} based on its message_class property and then pass that
	 * {@link Zarafa.core.data.IPMRecord IPMRecord} to {@link Zarafa.core.ui.ContentPanel ContentPanel} to open it.
	 * Also, it will send list request for reminders and will reset reminder polling interval.
	 *
	 * @param {Zarafa.core.data.MAPIStore} store The store which fired the event
	 * @param {String} action [Ext.data.Api.actions.create|update|destroy]
	 * @param {Object} result The 'data' picked-out out of the response for convenience
	 * @param {Ext.Direct.Transaction} res The transaction
	 * @param {Record/Record[]} records The records which were written to the server
	 * @private
	 */
	onWrite: function(store, action, result, res, records)
	{
		records = [].concat(records);

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

			if(record.getMessageAction("action_type") == "dismiss" || record.getMessageAction("action_type") == "snooze") {
				if(this.refreshStore) {
					// We convert the reminder record to IPMRecord and then open it
					if (record.getMessageAction('response_action') === true) {
						record = record.convertToIPMRecord();
						Zarafa.core.data.UIFactory.openViewRecord(record, {});
					}
					// send request to get updated data
					this.sendReminderRequest();

					this.refreshStore = false;
					break;
				}
			}
		}

		Zarafa.common.reminder.data.ReminderStore.superclass.onWrite.apply(this, arguments);
	}
});

Ext.reg('zarafa.reminderstore', Zarafa.common.reminder.data.ReminderStore);
/*
 * #dependsFile client/zarafa/core/data/RecordFactory.js
 * #dependsFile client/zarafa/core/data/RecordCustomObjectType.js
 */
Ext.namespace('Zarafa.common.restoreitem.data');

Zarafa.common.restoreitem.data.RestoreItemRecordFields = [
	{ name: 'entryid' },
	{ name: 'parent_entryid' },
	{ name: 'store_entryid' },
	{ name: 'icon_index' },
	{ name: 'display_name' },
	{ name: 'deleted_on', type: 'date', dateFormat: 'timestamp', defaultValue: null },
	{ name: 'content_count', type: 'int', defaultValue: 0 },
	{ name: 'sender_name' },
	{ name: 'subject' },
	{ name: 'message_size', type: 'int', defaultValue: 0 },
	{ name: 'message_class' },
	{ name: 'object_type' },
	{ name: 'message_delivery_time', type: 'date', dateFormat: 'timestamp', defaultValue: null },
	{ name: 'message_flags', type: 'int', defaultValue: Zarafa.core.mapi.MessageFlags.MSGFLAG_READ },
	{ name: 'hasattach', type: 'boolean', defaultValue: false }
];

/**
 * @class Zarafa.common.restoreitem.data.RestoreItemRecord
 * @extends Zarafa.core.data.MAPIRecord
 */
Zarafa.common.restoreitem.data.RestoreItemRecord = Ext.extend(Zarafa.core.data.MAPIRecord, {

	/**
	 * This method is actually required by {@link Zarafa.common.ui.IconClass IconClass}.
	 * Icon class checks that folder is a default folder or not, but we can't have default
	 * folders in restore item store, so its not necessary to check for default folder.
	 * This function will always return false to make sure that icon class will properly
	 * work with restore records.
	 */
	getDefaultFolderKey: function()
	{
		return undefined;
	},

	/**
	 * Returns a {@link Zarafa.hierarchy.data.MAPIStoreRecord MAPIStoreRecord}
	 * @return {Zarafa.hierarchy.data.MAPIStoreRecord} MAPIStoreRecord
	 */
	getMAPIStore: function()
	{
		var store = container.getHierarchyStore();
		return store.getById(this.get('store_entryid'));
	},

	/**
	 * This method is actually required by {@link Zarafa.common.ui.IconClass IconClass}
	 * but ipmsubtree can never be deleted nor restored, so we can safely returns false
	 * and make this record behave properly with the iconclass
	 * @return {Boolean} false by default.
	 */
	isIPMSubTree: function()
	{
		return false;
	},

	/**
	 * Convenience method for determining if the message has been read or not.
	 * @return {Boolean} True if this item has been read.
	 */
	isRead: function()
	{
		if (Ext.isDefined(this.get('message_flags'))) {
			return (this.get('message_flags') & Zarafa.core.mapi.MessageFlags.MSGFLAG_READ) > 0;
		} else {
			return true;
		}
	}
});

// Register a custom type to be used by the Record Factory
Zarafa.core.data.RecordCustomObjectType.addProperty('ZARAFA_RESTOREITEM');
Zarafa.core.data.RecordFactory.addFieldToCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_RESTOREITEM, Zarafa.common.restoreitem.data.RestoreItemRecordFields);
Zarafa.core.data.RecordFactory.setBaseClassToCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_RESTOREITEM, Zarafa.common.restoreitem.data.RestoreItemRecord);
Ext.namespace('Zarafa.common.restoreitem.dialogs');

/**
 * @class Zarafa.common.restoreitem.dialogs.RestoreItemContentPanel
 * @extends Zarafa.core.ui.RecordContentPanel
 * @xtype zarafa.restoreitemcontentpanel
 */
Zarafa.common.restoreitem.dialogs.RestoreItemContentPanel = Ext.extend(Zarafa.core.ui.RecordContentPanel, {

	/**
	 * @cfg {Zarafa.hierarchy.data.MAPIFolderRecord} folder default folder for the contextModel.
	 */
	folder: undefined,

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

		var folder = config.folder;
		var folderName = '';
		// if folder name is not defined, then use this title
		var title = _('Restore Softdeleted Items');
		if(folder) {
			folderName = folder.getFullyQualifiedDisplayName();

			if(!Ext.isEmpty(folderName)) {
				title = String.format(_('Restore From {0}'), folderName);
			}
		}

		Ext.applyIf(config, {
			xtype: 'zarafa.restoreitemcontentpanel',
			layout: 'fit',
			title: title,
			items: [{
				xtype: 'zarafa.restoreitempanel',
				folder: folder
			}]
		});

		Zarafa.common.restoreitem.dialogs.RestoreItemContentPanel.superclass.constructor.call(this,config);
	}
});

Ext.reg('zarafa.restoreitemcontentpanel', Zarafa.common.restoreitem.dialogs.RestoreItemContentPanel);
/*
 * #dependsFile client/zarafa/common/ui/grid/MapiMessageGridPanel.js
 */

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

/**
 * @class Zarafa.common.restoreitem.dialogs.RestoreItemPanel
 * @extends Zarafa.common.ui.grid.MapiMessageGrid
 * @xtype zarafa.restoreitempanel
 *
 * A gridPanel which provides UI for RestoreItem dialog to display the list of soft deleted items.
 * It also contains a {@link Zarafa.core.ui.ContentPanelToolbar ContentPanelToolbar}.
 */
Zarafa.common.restoreitem.dialogs.RestoreItemPanel = Ext.extend(Zarafa.common.ui.grid.MapiMessageGrid , {

	/**
	 * @cfg {Zarafa.hierarchy.data.MAPIFolderRecord} folder default folder for the contextModel.
	 */
	folder: undefined,

	/**
	 * The current type of soft deleted items list.
	 * Default value is 'message' as By default Grid is loaded with soft deleted message list.
	 * This String is changed by {@link #onRadioChecked}.
	 * @property
	 * @type String
	 * @private
	 */
	itemType: 'message',

	/**
	 * The {@link Zarafa.core.ContextModel} which is obtained by using {@link #folder}.
	 * @property
	 * @type Zarafa.core.ContextModel
	 */
	model: undefined,

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

		Ext.applyIf(config,{
			xtype: 'zarafa.restoreitempanel',
			border: false,
			store: new Zarafa.common.restoreitem.data.RestoreItemStore({
				folder: config.folder,
				autoLoad: {
					folder: config.folder
				}
			}),
			model: container.getContextByFolder(config.folder).getModel(),
			loadMask: true,
			supportLiveScroll: true,
			viewConfig: {
				forceFit: true,
				emptyText: '<div class="emptytext">' + _('There are no items to show in this list') + '</div>',
				getRowClass: this.viewConfigGetRowClass
			},
			tbar: {
				xtype: 'zarafa.contentpaneltoolbar',
				insertionPointBase: 'common.restoreitemcontentpanel',
				actionItems: this.createActionButtons()
			},
			colModel: this.initMessageColumnModel(),
			sm: new Ext.grid.RowSelectionModel()
		});

		Zarafa.common.restoreitem.dialogs.RestoreItemPanel.superclass.constructor.call(this,config);

	},

	/**
	 * Initialize event handlers
	 * @private
	 */
	initEvents: function()
	{
		Zarafa.common.restoreitem.dialogs.RestoreItemPanel.superclass.initEvents.call(this);

		this.mon(this.store, 'remove', this.disableToolbarButton, this);
		this.mon(this.getSelectionModel(), 'selectionchange', this.onSelectionChange, this);
		this.mon(this.getView(), 'livescrollstart', this.onLiveScrollStart, this);
		this.mon(this.getView(), 'beforesort', this.onBeforeSort, this);

	},

	/**
	 * Apply custom style and content for the row body. This will always
	 * apply the Read/UnRead style to the entire row.
	 *
	 * @param {Ext.data.Record} record The {@link Ext.data.Record Record} corresponding to the current row.
	 * @param {Number} rowIndex The row index
	 * @param {Object} rowParams A config object that is passed to the row template during
	 * rendering that allows customization of various aspects of a grid row.
	 * @param {Ext.data.Store} store The Ext.data.Store this grid is bound to
	 * @return {String} a CSS class name to add to the row
	 * @private
	 */
	viewConfigGetRowClass:function(record, rowIndex, rowParams, store)
	{
		return record.isRead() ? 'mail_read' : 'mail_unread';
	},

	/**
	 * Function will create buttons for top toolbar and it will be
	 * attached to zarafa.contentpaneltoolbar
	 * @return {Array} array consist of buttons for restore content panel
	 * @private
	 */
	createActionButtons: function()
	{
		var radioGroupName = Ext.id(null, 'restoreitem-');
		return [{
			xtype: 'button',
			overflowText: _('Restore'),
			text: _('Restore'),
			iconCls: 'icon_restore',
			handler: this.onRestore,
			disabled: true,
			ref: '../restoreButton',
			scope: this
		}, {
			xtype: 'button',
			overflowText: _('Restore All'),
			text: _('Restore All'),
			iconCls: 'icon_restore',
			handler: this.onRestoreAll,
			disabled: true,
			ref: '../restoreAllButton',
			scope: this
		},{
			xtype: 'button',
			overflowText: _('Permanent Delete'),
			text: _('Permanent Delete'),
			iconCls: 'icon_delete',
			handler: this.onPermanentDelete,
			disabled: true,
			ref: '../permanentDeleteButton',
			scope: this
		},{
			xtype: 'button',
			overflowText: _('Delete All'),
			text: _('Delete All'),
			iconCls: 'icon_delete',
			handler: this.onDeleteAll,
			disabled: true,
			ref: '../deleteAllButton',
			scope: this
		},{
			xtype: 'tbfill'
		},{
			xtype: 'radio',
			width: 90,
			boxLabel: _('Messages'),
			name: radioGroupName,
			inputValue: 'message',
			checked: true,
			listeners: {
				check: this.onRadioChecked,
				scope: this
			}
		},{
			xtype: 'radio',
			width: 70,
			boxLabel: _('Folders'),
			name: radioGroupName,
			inputValue: 'folder',
			listeners: {
				check: this.onRadioChecked,
				scope: this
			}
		}];
	},

	/**
	 * Event handler which is fired when a {@link Ext.form.Radio}
	 * has been checked. This will send request to load data in store.
	 * @param {Ext.form.Radio} radio The radio which was checked
	 * @param {Boolean} checked The new checked value
	 * @private
	 */
	onRadioChecked: function(radio, checked)
	{
		// load data about deleted message or folder in store based on the selected radio
		if (checked === true) {
			this.itemType = radio.inputValue;
			this.store.setItemType(this.itemType);
			this.store.load();
		}
	},

	/**
	 * Event handler when the "Permanent Delete" button has been pressed.
	 * This will obtain the selected record(s) to be hard-deleted by
	 * {@link Ext.grid.RowSelectionModel#getSelections getSelection} method.
	 * @param {Ext.Button} button button component
	 * @param {Ext.EventObject} eventObj event object for the click event.
	 * @private
	 */
	onPermanentDelete: function(button, eventObj)
	{
		var records = this.getSelectionModel().getSelections();
		this.doPermanentDelete(records);
	},

	/**
	 * Event handler when the "Delete All" button has been pressed.
	 * This will raise a Conformation which ask user that he/she want to go ahead and
	 * Permanently delete all the items or not.
	 * @param {Ext.Button} button button component
	 * @param {Ext.EventObject} eventObj event object for the click event.
	 * @private
	 */
	onDeleteAll: function(button, eventObj)
	{
		Ext.MessageBox.show({
			title: _('Delete all items'),
			msg: _('Are you sure you want to permanently delete all items?'),
			buttons: Ext.MessageBox.YESNO,
			fn: function(buttonClicked) {
				if (buttonClicked == 'yes') {
					this.doPermanentDelete([], true);
				}
			},
			scope: this
		});
	},

	/**
	 * Helper for deleting / restoring items.
	 * @param {Zarafa.common.restoreitem.data.RestoreItemRecord[]} records The records that should be hard-deleted
	 * @param {boolean} bulkAction true to either restore/delete all folder/items.
	 * @private
	 */
	doAction: function(records, action, bulkAction)
	{
		if (bulkAction) {
			this.store.load({
				folder: this.folder,
				params: {
					message_action: {
						action_type: action + "All"
					}
				}
			});
		} else {
			var saveRecords = records.map(function(record) {
				record.addMessageAction('action_type', action + this.itemType);
				this.store.remove(record);
				return record;
			}, this);
			this.store.save(saveRecords);
		}
	},

	/**
	 * This will Permanently delete the selected record(s) which is passed as argument.
	 * @param {Zarafa.common.restoreitem.data.RestoreItemRecord[]} records The records that should be hard-deleted
	 * @param {boolean} bulkAction true to permanently delete all items.
	 * @private
	 */
	doPermanentDelete: function(records, bulkAction)
	{
		this.doAction(records, 'delete', bulkAction);
	},

	/**
	 * Event handler when the "Restore" button has been pressed.
	 * This will obtain the selected record(s) to be restored by
	 * {@link Ext.grid.RowSelectionModel#getSelections getSelection} method.
	 * @param {Ext.Button} button button component
	 * @param {Ext.EventObject} eventObj event object for the click event.
	 * @private
	 */
	onRestore: function(button, eventObj)
	{
		var records = this.getSelectionModel().getSelections();
		this.doRestore(records);
	},

	/**
	 * Event handler when the "Restore All" button has been pressed.
	 * This will raise a Conformation which ask user that he/she want to go ahead and
	 * restore all the items or not.
	 * @param {Ext.Button} button button component
	 * @param {Ext.EventObject} eventObj event object for the click event.
	 * @private
	 */
	onRestoreAll: function(button, eventObj)
	{
		Ext.MessageBox.show({
			title: _('Restore all items'),
			msg: _('Are you sure you want to restore all items?'),
			buttons: Ext.MessageBox.YESNO,
			fn: function(buttonClicked) {
				if (buttonClicked == 'yes') {
					this.doRestore([], true);
				}

			},
			scope: this
		});
	},

	/**
	 * This will restore the selected record(s) which is passed as argument.
	 * @param {Zarafa.common.restoreitem.data.RestoreItemRecord[]} records The records that should be restored
	 * @param {boolean} bulkAction true to restore all items.
	 * @private
	 */
	doRestore: function(records, bulkAction)
	{
		this.doAction(records, 'restore', bulkAction);
	},

	/**
	 * Event handler which is called when the {@link #store} has been loaded.
	 * The Appropriate Column Model will be configured accordingly.
	 * @param {Ext.data.Store} store The store which was loaded
	 * @param {Ext.data.Record[]} records The records which were loaded from the store
	 * @param {Object} options The options which were used to load the data
	 * @private
	 */
	onStoreLoad: function( store, records, options )
	{
		if (Ext.isDefined(options.params.itemType) && options.params.itemType === 'folder') {
			this.reconfigure(store, this.initFolderColumnModel());
		} else {
			this.reconfigure(store, this.initMessageColumnModel());
		}

		this.getSelectionModel().selectFirstRow();

		this.disableToolbarButton(store);

		Zarafa.common.restoreitem.dialogs.RestoreItemPanel.superclass.onStoreLoad.apply(this, arguments);
	},

	/**
	 * Event handler which is called when the selection has been changed.
	 * The tool bar buttons for Permanent-Delete/Restore will be activate/deactivate accordingly.
	 * @param {Ext.grid.RowSelectionModel} selectionModel The selection model used by the grid.
	 * @private
	 */
	onSelectionChange: function(selectionModel)
	{
		var noSelection = (selectionModel.hasSelection() === false);

		this.restoreButton.setDisabled(noSelection);
		this.permanentDeleteButton.setDisabled(noSelection);
	},

	/**
	 * Event handler for the {@link Ext.data.Store#remove} event which is fired
	 * by the {@link Ext.data.Store}.
	 * This will update the value of {@link #statusMessage}.
	 * @param {Ext.data.Store} store The store which fired the event
	 * @param {Ext.data.Record} record The record which was updated
	 * @param {Number} index The index from where the records were deleted
	 * @private
	 */
	setStatusMessage: function(store, record, index)
	{
		this.statusMessage.setValue(String.format(ngettext('Total {0} recoverable item', 'Total {0} recoverable items', store.getCount()), store.getCount()));
	},

	/**
	 * Event handler for the {@link Ext.data.Store#remove} event which is fired
	 * by the {@link Ext.data.Store}.
	 * This will activate/deactivate tool bar buttons for DeleteAll/RestoreAll accordingly.
	 * @param {Ext.data.Store} store The store which fired the event
	 * @param {Ext.data.Record} record The record which was updated
	 * @param {Number} index The index from where the records were deleted
	 * @private
	 */
	disableToolbarButton: function(store, record, index)
	{
		var noItem = (store.getCount() === 0);
		this.deleteAllButton.setDisabled(noItem);
		this.restoreAllButton.setDisabled(noItem);
	},

	/**
	 * Creates and returns a column model object to display Folder related columns,
	 * used in {@link Ext.grid.GridPanel.colModel colModel} config
	 * @return {Ext.grid.ColumnModel} column model object
	 * @private
	 */
	initFolderColumnModel: function()
	{
		return new Ext.grid.ColumnModel({
			columns: [{
				dataIndex: 'icon_index',
				headerCls: 'zarafa-icon-column',
				header: '<p class="icon_index">&nbsp;<span class="title">Icon</span></p>',
				tooltip: _('Sort by: Icon'),
				width: 24,
				sortable: true,
				fixed: true,
				renderer: Zarafa.common.ui.grid.Renderers.icon
			},{
				dataIndex:'display_name',
				header: _('Name'),
				tooltip: _('Sort by: Name'),
				sortable: true,
				renderer: Ext.util.Format.htmlEncode
			},{
				dataIndex:'deleted_on',
				header: _('Deleted On'),
				tooltip: _('Sort by: Deleted On'),
				sortable: true,
				renderer: Zarafa.common.ui.grid.Renderers.datetime
			},{
				dataIndex: 'content_count',
				header: _('Item Count'),
				tooltip: _('Sort by: Item Count'),
				sortable: true,
				renderer: Ext.util.Format.htmlEncode
			}]
		});
	},

	/**
	 * Creates and returns a column model object to display Messages related columns,
	 * used in {@link Ext.grid.GridPanel.colModel colModel} config
	 * @return {Ext.grid.ColumnModel} column model object
	 * @private
	 */
	initMessageColumnModel: function()
	{
		return new Ext.grid.ColumnModel({
			columns: [{
				dataIndex: 'icon_index',
				headerCls: 'zarafa-icon-column',
				header: '<p class="icon_index">&nbsp;<span class="title">Icon</span></p>',
				tooltip: _('Sort by: Icon'),
				width: 24,
				sortable: true,
				fixed: true,
				renderer: Zarafa.common.ui.grid.Renderers.icon
			},{
				dataIndex: 'hasattach',
				headerCls: 'zarafa-icon-column',
				header: '<p class=\'icon_paperclip\'>&nbsp;</p>',
				tooltip: _('Sort by: Attachment'),
				width: 24,
				sortable: true,
				fixed: true,
				renderer: Zarafa.common.ui.grid.Renderers.attachment
			},{
				dataIndex:'sender_name',
				header: _('From'),
				tooltip: _('Sort by: From'),
				sortable: true,
				renderer: Ext.util.Format.htmlEncode
			},{
				dataIndex: 'subject',
				header: _('Subject'),
				tooltip: _('Sort by: Subject'),
				sortable: true,
				renderer: Ext.util.Format.htmlEncode
			},{
				dataIndex:'deleted_on',
				header: _('Deleted On'),
				tooltip: _('Sort by: Deleted On'),
				sortable: true,
				renderer: Zarafa.common.ui.grid.Renderers.datetime
			},{
				dataIndex: 'message_delivery_time',
				header: _('Received'),
				tooltip: _('Sort by: Received'),
				sortable: true,
				renderer: Zarafa.common.ui.grid.Renderers.datetime
			},{
				dataIndex:'message_size',
				header: _('Size'),
				tooltip: _('Sort by: Size'),
				sortable: true,
				renderer: Zarafa.common.ui.grid.Renderers.size
			}]
		});
	},

	/**
	 * Event handler which triggered when scrollbar gets scrolled more then 90% of it`s height.
	 * it will be used to start live scroll on {@link Zarafa.core.data.ListModuleStore ListModuleStore}.
	 * also it will register event on {@link Zarafa.core.data.ListModuleStore ListModuleStore} to get
	 * updated batch of mails status.
	 *
	 * @param {Number} cursor the cursor contains the last index of record in grid.
	 * @private
	 */
	onLiveScrollStart: function(cursor)
	{
		this.model.startLiveScroll(cursor, this.getStore());
	},

	/**
	 * Event handler which triggered when header of grid was clicked to apply the sorting
	 * on {@link Zarafa.common.restoreitem.dialogs.RestoreItemPanel RestoreItemPanel}. it will first stop the
	 * {@link Zarafa.core.ContextModel#stopLiveScroll live scroll} and then apply the sorting.
	 * @private
	 */
	onBeforeSort: function()
	{
		this.model.stopLiveScroll();
	}
});

Ext.reg('zarafa.restoreitempanel',Zarafa.common.restoreitem.dialogs.RestoreItemPanel);
/*
 * #dependsFile client/zarafa/common/rules/data/ActionFlags.js
 */
Ext.namespace('Zarafa.common.rules.data');

/**
 * @class Zarafa.common.rules.data.ActionProfiles
 * @singleton
 */
Zarafa.common.rules.data.ActionProfiles = [{
	name: _('Move the message to folder…'),
	value: Zarafa.common.rules.data.ActionFlags.MOVE
},{
	name: _('Copy the message to folder…'),
	value: Zarafa.common.rules.data.ActionFlags.COPY
},{
	name: _('Delete the message'),
	value: Zarafa.common.rules.data.ActionFlags.DELETE
},{
	name: _('Redirect the message to…'),
	value: Zarafa.common.rules.data.ActionFlags.REDIRECT
},{
	name: _('Forward the message to…'),
	value: Zarafa.common.rules.data.ActionFlags.FORWARD
},{
	name: _('Forward the message as attachment to…'),
	value: Zarafa.common.rules.data.ActionFlags.FORWARD_ATTACH
}, {
	name: _('Mark the message as read…'),
	value: Zarafa.common.rules.data.ActionFlags.MARK_AS_READ
}];
/*
 * #dependsFile client/zarafa/common/rules/data/ConditionFlags.js
 */
Ext.namespace('Zarafa.common.rules.data');

/**
 * @class Zarafa.common.rules.data.ConditionProfiles
 * @singleton
 */
Zarafa.common.rules.data.ConditionProfiles = [{
	name: _('is received from…'),
	value: Zarafa.common.rules.data.ConditionFlags.RECEIVED_FROM
},{
	name: _('includes these words in the sender\'s address…'),
	value: Zarafa.common.rules.data.ConditionFlags.SENDER_WORDS
},{
	name: _('includes these words in the recipient\'s address…'),
	value: Zarafa.common.rules.data.ConditionFlags.RECIPIENT_WORDS
},{
	name: _('includes these words in the subject…'),
	value: Zarafa.common.rules.data.ConditionFlags.SUBJECT_WORDS
},{
	name: _('includes these words in the body…'),
	value: Zarafa.common.rules.data.ConditionFlags.BODY_WORDS
},{
	name: _('includes these words in the transport headers…'),
	value: Zarafa.common.rules.data.ConditionFlags.TRANSPORTHEADER_WORDS
},{
	name: _('has importance…'),
	value: Zarafa.common.rules.data.ConditionFlags.IMPORTANCE
},{
	name: _('has an attachment'),
	value: Zarafa.common.rules.data.ConditionFlags.ATTACHMENT
},{
	name: _('is sent to…'),
	value: Zarafa.common.rules.data.ConditionFlags.SENT_TO
},{
	name: _('is sent only to me'),
	value: Zarafa.common.rules.data.ConditionFlags.SENT_TO_ME_ONLY
},{
	name: _('has my name in the Cc field'),
	value: Zarafa.common.rules.data.ConditionFlags.SENT_CC_ME
},{
	name: _('does not have my name in the To or Cc field'),
	value: Zarafa.common.rules.data.ConditionFlags.NAME_BCC
},{
	name: _('has my name in the To or Cc field'),
	value: Zarafa.common.rules.data.ConditionFlags.NAME_TO_CC
},{
	name: _('has sensitivity…'),
	value: Zarafa.common.rules.data.ConditionFlags.SENSITIVITY
},{
	name: _('is received after…'),
	value: Zarafa.common.rules.data.ConditionFlags.RECEIVED_AFTER
},{
	name: _('is received before…'),
	value: Zarafa.common.rules.data.ConditionFlags.RECEIVED_BEFORE
},{
	name: _('size is at least…'),
	value: Zarafa.common.rules.data.ConditionFlags.ATLEAST_SIZE
},{
	name: _('size is at most…'),
	value: Zarafa.common.rules.data.ConditionFlags.ATMOST_SIZE
},{
	name: _('has my name in the To field'),
	value: Zarafa.common.rules.data.ConditionFlags.SENT_TO_ME
},{
	name: _('is received (all messages)'),
	value: Zarafa.common.rules.data.ConditionFlags.NONE
}];
/*
 * #dependsFile client/zarafa/common/rules/data/ConditionFlags.js
 */
Ext.namespace('Zarafa.common.rules.data');

/**
 * @class Zarafa.common.rules.data.ExceptionProfiles
 * @singleton
 */
Zarafa.common.rules.data.ExceptionProfiles = [{
    name: _('is received from…'),
    value: Zarafa.common.rules.data.ConditionFlags.RECEIVED_FROM
},{
    name: _('includes these words in the sender\'s address…'),
    value: Zarafa.common.rules.data.ConditionFlags.SENDER_WORDS
},{
    name: _('includes these words in the recipient\'s address…'),
    value: Zarafa.common.rules.data.ConditionFlags.RECIPIENT_WORDS
},{
    name: _('includes these words in the subject…'),
    value: Zarafa.common.rules.data.ConditionFlags.SUBJECT_WORDS
},{
    name: _('includes these words in the body…'),
    value: Zarafa.common.rules.data.ConditionFlags.BODY_WORDS
},{
    name: _('includes these words in the transport headers…'),
    value: Zarafa.common.rules.data.ConditionFlags.TRANSPORTHEADER_WORDS
},{
    name: _('has importance…'),
    value: Zarafa.common.rules.data.ConditionFlags.IMPORTANCE
},{
    name: _('has an attachment'),
    value: Zarafa.common.rules.data.ConditionFlags.ATTACHMENT
},{
    name: _('is sent to…'),
    value: Zarafa.common.rules.data.ConditionFlags.SENT_TO
},{
    name: _('is sent only to me'),
    value: Zarafa.common.rules.data.ConditionFlags.SENT_TO_ME_ONLY
},{
    name: _('has my name in the Cc field'),
    value: Zarafa.common.rules.data.ConditionFlags.SENT_CC_ME
},{
    name: _('does not have my name in the To or Cc field'),
    value: Zarafa.common.rules.data.ConditionFlags.NAME_BCC
},{
    name: _('has my name in the To or Cc field'),
    value: Zarafa.common.rules.data.ConditionFlags.NAME_TO_CC
},{
    name: _('has sensitivity…'),
    value: Zarafa.common.rules.data.ConditionFlags.SENSITIVITY
},{
    name: _('is received after…'),
    value: Zarafa.common.rules.data.ConditionFlags.RECEIVED_AFTER
},{
    name: _('is received before…'),
    value: Zarafa.common.rules.data.ConditionFlags.RECEIVED_BEFORE
},{
    name: _('size is at least…'),
    value: Zarafa.common.rules.data.ConditionFlags.ATLEAST_SIZE
},{
    name: _('size is at most…'),
    value: Zarafa.common.rules.data.ConditionFlags.ATMOST_SIZE
},{
    name: _('has my name in the To field'),
    value: Zarafa.common.rules.data.ConditionFlags.SENT_TO_ME
}];
/*
 * #dependsFile client/zarafa/common/rules/data/ConditionFlags.js
 */
Ext.namespace('Zarafa.common.rules.data');

/**
 * @class Zarafa.common.rules.data.RulesData
 *
 * Static methods for getting condition definitions and actions definitions.
 * @singleton
 */
Zarafa.common.rules.data.RulesData = {

	/**
	 * Function will return key-value pair of Object which contains key as {@link Zarafa.common.rules.data.ConditionFlags}
	 * and value as Function. These function will be used to generate rule condition restrictions.
	 * This function will be called by {@link Zarafa.common.data.ConditionRuleFactory#constructor}
	 * to register condition definitions.
	 *
	 * @return {Object} Object of Condition Definitions.
	 */
	getConditionRestriction: function ()
	{
		var restrictionFactory = Zarafa.core.data.RestrictionFactory;
		var restrictions = Zarafa.core.mapi.Restrictions;
		var rulesData = this;

		var conditionFlags = Zarafa.common.rules.data.ConditionFlags;
		return {
			[conditionFlags.ATLEAST_SIZE]: function (options) {
				// options.value should be in bytes unit.
				return restrictionFactory.dataResProperty('PR_MESSAGE_SIZE', restrictions.RELOP_GE, options.value);
			},
			[conditionFlags.ATMOST_SIZE]: function (options) {
				// options.value should be in bytes unit.
				return restrictionFactory.dataResProperty('PR_MESSAGE_SIZE', restrictions.RELOP_LE, options.value);
			},
			[conditionFlags.SUBJECT_WORDS]: function (options) {
				return rulesData.getCommonWordConditions('PR_SUBJECT', options.store);
			},
			[conditionFlags.BODY_WORDS]: function (options) {
				return rulesData.getCommonWordConditions('PR_BODY', options.store);
			},
			[conditionFlags.TRANSPORTHEADER_WORDS]: function (options) {
				return rulesData.getCommonWordConditions('PR_TRANSPORT_MESSAGE_HEADERS', options.store);
			},
			[conditionFlags.SENDER_WORDS]: function (options) {
				// requires store in which the words are stored
				if (!options.store) {
					return false;
				}

				var conditions = [];
				options.store.each(function (word) {
					var value = Zarafa.core.Util.stringToHex(word.get('words'));
					conditions.push(restrictionFactory.dataResContent('PR_SENDER_SEARCH_KEY', restrictions.FL_SUBSTRING, value));
				}, this);

				return rulesData.createOrRestriction(conditions);
			},
			[conditionFlags.RECIPIENT_WORDS]: function (options) {
				// requires store in which the words are stored
				if (!options.store) {
					return false;
				}

				var conditions = [];
				options.store.each(function (word) {
					conditions.push(
						restrictionFactory.createResSubRestriction(
							'PR_MESSAGE_RECIPIENTS',
							restrictionFactory.dataResContent(
								'PR_SMTP_ADDRESS',
								restrictions.FL_SUBSTRING | restrictions.FL_IGNORECASE,
								word.get('words')
							)
						)
					);
				}, this);

				return rulesData.createOrRestriction(conditions);
			},
			[conditionFlags.ATTACHMENT]: function () {
				return restrictionFactory.dataResBitmask('PR_MESSAGE_FLAGS',
					restrictions.BMR_NEZ,
					Zarafa.core.mapi.MessageFlags.MSGFLAG_HASATTACH);
			},
			[conditionFlags.IMPORTANCE]: function (options) {
				// options.value should be type of {@link Zarafa.core.mapi.Importance}
				if (!options || Zarafa.core.mapi.Importance.getDisplayName(options.value) === '') {
					return false;
				}

				return restrictionFactory.dataResProperty('PR_IMPORTANCE', restrictions.RELOP_EQ, options.value);
			},
			[conditionFlags.RECEIVED_AFTER]: function (options) {
				// options.value should be type of {@link Zarafa.core.mapi.Importance}
				return restrictionFactory.dataResProperty('PR_MESSAGE_DELIVERY_TIME', restrictions.RELOP_GT, options.value);
			},
			[conditionFlags.RECEIVED_BEFORE]: function (options) {
				// options.value should be time value
				return restrictionFactory.dataResProperty('PR_MESSAGE_DELIVERY_TIME', restrictions.RELOP_LT, options.value);
			},
			[conditionFlags.SENSITIVITY]: function (options) {
				// options.value should be time value
				if (!options || Zarafa.core.mapi.Sensitivity.getDisplayName(options.value) === '') {
					return false;
				}

				return restrictionFactory.dataResProperty('PR_SENSITIVITY', restrictions.RELOP_EQ, options.value);
			},
			[conditionFlags.SENT_TO]: function (options) {
				// options.store should be type of {Zarafa.core.data.IPMRecipientStore}
				if (!options || !options.store) {
					return false;
				}
				var conditions = [];
				options.store.each(function (recipient) {
					if (recipient.isResolved()) {
						conditions.push(restrictionFactory.createResSubRestriction('PR_MESSAGE_RECIPIENTS',
							restrictionFactory.dataResComment(
								restrictionFactory.dataResProperty('PR_SEARCH_KEY', restrictions.RELOP_EQ, recipient.get('search_key'), '0x00010102'),
								{
									'0x60000003': Zarafa.core.mapi.RecipientType.MAPI_TO,
									'0x00010102': recipient.get('search_key'),
									'0x0001001E': recipient.get('display_name') + ' <' + recipient.get('smtp_address') + '>',
									'PR_DISPLAY_TYPE': recipient.get('display_type')
								})
						));
					}
				}, this);

				return rulesData.createOrRestriction(conditions);
			},
			[conditionFlags.RECEIVED_FROM]: function (options) {
				// options.store should be type of {Zarafa.core.data.IPMRecipientStore}
				if (!options || !options.store) {
					return false;
				}
				var conditions = [];
				options.store.each(function (recipient) {
					if (recipient.isResolved()) {
						conditions.push(restrictionFactory.dataResComment(
							restrictionFactory.dataResProperty('PR_SENDER_SEARCH_KEY', restrictions.RELOP_EQ, recipient.get('search_key'), '0x00010102'),
							{
								'0x60000003': Zarafa.core.mapi.RecipientType.MAPI_TO,
								'0x00010102': recipient.get('search_key'),
								'0x0001001E': recipient.get('display_name') + ' <' + recipient.get('smtp_address') + '>',
								'PR_DISPLAY_TYPE': recipient.get('display_type')
							}));
					}
				}, this);

				return rulesData.createOrRestriction(conditions);
			},
			[conditionFlags.SENT_TO_ME]: function () {
				return restrictionFactory.dataResProperty('PR_MESSAGE_TO_ME',
					restrictions.RELOP_EQ,
					true,
					'0x0057000B');
			},
			[conditionFlags.SENT_TO_ME_ONLY]: function () {
				return restrictionFactory.createResAnd([
					// The PR_MESSAGE_TO_ME property should be set to 'true'
					restrictionFactory.dataResProperty('PR_MESSAGE_TO_ME', restrictions.RELOP_EQ, true),
					// The PR_DISPLAY_TO property should not contain the ';' character (this implies a single recipient).
					restrictionFactory.createResNot(
						restrictionFactory.dataResContent('PR_DISPLAY_TO', restrictions.FL_SUBSTRING, ';')
					),
					// The PR_DISPLAY_CC property should be empty
					restrictionFactory.dataResProperty('PR_DISPLAY_CC', restrictions.RELOP_EQ, '')
				]);
			},
			[conditionFlags.NAME_BCC]: function (options) {
				// options.value should be boolean according to gromox version.
				if (!Ext.isBoolean(options.value)) {
					return false;
				}

				return restrictionFactory.createResAnd([
					restrictionFactory.dataResProperty('PR_MESSAGE_RECIP_ME', restrictions.RELOP_EQ, options.value),
					restrictionFactory.dataResProperty('PR_MESSAGE_CC_ME', restrictions.RELOP_EQ, false),
					restrictionFactory.dataResProperty('PR_MESSAGE_TO_ME', restrictions.RELOP_EQ, false)
				]);
			},
			[conditionFlags.SENT_CC_ME]: function () {
				return restrictionFactory.createResAnd([
					restrictionFactory.dataResProperty('PR_MESSAGE_CC_ME', restrictions.RELOP_EQ, true),
					restrictionFactory.dataResProperty('PR_MESSAGE_RECIP_ME', restrictions.RELOP_EQ, true),
					restrictionFactory.dataResProperty('PR_MESSAGE_TO_ME', restrictions.RELOP_EQ, false)
				]);
			},
			[conditionFlags.NAME_TO_CC]: function () {
				return restrictionFactory.dataResProperty('PR_MESSAGE_RECIP_ME',
					restrictions.RELOP_EQ, true, '0x0059000B');
			},
			[conditionFlags.NONE]: function () {
				// An PR_MESSAGE_CLASS must always exists, therefore this rule is always true.
				return restrictionFactory.dataResExist('PR_MESSAGE_CLASS');
			},
			[conditionFlags.UNKNOWN]: false
		};
	},

	/**
	 * This is common function which will create and return OR restriction according to given conditions in parameter.
	 *
	 * @param {Array} conditions array for which we need to create OR restriction.
	 * @return {Array} restriction for conditions or false if conditions array is empty.
	 */
	createOrRestriction: function(conditions)
	{
		var restrictionFactory = Zarafa.core.data.RestrictionFactory;

		// If there was only 1 condition, we don't need to convert
		// it to a OR subrestriction. If we have more then 1 condition,
		// then we should create the OR restriction.
		if (Ext.isArray(conditions)) {
			if (conditions.length === 1) {
				return conditions[0];
			} else if (conditions.length > 1) {
				return restrictionFactory.createResOr(conditions);
			}
		}

		return false;
	},

	/**
	 * Function will return restriction according to given property in parameter.
	 * This is common function to generate restriction for condition types such as {@link Zarafa.common.rules.data.ConditionFlags.SUBJECT_WORDS},
	 * {@link Zarafa.common.rules.data.ConditionFlags.BODY_WORDS} and {@link Zarafa.common.rules.data.ConditionFlags.TRANSPORTHEADER_WORDS}.
	 *
	 * @param {String} property which required to generate restriction according to condition type.
	 * @param {Ext.data.Store} store The store in which the words are stored.
	 * @return {Array} restriction for condition or returns false if store is not given in parameter.
	 */
	getCommonWordConditions: function (property, store)
	{
		// requires store in which the words are stored
		if (!store) {
			return false;
		}

		var restrictionFactory = Zarafa.core.data.RestrictionFactory;
		var restrictions = Zarafa.core.mapi.Restrictions;
		var conditions = [];

		store.each(function (word) {
			conditions.push(restrictionFactory.dataResContent(property, restrictions.FL_SUBSTRING | restrictions.FL_IGNORECASE, word.get('words')));
		}, this);

		return this.createOrRestriction(conditions);
	},

	/**
	 * Function will return key-value pair of Object which contains key as {@link Zarafa.common.rules.data.actionFlags}
	 * and value as Function. These function will be used to generate rule action object.
	 * This function will be called by {@link Zarafa.common.data.ActionRulesFactory#constructor}
	 * to register action definitions.
	 *
	 * @return {Object} Object of Action Definitions.
	 */
	getAction: function ()
	{
		var actionFlags = Zarafa.common.rules.data.ActionFlags;
		var rulesData = this;

		return {
			[actionFlags.MOVE]: function (options) {
				return rulesData.getFolderSelectionAction(actionFlags.MOVE, options);
			},
			[actionFlags.COPY]: function (options) {
				return rulesData.getFolderSelectionAction(actionFlags.COPY, options);
			},
			[actionFlags.DELETE]: function (options) {
				return rulesData.getFolderSelectionAction(actionFlags.DELETE, options);
			},
			[actionFlags.MARK_AS_READ]: function () {
				var action = {};
				action.action = Zarafa.core.mapi.RuleActions.OP_MARK_AS_READ;
				action.flags = 0;
				action.flavor = 0;
				return action;
			},
			[actionFlags.REDIRECT]: function (options) {
				return rulesData.getUserSelectionAction(actionFlags.REDIRECT, options);
			},
			[actionFlags.FORWARD]: function (options) {
				return rulesData.getUserSelectionAction(actionFlags.FORWARD, options);
			},
			[actionFlags.FORWARD_ATTACH]: function (options) {
				return rulesData.getUserSelectionAction(actionFlags.FORWARD_ATTACH, options);
			},
			[actionFlags.UNKNOWN]: false
		};
	},

	/**
	 * Function will return restriction according to given actionType in parameter.
	 * This is common function to get action for action types such as {@link Zarafa.common.rules.data.actionFlags.MOVE},
	 * {@link Zarafa.common.rules.data.actionFlags.DELETE} and {@link Zarafa.common.rules.data.actionFlags.COPY}.
	 *
	 * @param {Zarafa.common.rules.data.actionFlags} actionType which required to get action according to given action type.
	 * @param {Object} options object that must contain value required to generate action according to action type.
	 * @return {Object} action for given action type or returns false if given actionType is wrong or required option is not given.
	 */
	getFolderSelectionAction: function (actionType, options)
	{
		var action = {};
		var actionFlags = Zarafa.common.rules.data.ActionFlags;
		var ruleActions = Zarafa.core.mapi.RuleActions;

		// Folder is required to set the action.
		// If method is called other than Rule component then only Folder is not required but for the Delete action,
		// Folder is necessary.
		if (options) {
			if (!options.folder) {
				return false;
			}
			action.folderentryid = options.folder.get('entryid');
			action.storeentryid = options.folder.get('store_entryid');
		} else {
			// wastebasket folder must be provided for delete action.
			if (actionType === actionFlags.DELETE) {
				return false;
			}
		}

		if (actionType === actionFlags.COPY) {
			action.action = ruleActions.OP_COPY;
		} else {
			action.action = ruleActions.OP_MOVE;
		}

		action.flags = 0;
		action.flavor = 0;
		return action;
	},

	/**
	 * Function will return restriction according to given actionType in parameter.
	 * This is common function to get action for action types such as {@link Zarafa.common.rules.data.actionFlags.FORWARD},
	 * {@link Zarafa.common.rules.data.actionFlags.FORWARD_ATTACH} and {@link Zarafa.common.rules.data.actionFlags.REDIRECT}.
	 *
	 * @param {Zarafa.common.rules.data.actionFlags} actionType which required to get action according to given action type.
	 * @param {Object} options options object that must contain value required to generate action according to action type.
	 * @return {Object} action for given action type or returns false if given actionType is wrong or required option is not given.
	 */
	getUserSelectionAction: function (actionType, options)
	{
		var action = {};
		var actionFlags = Zarafa.common.rules.data.ActionFlags;
		var ruleActions = Zarafa.core.mapi.RuleActions;
		var flavorFlags = Zarafa.core.mapi.FlavorFlags;

		// No recipients selected, this means
		// we can't create an action.
		if (!options || !options.store || options.store.getCount() === 0) {
			return false;
		}

		// Set the Address list in the action
		action.adrlist = [];

		options.store.each(function (recipient) {
			action.adrlist.push({
				PR_ENTRYID: recipient.get('entryid'),
				PR_OBJECT_TYPE: recipient.get('object_type'),
				PR_DISPLAY_NAME: recipient.get('display_name'),
				PR_DISPLAY_TYPE: recipient.get('display_type'),
				PR_EMAIL_ADDRESS: recipient.get('email_address') || recipient.get('smtp_address'),
				PR_SMTP_ADDRESS: recipient.get('smtp_address'),
				PR_ADDRTYPE: recipient.get('address_type'),
				PR_RECIPIENT_TYPE: recipient.get('recipient_type'),
				PR_SEARCH_KEY: recipient.get('search_key')
			});
		}, this);

		action.action = ruleActions.OP_FORWARD;
		action.flags = 0;
		action.flavor = 0;

		if (actionType === actionFlags.REDIRECT) {
			action.flavor = flavorFlags.FWD_PRESERVE_SENDER | flavorFlags.FWD_DO_NOT_MUNGE_MSG;
		} else if (actionType === actionFlags.FORWARD_ATTACH) {
			action.flavor = flavorFlags.FWD_AS_ATTACHMENT;
		}
		return action;
	}
};
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.common.rulesdata.RulesProxy
 * @extends Zarafa.core.data.IPMProxy
 *
 * A special proxy which handles rules, the rules are send slightly different to the
 * server then normal records, instead of creating 1 request per record being saved,
 * we will send all records in a single request so the server can really save
 * them as a batch.
 */
Zarafa.common.rules.data.RulesProxy = Ext.extend(Zarafa.core.data.IPMProxy, {
	/**
	 * This will create a {@link Zarafa.core.data.ProxyResponseHandler ProxyResponseHandler} object
	 * which will be used by the {@link Zarafa.core.data.ResponseRouter ResponseRouter} when the
	 * response for the given request has returned.
	 *
	 * @param {String} modulename The modulename which is being accessed with this request
	 * @param {Zarafa.core.Actions} serverAction The action to perform on the server.
	 * @param {Ext.data.Api.action} action name of the action to perform.
	 * @param {Ext.data.Record[]} records list of records to operate on.
	 * @param {Object} parameters object containing user parameters such as range (pagination) information, sorting information, etc.
	 * @param {Ext.data.DataReader} reader data reader. Converts raw JavaScript objects (in our case) to instances of {@link Ext.data.Record}
	 * @param {Function} callback call back function to call when the request has finished successfully.
	 * @param {Object} scope scope for the call back function.
	 * @param {Object} args arguments object. This will be passed to the call back function on successful read.
	 * @return {Object} An instance of the {@link Zarafa.core.data.ProxyResponseHandler ProxyResponseHandler}
	 * which should be used for this request.
	 * @private
	 */
	getResponseHandlerForRequest: function(modulename, serverAction, action, records, parameters, reader, callback, scope, args)
	{
		return new Zarafa.common.rules.data.RulesResponseHandler({
			proxy: this,
			action: action,
			reader: reader,
			sendRecords: records,
			options: args,
			callback: callback,
			scope: scope
		});
	},

	/**
	 * @param {Ext.data.Api.action} action name of the action to perform. Either 'create' or 'update'.
	 * @param {Ext.data.Record[]} records list of records to operate on.
	 * @param {Object} parameters object containing user parameters such as range (pagination) information, sorting information, etc.
	 * @param {Ext.data.DataReader} reader data reader. Converts raw JavaScript objects (in our case) to instances of {@link Ext.data.Record}
	 * @param {Function} callback call back function to call when the request has finished successfully.
	 * @param {Object} scope scope for the call back function.
	 * @param {Object} args arguments object. This will be passed to the call back function on successful read.
	 * @private
	 */
	createUpdateAction: function(action, records, parameters, reader, callback, scope, args)
	{
		// Force the listmodule action, as we will edit all rules
		// in complete batches.
		Ext.applyIf(args, {
			listRequest: true
		});

		// When no records are sent, add the store id.
		if (Ext.isEmpty(records)) {
			parameters = {store_entryid: parameters.jsonData.store_entryid};
		} else {
			// Unwrap the serialized data and pass it as parameters directly
			parameters = parameters.jsonData[reader.meta.root];
		}

		Zarafa.common.rules.data.RulesProxy.superclass.createUpdateAction.apply(this, arguments);
	},

	/**
	 * Performs a destroy action on one or more records.
	 * @param {Ext.data.Api.action} action name of the action to perform. Always 'destroy'.
	 * @param {Ext.data.Record[]} records list of records to operate on.
	 * @param {Object} parameters object containing user parameters such as range (pagination) information, sorting information, etc.
	 * @param {Ext.data.DataReader} reader data reader. Converts raw JavaScript objects (in our case) to instances of {@link Ext.data.Record}
	 * @param {Function} callback call back function to call when the request has finished successfully.
	 * @param {Object} scope scope for the call back function.
	 * @param {Object} args arguments object. This will be passed to the call back function on successful read.
	 * @private
	 */
	destroyAction: function(action, records, parameters, reader, callback, scope, args)
	{
		// Force the listmodule action, as we will edit all rules
		// in complete batches.
		Ext.applyIf(args, {
			listRequest: true
		});
		// Unwrap the serialized data and pass it as parameters directly
		parameters = parameters.jsonData[reader.meta.root];

		Zarafa.common.rules.data.RulesProxy.superclass.destroyAction.apply(this, arguments);
	},

	/**
	 * Performs a read action on one or more records.
	 * @param {Ext.data.Api.action} action name of the action to perform. Always 'open'.
	 * @param {Ext.data.Record[]} records list of records to operate on.
	 * @param {Object} parameters object containing user parameters such as range (pagination) information, sorting information, etc.
	 * @param {Ext.data.DataReader} reader data reader. Converts raw JavaScript objects (in our case) to instances of {@link Ext.data.Record}
	 * @param {Function} callback call back function to call when the request has finished successfully.
	 * @param {Object} scope scope for the call back function.
	 * @param {Object} args arguments object. This will be passed to the call back function on successful read.
	 * @private
	 */
	openAction: function(action, records, parameters, reader, callback, scope, args)
	{
		// Force the listmodule action, as we will edit all rules
		// in complete batches.
		Ext.applyIf(args, {
			listRequest: true
		});
		// Unwrap the serialized data and pass it as parameters directly
		parameters = parameters.jsonData[reader.meta.root];

		Zarafa.common.rules.data.RulesProxy.superclass.openAction.apply(this, arguments);
	}
});
/*
 * #dependsFile client/zarafa/core/data/RecordFactory.js
 * #dependsFile client/zarafa/core/data/RecordCustomObjectType.js
 * #dependsFile client/zarafa/core/mapi/RuleStates.js
 */
Ext.namespace('Zarafa.common.rules.data');

Zarafa.common.rules.data.RulesRecordFields = [
	{name: 'rule_id', type: 'number'},
	{name: 'rule_name', type: 'string'},
	/*
	 * identifies the client application that owns the rule. should always be RuleOrganizer
	 * that is also same for outlook, for delegate rule it will be Schedule+ EMS interface
	 */
	{name: 'rule_provider', type: 'string', defaultValue: 'RuleOrganizer'},
	// property is not used and should always be 0
	{name: 'rule_level', type: 'number', defaultValue: 0},

	{name: 'rule_sequence', type: 'number'},
	{name: 'rule_state', type: 'number', defaultValue: Zarafa.core.mapi.RuleStates.ST_ENABLED},
	{name: 'rule_condition', allowBlank: false},
	{name: 'rule_actions', allowBlank: false},
	{name: 'rule_msg_atleast_size_unit', type: 'string'},
	{name: 'rule_msg_atmost_size_unit', type: 'string'},
	{name: 'rule_exception_atleast_size_unit', type: 'string'},
	{name: 'rule_exception_atmost_size_unit', type: 'string'}
];

/**
 * @class Zarafa.common.rules.data.RulesRecord
 * @extends Zarafa.core.data.MAPIRecord
 *
 * Record will hold information about delegates.
 */
Zarafa.common.rules.data.RulesRecord = Ext.extend(Zarafa.core.data.MAPIRecord, {
	/**
	 * The base array of ID properties which is copied to the {@link #idProperties}
	 * when the record is being created.
	 * @property
	 * @type Array
	 * @private
	 */
	baseIdProperties: [ 'rule_id' ],

	/**
	 * Indicates that the 'rules_condition' property is valid, and is allowed
	 * to be saved to the server. This can be set by {@link #setConditionsValid}
	 * and will be used by {@link #isValid}.
	 * @property
	 * @type Boolean
	 */
	conditionsValid: true,

	/**
	 * Indicates that the 'rules_actions' property is valid, and is allowed
	 * to be saved to the server. This can be set by {@link #setActionsValid}
	 * and will be used by {@link #isValid}.
	 * @property
	 * @type Boolean
	 */
	actionsValid: true,

	/**
	 * Copy the {@link Zarafa.common.rules.data.RuleRecord RuleRecord} to a new instance
	 * @param {String} newId (optional) A new Record id, defaults to the id of the record being copied. See id.
	 * @return {Zarafa.common.rules.data.RuleRecord} The copy of the record.
	 */
	copy: function(newId)
	{
		var copy = Zarafa.core.data.RecordFactory.createRecordObjectByCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_RULE, this.data, newId || this.id);

		copy.idProperties = this.idProperties.clone();
		copy.phantom = this.phantom;

		return copy.applyData(this);
	},

	/**
	 * Applies all data from an {@link Zarafa.common.rules.data.RuleRecord RuleRecord}
	 * to this instance. This will update all data.
	 *
	 * @param {Zarafa.common.rules.data.RuleRecord} record The record to apply to this
	 * @return {Zarafa.common.rules.data.RuleRecord} this
	 */
	applyData: function(record)
	{
		this.beginEdit();

		Ext.apply(this.data, record.data);
		Ext.apply(this.modified, record.modified);

		this.dirty = record.dirty;

		this.endEdit();

		return this;
	},

	/**
	 * Compare this {@link Zarafa.common.rules.data.RulesRecord RulesRecord} instance
	 * with another one to see if they are same.
	 *
	 * @param {Zarafa.common.rules.data.RulesRecord} record The Record to compare with
	 * @return {Boolean} True if the records are same.
	 */
	equals: function(record)
	{
		// Simplest case, do we have the same object...
		if (this === record) {
			return true;
		}

		return this.get('rule_id') === record.get('rule_id');
	},

	/**
	 * @return {Boolean} always returns true because we don't need to get extra information
	 * about this record from server.
	 */
	isOpened: function()
	{
		return true;
	},

	/**
	 * By default returns <tt>false</tt> if any {@link Ext.data.Field field} within the
	 * record configured with <tt>{@link Ext.data.Field#allowBlank} = false</tt> returns
	 * <tt>true</tt> from an {@link Ext}.{@link Ext#isEmpty isempty} test.
	 * Additionally this checks for {@link #conditionsValid} and {@link #actionsValid}.
	 * @return {Boolean}
	 */
	isValid: function()
	{
		return this.conditionsValid && this.actionsValid && Zarafa.common.rules.data.RulesRecord.superclass.isValid.apply(this, arguments);
	},

	/**
	 * Indicate that the 'rules_condition' property is either valid or invalid,
	 * if this function is called with 'false' as argument, then {@link #isValid}
	 * will return false as well, preventing the rule from being saved.
	 * @param {Boolean} valid True if the 'rules_condition' property is valid
	 */
	setConditionsValid: function(valid)
	{
		this.conditionsValid = valid;
	},

	/**
	 * Indicate that the 'rules_actions' property is either valid or invalid,
	 * if this function is called with 'false' as argument, then {@link #isValid}
	 * will return false as well, preventing the rule from being saved.
	 * @param {Boolean} valid True if the 'rules_actions' property is valid
	 */
	setActionsValid: function(valid)
	{
		this.actionsValid = valid;
	},

	/**
	 * Rules Record's 'rule_condition' property contains Conditions and Exceptions.
	 * This function will parse the conditions from {@link Zarafa.core.data.IPMRecord record} record's
	 * 'rule_condition' property. And return Array consists of only conditions.
	 * @param {Object} container is {@link Zarafa.common.rules.dialogs.RulesConditionContainer RulesConditionContainer}
	 * or {@link Zarafa.common.rules.dialogs.RulesExceptionContainer RulesExceptionContainer}
	 * or {@link Zarafa.common.rules.dialogs.BaseContainer BaseContainer}.
	 * Which contain {@link #getConditionsArray} method to get conditions in array.
	 * @return {Array} returns array of conditions.
	 */
	getConditions: function(container)
	{
		var conditions = this.get('rule_condition');

		if (conditions)  {
			conditions = container.getConditionsArray(conditions);
		}
		// If condition is single and null or false.
		else if (conditions !== '') {
			return [conditions];
		}

		// If conditions array contains more than one conditions than only there is chance of exception
        // and conditions array needs to be filtered out.
		if (conditions && conditions.length > 1) {
			conditions = conditions.filter(function(condition) {
				// If condition is null or condition value is not NOT condition and Invalid Exception,
				// only then include the condition in conditions array.
				if (!condition || condition[Zarafa.core.mapi.Restrictions.VALUE] !== Zarafa.core.mapi.Restrictions.RES_NOT &&
					condition !== Zarafa.common.rules.data.ExceptionsConstants.INVALID_EXCEPTION){
					return true;
				}
			});
		}

		return conditions;
	},

	/**
	 * Rules Record's 'rule_condition' property contains Conditions and Exceptions.
	 * This function will parse the exceptions from  {@link Zarafa.core.data.IPMRecord record} record's
	 * 'rule_condition' property. And returns Array consists of only exceptions.
	 * @param {Object} container is {@link Zarafa.common.rules.dialogs.RulesConditionContainer RulesConditionContainer}
	 * or {@link Zarafa.common.rules.dialogs.RulesExceptionContainer RulesExceptionContainer}
	 * or {@link Zarafa.common.rules.dialogs.BaseContainer BaseContainer}.
	 * Which contains {@link #getConditionsArray} method to get conditions in array.
	 * @return {Array} returns array of exceptions.
	 */
	getExceptions: function(container)
	{
		var conditions = this.get('rule_condition');
		if (conditions)  {
			conditions = container.getConditionsArray(conditions);
		}

		if (conditions && conditions.length > 1) {
			conditions = conditions.filter(function(condition){
                // If condition value is  NOT condition or Invalid Exception,
                // only then include the condition in conditions array and treat that as exception.
				if ( condition && condition[Zarafa.core.mapi.Restrictions.VALUE] === Zarafa.core.mapi.Restrictions.RES_NOT ||
					condition === Zarafa.common.rules.data.ExceptionsConstants.INVALID_EXCEPTION){
					return true;
				}
			});

			if(!Ext.isEmpty(conditions)) {
				return conditions;
			}
		}

		// If there is no Exceptions selected by user.
		return null;
	}
});

// Register a custom type to be used by the Record Factory
Zarafa.core.data.RecordCustomObjectType.addProperty('ZARAFA_RULE');

Zarafa.core.data.RecordFactory.setBaseClassToCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_RULE, Zarafa.common.rules.data.RulesRecord);
Zarafa.core.data.RecordFactory.addFieldToCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_RULE, Zarafa.common.rules.data.RulesRecordFields);
/**
 * @class Zarafa.common.rules.dialogs.AtMostSizeLink
 * @extends Zarafa.common.rules.dialogs.AtLeatSizeLink
 * @xtype zarafa.atmostsizelink
 *
 * Condition component for the {@link Zarafa.common.rules.data.ConditionFlags#ATMOST_SIZE}
 * condition. This will allow the user to input a size and select a size unit. This will generate a proper
 * condition for it and set 'rule_msg_atmost_size_unit' record property.
 */
Zarafa.common.rules.dialogs.AtMostSizeLink = Ext.extend(Zarafa.common.rules.dialogs.AtLeatSizeLink, {
  /**
   * This property is to identify the current condition is AtMostSizeLink.
   * This will help to distinguish between {@link Zarafa.common.rules.dialogs.AtMostSizeLink AtMostSizeLink} class and
   * {@link Zarafa.common.rules.dialogs.AtLeatSizeLink AtLeatSizeLink} class.
   * This is used in {@link Zarafa.common.rules.dialogs.AtLeatSizeLink#getCondition#setSizeUnit} functions.
   * @property
   * @type Boolean
   */
  atMostSizeLink: true
});

Ext.reg('zarafa.atmostsizelink', Zarafa.common.rules.dialogs.AtMostSizeLink);Ext.namespace('Zarafa.common.rules.dialogs');

/**
 * @class Zarafa.common.rules.dialogs.ReceivedBeforeLink
 * @extends Zarafa.common.rules.dialogs.ReceivedAfterLink
 * @xtype zarafa.receivedbeforelink
 *
 * Condition component for the {@link Zarafa.common.rules.data.ConditionFlags#RECEIVED_BEFORE RECEIVED_BEFORE}
 * condition. This will allow the user to select a date and can generate a proper
 * condition for it.
 */
Zarafa.common.rules.dialogs.ReceivedBeforeLink = Ext.extend(Zarafa.common.rules.dialogs.ReceivedAfterLink, {
	/**
	 * Obtain the condition as configured by the user
	 * @return {Object} The condition
	 */
	getCondition: function()
	{
		if (this.isModified !== true) {
			return this.condition;
		}

		var value = this.receivedDate.getValue().getTime()/1000;
		var conditionFactory = container.getRulesFactoryByType(Zarafa.common.data.RulesFactoryType.CONDITION);
		var conditionDefinition = conditionFactory.getConditionById(this.conditionFlag);

		return conditionDefinition({value: value});
	}
});

Ext.reg('zarafa.receivedbeforelink', Zarafa.common.rules.dialogs.ReceivedBeforeLink);
Ext.namespace('Zarafa.common.rules.dialogs');

/**
 * @class Zarafa.common.rules.dialogs.RulesEditContentPanel
 * @extends Zarafa.core.ui.RecordContentPanel
 * @xtype zarafa.ruleseditcontentpanel
 *
 * {@link Zarafa.common.rules.dialogs.RulesEditContentPanel RulesEditContentPanel} will be used to edit the rules.
 */
Zarafa.common.rules.dialogs.RulesEditContentPanel = Ext.extend(Zarafa.core.ui.RecordContentPanel, {
	/**
	 * @constructor
	 * @param config Configuration structure
	 */
	constructor: function(config)
	{
		config = config || {};

		// Add in some standard configuration data.
		Ext.applyIf(config, {
			// Override from Ext.Component
			xtype: 'zarafa.ruleseditcontentpanel',
			// Override from Ext.Component
			layout: 'fit',
			modal: true,
			cls: 'k-ruleseditpanel',
			recordComponentPluginConfig: Ext.applyIf(config.recordComponentPluginConfig || {}, {
				allowWrite: true,
				useShadowStore: true
			}),
			autoSave: false,
			width: 600,
			height: 457,
			title: _('Inbox Rule'),
			items: [{
				xtype: 'zarafa.ruleseditpanel',
				storeEntryId: config.record.getStore().storeEntryId,
				buttons: [{
					text: _('Save'),
					handler: this.onOk,
					scope: this
				},{
					text: _('Cancel'),
					handler: this.onCancel,
					scope: this
				}]
			}]
		});

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

Ext.reg('zarafa.ruleseditcontentpanel', Zarafa.common.rules.dialogs.RulesEditContentPanel);
Ext.namespace('Zarafa.common.sendas.dialogs');

/**
 * @class Zarafa.common.sendas.dialogs.SendAsEditContentPanel
 * @extends Zarafa.common.recipientfield.ui.EditRecipientContentPanel
 * @xtype zarafa.sendaseditcontentpanel
 *
 * {@link Zarafa.common.sendas.dialogs.SendAsEditContentPanel SendAsEditContentPanel} will be used to edit sendas addresses.
 */
Zarafa.common.sendas.dialogs.SendAsEditContentPanel = Ext.extend(Zarafa.common.recipientfield.ui.EditRecipientContentPanel, {
	/**
	 * @cfg {Boolean} removeOnCancel Remove the {@link Zarafa.core.data.IPMRecipientRecord record} from store
	 * while user press "cancel" button.
	 */
	removeOnCancel: true,

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

		Ext.applyIf(config, {
			xtype: 'zarafa.sendaseditcontentpanel',
			title: _('Add/Edit sender')
		});

		Zarafa.common.sendas.dialogs.SendAsEditContentPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Event handler which is raised when the user clicks the "Ok" {@link Ext.Button button}
	 * @private
	 */
	onOk: function()
	{
		var editForm = this.formPanel.getForm();
		var record = this.record;

		if (!editForm.isValid()) {
			return;
		}

		editForm.updateRecord(record);

		if (record.dirty === false) {
			this.close();
			return;
		}

		if (record.store.isRecipientExists(record)) {
			record.reject();
			Ext.Msg.alert(_('Duplicate recipient'), _('Recipient already exists.'));
			return;
		}

		record.generateOneOffEntryId();
		this.close();
	},

	/**
	 * Function will be called when user clicks on close tool on the {@link Ext.Window}
	 * and should remove phantom record if needed.
	 * @protected
	 */
	closeWrap: function()
	{
		this.removePhantomRecord();
		Zarafa.common.sendas.dialogs.SendAsEditContentPanel.superclass.closeWrap.apply(this, arguments);
	},

	/**
	 * Event handler which is raised when the user clicks the "Cancel" {@link Ext.Button button}
	 * @private
	 */
	onCancel: function()
	{
		this.removePhantomRecord();
		Zarafa.common.sendas.dialogs.SendAsEditContentPanel.superclass.onCancel.call(this);
	},

	/**
	 * Function is used to remove {@link Zarafa.core.data.IPMRecipientRecord SendAsRecipient}
	 * from {Zarafa.core.data.IPMRecipientStore} when {@link #removeOnCancel}
	 * is true and user has closed the dialog without saving it.
	 * @private
	 */
	removePhantomRecord: function()
	{
		if (this.removeOnCancel === true && this.record.phantom) {
			this.record.store.remove(this.record);
		}
	}
});

Ext.reg('zarafa.sendaseditcontentpanel', Zarafa.common.sendas.dialogs.SendAsEditContentPanel);
/**
 * #dependsFile client/zarafa/common/ui/layout/SwitchBorderLayout.js
 * #dependsFile client/zarafa/common/ui/layout/SwitchBorderLayoutRegion.js
 */
Ext.namespace('Zarafa.common.ui.layout');

/**
 * @class Zarafa.common.ui.layout.SwitchBorderLayout.SwitchSplitRegion
 * @extends Ext.layout.BorderLayout.SplitRegion
 *
 * Extension to the {@link Ext.layout.BorderLayout.SplitRegion} for usage
 * in the {@link Zarafa.common.ui.layout.SwitchBorderLayout}. This defines
 * a resizable area in the layout
 */
Zarafa.common.ui.layout.SwitchBorderLayout.SwitchSplitRegion = Ext.extend(Ext.layout.BorderLayout.SplitRegion, {
	/**
	 * True if the region is currently hidden. This is controlled
	 * by {@link #setVisible}. If not defined, then it will depend on the visibility of the
	 * {@link #panel}.
	 * @property
	 * @type Boolean
	 */
	hidden: undefined,

	/**
	 * The old {@link Ext.Component#getWidth width} or {@link Ext.Component#getHeight height}
	 * or the {@link #panel} when it was being {@link #setVisible hidden}. This value represents
	 * the height when the {@link #position} is 'north' or 'south', it will represent the
	 * width otherwise.
	 * @property
	 * @type Number
	 */
	oldSize: undefined,

	/**
	 * @constructor
	 * @param {Zarafa.common.ui.layout.SwitchBorderLayout} layout The parent layout
	 * @param {Object} config The configuration object to apply
	 * @param {String} pos The position string ('north', 'west', 'south', 'east') for which
	 * region to object is created
	 */
	constructor: function(layout, config, pos)
	{
		Zarafa.common.ui.layout.SwitchBorderLayout.SwitchSplitRegion.superclass.constructor.call(this, layout, config, pos);

		// Small fix, the region is taken of config which might have been
		// used for the alternate region. So fixup the region with the position
		this.region = pos;

		// applyLayout is selected from the 'splitSettings' property, so we can't really
		// extend it. Lets just prefix the function with our addition.
		this.applyLayout = this.applyLayout.createInterceptor(this.preApplyLayout, this);
	},

	/**
	 * Enables the visibility of the region (not the {@link #panel}!).
	 * This will update the {@link #hidden} property. If the region is
	 * being hidden, then the {@link #oldSize} will be saved, when the
	 * region is shown again, the {@link #oldSize} will be restored to
	 * the {@link #panel}.
	 *
	 * @param {Boolean} visible False to mark the region as hidden
	 */
	setVisible: Zarafa.common.ui.layout.SwitchBorderLayout.SwitchRegion.prototype.setVisible,

	/**
	 * Check if the region is currently {@link #hidden visible}. If the {@link #hidden} property
	 * is undefined, it will check the visibility of the {@link #panel}.
	 * @return {Boolean} True when the region is visible
	 */
	isVisible: Zarafa.common.ui.layout.SwitchBorderLayout.SwitchRegion.prototype.isVisible,

	/**
	 * Obtain the desired size for the region, this will use {@link #restrictSize}
	 * to restrict the values to the allowed boundaries.
	 * @return {Object} size The size for the region
	 */
	getSize: function() {
		var size = Zarafa.common.ui.layout.SwitchBorderLayout.SwitchSplitRegion.superclass.getSize.call(this);
		return this.restrictSize(size);
	},

	/**
	 * Restrict the given size object (as obtained by {@link #getSize}) to ensure that
	 * the {@link Ext.Component#minHeight}/{@ink Ext.Component#minWidth} is enforced.
	 * @param {Object} size The desired size
	 * @return {Object} The restricted size
	 * @private
	 */
	restrictSize: Zarafa.common.ui.layout.SwitchBorderLayout.SwitchRegion.prototype.restrictSize,

	/**
	 * Render the components for this region (e.g. the {@link #splitEl split element}.
	 * @param {Ext.Element} ct The parent container in which this region is added
	 * @param {Ext.Component} p The component which is handled by this region
	 * @param {Boolean} fakeId True if a fake ID should be used for creating the
	 * {@link #splitEl split element}, normally the ID from the component ('p') will
	 * be used.
	 */
	render: function(ct, p, fakeId)
	{
		if (fakeId === true) {
			var oldId = p.id;
			p.id += '-switch';
			Zarafa.common.ui.layout.SwitchBorderLayout.SwitchSplitRegion.superclass.render.apply(this, arguments);
			p.id = oldId;
		} else {
			Zarafa.common.ui.layout.SwitchBorderLayout.SwitchSplitRegion.superclass.render.apply(this, arguments);
		}
	},

	/**
	 * {@Link Function#createInterceptor interceptor} for the {@link #applyLayout} function.
	 * This will {@link Ext.Element#setPositioning reposition} the {@link #splitEl split element}
	 * to ensure it will be visible by the user
	 * @param {Object} box The positioning information for the split element
	 * @private
	 */
	preApplyLayout: function(box)
	{
		this.splitEl.setPositioning({ left: box.x + 'px', top: box.y + box.height + 'px' });
	},

	/**
	 * Event handler which is fired when the {@link #split} element has been moved.
	 * This will recalculate the width of the {@link #panel} and fire a {@link Ext.Container#doLayout}.
	 * @param {Ext.SplitBar} split The splitbar which was moved
	 * @param {Number} newSize The new size which should be applied to the panel
	 * @private
	 */
	onSplitMove: function(split, newSize)
	{
		var s = this.panel.getSize();
		this.lastSplitSize = newSize;
		if(this.position == 'north' || this.position == 'south'){
			this.panel.setSize(s.width, newSize);
			this.state.height = newSize;
		} else {
			this.panel.setSize(newSize, s.height);
			this.state.width = newSize;
		}
		// Apply the doLayout on the container, this will
		// ensure that all subchildren will also be laid
		// out correctly again.
		this.layout.container.doLayout();
		return false;
	}
});
Ext.namespace('Zarafa.common.widget');

/**
 * @class Zarafa.common.widget.DatepickerWidget
 * @extends Zarafa.core.ui.widget.AbstractFolderWidget
 *
 * Widget that displays the appointments for today, from the default
 * calendar. It only displays appointments that occur on or after the
 * current time, so outdated information is never shown.
 *
 * Reload time is configurable per instance of the
 * widget (keys: 'reloadinterval', default 5 minutes). These values are in
 * saved in milliseconds but displayed in seconds. The reload
 * interval is how often the calendar is fully reloaded from the
 * server, to show records that were added to the folder
 * outside of grommunio Web.
 */
Zarafa.common.widget.DatepickerWidget = Ext.extend(Zarafa.core.ui.widget.AbstractFolderWidget, {

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

		var store = new Zarafa.calendar.AppointmentStore();

		Ext.applyIf(config, {
			autoScroll: false,
			autoHeight: true,
			layout: 'hbox',
			folderType: 'calendar',
			store: store,
			items: [{
				xtype: 'zarafa.datepicker',
				ref: 'datePicker',
				hideLabel: true,
				showWeekNumber: true,
				showToday: true,
				width: '100%',
				listeners: {
					scope: this
				},
				handler: this.datePickerHandler
			}]
		});


		Zarafa.common.widget.DatepickerWidget.superclass.constructor.call(this, config);

		// FIXME Workaround for KW-1812 (not rendering busy days bold when coming back to current month)
		// We use the polling interval setting multiplied by 1000, because the interval is in seconds.
		Ext.TaskMgr.start({
			run: this.updateFilter,
			interval: container.getSettingsModel().get('zarafa/v1/main/reminder/polling_interval') * 1000,
			scope: this
		});
	},

	/**
	 * FIXME React on clicks: switch to calendar-view + selected date
	 * Event handler which is triggered when user clicks on any date
	 * @param {DatePicker} DatePicker object
	 * @param {Date} Date that was clicker
	 * @private
	 */
	datePickerHandler: Ext.emptyFn,

	/**
	 * Update the DatePicker when the calendar is changed
	 * @private
	 */
	updateFilter: function ()
	{
		if (this.datePicker) {
			this.datePicker.show();
		}

		return Zarafa.common.widget.DatepickerWidget.superclass.updateFilter.apply(this, arguments);
	}
});

Zarafa.onReady(function () {
	container.registerWidget(new Zarafa.core.ui.widget.WidgetMetaData({
		name: 'datepicker',
		iconCls: 'icon_widget_calendar',
		displayName: _('Datepicker'),
		widgetConstructor: Zarafa.common.widget.DatepickerWidget
	}));
});
Ext.namespace('Zarafa.contact');

/**
 * @class Zarafa.contact.ContactContextModel
 * @extends Zarafa.core.ContextModel
 */
Zarafa.contact.ContactContextModel = Ext.extend(Zarafa.core.ContextModel, {
	/**
	 * The currently selected character, this is updated through
	 * {@link #setRestrictionCharacter} and when this field changes,
	 * the {@link #characterchange} event will be fired.
	 * When this context is {@link #stateful stateful}, this option will be
	 * saved in the settings.
	 * @property
	 * @type Mixed
	 */
	current_character: 'a',

	/**
	 * When searching, this property marks the {@link Zarafa.core.ContextModel#getCurrentDataMode datamode}
	 * which was used before {@link #onSearchStart searching started} the datamode was switched to
	 * {@link Zarafa.contact.data.DataModes#SEARCH}.
	 * @property
	 * @type Mixed
	 * @private
	 */
	oldDataMode: undefined,

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

		if(!Ext.isDefined(config.store)) {
			config.store = new Zarafa.contact.ContactStore();
		}

		Ext.applyIf(config, {
			current_data_mode: Zarafa.contact.data.DataModes.ALL
		});

		this.addEvents(
			/**
			 * @event characterchange
			 * Fires when the restriction character changed.
			 * @param {Zarafa.core.ContextModel} model this model.
			 * @param {String} character new character restriction.
			 * @param {String} oldCharacter previous character restriction.
			 */
			'characterchange'
		);

		Zarafa.contact.ContactContextModel.superclass.constructor.call(this, config);

		this.on({
			'searchstart': this.onSearchStart,
			'searchstop': this.onSearchStop,
			scope: this
		});
	},

	/**
	 * Called during the {@link Zarafa.core.Context#enable enabling} of the {@link Zarafa.core.Context context}.
	 * Secondly it will {@link #setFolders set the} {@link #folders folder} to this object to {@link #load} the {@link #store}.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder MAPI folder to show.
	 * @param {Boolean} suspended True to enable the ContextModel {@link #suspendLoading suspended}
	 */
	enable: function(folder, suspended)
	{
		// Enable the superclass with suspended enabled, so we can safely change the
		// restriction character
		Zarafa.contact.ContactContextModel.superclass.enable.call(this, folder, true);
		this.setRestrictionCharacter(this.getRestrictionCharacter(), true);

		// We enabled the superclass as suspended,
		// so time to resume it now.
		if (suspended !== true) {
			this.resumeLoading();
		}
	},

	/**
	 * Create a new {@link Zarafa.core.data.IPMRecord IPMRecord} which must be used within
	 * {@link Zarafa.contact.dialogs.ContactDialog ContactDialog}.
	 * @param {Zarafa.core.IPMFolder} folder (optional) The target folder in which the new record must be
	 * created. If this is not provided the default folder will be used.
	 * @param {Boolean} isDistlist True to create a distributionlist rather then a Contact
	 * @return {Zarafa.coore.data.IPMRecord} The new {@link Zarafa.core.data.IPMRecord IPMRecord}.
	 */
	createRecord: function(folder, isDistlist)
	{
		folder = folder || this.getDefaultFolder();

		var record = Zarafa.core.data.RecordFactory.createRecordObjectByMessageClass(isDistlist ? 'IPM.Distlist' : 'IPM.Contact', {
			store_entryid: folder.get('store_entryid'),
			parent_entryid: folder.get('entryid'),
			icon_index: isDistlist ? Zarafa.core.mapi.IconIndex['contact_distlist'] : Zarafa.core.mapi.IconIndex['contact_user']
		});

		return record;
	},

	/**
	 * Event handler which is executed right before the {@link #datamodechange}
	 * event is fired. This allows subclasses to initialize the {@link #store}.
	 * This will apply a restriction to the {@link #store} if needed.
	 *
	 * @param {Zarafa.contact.ContactContextModel} model The model which fired the event.
	 * @param {Zarafa.contact.data.DataModes} newMode The new selected DataMode.
	 * @param {Zarafa.contact.data.DataModes} oldMode The previously selected DataMode.
	 * @private
	 */
	onDataModeChange: function(model, newMode, oldMode)
	{
		Zarafa.contact.ContactContextModel.superclass.onDataModeChange.call(this, model, newMode, oldMode);

		if (newMode !== oldMode && oldMode === Zarafa.contact.data.DataModes.SEARCH) {
			this.stopSearch();
		}

		// also reload the store
		switch(newMode) {
			case Zarafa.contact.data.DataModes.CHARACTER_RESTRICT:
				this.load({
					params: {
						restriction: { 'search': this.createCharacterRestriction() }
					}
				});
				break;
			case Zarafa.contact.data.DataModes.ALL:
				this.load({
					params: {
						restriction: {}
					}
				});
				break;
			case Zarafa.contact.data.DataModes.SEARCH:
				break;
		}
	},

	/**
	 * Sets the current character for the {@link Zarafa.contact.data.DataModes#CHARACTER_RESTRICT} datamode.
	 * Fires the {@link #characterchange} event.
	 * @param {String} character The character by which the contacts should be restricted.
	 * @param {Boolean} init (optional) True when this function is called during initialization
	 * and it should force the change of the character.
	 */
	setRestrictionCharacter: function(character, init)
	{
		if (init === true || this.current_character !== character) {
			var oldCharacter = this.current_character;
			this.current_character = character;

			this.onCharacterChange(this, this.current_character, oldCharacter);

			// fire mode change event
			this.fireEvent('characterchange', this, this.current_character, oldCharacter);
		}
	},

	/**
	 * @return {String} The currently selected {@link #current_character character}.
	 */
	getRestrictionCharacter: function()
	{
		return this.current_character;
	},

	/**
	 * Event handler which is executed right before the {@link #characterchange}
	 * event is fired. This allows subclasses to initialize the {@link #store}.
	 *
	 * @param {Zarafa.core.ContextModel} model The model which fired the event.
	 * @param {Mixed} newCharacter The new selected character
	 * @param {Mixed} oldCharacter The previously selected character
	 * @private
	 */
	onCharacterChange: function(mode, newCharacter, oldCharacter)
	{
		if (this.current_data_mode === Zarafa.contact.data.DataModes.CHARACTER_RESTRICT) {
			var params = {
				start: 0
			};

			if (newCharacter) {
				params.restriction = {
					'search': this.createCharacterRestriction(newCharacter)
				};
			}

			this.load({
				params: params
			});
		}
	},

	/**
	 * Function will create restriction that will be applied to {@link Zarafa.contact.ContactStore ContactStore},
	 * and used when fetching data for {@link Zarafa.contact.ui.ContactCardView ContactCardView}.
	 * @param {String} character character that is currently selected.
	 * @return {Object} restriction that will be applied to store.
	 * @private
	 */
	createCharacterRestriction: function(character)
	{
		var Factory = Zarafa.core.data.RestrictionFactory;
		var Restrictions = Zarafa.core.mapi.Restrictions;
		var restriction;
		var fileAs = 'PT_STRING8:PSETID_Address:0x8005';

		if (Ext.isEmpty(character)) {
			character = this.current_character || '...';
		}

		switch(character) {
			case '...':
				// don't apply any restriction
				restriction = {};
				break;
			case '123':
				// find contacts starting with numeric characters '0' to '9'
				restriction = Factory.createResAnd([
					Factory.dataResProperty(fileAs, Restrictions.RELOP_GE, '0'),
					Factory.dataResProperty(fileAs, Restrictions.RELOP_LE, '9')
				]);
				break;
			case 'z':
				// find contacts ending after character 'z'
				restriction = Factory.dataResProperty(fileAs, Restrictions.RELOP_GE, 'z');
				break;
			default:
				var nextCharacter = String.fromCharCode(character.charCodeAt(0) + 1);
				restriction = Factory.createResAnd([
					Factory.dataResProperty(fileAs, Restrictions.RELOP_GE, character),
					Factory.dataResProperty(fileAs, Restrictions.RELOP_LE, nextCharacter)
				]);
		}

		return restriction;
	},

	/**
	 * Handler for 'charachterchange' event, which calls {@link #saveState} only
	 * when the current character is not the same as oldCharacter.
	 *
	 * @param {Zarafa.core.ContextModel} contextModel the contextModel which states needs to be saved.
	 * @param {String} character new character restriction.
	 * @param {String} oldCharacter new character restriction.
	 */
	saveCharacterChangeState: function(contextModel, character, oldCharacter)
	{
		if (character != oldCharacter) {
			this.saveState();
		}
	},

	/**
	 * Register the {@link #stateEvents state events} to the {@link #saveState} callback function.
	 * @private
	 */
	initStateEvents: function()
	{
		Zarafa.contact.ContactContextModel.superclass.initStateEvents.call(this);
		this.on('characterchange', this.saveCharacterChangeState, this, {delay: 100});
	},

	/**
	 * When {@link #stateful} the State object which should be saved into the
	 * {@link Ext.state.Manager}.
	 * @return {Object} The state object
	 * @protected
	 */
	getState: function()
	{
		var state = Zarafa.contact.ContactContextModel.superclass.getState.call(this) || {};
		return Ext.apply(state, { current_character: this.current_character });
	},

	/**
	 * Event handler for the {@link #searchstart searchstart} event.
	 * This will {@link #setDataMode change the datamode} to {@link Zarafa.contact.data.DataModes#SEARCH search mode}.
	 * The previously active {@link #getCurrentDataMode view} will be stored in the {@link #oldDataMode} and will
	 * be recovered when the {@link #onSearchStop search is stopped}.
	 * @param {Zarafa.core.ContextModel} model The model which fired the event
	 * @private
	 */
	onSearchStart: function(model)
	{
		if(this.getCurrentDataMode() != Zarafa.contact.data.DataModes.SEARCH){
			this.oldDataMode = this.getCurrentDataMode();
			this.setDataMode(Zarafa.contact.data.DataModes.SEARCH);
		}
	},

	/**
	 * Event handler for the {@link #searchstop searchstop} event.
	 * This will {@link #setDataMode change the datamode} to the {@link #oldDataMode previous datamode}.
	 * @param {Zarafa.core.ContextModel} model The model which fired the event
	 * @private
	 */
	onSearchStop: function(model)
	{
		if (this.getCurrentDataMode() === Zarafa.contact.data.DataModes.SEARCH) {
			this.setDataMode(this.oldDataMode);
		}
		delete this.oldDataMode;
	},

	/**
	 * Sets the selected folder list directly.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord[]} folders selected folders as an array of
	 * {@link Zarafa.hierarchy.data.MAPIFolderRecord MAPIFolder} objects.
	 */
	setFolders: function(folders)
	{
		// Suspend loading, so we can safely change the restriction character
		this.suspendLoading(true);
		Zarafa.contact.ContactContextModel.superclass.setFolders.call(this,folders);
		this.setRestrictionCharacter(this.getRestrictionCharacter(), true);

		// Resume loading after applying restriction
		this.resumeLoading();
	}
});
/*
 * #dependsFile client/zarafa/core/data/RecordFactory.js
 * #dependsFile client/zarafa/core/data/RecordCustomObjectType.js
 * #dependsFile client/zarafa/core/mapi/DistlistType.js
 */
Ext.namespace('Zarafa.contact');

/**
 * @class Zarafa.contact.DistlistMemberFields
 * Array of default fields for the {@link Zarafa.contact.DistlistMemberRecord} object.
 * These fields will always be added, regardless of the exact type of
 * {@link Zarafa.contact.DistlistMemberRecord record}.
 */
Zarafa.contact.DistlistMemberFields = [
	{name: 'entryid'},
	{name: 'display_name'},
	{name: 'address_type', type: 'string', defaultValue: 'SMTP'},
	{name: 'distlist_type', type: 'int', defaultValue: Zarafa.core.mapi.DistlistType.DL_EXTERNAL_MEMBER},
	{name: 'email_address'},
	{name: 'smtp_address'}
];

/**
 * @class Zarafa.contact.DistlistMemberRecord
 * @extends Ext.data.Record
 */
Zarafa.contact.DistlistMemberRecord = Ext.extend(Ext.data.Record, {
	idProperties: ['entryid'],

	/**
	 * Copy the {@link Zarafa.contact.DistlistMemberRecord Record} to a new instance
	 * @param {String} newId (optional) A new Record id, defaults to the id of the record being copied. See id.
	 * @return {Zarafa.contact.DistlistMemberRecord} The copy of the record.
	 */
	copy: function(newId)
	{
		var copy = Zarafa.core.data.RecordFactory.createRecordObjectByCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_DISTLIST_MEMBER, this.data, newId || this.id);

		copy.idProperties = this.idProperties.clone();
		copy.phantom = this.phantom;

		return copy.applyData(this);
	},

	/**
	 * Applies all data from an {@link Zarafa.contact.DistlistMemberRecord DistlistMemberRecord}
	 * to this instance. This will update all data.
	 *
	 * @param {Zarafa.contact.DistlistMemberRecord} record The record to apply to this
	 * @return {Zarafa.contact.DistlistMemberRecord} this
	 */
	applyData: function(record)
	{
		this.beginEdit();

		Ext.apply(this.data, record.data);
		Ext.apply(this.modified, record.modified);

		this.dirty = record.dirty;

		this.endEdit();

		return this;
	},

	/**
	 * Compare this {@link Zarafa.core.data.MAPIRecord record} instance with another one to see
	 * if they are same
	 *
	 * @param {Zarafa.core.data.MAPIRecord} record The Record to compare with
	 * @return {Boolean} True if the records are the same.
	 */
	equals: function(record)
	{
		return Zarafa.core.EntryId.compareEntryIds(this.get('entryid'), record.get('entryid'));
	},

	/**
	 * Convert this distlist member into a {@link Zarafa.addressbook.AddressBookRecord record}
	 * which can be used in the addressbook.
	 *
	 * @return {Zarafa.addressbook.AddressBookRecord} The addressbook record which
	 * is represented by this recipient.
	 */
	convertToAddressBookRecord: function()
	{
		var entryid = this.get('entryid');
		var distlistType = this.get('distlist_type');

		// use the distlist_type to determine which ObjectType of the
		// addressbook entry. This will allow us to open the correct dialog.
		var objectType = Zarafa.core.mapi.ObjectType.MAPI_MAILUSER;
		if(distlistType == Zarafa.core.mapi.DistlistType.DL_DIST_AB) {
			objectType = Zarafa.core.mapi.ObjectType.MAPI_DISTLIST;
		}

		return Zarafa.core.data.RecordFactory.createRecordObjectByObjectType(objectType, {
			entryid: entryid,
			object_type: objectType
		}, entryid);
	},

	/**
	 * Convert this distlist member into a {@link Zarafa.contact.ContactRecord}
	 * which can be used in the addressbook.
	 *
	 * @return {Zarafa.contact.ContactRecord} The addressbook record which
	 * is represented by this distlist member.
	 */
	convertToContactRecord: function()
	{
		var entryid = this.get('entryid');
		var distlistType = this.get('distlist_type');

		// use the distlist_type to determine message_class of contact record
		// addressbook entry. This will allow us to open the correct dialog.
		var messageClass = 'IPM.Contact';
		if(distlistType === Zarafa.core.mapi.DistlistType.DL_DIST) {
			messageClass = 'IPM.DistList';
		}

		// When selected from the Address Book, the Contact will contain the Contact Provider
		// GUID inside the Entryid. To correctly open the Contact, we have to unwrap this entryid
		// to get the normal entryid back.
		if (Zarafa.core.EntryId.hasContactProviderGUID(entryid)) {
			entryid = Zarafa.core.EntryId.unwrapContactProviderEntryId(entryid);
		}

		return Zarafa.core.data.RecordFactory.createRecordObjectByMessageClass(messageClass, {
			entryid: entryid,
			message_class: messageClass,
			object_type: Zarafa.core.mapi.ObjectType.MAPI_MESSAGE
		}, entryid);
	},

	// below functions are used by recipient field for resolving, but we don't it at the moment
	/**
	 * @return {Boolean} True if this member has been {@link #attemptedToResolve attempted to resolve},
	 * but turned out to be ambiguous. Currently it only returns false.
	 */
	isAmbiguous: function()
	{
		return false;
	},

	/**
	 * @return {Boolean} True if it was attempted to resolve this recipient. Currently only returns true.
	 */
	attemptedToResolve: function()
	{
		return true;
	},

	/**
	 * Checks if a member is resolved or not, currently only returns true.
	 * @return {Boolean} True if this member has been resolved.
	 */
	isResolved: function()
	{
		return true;
	}
});

// Register a custom type to be used by the Record Factory
Zarafa.core.data.RecordCustomObjectType.addProperty('ZARAFA_DISTLIST_MEMBER');

Zarafa.core.data.RecordFactory.addFieldToCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_DISTLIST_MEMBER, Zarafa.contact.DistlistMemberFields);
Zarafa.core.data.RecordFactory.setBaseClassToCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_DISTLIST_MEMBER, Zarafa.contact.DistlistMemberRecord);
/*
 * #dependsFile client/zarafa/core/data/RecordCustomObjectType.js
 */
Ext.namespace('Zarafa.contact.data');

/**
 * @class Zarafa.contact.data.JsonMemberReader
 * @extends Zarafa.core.data.JsonReader
 */
Zarafa.contact.data.JsonMemberReader = Ext.extend(Zarafa.core.data.JsonReader, {
	/**
	 * @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_DISTLIST_MEMBER,

	/**
	 * @constructor
	 * @param {Object} meta Metadata configuration options.
	 * @param {Object} recordType (optional) Optional Record type matches the type
	 * which must be read from response. If no type is given, it will use the
	 * record type for the {@link Zarafa.core.data.RecordCustomObjectType#ZARAFA_DISTLIST_MEMBER}.
	 */
	constructor: function(meta, recordType)
	{
		meta = Ext.applyIf(meta || {}, {
			dynamicRecord: false
		});

		// If no recordType is provided, force the type to be a Distlist Member
		if (!Ext.isDefined(recordType)) {
			recordType = Zarafa.core.data.RecordFactory.getRecordClassByCustomType(meta.customObjectType || this.customObjectType);
		}

		Zarafa.contact.data.JsonMemberReader.superclass.constructor.call(this, meta, recordType);
	}
});
Ext.namespace('Zarafa.contact.dialogs');

/**
 * @class Zarafa.contact.dialogs.ContactAddressContentPanel
 * @extends Zarafa.core.ui.RecordContentPanel
 *
 * This class will be used to create a detailed address content panel
 *
 * @xtype zarafa.contactaddresscontentpanel
 */
Zarafa.contact.dialogs.ContactAddressContentPanel = Ext.extend(Zarafa.core.ui.RecordContentPanel, {
	/**
	 * @cfg {Zarafa.contact.data.ContactDetailsParser} parser parser object that will be used to parse information
	 */
	parser: null,

	/**
	 * @cfg {String} property property that will be modified
	 */
	property: null,

	/**
	 * @cfg {Object} parsedData if data is already parsed then it can be passed here,
	 * so no need to prase same data again
	 */
	parsedData: null,

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

		config = Ext.applyIf(config, {
			layout: 'fit',
			border: false,
			xtype: 'zarafa.contactaddresscontentpanel',
			title: _('Check address'),
			recordComponentPluginConfig: Ext.applyIf(config.recordComponentPluginConfig || {}, {
				allowWrite: true
			}),
			autoSave: false,
			width: 400,
			height: 300,
			items: [{
				xtype: 'zarafa.contactaddresspanel',
				ref: 'mainPanel',
				parser: config.parser,
				parsedData: config.parsedData,
				property: config.property,
				buttons: [{
					text: _('Ok'),
					handler: this.onOk,
					scope: this
				},{
					text: _('Cancel'),
					scope: this,
					handler: this.onCancel
				}]
			}]
		});

		Zarafa.contact.dialogs.ContactAddressContentPanel.superclass.constructor.call(this, config);
	}
});

// register panel
Ext.reg('zarafa.contactaddresscontentpanel', Zarafa.contact.dialogs.ContactAddressContentPanel);
Ext.namespace('Zarafa.contact.dialogs');

/**
 * @class Zarafa.contact.dialogs.ContactContentPanel
 * @extends Zarafa.core.ui.RecordContentPanel
 *
 * this class will be used to create a contact panel
 * @xtype zarafa.contactcontentpanel
 */
Zarafa.contact.dialogs.ContactContentPanel = Ext.extend(Zarafa.core.ui.RecordContentPanel, {
	/**
	 * The parser object which will be used to parse and combine different parts of name, address, phone number.
	 * @property
	 * @type Zarafa.contact.data.ContactDetailsParser
	 */
	contactParser: undefined,

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

		Ext.applyIf(config, {
			xtype: 'zarafa.contactcontentpanel',
			layout: 'fit',
			title: _('Contact'),
			recordComponentPluginConfig: Ext.applyIf(config.recordComponentPluginConfig || {}, {
				allowWrite: true
			}),
			closeOnSave: true,
			confirmClose: true,
			items: this.createPanel()
		});

		Zarafa.contact.dialogs.ContactContentPanel.superclass.constructor.call(this, config);

		this.initContactDetailsParser();
	},

	/**
	 * Add the main Window Panel to the content panel.
	 * This will contain a {@link Zarafa.core.ui.ContentPanelToolbar ContentPanelToolbar}
	 * and a {@link Zarafa.contact.dialogs.ContactPanel ContactPanel}.
	 * @private
	 */
	createPanel: function()
	{
		// Create a new panel and add it.
		return [{
			xtype: 'zarafa.contactpanel',
			tbar: {
				xtype: 'zarafa.contacttoolbar'
			}
		}];
	},

	/**
	 * Initialize contact details parser, which will be used to parse name, phone and address info
	 * by default {@link Zarafa.contact.data.ContactDetailsParser ContactDetailsParser} is used but
	 * user can extend this object and use it's own custom parser using insertion points.
	 */
	initContactDetailsParser: function()
	{
		var parserType = container.getSharedComponent(Zarafa.core.data.SharedComponentType['contact.detailsparser']);
		if (parserType) {
			this.contactParser = new parserType();
		}
	},

	/**
	 * Update the {@link Ext.Panel Panel} with the given {@link Zarafa.core.data.IPMRecord IPMRecord}
	 * @param {Zarafa.core.data.IPMRecord} record The record to update the panel with
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 */
	update: function(record, contentReset)
	{
		if(contentReset){
			this.updateIconFromRecord(record);
		}
		this.updateTitleFromRecord(record);
	},

	/**
	 * Update this panel's icon class from the record that it contains
	 * First obtains the icon class from a mapping, then calls {@link #setIcon}
	 *
	 * @param {Zarafa.core.data.MAPIRecord} record The record bound to this component
	 * @private
	 */
	updateIconFromRecord: function(record)
	{
		//TODO: create a new icon mapping for tabs
		var iconCls = Zarafa.common.ui.IconClass.getIconClass(record);
		this.setIcon(iconCls);
	},

	/**
	 * When record has been updated, title also has to be - for instance if we have the subject
	 * in the title and the subject changes
	 * Calls {@link #setTitle} this.setTitle in order to update
	 * @param {Zarafa.core.data.MAPIRecord} record The record that has been updated
	 */
	updateTitleFromRecord: function(record)
	{
		var display_name = record.get('display_name');
		if(!Ext.isEmpty(display_name)){
			this.setTitle(display_name);
		} else {
			this.setTitle(this.initialConfig.title);
		}
	}
});

// register panel
Ext.reg('zarafa.contactcontentpanel', Zarafa.contact.dialogs.ContactContentPanel);
/*
 * #dependsFile client/zarafa/contact/data/ContactConfig.js
 */
Ext.namespace('Zarafa.contact.dialogs');

/**
 * @class Zarafa.contact.dialogs.ContactNameContentPanel
 * @extends Zarafa.core.ui.RecordContentPanel
 *
 * this class will be used to create a detailed contact name content panel
 *
 * @xtype zarafa.contactnamecontentpanel
 */
Zarafa.contact.dialogs.ContactNameContentPanel = Ext.extend(Zarafa.core.ui.RecordContentPanel, {
	/**
	 * @cfg {Zarafa.contact.data.ContactDetailsParser} parser parser object that will be used to parse information
	 */
	parser: null,

	/**
	 * @cfg {Zarafa.contact.dialogs.parsedNameRecord} parsedData if data is already parsed then it can be passed here,
	 * so no need to prase same data again
	 */
	parsedData: null,

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

		Ext.applyIf(config, {
			xtype: 'zarafa.contactnamecontentpanel',
			layout: 'fit',
			border: false,
			title: _('Check full name'),
			recordComponentPluginConfig: Ext.applyIf(config.recordComponentPluginConfig || {}, {
				allowWrite: true
			}),
			autoSave: false,
			width: 400,
			height: 250,
			items: [{
				xtype: 'zarafa.contactnamepanel',
				ref: 'mainPanel',
				parser: config.parser,
				parsedData: config.parsedData,
				buttons: [{
					text: _('Ok'),
					handler: this.onOk,
					scope: this
				},{
					text: _('Cancel'),
					scope: this,
					handler: this.onCancel
				}]
			}]
		});

		Zarafa.contact.dialogs.ContactNameContentPanel.superclass.constructor.call(this, config);
	}
});

// register panel
Ext.reg('zarafa.contactnamecontentpanel', Zarafa.contact.dialogs.ContactNameContentPanel);
Ext.namespace('Zarafa.contact.dialogs');

/**
 * @class Zarafa.contact.dialogs.ContactOptionsContentPanel
 * @extends Zarafa.core.ui.RecordContentPanel
 * @xtype zarafa.contactoptionscontentpanel
 */
Zarafa.contact.dialogs.ContactOptionsContentPanel = Ext.extend(Zarafa.core.ui.RecordContentPanel, {
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

		config = Ext.applyIf(config, {
			xtype: 'zarafa.contactoptionscontentpanel',
			layout: 'fit',
			title: _('Message Options'),
			recordComponentPluginConfig: Ext.applyIf(config.recordComponentPluginConfig || {}, {
				allowWrite: true
			}),
			autoSave: config.modal ? false : true,
			width: 360,
			height: 220,
			items: [{
				xtype: 'zarafa.contactoptionspanel',
				buttons: [{
					text: _('Ok'),
					handler: this.onOk,
					scope: this
				}]
			}]
		});

		Zarafa.contact.dialogs.ContactOptionsContentPanel.superclass.constructor.call(this, config);
	}
});

Ext.reg('zarafa.contactoptionscontentpanel', Zarafa.contact.dialogs.ContactOptionsContentPanel);
/*
 * #dependsFile client/zarafa/contact/data/ContactDetailsParser.js
 */
Ext.namespace('Zarafa.contact.dialogs');

/**
 * @class Zarafa.contact.dialogs.ContactPanel
 * @extends Ext.Panel
 * @xtype zarafa.contactpanel
 *
 * This class is used as wrapper class for all tabs, individual tab will have its own class,
 * extra tabs can be added using insertion point in this dialog.
 */
Zarafa.contact.dialogs.ContactPanel = Ext.extend(Ext.Panel, {
	// Insertion points for this class
	/**
	 * @insert context.contact.contactcontentpanel.tabs
	 * can be used to add extra tabs to contactcontentpanel by 3rd party plugins
	 * @param {Zarafa.contact.dialogs.ContactPanel} panel This contactpanel
	 */

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

		Ext.applyIf(config, {
			xtype: 'zarafa.contactpanel',
			cls: 'zarafa-contactcreatepanel',
			border: false,
			layout: 'fit',
			items: this.createTabPanel()
		});

		Zarafa.contact.dialogs.ContactPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Initialize {@link Ext.TabPanel tabpanel) that will contain all the fields and forms
	 * @return {Object} Configuration object for the form panel
	 * @private
	 */
	createTabPanel: function()
	{
		return [{
			xtype: 'tabpanel',
			activeTab: 0,
			border: false,
			defaults: {
				layout: 'column',
				autoHeight: false		// autoScroll won't work if height is auto
			},
			items: [
				{
					xtype: 'zarafa.contactgeneraltab'
				}, {
					xtype: 'zarafa.contactdetailtab'
				},
				// Add insertion point
				container.populateInsertionPoint('context.contact.contactcontentpanel.tabs', this)
			]
		}];
	}
});

Ext.reg('zarafa.contactpanel', Zarafa.contact.dialogs.ContactPanel);
Ext.namespace('Zarafa.contact.dialogs');

/**
 * @class Zarafa.contact.dialogs.ContactPhoneContentPanel
 * @extends Zarafa.core.ui.RecordContentPanel
 *
 * This class will be used to create a detailed phone content panel,
 * This panel doesn't support passing already parsed data as there isn't any
 * functionality needed that will automatically trigger this panel so data will
 * be parsed in this content panel only
 *
 * @xtype zarafa.contactphonecontentpanel
 */
Zarafa.contact.dialogs.ContactPhoneContentPanel = Ext.extend(Zarafa.core.ui.RecordContentPanel, {
	/**
	 * @cfg {Zarafa.contact.data.ContactDetailsParser} parser parser object that will be used to parse information
	 */
	parser: null,

	/**
	 * @cfg {String} property property that will be modified
	 */
	property: null,

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

		Ext.applyIf(config, {
			xtype: 'zarafa.contactphonecontentpanel',
			layout: 'fit',
			border: false,
			title: _('Check phone number'),
			recordComponentPluginConfig: Ext.applyIf(config.recordComponentPluginConfig || {}, {
				allowWrite: true
			}),
			autoSave: false,
			width: 400,
			height: 250,
			items: [{
				xtype: 'zarafa.contactphonepanel',
				ref: 'mainPanel',
				parser: config.parser,
				parsedData: config.parsedData,
				property: config.property,
				buttons: [{
					text: _('Ok'),
					handler: this.onOk,
					scope: this
				},{
					text: _('Cancel'),
					scope: this,
					handler: this.onCancel
				}]
			}]
		});

		Zarafa.contact.dialogs.ContactPhoneContentPanel.superclass.constructor.call(this, config);
	}
});

// register panel
Ext.reg('zarafa.contactphonecontentpanel', Zarafa.contact.dialogs.ContactPhoneContentPanel);
Ext.namespace('Zarafa.contact.dialogs');

/**
 * @class Zarafa.contact.dialogs.ContactToolbar
 * @extends Zarafa.core.ui.ContentPanelToolbar
 * @xtype zarafa.contacttoolbar
 */
Zarafa.contact.dialogs.ContactToolbar = Ext.extend(Zarafa.core.ui.ContentPanelToolbar, {
	// Insertion points for this class
	/**
	 * @insert context.contact.contactcontentpanel.toolbar.actions
	 * Insertion point for the Actions buttons in the Contact Toolbar
	 * @param {Zarafa.contact.dialogs.ContactToolbar} toolbar This toolbar
	 */
	/**
	 * @insert context.contact.contactcontentpanel.toolbar.options
	 * Insertion point for the Options buttons in the Contact Toolbar
	 * @param {Zarafa.contact.dialogs.ContactToolbar} toolbar This toolbar
	 */

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

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

		Ext.applyIf(config, {
			insertionPointBase: 'context.contact.contactcontentpanel',
			actionItems: this.createActionButtons(),
			optionItems: this.createOptionButtons()
		});

		Zarafa.contact.dialogs.ContactToolbar.superclass.constructor.call(this, config);
	},

	/**
	 * Create all buttons which should be added by default the the 'Action' Buttons
	 * This contains the buttons to save the message or delete it.
	 *
	 * @return {Array} The {@link Ext.Button Button} elements which should be
	 * added in the Options section of the {@link Ext.Toolbar Toolbar}.
	 * @private
	 */
	createActionButtons: function()
	{
		return [{
			xtype: 'button',
			ref: 'saveCloseBtn',
			text: _('Save')+' & '+_('Close'),
			overflowText: _('Save')+' & '+_('Close'),
			tooltip: _('Save')+' & '+_('Close') + Zarafa.core.KeyMapMgr.formatShortcutHint('Ctrl + S', true),
			cls: 'zarafa-action',
			iconCls: 'icon_save_white',
			handler: this.onSave,
			scope: this
		}, {
			xtype: 'button',
			ref: 'deleteBtn',
			overflowText: _('Delete'),
			tooltip:_('Delete'),
			iconCls: 'icon_delete',
			handler: this.onDelete,
			scope: this
		}, {
			xtype: 'zarafa.attachmentbutton',
			ref: 'addAttachment',
			plugins: [ 'zarafa.recordcomponentupdaterplugin' ],
			overflowText: _('Add Attachment'),
			tooltip: _('Add attachments to this contact'),
			iconCls: 'icon_paperclip',
			// Add a listener to the component added event to set use the correct update function when the toolbar overflows
			// (i.e. is too wide for the panel) and Ext moves the button to a menuitem.
			listeners: {
				added: this.onAttachmentButtonAdded,
				scope: this
			}
		},{
			xtype: 'button',
			overflowText: _('Print'),
			tooltip: _('Print') + Zarafa.core.KeyMapMgr.formatShortcutHint('Ctrl + P', true),
			iconCls: 'icon_print',
			handler: this.onPrint,
			scope: this
		}];
	},

	/**
	 * Event listener for the added event of the {@link Zarafa.common.attachment.ui.AttachmentButton attachmentButton}
	 * Adds the update function to the item when Ext converts the button to a menu item
	 * (which happens when the toolbar overflows, i.e. is too wide for the containing panel)
	 *
	 * @param {Ext.Component} item The item that was added. This can be a {@link Zarafa.common.attachment.ui.AttachmentButton}
	 * or a {@link Ext.menu.Item}
	 */
	onAttachmentButtonAdded: function(item)
	{
		if ( item.isXType('menuitem') ){
			// Set the update function to the update function of the original button
			// otherwise the Ext.Component.update function would be called by the recordcomponentupdaterplugin
			item.update = Zarafa.common.attachment.ui.AttachmentButton.prototype.update.createDelegate(this.addAttachment);
		}
	},

	/**
	 * Create all buttons which should be added by default the the 'Options' Buttons.
	 * This contains the buttons to change categories and private property.
	 *
	 * @return {Array} The {@link Ext.Button Button} elements which should be
	 * added in the Options section of the {@link Ext.Toolbar Toolbar}.
	 * @private
	 */
	createOptionButtons: function()
	{
		return [{
			xtype: 'button',
			overflowText: _('Categories'),
			tooltip: {
				text: _('Open the categories dialog')
			},
			iconCls: 'icon_categories',
			handler: this.onCategories,
			scope: this
		}, {
			xtype: 'button',
			overflowText: _('Private'),
			tooltip: {
				text: _('Mark this contact as private')
			},
			iconCls: 'icon_private',
			ref: 'setPrivate',
			enableToggle: true,
			toggleHandler: this.onPrivateGroupToggle,
			scope: this
		}];
	},

	/**
	 * Event handler which is called when the user pressed the 'Save' button.
	 * This will save the {@link Zarafa.core.data.IPMRecord IPMRecord}.
	 * @private
	 */
	onSave: function()
	{
		this.record.updateTimezoneInformation();
		this.dialog.saveRecord();
	},

	/**
	 * Event handler which is called when the user pressed the 'Delete' button.
	 * This will delete the {@link Zarafa.core.data.IPMRecord IPMRecord}.
	 * @private
	 */
	onDelete: function()
	{
		this.dialog.deleteRecord();
	},
	/**
	 * Event handler which is called when the user pressed the 'Print' button.
	 * This will print the {@link Zarafa.core.data.IPMRecord IPMRecord}.
	 * @private
	 */
	onPrint: function()
	{
		Zarafa.common.Actions.openPrintDialog(this.record);
	},

	/**
	 * Event handler which is called when the user pressed the 'Categories' button.
	 * This will open the {@link Zarafa.common.categories.dialogs.CategoriesContentPanel CategoriesContentPanel}.
	 * @private
	 */
	onCategories: function()
	{
		Zarafa.common.Actions.openCategoriesContent(this.record, {autoSave: false});
	},

	/**
	 * Event handler which is called when the PrivateGroup button
	 * has been toggled. If this is the case 'private' is updated.
	 *
	 * @param {Ext.Button} button The button which was toggled
	 * @private
	 */
	onPrivateGroupToggle: function(button)
	{
		this.record.set('private', button.pressed);
		if (button.pressed === true) {
			this.record.set('sensitivity', Zarafa.core.mapi.Sensitivity['PRIVATE']);
		} else {
			this.record.set('sensitivity', Zarafa.core.mapi.Sensitivity['NONE']);
		}
	},

	/**
	 * Updates the toolbar by updating the Toolbar buttons based on the settings
	 * from the {@link Zarafa.core.data.IPMRecord record}.
	 *
	 * @param {Zarafa.core.data.IPMRecord} 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(record.isSubMessage()) {
			// hide all buttons which is used to save changes
			this.deleteBtn.setVisible(false);
			this.setPrivate.setVisible(false);
			this.saveCloseBtn.setVisible(false);
			this.addAttachment.setVisible(false);
		} else {
			this.deleteBtn.setVisible(true);
			this.setPrivate.setVisible(true);
			this.saveCloseBtn.setVisible(true);
			this.addAttachment.setVisible(true);

			// Only enable Disabled button when it is not a phantom
			this.deleteBtn.setDisabled(record.phantom === true);

			if (contentReset === true || record.isModifiedSinceLastUpdate('private')) {
				this.setPrivate.toggle(record.get('private'), true);
			}
		}

		this.doLayout();
	}
});

Ext.reg('zarafa.contacttoolbar', Zarafa.contact.dialogs.ContactToolbar);
Ext.namespace('Zarafa.contact.dialogs');

/**
 * @class Zarafa.contact.dialogs.DistlistContentPanel
 * @extends Zarafa.core.ui.RecordContentPanel
 * @xtype zarafa.distlistcontentpanel
 *
 * this class will be used to create a distlist contentpanel
 */
Zarafa.contact.dialogs.DistlistContentPanel = Ext.extend(Zarafa.core.ui.RecordContentPanel, {
	/**
	 * @constructor
	 * @param {Object} config Configuration structure
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			layout: 'fit',
			xtype: 'zarafa.distlistcontentpanel',
			title: _('Distribution List'),
			recordComponentPluginConfig: Ext.applyIf(config.recordComponentPluginConfig || {}, {
				allowWrite: true
			}),
			confirmClose: true,
			closeOnSave: true,
			items: [ this.createPanel() ]
		});

		Zarafa.contact.dialogs.DistlistContentPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Add the main Window Panel to the dialog.
	 * This will contain a {@link Zarafa.core.ui.ContentPanelToolbar ContentPanelToolbar}
	 * and a {@link Zarafa.contact.dialogs.DistListPanel DistListPanel}.
	 * @return {Object} configuration object for the panel.
	 * @private
	 */
	createPanel: function()
	{
		// Create a new panel and add it.
		return {
			xtype: 'zarafa.distlistpanel',
			tbar: {
				xtype: 'zarafa.distlistcontentpaneltoolbar'
			}
		};
	},

	/**
	 * Update the {@link Ext.Panel Panel} with the given {@link Zarafa.core.data.IPMRecord IPMRecord}
	 * @param {Zarafa.core.data.IPMRecord} record The record to update the panel with
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 */
	update: function(record, contentReset)
	{
		if(contentReset){
			this.updateIconFromRecord(record);
		}
		this.updateTitleFromRecord(record);
	},

	/**
	 * Update this panel's icon class from the record that it contains
	 * First obtains the icon class from a mapping, then calls {@link #setIcon}
	 *
	 * @param {Zarafa.core.data.MAPIRecord} record The record bound to this component
	 * @private
	 */
	updateIconFromRecord: function(record)
	{
		//TODO: create a new icon mapping for tabs
		var iconCls = Zarafa.common.ui.IconClass.getIconClass(record);
		this.setIcon(iconCls);
	},

	/**
	 * When record has been updated, title also has to be - for instance if we have the subject
	 * in the title and the subject changes
	 * Calls {@link #setTitle} this.setTitle in order to update
	 * @param {Zarafa.core.data.MAPIRecord} record The record that has been updated
	 */
	updateTitleFromRecord: function(record)
	{
		var display_name = record.get('display_name');
		if(!Ext.isEmpty(display_name)) {
			this.setTitle(display_name);
		} else {
			this.setTitle(this.initialConfig.title);
		}
	}
});

// register panel
Ext.reg('zarafa.distlistcontentpanel', Zarafa.contact.dialogs.DistlistContentPanel);
Ext.namespace('Zarafa.contact.dialogs');

/**
 * @class Zarafa.contact.dialogs.DistlistContentPanelToolbar
 * @extends Zarafa.core.ui.ContentPanelToolbar
 * @xtype zarafa.distlistcontentpaneltoolbar
 */
Zarafa.contact.dialogs.DistlistContentPanelToolbar = Ext.extend(Zarafa.core.ui.ContentPanelToolbar, {
	// Insertion points for this class
	/**
	 * @insert context.contact.distlistcontentpanel.toolbar.actions
	 * Insertion point for the Actions buttons in the Contact Content Panel Toolbar
	 * @param {Zarafa.contact.dialogs.DistlistContentPanelToolbar} toolbar This toolbar
	 */
	/**
	 * @insert context.contact.distlistcontentpanel.toolbar.options
	 * Insertion point for the Options buttons in the Contact ContentPanel Toolbar
	 * @param {Zarafa.contact.dialogs.DistlistContentPanelToolbar} toolbar This toolbar
	 */

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

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

		Ext.applyIf(config, {
			insertionPointBase: 'context.contact.distlistcontentpanel',
			actionItems: this.createActionButtons(),
			optionItems: this.createOptionButtons()
		});

		Zarafa.contact.dialogs.DistlistContentPanelToolbar.superclass.constructor.call(this, config);
	},

	/**
	 * Create all buttons which should be added by default the the 'Action' buttons.
	 * This contains the buttons to save the message or delete it.
	 *
	 * @return {Array} The {@link Ext.Button Buttons} elements which should be
	 * added in the Action section of the {@link Ext.Toolbar Toolbar}.
	 * @private
	 */
	createActionButtons: function()
	{
		return [{
			xtype: 'button',
			text: _('Save')+' & '+_('Close'),
			overflowText: _('Save')+' & '+_('Close'),
			tooltip: _('Save')+' & '+_('Close') + Zarafa.core.KeyMapMgr.formatShortcutHint('Ctrl + S', true),
			cls: 'zarafa-action',
			iconCls: 'icon_save_white',
			handler: this.onSave,
			scope: this,
			ref: 'saveCloseBtn'
		}, {
			xtype: 'button',
			ref: 'deleteBtn',
			overflowText: _('Delete'),
			tooltip: _('Delete'),
			iconCls: 'icon_delete',
			handler: this.onDelete,
			scope: this
		}, {
			xtype: 'button',
			overflowText: _('Print'),
			tooltip: _('Print') + Zarafa.core.KeyMapMgr.formatShortcutHint('Ctrl + P', true),
			iconCls: 'icon_print',
			handler: this.onPrint,
			scope: this
		}];
	},

	/**
	 * Create all buttons which should be added by default the the 'Options' buttons.
	 * This contains the buttons to change categories and private property.
	 *
	 * @return {Array} The {@link Ext.Button Buttons} elements which should be
	 * added in the Options section of the {@link Ext.Toolbar Toolbar}.
	 * @private
	 */
	createOptionButtons: function()
	{
		return [{
			xtype: 'button',
			overflowText: _('Categories'),
			tooltip: {
				text: _('Open the categories dialog')
			},
			iconCls: 'icon_categories',
			handler: this.onCategories,
			scope: this
		}, {
			xtype: 'button',
			overflowText: _('Private'),
			tooltip: {
				text: _('Mark this Distribution list as private')
			},
			iconCls: 'icon_private',
			ref: 'setPrivate',
			toggleGroup: 'privateGroup',
			handler: this.onPrivateGroupToggle,
			scope: this
		}];
	},

	/**
	 * Event handler which is called when the user pressed the 'Save' button.
	 * This will save the {@link Zarafa.core.data.IPMRecord IPMRecord} and
	 * will close {@link Zarafa.contact.dialogs.DistlistContentPanel DistlistEditContentPanel}.
	 * @private
	 */
	onSave: function()
	{
		this.dialog.saveRecord();
	},

	/**
	 * Event handler which is called when the user pressed the 'Delete' button.
	 * This will delete the {@link Zarafa.core.data.IPMRecord IPMRecord}.
	 * @private
	 */
	onDelete: function()
	{
		this.dialog.deleteRecord();
	},
	/**
	 * Event handler which is called when the user pressed the 'Print' button.
	 * This will print the {@link Zarafa.core.data.IPMRecord IPMRecord}.
	 * @private
	 */
	onPrint: function()
	{
		Zarafa.common.Actions.openPrintDialog(this.record);
	},
	/**
	 * Event handler which is called when the user pressed the 'Categories' button.
	 * This will open the {@link Zarafa.common.categories.dialogs.CategoriesContentPanel CategoriesContentPanel}.
	 * @private
	 */
	onCategories: function()
	{
		Zarafa.common.Actions.openCategoriesContent(this.record, {autoSave: false});
	},

	/**
	 * Event handler which is called when the PrivateGroup button
	 * has been toggled. If this is the case 'private' is updated.
	 *
	 * @param {Ext.Button} button The button which was toggled
	 * @private
	 */
	onPrivateGroupToggle: function(button)
	{
		this.record.set('private', button.pressed);

		if (button.pressed) {
			this.record.set('sensitivity', Zarafa.core.mapi.Sensitivity['PRIVATE']);
		} else {
			this.record.set('sensitivity', Zarafa.core.mapi.Sensitivity['NONE']);
		}
	},

	/**
	 * Updates the toolbar by updating the Toolbar buttons based on the settings
	 * from the {@link Zarafa.core.data.IPMRecord record}.
	 *
	 * @param {Zarafa.core.data.IPMRecord} 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(record.isSubMessage()) {
			// hide all buttons which is used to save changes
			this.deleteBtn.setVisible(false);
			this.setPrivate.setVisible(false);
			this.saveCloseBtn.setVisible(false);
		} else {
			this.deleteBtn.setVisible(true);
			this.setPrivate.setVisible(true);
			this.saveCloseBtn.setVisible(true);

			// Only enable Disabled button when it is not a phantom
			this.deleteBtn.setDisabled(record.phantom === true);

			if (contentReset === true || record.isModified('private')) {
				this.setPrivate.toggle(record.get('private'), true);
			}
		}
	}
});

Ext.reg('zarafa.distlistcontentpaneltoolbar', Zarafa.contact.dialogs.DistlistContentPanelToolbar);
/*
 * #dependsFile client/zarafa/contact/dialogs/DistlistMemberGridColumnModel.js
 */
Ext.namespace('Zarafa.contact.dialogs');

/**
 * @class Zarafa.contact.dialogs.DistlistMembersTab
 * @extends Ext.form.FormPanel
 * @xtype zarafa.distlistmemberstab
 *
 * This class is used to create layout of members tab panel.
 */
Zarafa.contact.dialogs.DistlistMembersTab = Ext.extend(Ext.form.FormPanel, {
	/**
	 * @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, {
			xtype: 'zarafa.distlistmemberstab',
			cls: 'zarafa-distlist-createpanel-memberstab',
			title: _('Members'),
			layout: 'vbox',
			layoutConfig: {
				align: 'stretch',
				pack: 'start'
			},
			defaults: {
				header: false,
				margins: {top:0, right:0, bottom:6, left:0},
				border: false
			},
			items: this.getMembersTabItems()
		});

		Zarafa.contact.dialogs.DistlistMembersTab.superclass.constructor.call(this, config);
	},

	/**
	 * Function will return items for members tab.
	 */
	getMembersTabItems: function()
	{
		return [{
					layout:'form',
					items: [{
						xtype: 'textfield',
						name: 'subject',
						labelStyle: 'text-align: left',
						fieldLabel: _('Name'),
						anchor: '100%',
						listeners:{
							change: this.onPropertyChange,
							scope: this
						}
					}]
				},{
					layout:'table',
					cls: 'buttons',
					layoutConfig: {
						columns: 5
					},
					items: [{
						xtype: 'button',
						autoWidth: true,
						text: _('Select Members') + '...',
						handler: this.showAddressBookContent,
						scope: this
					},{
						xtype: 'spacer',
						width: 6
					},{
						xtype: 'button',
						autoWidth: true,
						handler: this.addExternalMember,
						scope: this,
						text: _('Add New') + '...'
					},{
						xtype: 'spacer',
						width: 6
					},{
						xtype: 'button',
						autoWidth: true,
						text: _('Remove'),
						handler: this.removeMembers,
						scope: this
					}]
				},{
					xtype: 'grid',
					flex: 1,
					viewConfig: {
						autoFill: true
					},
					// provide a dummy store when rendering components
					// this will be replaced by recipient store when record data
					// will be available in the dialog
					store: new Ext.data.Store({
						autoDestroy: true
					}),
					listeners: {
						rowdblclick: this.onRowDblClick
					},
					ref: 'membersGrid',
					autoExpandColumn: 'display_name',
					colModel: new Zarafa.contact.dialogs.DistlistMemberGridColumnModel()
				}];
	},

	/**
	 * Load record into form
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record to load
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 */
	update: function(record, contentReset)
	{
		if(Ext.isEmpty(record)) {
			return;
		}

		this.record = record;

		this.getForm().loadRecord(record);

		// Assign a store to members grid.
		if(contentReset && record.isOpened()) {
			// apply sorting
			record.getMemberStore().sort('fileas', 'asc');

			// reconfigure grid to use member store
			this.membersGrid.reconfigure(record.getMemberStore(), this.membersGrid.colModel);
		}
	},

	/**
	 * Update record from form, Get values from the form.
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record to update
	 * @private
	 */
	updateRecord: function(record)
	{
		record.beginEdit();

		this.getForm().updateRecord(record);

		// Also set subject dependent properties.
		record.set('fileas', record.get('subject'));
		record.set('display_name', record.get('subject'));

		record.endEdit();
	},

	/**
	 * Function Opens a {@link Zarafa.addressbook.dialogs.ABMultiUserSelectionContentPanel ABMultiUserSelectionContentPanel}
	 * to select members from addressbook.
	 */
	showAddressBookContent: function()
	{
		Zarafa.contact.Actions.openMembersSelectionContent(this.record);
	},

	/**
	 * Function will open {@link Zarafa.contact.dialogs.ABMultiUserSelectionContentPanel ABMultiUserSelectionContentPanel}
	 * for adding external member.
	 */
	addExternalMember: function()
	{
		Zarafa.contact.Actions.openDistlistExternalMemberContent(undefined, { parentRecord: this.record });
	},

	/**
	 * Function will remove selected members from the membersGrid.
	 */
	removeMembers: function()
	{
		var store = this.record.getMemberStore();
		var selectedRecords = this.membersGrid.getSelectionModel().getSelections();

		store.remove(selectedRecords);
	},

	/**
	 * Event handler which is triggered when one of the Input fields
	 * has been changed by the user. It will validate the new value,
	 * and if correct, will apply it to the {@link Zarafa.core.data.IPMRecord record}.
	 * @param {Ext.form.Field} field The {@link Ext.form.Field field} which was changed.
	 * @param {Mixed} newValue The new value
	 * @param {Mixed} oldValue The old value
	 * @private
	 */
	onPropertyChange: function(field, newValue, oldValue)
	{
		this.record.set(field.getName(), newValue);
	},

	/**
	 * Event handler which is fired when a row in the grid has been double-clicked.
	 * This will open the selected addressbook item in a new dialog.
	 * @param {Ext.grid.GridPanel} grid The grid which fired the event
	 * @param {Number} rowIndex The index of the row which was double clicked
	 * @param {Ext.EventObject} event The event
	 */
	onRowDblClick: function(grid, rowIndex, event)
	{
		var record = this.getStore().getAt(rowIndex);
		Zarafa.contact.Actions.openDistlistMember(record, this.record);
	}
});

Ext.reg('zarafa.distlistmemberstab', Zarafa.contact.dialogs.DistlistMembersTab);

// -*- coding: utf-8; indent-tabs-mode: nil -*-
Ext.namespace('Zarafa.contact.printer');

/**
 * @class Zarafa.contact.printer.ContactRenderer
 * @extends Zarafa.common.printer.renderers.RecordRenderer
 *
 * A printer for contacts in the same layout as for emails
 */
Zarafa.contact.printer.ContactRenderer = Ext.extend(Zarafa.common.printer.renderers.RecordRenderer, {

	/**
	 * Generate the XTemplate HTML text for printing a single contact or distlist.
	 * @param {Zarafa.core.data.MAPIRecord} record The task item to print
	 * @return {String} The HTML for the XTemplate to print
	 */
	generateBodyTemplate: function(record) {
		var html = '';
		html += '<b>{fullname}</b>\n';
		html += '<hr>\n';
		html += '<table>\n';
		html += this.optionalRow(_('Full Name'), 'display_name', '{display_name}');
		html += this.optionalRow(_('Last Name'), 'surname', '{surname}');
		html += this.optionalRow(_('First Name'), 'given_name', '{given_name}');
		html += this.optionalRow(_('Job Title'), 'title', '{title}');
		html += this.optionalRow(_('Department'), 'department_name', '{department_name}');
		html += this.optionalRow(_('Company'), 'company_name', '{company_name}');
		html += this.addRow('', '');  // separator
		html += this.optionalRow(_('Business Address'), 'business_address',
			// american formatted address. translations can add more <br> and change sequence
			'{business_address_street}<br>'
			+ '{business_address_city} {business_address_state} {business_address_postal_code}<br>'
			+ '{business_address_country}');
		html += this.optionalRow(_('IM Address'), 'im', '{im}');
		html += this.addRow('', '');  // separator
		html += this.optionalRow(_('Home Address'), 'home_address',
			'{home_address_street}<br>'
			+ '{home_address_city} {home_address_state} {home_address_postal_code}<br>'
			+ '{home_address_country}');
		html += this.addRow('', '');  // separator
		html += this.optionalRow(_('Business'), 'business_telephone_number', '{business_telephone_number}');
		html += this.optionalRow(_('Home'), 'home_telephone_number', '{home_telephone_number}');
		html += this.optionalRow(_('Mobile'), 'cellular_telephone_number', '{cellular_telephone_number}');
		html += this.optionalRow(_('Business Fax'), 'business_fax_number', '{business_fax_number}');
		html += this.addRow('', '');  // separator
		html += this.optionalRow(_('Email'), 'email_address_1', '{email_address_1}');
		html += this.optionalRow(_('Email Display As'), 'email_address_display_name_1', '{email_address_display_name_1}');
		html += this.optionalRow(_('Email 2'), 'email_address_2', '{email_address_2}');
		html += this.optionalRow(_('Email 2 Display As'), 'email_address_display_name_2', '{email_address_display_name_2}');
		html += this.optionalRow(_('Email 3'), 'email_address_3', '{email_address_3}');
		html += this.optionalRow(_('Email 3 Display As'), 'email_address_display_name_3', '{email_address_display_name_3}');
		html += this.addRow('', '');  // separator
		// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
		html += this.optionalRow(_('Birthday'), 'birthday', '{birthday:date("' + _("jS F Y") + '")}'); // 1st January 1912
		// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
		html += this.optionalRow(_('Anniversary'), 'wedding_anniversary', '{wedding_anniversary:date("' + _("jS F Y") + '")}');
		html += this.optionalRow(_('Spouse/Partner'), 'spouse_name', '{spouse_name}');
		html += this.optionalRow(_('Profession'), 'profession', '{profession}');
		html += this.optionalRow(_('Assistant'), 'assistant', '{assistant}');
		html += this.addRow('', '');  // separator
		html += this.optionalRow(_('Categories'), 'categories', '{categories}');
		html += this.optionalRow(_('Attachments'), 'attachment_names', '{attachment_names}');
		html += '</table><br><p>\n';
		html += record.getBody(true);
		html += '</p>\n';
		return html;
	}
});

// -*- coding: utf-8; indent-tabs-mode: nil -*-
Ext.namespace('Zarafa.contact.printer');

/**
 * @class Zarafa.contact.printer.DistlistRenderer
 * @extends Zarafa.common.printer.renderers.RecordRenderer
 *
 * A printer for distribution lists using the same layout as for emails
 */
Zarafa.contact.printer.DistlistRenderer = Ext.extend(Zarafa.common.printer.renderers.RecordRenderer, {

	/**
	 * Generate the XTemplate HTML text for printing a single contact or distlist.
	 * @param {Zarafa.core.data.MAPIRecord} record The task item to print
	 * @return {String} The HTML for the XTemplate to print
	 */
	generateBodyTemplate: function(record) {
		var html = '';
		html += '<b>{fullname}</b>\n';
		html += '<hr>\n';
		html += '<table>\n';
		html += this.optionalRow(_('Full Name'), 'display_name', '{display_name}');
		html += '</table><br><p>\n';
		html += _('Members') + '\n';
		html += '<table>\n';
		html += '<tpl for="members">\n';
		html += this.addRow('', '{display_name:undef}', '{smtp_address:undef}');
		html += '</tpl>\n';
		html += '</table><br><p>\n';
		html += record.getBody(true);
		html += '</p>\n';
		return html;
	},

	/**
	 * Returns the data for the XTemplate used in generateBodyTemplate()
	 * @param {Zarafa.core.data.MAPIRecord} record The task item to print
	 * @return {Object} XTemplate data
	 */
	prepareData: function(record) {
		var data = Zarafa.contact.printer.DistlistRenderer.superclass.prepareData.apply(this, arguments);
		data.members = Ext.pluck(record.getMemberStore().getRange(), 'data');

		// HTML Encode all properties of the members
		for (var i = 0; i < data.members.length; i++) {
			// Make an explicit copy of the member, otherwise we
			// will save the escaped data to the distlist.
			data.members[i] = Ext.apply({}, data.members[i]);
			var member = data.members[i];

			// Users and one-off members expose their address in 'smtp_address',
			// but fall back to 'email_address' so an address is printed whenever
			// one is known. Nested distribution lists have no address and are
			// left blank.
			member.smtp_address = member.smtp_address || member.email_address || '';

			for (var key in member) {
				if(Ext.isString(member[key])) {
					member[key] = Ext.util.Format.htmlEncode(member[key]);
				}
			}
		}

		return data;
	}
});

/*
 * #dependsFile client/zarafa/contact/ui/ContactCardView.js
 * #dependsFile client/zarafa/common/ui/CharacterStrip.js
 */
Ext.namespace('Zarafa.contact.ui');

/**
 * @class Zarafa.contact.ui.ContactCardPanel
 * @extends Ext.Panel
 * @xtype zarafa.contactcardpanel
 *
 * This is a wrapper panel for contact card view as data view doesn't support adding paging bar
 * so we need to add a wrapper panel to handle that, this class will also add {@link Zarafa.common.ui.CharacterStrip CharacterStrip}
 * to right side of view.
 */
Zarafa.contact.ui.ContactCardPanel = Ext.extend(Ext.Panel, {
	/**
	 * @cfg {Zarafa.contact.ContactContext} context The context to which this panel belongs
	 */
	context: undefined,

	/**
	 * The {@link Zarafa.contact.ContactContextModel} which is obtained from the {@link #context}.
	 * @property
	 * @type Zarafa.contact.ContactContextModel
	 */
	model: undefined,

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

		if (!Ext.isDefined(config.model) && Ext.isDefined(config.context)) {
			config.model = config.context.getModel();
		}

		Ext.applyIf(config, {
			xtype: 'zarafa.contactcardpanel',
			border: false,
			layout: 'border',
			layoutConfig: {
				targetCls: ''		// hack to remove background from panel
			},
			/*
			 * Providing extra container for scrolling. So that mouse events, like mouse up and mouse down, doesn't fire in scrollbar of contact card view.
			 * Because, IE9 does not fire the 'mouseup' event while clicking on scrollbar which cause an unnecessary drag start and selection.
			 * For more info: https://connect.microsoft.com/IE/feedback/details/783058/scrollbar-trigger-mousedown-but-not-mouseup
			 */
			items: [{
				xtype: 'container',
				autoScroll: true,
				region: 'center',
				items: [{
					xtype: 'zarafa.contactcardview',
					context: config.context
				}]
			}, {
				region: 'east',
				xtype: 'zarafa.characterstrip',
				ref: 'characterStrip',
				cls: 'contact-characterstrip',
				selectedChar: config.model.getRestrictionCharacter(),
				listeners: {
					selectionchanged: this.onSelectionChanged,
					selectioncleared: this.onSelectionCleared,
					scope: this
				}
			}]
		});

		Zarafa.contact.ui.ContactCardPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Event handler for the {@link Zarafa.common.ui.CharacterStrip#selectionchanged} event.
	 * This will {@link Zarafa.contact.ContactContextModel#setRestrictionCharacter apply} the
	 * character on the {@link #model}.
	 *
	 * @param {Zarafa.common.ui.CharacterStrip} strip Instance of {@link Zarafa.common.ui.CharacterStrip}
	 * @param {String} character character that is selected currently
	 * @private
	 */
	onSelectionChanged: function(strip, character)
	{
		this.model.setRestrictionCharacter(character);
	},

	/**
	 * Event handler for the {@link Zarafa.common.ui.CharacterStrip#selectioncleared} event.
	 * This will {@link Zarafa.contact.ContactContextModel#setRestrictionCharacter reset} the
	 * character on the {@link #model}.
	 *
	 * @param {Zarafa.common.ui.CharacterStrip} strip Instance of {@link Zarafa.common.ui.CharacterStrip}
	 * @private
	 */
	onSelectionCleared: function(strip)
	{
		this.model.setRestrictionCharacter('');
	}
});

Ext.reg('zarafa.contactcardpanel', Zarafa.contact.ui.ContactCardPanel);
Ext.namespace('Zarafa.contact.ui');

/**
 * @class Zarafa.contact.ui.ContactGrid
 * @extends Zarafa.common.ui.grid.MapiMessageGrid
 * @xtype zarafa.contactgrid
 *
 * this view is used to list all the contacts as a list view
 */
Zarafa.contact.ui.ContactGrid = Ext.extend(Zarafa.common.ui.grid.MapiMessageGrid, {
	/**
	 * @cfg {Zarafa.contact.ContactContext} context The context to which this panel belongs
	 */
	context: undefined,

	/**
	 * The {@link Zarafa.contact.ContactContextModel} which is obtained from the {@link #context}.
	 * @property
	 * @type Zarafa.contact.ContactContextModel
	 */
	model: undefined,

	/**
	 * The {@link Zarafa.contact.ContactStore} which is obtained from the {@link #model}.
	 * @property
	 * @type Zarafa.contact.ContactStore
	 */
	store: undefined,

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

		if (!Ext.isDefined(config.model) && Ext.isDefined(config.context)) {
			config.model = config.context.getModel();
		}
		if (!Ext.isDefined(config.store) && Ext.isDefined(config.model)) {
			config.store = config.model.getStore();
		}
		config.store = Ext.StoreMgr.lookup(config.store);

		Ext.applyIf(config, {
			xtype: 'zarafa.contactgrid',
			ariaLabel: _('Contacts list'),
			border: false,
			stateful: true,
			statefulRelativeDimensions: false,
			autoExpandColumn: 'fileas',

			loadMask: this.initLoadMask(),
			viewConfig: this.initViewConfig(),
			selModel: this.initSelectionModel(),
			colModel: this.initColumnModel(),
			enableDragDrop: true,
			ddGroup: 'dd.mapiitem'
		});

		Zarafa.contact.ui.ContactGrid.superclass.constructor.call(this, config);
	},

	/**
	 * @return {Object} view config object
	 * @private
	 */
	initViewConfig: function()
	{
		return {
		};
	},

	/**
	 * @return {Object} loas mask config object
	 * @private
	 */
	initLoadMask: function()
	{
		return {
			msg: _('Loading contacts') + '...'
		};
	},

	/**
	 * creates and returns a column model object, used in {@link Ext.grid.EditorGridPanel.colModel colModel} config
	 * @return {Ext.grid.ColumnModel} column model object
	 * @private
	 */
	initColumnModel: function()
	{
		return new Zarafa.contact.ui.ContactGridColumnModel();
	},

	/**
	 * creates and returns a selection model object, used in {@link Ext.grid.GridPanel.selModel selModel} config
	 * @return {Ext.grid.RowSelectionModel} selection model object
	 * @private
	 */
	initSelectionModel: function()
	{
		return new Ext.grid.RowSelectionModel({
			singleSelect: false,
			listeners: {
				scope: this,
				selectionchange: this.onSelectionChange
			}
		});
	},

	/**
	 * Returns {@link Zarafa.contact.ui.ContactMainPanel ContactMainPanel} object which instantiated all the views
	 * @return {Zarafa.contact.ui.ContactMainPanel} contact main panel
	 */
	getMainPanel: function()
	{
		return this.ownerCt;
	},

	/**
	 * initialize events for the grid panel
	 * @private
	 */
	initEvents: function()
	{
		Zarafa.contact.ui.ContactGrid.superclass.initEvents.apply(this, arguments);

		this.on('rowdblclick', this.onRowDblClick, this);
	},

	/**
	 * Event handler which is triggered when user double clicks on a row.
	 * @param {Ext.grid.GridPanel} grid grid panel object
	 * @param {Number} rowIndex	index of row
	 * @param {Ext.event} eventObj object of the event
	 * @private
	 */
	onRowDblClick: function(grid, rowIndex, eventObj)
	{
		Zarafa.contact.Actions.openDialog(grid.getSelectionModel().getSelections());
	},

	/**
	 * Event handler which is triggered when the {@link Zarafa.contact.ui.ContactGrid ContactGrid}
	 * {@link Zarafa.core.data.IPMRecord record} selection is changed. This will inform
	 * the {@link Zarafa.contact.ContactContextModel contextmodel} about the change.
	 *
	 * @param {Ext.grid.RowSelectionModel} selectionModel The selection model used by the grid.
	 * @private
	 */
	onSelectionChange: function(selectionModel)
	{
		this.model.setSelectedRecords(selectionModel.getSelections());
	}
});

Ext.reg('zarafa.contactgrid', Zarafa.contact.ui.ContactGrid);
Ext.namespace('Zarafa.contact.ui');

/**
 * @class Zarafa.contact.ui.MemberBox
 * @extends Zarafa.common.recipientfield.ui.RecipientBox
 * @xtype zarafa.memberbox
 *
 * Special {@link Zarafa.common.ui.Box box} which is used
 * for displaying Distlist Member records in the {@link Zarafa.contact.ui.MemberBoxField}.
 * This works similar to the {@link Zarafa.common.recipientfield.ui.RecipientBox} regarding
 * resolving, but it will mark any non-distlist member user as invalid.
 */
Zarafa.contact.ui.MemberBox = Ext.extend(Zarafa.common.recipientfield.ui.RecipientBox, {
	/**
	 * Check if the given {@link Ext.data.Record record} is valid. This function can be
	 * overridden by the childclasses to indicate if the given record is valid.
	 *
	 * This class will check if the distlist_type is valid.
	 *
	 * @param {Zarafa.core.data.MAPIRecord} record The record to check
	 * @return {Boolean} True if the record is valid
	 * @protected
	 */
	isValidRecord: function(record)
	{
		var distlistType = record.get('distlist_type');

		if(Ext.isDefined(Zarafa.core.mapi.DistlistType.getName(distlistType))) {
			return true;
		}

		return false;
	},

	/**
	 * Function which can be overridden to provide custom formatting for the given {@link Ext.data.Record}
	 * to the {@link #update} function. The data object returned here is used by the {@link #textTpl template}
	 * to render the contents of the box.
	 * @param {Ext.data.Record} record The record which is going to be rendered
	 * @return {Object} The data object which can be passed to {@link #textTpl}.
 	 * @private
	 */
	prepareData: function(record)
	{
		var prepared = Zarafa.contact.ui.MemberBox.superclass.prepareData.apply(this, arguments);

		prepared.distlist_type = record.get('distlist_type');

		return prepared;
	},

	/**
	 * Function which can be overridden to provide custom icon rendering for the given {@link Ext.data.Record}
	 * to the {@link #iconEl} element. The string returned here is the CSS class which will be set on the
	 * {@link #iconEl}.
	 * @param {Ext.data.Record} record The record which is going to be rendered
	 * @return {String} The CSS class which must be applied to the {@link #iconEl}.
	 * @private
	 */
	prepareIcon: function(record)
	{
		if (this.isValidRecord(record)) {
			return Zarafa.common.ui.IconClass.getIconClass(record);
		}
	}
});

Ext.reg('zarafa.memberbox', Zarafa.contact.ui.MemberBox);
Ext.namespace('Zarafa.contact.widget');

/**
 * @class Zarafa.contact.widget.QuickContactWidget
 * @extends Zarafa.core.ui.widget.AbstractQuickItemWidget
 *
 * Widget for creating a contact quickly with a minimum set of
 * input fields
 */
Zarafa.contact.widget.QuickContactWidget = Ext.extend(Zarafa.core.ui.widget.AbstractQuickItemWidget, {

	/**
	 * The parser object which will be used to parse and combine different parts of name, address, phone number.
	 * @property
	 * @type Zarafa.contact.data.ContactDetailsParser
	 */
	contactParser: undefined,

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

		if (!config.contactParser) {
			var parserType = container.getSharedComponent(Zarafa.core.data.SharedComponentType['contact.detailsparser']);
			if (parserType) {
				config.contactParser = new parserType();
			}
		}

		Ext.applyIf(config, {
			wrapCfg: {
				recordComponentPluginConfig: Ext.applyIf(config.recordComponentPluginConfig || {}, {
					allowWrite: true
				}),
				layout: 'fit',
				items: [{
					xtype: 'form',
					ref: 'formPanel',
					layout: {
						type: 'vbox',
						align: 'stretch'
					},
					border: false,
					bodyStyle: 'background-color: inherit; padding: 5px;',
					defaults: {
						border: false,
						labelLength: 100,
						style: 'padding-bottom: 2px'
					},
					items: [{
						xtype: 'zarafa.compositefield',
						hideLabel: true,
						anchor: '100%',
						height: 30,
						items: [{
							xtype: 'textfield',
							flex: 1,
							name: 'display_name',
							emptyText: _('Full Name') + ':',
							ref: '../../fullnameField',
							listeners: {
								scope: this,
								change: this.onFullNameChange
							}
						}]
					},{
						xtype: 'zarafa.compositefield',
						hideLabel: true,
						anchor: '100%',
						height: 33,
						items: [{
							xtype: 'splitbutton',
							width: 100,
							text: _('Mobile') + ':',
							handler: this.onPhoneButtonClick,
							scope: this,
							menu: this.initPhoneButtonMenu('cellular_telephone_number')
						},{
							xtype: 'textfield',
							ref: '../../telephoneField',
							flex: 1,
							name: 'telephone_number',
							property: 'cellular_telephone_number',
							listeners: {
								scope: this,
								change: this.onTelephoneNumberChange
							}
						}]
					},{
						xtype: 'zarafa.compositefield',
						hideLabel: true,
						anchor: '100%',
						items: [{
							xtype: 'textfield',
							flex: 1,
							emptyText: _('Email') + ':',
							ref: '../../mailAddressField',
							name: 'email_address_1',
							height: 30,
							listeners: {
								scope: this,
								change: this.onChange
							}
						}]
					},{
						xtype: 'zarafa.editorfield',
						ref: '../editorField',
						htmlName: 'html_body',
						plaintextName: 'body',
						hideLabel: true,
						flex: 1,
						useHtml: true,
						defaultValue: '',
						listeners: {
							change: this.onBodyChange,
							scope: this
						}
					}]
				}]
			},
			buttons: [{
				text: _('Save'),
				cls: 'zarafa-action',
				style: 'padding-bottom: 5px',
				handler: this.onSave,
				scope: this
			},{
				text: _('Discard'),
				style: 'padding-bottom: 5px',
				handler: this.onDiscard,
				scope: this
			}]
		});

		Zarafa.contact.widget.QuickContactWidget.superclass.constructor.call(this, config);
	},

	/**
	 * Function will initialize button menu config object for the telephone buttons.
	 * group config option is used to group all checkbox items into a single select radio button group
	 * and its name should be unique across all instances of menu button
	 * @param {String} property will be used to show default selection
	 * @private
	 */
	initPhoneButtonMenu: function(property)
	{
		return {
			xtype: 'menu',
			listeners: {
				click: this.onMenuItemSelection,
				scope: this
			},
			defaults: {
				xtype: 'menucheckitem',
				group: 'phone_number'
			},
			items: [{
				text: _('Business'),
				property: 'business_telephone_number',
				checked: property == 'business_telephone_number'
			},{
				text: _('Company'),
				property: 'company_telephone_number',
				checked: property == 'company_telephone_number'
			},{
				text: _('Home'),
				property: 'home_telephone_number',
				checked: property == 'home_telephone_number'
			},{
				text: _('Mobile'),
				property: 'cellular_telephone_number',
				checked: property == 'cellular_telephone_number'
			},{
				text: _('Other'),
				property: 'other_telephone_number',
				checked: property == 'other_telephone_number'
			}]
		};
	},

	/**
	 * Function will be called whenever selection of address or telephone number
	 * will be changed, this function will change text of button and also change value
	 * of the corresponding textfield.
	 * @param {Ext.menu.Menu} Menu button manu
	 * @param {Ext.menu.CheckItem} CheckItem menu item that is selected
	 * @param {Ext.EventObject} EventObjectt event object
	 * @private
	 */
	onMenuItemSelection: function(menu, menuItem, eventObj)
	{
		if (!Ext.isEmpty(menuItem)) {
			var compositeField = menu.findParentByType('zarafa.compositefield');
			var buttonField = compositeField.findByType('splitbutton')[0];
			var textField = compositeField.findByType('textfield')[0];

			if(!Ext.isEmpty(buttonField) && !Ext.isEmpty(textField)) {
				// update text of button
				buttonField.setText(menuItem.initialConfig.text);

				// update corresponding textfield with new value
				textField.setValue(this.record.get(menuItem.property));
				textField.property = menuItem.property;
			}
		}
	},

	/**
	 * Function that will be called when one of the phone buttons is clicked,
	 * this function is used as wrapper to discard arguments passed with the handler
	 * and it will call function that will open the {@link Zarafa.contact.dialogs.ContactPhoneContentPanel ContactPhoneContentPanel}.
	 * @param {Ext.SplitButton} buttonEl split button element which was clicked.
	 * @param {Ext.EventObject} eventObj event object for the click event.
	 * @private
	 */
	onPhoneButtonClick: function(buttonEl, eventObj)
	{
		this.showDetailedPhoneContent(buttonEl.ownerCt.findByType('textfield')[0].property);
	},

	/**
	 * Event handler which is triggered when one of the Input fields
	 * has been changed by the user. It will validate the new value,
	 * and if correct, will apply it to the {@link Zarafa.core.data.IPMRecord record}.
	 * @param {Object} field The field updated field
	 * @param {Object} value The value of the field updated
	 * @private
	 */
	onChange: function(field, value)
	{
		this.wrap.record.set(field.name, value);
	},

	/**
	 * Event handler which is triggered when one of the Input fields
	 * has been changed by the user. It will validate the new value,
	 * and if correct, will apply it to the {@link Zarafa.core.data.IPMRecord record}.
	 * @param {Object} field The field updated field
	 * @param {Object} value The value of the field updated
	 * @private
	 */
	onTelephoneNumberChange: function(field, value)
	{
		this.wrap.record.set(field.property, value);
	},

	/**
	 * Event handler which is triggered when one of the Input fields
	 * has been changed by the user. It will validate the new value,
	 * and if correct, will apply it to the {@link Zarafa.core.data.IPMRecord record}.
	 * @param {Object} field The field updated field
	 * @param {Object} value The value of the field updated
	 * @private
	 */
	onFullNameChange: function(field, newValue)
	{
		var parsedData = this.contactParser.parseInfo('name', newValue);

		// sync properties
		this.wrap.record.set('display_name', newValue);
		this.wrap.record.set('display_name_prefix', parsedData['display_name_prefix']);
		this.wrap.record.set('given_name', parsedData['given_name']);
		this.wrap.record.set('middle_name', parsedData['middle_name']);
		this.wrap.record.set('surname', parsedData['surname']);
		this.wrap.record.set('generation', parsedData['generation']);

		// check for incomplete data and show detailed name dialog
		if(parsedData['incomplete_info'] === true) {
			var settingValue = container.getSettingsModel().get('zarafa/v1/contexts/contact/show_name_dialog');
			if(settingValue == true && !Ext.isEmpty(newValue)) {
				// show detailed dialog for full name
				this.showDetailedNameContent(parsedData);
			}
		}
	},

	/**
	 * Event handler which is triggered when one of the Input fields
	 * has been changed by the user. It will validate the new value,
	 * and if correct, will apply it to the {@link Zarafa.core.data.IPMRecord record}.
	 * @param {Ext.form.Field} field The {@link Ext.form.Field field} which was changed.
	 * @param {Mixed} newValue The new value
	 * @param {Mixed} oldValue The old value
	 * @private
	 */
	onBodyChange: function(field, newValue, oldValue)
	{
		this.wrap.record.beginEdit();
		if (field.isXType && field.isXType('zarafa.htmleditor')) {
			this.wrap.record.set('isHTML', true);
		} else {
			this.wrap.record.set('isHTML', false);
		}
		this.wrap.record.set(field.name, newValue);
		this.wrap.record.endEdit();
	},

	/**
	 * Function will open detailed name dialog to enter incomplete information
	 * @param {Object} parsedData if string is already parsed into object then we can pass
	 * that object here in componentConfig so parsing will not be done twice
	 * @private
	 */
	showDetailedNameContent: function(parsedData)
	{
		Zarafa.contact.Actions.openDetailedNameContent(this.wrap.record, { parser: this.contactParser, parsedData: parsedData });
	},

	/**
	 * Function will open detailed phone dialog to enter incomplete information
	 * @param {String} property property that will be modified
	 * @private
	 */
	showDetailedPhoneContent: function(property)
	{
		Zarafa.contact.Actions.openDetailedPhoneContent(this.wrap.record, { parser: this.contactParser, property: property });
	},

	/**
	 * Create a new record which must be edited by this widget.
	 * @return {Ext.data.Record} record The record to load into the {@link #wrap}
	 * @protected
	 */
	createRecord: function()
	{
		var folder = container.getHierarchyStore().getDefaultFolder('contact');
		var context = container.getContextByName('contact');
		var model = context.getModel();

		return model.createRecord(folder);
	},

	/**
	 * Updates the widget by loading data from the record into the {@link #wrap}.
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record update the panel with.
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 * @protected
	 */
	update: function(record, contentReset)
	{
		this.wrap.formPanel.getForm().loadRecord(record);
		this.wrap.telephoneField.setValue(record.get(this.wrap.telephoneField.property));
	},

	/**
	 * Updates the widget by loading data from the record into the {@link #wrap}.
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record to update
	 * @protected
	 */
	updateRecord: function(record)
	{
		record.beginEdit();
		this.wrap.formPanel.getForm().updateRecord(record);
		this.onFullNameChange(this.wrap.fullnameField, this.wrap.fullnameField.getValue());

		record.updateSubject();
		record.updateAddressbookProps();

		record.endEdit();
	},

	/**
	 * Event handler which is fired when the user pressed the 'Save' button.
	 * This will call {@link Zarafa.core.ui.MessageContentPanel#saveRecord} to start
	 * sending the mail.
	 * @private
	 */
	onSave: function()
	{
		this.wrap.saveRecord();
	},

	/**
	 * Event handler which is fired when the user pressed the 'Disacrd' button.
	 * This will call {@link #reset} to clear the contents.
	 * @private
	 */
	onDiscard: function()
	{
		this.reset();
	}
});

Zarafa.onReady(function() {
	container.registerWidget(new Zarafa.core.ui.widget.WidgetMetaData({
		name: 'quickcontact',
		iconCls: 'icon_widget_new_contact',
		displayName: _('Quick Contact'),
		widgetConstructor: Zarafa.contact.widget.QuickContactWidget
	}));
});
Ext.namespace('Zarafa.hierarchy.data');

/**
 * @class Zarafa.hierarchy.data.HierarchyNotificationResponseHandler
 * @extends Zarafa.core.data.AbstractNotificationResponseHandler
 *
 * The default {@link Zarafa.core.data.AbstractNotificationResponseHandler ResponseHandler}
 * for Hierarchy Notifications. This can handle folder changes, and newMail updates.
 */
Zarafa.hierarchy.data.HierarchyNotificationResponseHandler = Ext.extend(Zarafa.core.data.AbstractNotificationResponseHandler, {
	/**
	 * Handle the {@link Zarafa.core.Actions#folder 'folder'} action.
	 * This will check if the folder was created, updated or deleted,
	 * and generate the correct notification.
	 * @param {Object} data The entire response object which will be processed during this transaction.
	 * @private
	 */
	doFolders: function(data)
	{
		var responseObj;
		var folders = data.item;

		if (Ext.isEmpty(folders)) {
			return;
		}

		if (!Array.isArray(folders)) {
			folders = [ folders ];
		}

		try {
			responseObj = this.reader.readResponse(Ext.data.Api.actions.read, {item: folders});
		} catch(e) {
			return false;
		}

		for (var i = 0, len = responseObj.data.length; i < len; i++) {
			var rawFolder = folders[i];
			var folder = responseObj.data[i];

			var mapiFolderRecord = this.store.getById(folder.entryid);
			if (mapiFolderRecord) {
				// Check the 'folderdelete' field inside the rawdata.
				// 'folderdelete' is a notification flag set by PHP which indicates that the
				// reason for this notification is that the folder has been deleted. We must check
				// this flag in the raw data rather then the Record itself, since 'folderdelete'
				// is not defined as an official field for the FolderRecord. Neither should it be registered,
				// as it is only a notification flag.
				if (rawFolder.folderdelete) {
					this.addNotification(Zarafa.core.data.Notifications.objectDeleted, mapiFolderRecord, folder);
				} else {
					this.addNotification(Zarafa.core.data.Notifications.objectModified, mapiFolderRecord, folder);
				}
			} else if (!rawFolder.folderdelete) {
				this.addNotification(Zarafa.core.data.Notifications.objectCreated, null, folder);
			}
		}
	},

	/**
	 * Handle the {@link Zarafa.core.Actions#newmail 'newmail'} action.
	 * This will obtain the list of {@link Zarafa.core.data.IPMStore stores}
	 * which must be updated, and generate the Notification.
	 * @param {Object} data The entire response object which will be processed during this transaction.
	 * @private
	 */
	doNewmail: function(data)
	{
		var folderEntryids = Ext.pluck(data.item, 'entryid');
		var folderStores = Zarafa.core.data.IPMStoreMgr.getStoresForFolders(folderEntryids);

		this.addNotification(Zarafa.core.data.Notifications.newMail, folderStores, data);
	},

	/**
	 * Handle the stores action we receive as notification.
	 * This will obtain the list of {@link Zarafa.hierarchy.data.MAPIStoreRecord MAPIStoreRecord}
	 * which must be updated, and generate the Notification.
	 * @param {Object} data The entire response object which will be processed during this transaction.
	 * @private
	 */
	doStores: function(data)
	{
		var stores = data.item;
		var mapiStoreIds = Ext.pluck(stores, 'store_entryid');

		var mapiStoreRecord = [];
		for (var i = 0, len = mapiStoreIds.length; i < len; i++) {
			var storeRecord = this.store.getById(mapiStoreIds[i]);
			// We must check if we have a store record, because a keepalive request might be sent before the store loaded.
			// The notifier that will be sent in the response will trigger this function and because we don't have the
			// store record yet, we will eventually end up with a javascript error if we put undefined in the mapiStoreRecord array.
			if ( Ext.isDefined(storeRecord) ){
				mapiStoreRecord.push(storeRecord);
			}
		}

		this.addNotification(Zarafa.core.data.Notifications.objectModified, mapiStoreRecord, stores);
	}
});
Ext.namespace('Zarafa.hierarchy.data');

/**
 * @class Zarafa.hierarchy.data.HierarchyResponseHandler
 * @extends Zarafa.core.data.ProxyResponseHandler
 */
Zarafa.hierarchy.data.HierarchyResponseHandler = Ext.extend(Zarafa.core.data.ProxyResponseHandler, {
	/**
	 * Handles the list response. Gathers the stores from the response data, converts each entry
	 * into a {@link Zarafa.core.MAPIStore MAPIStore} and pushes them into the collectedItems.
	 * @param {Object} data The response object belonging to the given command.
	 * @return {Boolean} False when action could not be handled successfully. This will
	 * not cancel the transaction itself, but rather causes the 'success' argument for the
	 * {@link #done} function to be false.
	 */
	doList: function(response)
	{
		this.receivedRecords = this.readRecordsFromResponse(response, 'item');
	},

	/**
	 * Handles the 'folders' response. Gathers the folders which have been modified
	 * by the request, converts them into {@link Zarafa.hierarchy.data.MAPIFolderRecord Folders},
	 * and pushes them into the collectedItems.
	 * @param {Object} data The response object belonging to the given command.
	 * @return {Boolean} False when action could not be handled successfully. This will
	 * not cancel the transaction itself, but rather causes the 'success' argument for the
	 * {@link #done} function to be false.
	 */
	doFolders: function(response)
	{
		this.receivedRecords = this.receivedRecords.concat(this.correlateRecordFromResponse({ item: response }));
	},

	/**
	 * Handles the 'item' response. Gathers the folders which have been modified
	 * by the request, converts them into {@link Zarafa.hierarchy.data.MAPIFolderRecord Folders},
	 * and pushes them into the collectedItems.
	 * @param {Object} data The response object belonging to the given command.
	 * @return {Boolean} False when action could not be handled successfully. This will
	 * not cancel the transaction itself, but rather causes the 'success' argument for the
	 * {@link #done} function to be false.
	 */
	doItem: function(response)
	{
		this.receivedRecords = this.receivedRecords.concat(this.correlateRecordFromResponse({ item: response }));
	},

	/**
	 * Handles the 'update' response. This will check if the item in the response is
	 * inside the {@link #sendRecords} and will convert the Response data into the
	 * updated {@link Ext.data.Record record} using {@link #correlateRecordFromResponse},
	 * and pushes them into the {@link #receivedRecords records list}.
	 * @param {Object} data The response object belonging to the given command.
	 * @return {Boolean} False when action could not be handled successfully. This will
	 * not cancel the transaction itself, but rather causes the 'success' argument for the
	 * {@link #done} function to be false.
	 */
	doUpdate: function(response)
	{
		this.receivedRecords = this.receivedRecords.concat(this.correlateRecordFromResponse({ item: response }));
	},

	/**
	 * Handles the 'ensure' response.
	 * @param {Object} response The response object belonging to the given command.
	 */
	doEnsure: function (response)
	{
		this.receivedRecords = response;
	}
});
/*
 * #dependsFile client/zarafa/hierarchy/data/HierarchyProxy.js
 */
Ext.namespace('Zarafa.hierarchy.data');

/**
 * @class Zarafa.hierarchy.data.HierarchyStore
 * @extends Zarafa.core.data.IPFStore
 * @xtype zarafa.hierarchystore
 *
 * {@link Zarafa.hierarchy.data.HierarchyStore HierarchyStore} holds
 * {@link Zarafa.hierarchy.data.MAPIStoreRecord MAPIStoreRecord} as records, which defines store information
 * of all opened stores(own, shared, public).
 */
Zarafa.hierarchy.data.HierarchyStore = Ext.extend(Zarafa.core.data.IPFStore, {
	/**
	 * @cfg {Boolean} stateful A flag which causes the store to create a {@link #state} object in which
	 * the state for the various {@link Zarafa.hierarchy.data.MAPIFolderRecord folders} can be saved.
	 * This class will not automatically get/set the state, but assumes that other {@link Ext.Component components}
	 * or {@link Zarafa.core.data.StatefulObservable stateful objects} will {@link #getState get} or
	 * {@link #applyState apply} the state.
	 */
	stateful: true,

	/**
	 * When {@link #stateful} the interaction object in which the state can be saved or obtained from.
	 * Typically access to this object can be done through {@link #getState} and {@link #applyState}.
	 * @property
	 * @type Zarafa.hierarchy.data.HierarchyState
	 */
	state: undefined,

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

		Ext.applyIf(config, {
			// The HierarchyStore is special and doesn't need
			// to register itself to the IPFStoreMgr, this because
			// the contents of the hierarchyStore is a list
			// of more IPFStores which will register themselves.
			standalone: true,
			proxy: new Zarafa.hierarchy.data.HierarchyProxy(),
			writer: new Zarafa.core.data.JsonWriter(),
			reader: new Zarafa.core.data.JsonReader({
				id: 'store_entryid',
				idProperty: 'store_entryid'
			})
		});

		this.addEvents(
			/**
			 * @event addFolder
			 * Fires when a folder has been created.
			 * @param {Zarafa.hierarchy.data.HierarchyStore} store The hierarchy store that fired the event
			 * @param {Zarafa.hierarchy.data.MAPIStoreRecord} storeRecord The MAPI store that contains the folder that was added
			 * @param {Zarafa.hierarchy.data.IPFRecord/Zarafa.hierarchy.data.IPFRecord[]} folder folder record that is added to the hierarchy store.
			 */
			'addFolder',
			/**
			 * @event removeFolder
			 * Fires when a folder has been removed.
			 * @param {Zarafa.hierarchy.data.HierarchyStore} store This hierarchy store that fired the event
			 * @param {Zarafa.hierarchy.data.MAPIStoreRecord} storeRecord The MAPI store that contained the folder that was removed
			 * @param {Zarafa.hierarchy.data.IPFRecord} folder folder which is removed from the hierarchy store.
			 */
			'removeFolder',
			/**
			 * @event updateFolder
			 * Fires when a folder has been updated
			 * @param {Zarafa.hierarchy.data.HierarchyStore} store
			 * @param {Zarafa.hierarchy.data.MAPIStoreRecord} storeRecord
			 * @param {Zarafa.hierarchy.data.IPFRecord} record folder which is updated in the hierarchy store.
			 * @param {String} operation The update operation being performed. Value may be one of
			 * {@link Ext.data.Record#EDIT}, {@link Ext.data.Record#REJECT}, {@link Ext.data.Record#COMMIT}.
			 */
			'updateFolder'
		);

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

		if (this.stateful !== false) {
			this.initState();
		}

		this.on({
			'beforesave': this.onBeforeSave,
			'load': this.onAfterLoad,
			'add': this.onAdd,
			'remove': this.onRemove,
			scope: this
		});
	},

	/**
	 * Compare a {@link Ext.data.Record#id ids} to determine if they are equal.
	 * This will apply the {@link Zarafa.core.EntryId#compareStoreEntryIds compareStoreEntryIds} function
	 * on both ids, as all records in this store will have a StoreEntryId as unique key.
	 * @param {String} a The first id to compare
	 * @param {String} b The second id to compare
	 * @protected
	 */
	idComparison: function(a, b)
	{
		return Zarafa.core.EntryId.compareStoreEntryIds(a, b);
	},

	/**
	 * <p>Loads the Record cache from the configured <tt>{@link #proxy}</tt> using the configured <tt>{@link #reader}</tt>.</p>
	 * <br> Function just adds 'list' as actionType in options and calls parent {@link Zarafa.core.data.IPFStore#load} method.
	 * <br> Check documentation of {@link Ext.data.Store#load} for more information.
	 */
	load: function(options)
	{
		// Use server-prefetched hierarchy data when available.
		// This eliminates the first AJAX round-trip after login.
		if (window.prefetchedHierarchy) {
			var prefetched = window.prefetchedHierarchy;
			delete window.prefetchedHierarchy;

			if (!Ext.isObject(options)) {
				options = {};
			}
			Ext.applyIf(options, {
				actionType: Zarafa.core.Actions['list']
			});

			var items = prefetched['item'] || [];
			var rawData = { count: items.length, item: items };

			var o = this.reader.readRecords(rawData);
			this.loadRecords(o, options, true);
			return;
		}

		if (!Ext.isObject(options)) {
			options = {};
		}

		if (!Ext.isObject(options.params)) {
			options.params = {};
		}

		// Load should always cancel the previous actions.
		if (!Ext.isDefined(options.cancelPreviousRequest)) {
			options.cancelPreviousRequest = true;
		}

		/*
		 * This store will only be used to get whole hierarchy data, so it will have
		 * actionType as 'list' always.
		 */
		Ext.applyIf(options, {
			actionType: Zarafa.core.Actions['list']
		});

		return Zarafa.hierarchy.data.HierarchyStore.superclass.load.call(this, options);
	},

	/**
	 * Remove records from the Store and fire the {@link #remove} event.
	 * On a HierarchyStore it is not possible to remove the Private or Public Store,
	 * a special event will be send to the server when a Shared Store is removed
	 * from the Hierarchy.
	 * @param {Zarafa.hierarchy.data.MAPIStore} record The MAPIStore which must be
	 * removed from the HierarchyStore.
	 * @param {boolean} removeFavorites false to not remove favoirts folder from hierarchy store. default is true.
	 */
	remove: function(record, removeFavorites)
	{
		if (Array.isArray(record)) {
			Ext.each(record, this.remove, this);
			return;
		}

		// Only allow Shared Stores to be removed
		if (record.isSharedStore()) {
			// removeFavorites is true in case when user tries to open the entire store
			// where some of the folders are already opened in that case we don't need to.
			// Remove existing favorites folder else remove all favorites marked folders which
			// belong to shared user because we are going to close the shared user hierarchy store.
			if (removeFavorites !== false){
				var favoritesStore = record.getFavoritesStore();
				var records = favoritesStore.query('store_entryid',record.get('store_entryid'));
				favoritesStore.remove(records.getRange());
			}
			Zarafa.hierarchy.data.HierarchyStore.superclass.remove.call(this, record);
		}
	},

	/**
	 * Open a Store in the Hierarchy. This will {@link #load} the hierarchy
	 * requesting the data for the given user store/folder. The returned data
	 * will be appended to the already loaded data.
	 * @param {String} username The name of the user for which the store will be opened
	 * @param {Zarafa.hierarchy.data.SharedFolderTypes} foldertype The foldertype which will be opened
	 * @param {Boolean} subfolders True if subfolders must be loaded (when foldertype is not 'all').
	 * @return {Boolean} False if the substore could not be opened
	 */
	open: function(username, foldertype, subfolders)
	{
		// The username must always be lowercase
		username = username.toLowerCase();

		// Check if the user is already present in the settings,
		// if that is the case we have to check if we can actually open
		// this new folder or not.
		var settings = container.getSettingsModel().get(
			'zarafa/v1/contexts/hierarchy/shared_stores/' + Zarafa.core.Util.bin2hex(username), true);
		if (settings && ((settings[foldertype] || settings['all']))) {
			return false;
		}

		var options = Ext.applyIf({
			params: {
				user_name: username,
				folder_type: foldertype,
				show_subfolders: subfolders
			},
			add: true,
			actionType: 'opensharedfolder',
			cancelPreviousRequest: false
		}, this.lastOptions);

		try {
			return this.execute('read', null, options); // <-- null represents rs. No rs for load actions.
		} catch (e) {
			this.handleException(e);
			return false;
		}
	},

	/**
	 * Called when the response from the server has been returned for the {@link #load} call.
	 * If the actionType indicates that a shared folder was opened for this call, this will check
	 * if the returned Store object is already present inside the store (this could happen when
	 * we already shared the 'Calendar' and are now adding the 'Contacts'). If that is the case
	 * the received {@link Zarafa.hierarchy.data.MAPIStoreRecord store} is
	 * {@link Zarafa.core.data.MAPIRecord#applyData merged} into the already existing
	 * {@link Zarafa.hierarchy.data.MAPIStoreRecord store}.
	 * @param {Object} o The cache of {@link Ext.data.Record records} as received by the server
	 * @param {Object} options The options used for loading the store
	 * @param {Boolean} success True if the records were successfully loaded from the store
	 * @private
	 */
	loadRecords: function(o, options, success)
	{
		if (this.isDestroyed) {
			return;
		}
		if (o && success === true && options && options.actionType === 'opensharedfolder') {
			var records = o.records;

			for (var i = 0, len = records.length; i < len; i++) {
				var record = records[i];
				var existingStore = this.getById(record.get('store_entryid'));

				// The store already existed previously, the server will have given
				// us the new folders only. So we obtain the previously loaded folders
				// and inject them into the response.
				if (existingStore) {
					if(options.params.folder_type === 'all') {
						this.remove(existingStore, false);
						this.save(existingStore, false);
					} else {
						record.createSubStores();

						var existingFolderStore = existingStore.getSubStore('folders');
						var newFolderStore = record.getSubStore('folders');

						// Go over all existing folders, if it doesn't exist yet in the
						// store which we received from the server, copy it into there.
						existingFolderStore.each(function(folder) {
							var newFolder = newFolderStore.getById(newFolderStore.data.getKey(folder));
							if (!newFolder) {
								newFolderStore.add(folder.copy());
							}
						}, this);
					}
				}
			}
		}

		Zarafa.hierarchy.data.HierarchyStore.superclass.loadRecords.call(this, o, options, success);
	},

	/**
	 * This will hook the event handlers {@link #onFolderAdd}, {@link #onFolderRemove} and {@link #onFolderUpdate},
	 * to the {@link Ext.data.Store#add}, {@link Ext.data.Store#remove} and {@link Ext.data.Store#update} events
	 * for the 'folders' substore for each store which is inside the hierarchy. This allows us to fire
	 * the {@link #addFolder}, {@link #removeFolder}, {@link #updateFolder} events.
	 * @param {Zarafa.hierarchy.data.MAPIStoreRecord[]} records The records which are hooked
	 * @private
	 */
	hookStoreRecord: function(records)
	{
		for (var i = 0, len = records.length; i < len; i++) {
			var folders = records[i].getSubStore('folders');
			folders.on({
				'add': this.onFolderAdd,
				'remove': this.onFolderRemove,
				'update': this.onFolderUpdate,
				scope: this
			});

			var favorites = records[i].getSubStore('favorites');
			if(Ext.isDefined(favorites)) {
				favorites.on({
					'add': this.onFolderAdd,
					'remove': this.onFolderRemove,
					'update': this.onFolderUpdate,
					scope: this
				});
			}
		}
	},

	/**
	 * This will unhook the events from {@link #hookStoreRecord}.
	 * @param {Zarafa.hierarchy.data.MAPIStoreRecord} record The record to unhook
	 * @private
	 */
	unhookStoreRecord: function(record)
	{
		var folders = record.getSubStore('folders');
		folders.un('add', this.onFolderAdd, this);
		folders.un('remove', this.onFolderRemove, this);
		folders.un('update', this.onFolderUpdate, this);

		var favorites = record.getSubStore('favorites');
		if(Ext.isDefined(favorites)) {
			favorites.un('add', this.onFolderAdd, this);
			favorites.un('remove', this.onFolderRemove, this);
			favorites.un('update', this.onFolderUpdate, this);
		}
	},

	/**
	 * Event handler which is fired when the Hierarchy has loaded,
	 * this will check if this was called to open a Shared Store or not.
	 *
	 * If this has been called because a shared store has been opened,
	 * this will update the settings so the folder will be automatically
	 * loaded next time the user logs in.
	 *
	 * In all cases, all stores which are loaded will be hooked using
	 * {@link #hookStoreRecord}.
	 *
	 * @param {Ext.data.Store} store The store which fired the event
	 * @param {Ext.data.Record[]} records The records which were loaded
	 * @param {Object} options The options which were send with the load request
	 * @private
	 */
	onAfterLoad: function(store, records, options)
	{
		if (options.actionType === 'opensharedfolder') {
			var settings = container.getSettingsModel();
			var user_name = options['params']['user_name'].toLowerCase();
			var folder_type = options['params']['folder_type'] || 'all';
			var subfolders = options['params']['show_subfolders'] || false;

			var settingsBase = 'zarafa/v1/contexts/hierarchy/shared_stores/' + Zarafa.core.Util.bin2hex(user_name);

			var shared = settings.get(settingsBase, true);
			if (shared && (shared[folder_type] || shared['all'])) {
				return;
			}

			settings.set(settingsBase + '/' + folder_type, {
				folder_type: folder_type,
				show_subfolders: subfolders
			});

			// when user tries to open shared folder, that particular folder will be opened immediately and context will be
			// switched automatically. if context not having folder than by default shared 'inbox' will be opened.
			// if user tries to open 'entire inbox' than folder is opened as per current context, if current context not having
			// folder than by default shared 'inbox' will be opened, and context will switched automatically.
			if(folder_type === 'all') {
				folder_type = container.getCurrentContext().getName();
				switch(folder_type){
					case 'calendar':
					case 'contacts':
					case 'tasks':
					case 'notes':
						break;
					default:
						folder_type = 'inbox';
						break;
				}
			}
			var folder = this.getFolder((records[0].get('default_folder_'+folder_type)));

			if (Ext.isDefined(folder)) {
				// If a user is trying to open a shared calendar folder, we need to check
				// whether there was any previously opened calendar or not. If yes, we open
				// that too instead of the previous one getting closed.
        if (folder_type === "calendar") {
          var context = container.getContextByFolder(folder);
          var state = context.getModel().getState();
          var lastUsedFolders = state.last_used_folders;
          var foldersToOpen = [folder];
          if (!Ext.isEmpty(lastUsedFolders)) {
            for (var storeEntryID in lastUsedFolders) {
							var lastUsedStore = this.getById(storeEntryID);
							if (lastUsedStore) {
								var folders = lastUsedStore.getSubStore('folders');
								var folderEntryIDs = lastUsedFolders[storeEntryID];
								for (var i = 0; i < folderEntryIDs.length; i++) {
									var lastUsedFolder = folders.getById(folderEntryIDs[i]);
									if (lastUsedFolder && lastUsedFolder.get('access') !== 0) {
										foldersToOpen.push(lastUsedFolder);
									}
								}
							}
            }
          }
          Zarafa.hierarchy.Actions.openFolder(foldersToOpen);
        } else {
					Zarafa.hierarchy.Actions.openFolder(folder);
				}
      }
		}

		// Add event handlers which are registered so we can listen for
		// the add, remove and update events for the folders.
		this.hookStoreRecord(records);

		// Set title counter for webapp tab.
		Zarafa.hierarchy.Actions.setTitleCounter(this);
	},

	/**
	 * 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.
	 * 
	 * @param {Zarafa.hierarchy.data.MAPIStore} record The MAPIStore which is going save.
	 * @param {boolean} removeFavorites false to not remove favoirts folder from server. default is true.
	 * @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(record, removeFavorites)
	{
		// If stores have been removed we have to provide a special
		// treatment, as stores can't be deleted, but only 'closed'.
		if (this.removed.length) {
			var stores = this.removed;

			var data = {
				'close': stores
			};

			if (this.fireEvent('beforesave', this, data) !== false) {
				try {
					var batch = ++this.batchCounter;
					this.execute('destroy', data['close'], {
						actionType: 'closesharedfolder',
						params : {
							// removeFavorites is false when user try to open entire store and some of the folders
							// are already been open in that case we don't need to remove favorites marks folders as we 
							// are opening entire store.
							remove_favorites: Ext.isDefined(removeFavorites) ? removeFavorites : true
						}
					}, batch);
				} catch (e) {
					this.handleException(e);
				}

				// Reset the removed list and
				// continue as planned.
				this.removed = [];
			}
		}

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

	/**
	 * Event handler which is fired when Hierarchy changes are about to be saved.
	 * This will check if a record is being destroyed, if that is a shared store,
	 * 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 stores = data['close'];

			settings.beginEdit();

			for (var i = 0, len = stores.length; i < len; i++) {
				var sharedstore = stores[i];
				var username = sharedstore.get('user_name').toString().toLowerCase();

				// The entire store is being closed, this can be used
				// when we were sharing the entire store, or we were
				// sharing one or more different default folders.
				// Both cases should be correctly removed using this
				// function, so we just remove all shared_stores settings
				// for the user rather then only the 'all' folder type.
				settings.remove('zarafa/v1/contexts/hierarchy/shared_stores/' + Zarafa.core.Util.bin2hex(username));

				sharedstore.addIdProp('user_name');
			}

			settings.endEdit();
		}
	},

	/**
	 * Event handler fired when a {@link Zarafa.hierarchy.data.MAPIStoreRecord store}
	 * has been added to the hierarchy. This will call {@link #hookStoreRecord}.
	 *
	 * @param {Zarafa.hierarchy.data.HierarchyStore} store This store
	 * @param {Zarafa.hierarchy.data.MAPIStoreRecord[]} records The record which is added
	 * @param {Number} index The index from where the record was added
	 * @private
	 */
	onAdd: function(store, records, index)
	{
		this.hookStoreRecord(records);
	},

	/**
	 * Event handler fired when a {@link Zarafa.hierarchy.data.MAPIStoreRecord store}
	 * has been added to the hierarchy. This will call {@link #unhookStoreRecord}.
	 *
	 * @param {Zarafa.hierarchy.data.HierarchyStore} store This store
	 * @param {Zarafa.hierarchy.data.MAPIStoreRecord} record The record which is removed
	 * @param {Number} index The index from where the record was removed
	 * @private
	 */
	onRemove: function(store, record, index)
	{
		this.unhookStoreRecord(record);
	},

	/**
	 * Event handler fired when a folder in one of the {@link Zarafa.hierarchy.data.MAPIStoreRecord stores}
	 * in this store has been {@link Ext.data.Store#add added}. This will fire the
	 * {@link #addFolder} event.
	 *
	 * @param {Zarafa.hierarchy.data.IPFSubStore} substore
	 * @param {Zarafa.hierarchy.data.IPFRecord} folder which is added to the hierarchy
	 * @param {Number} index The index in the substore from where the folder was added
	 * @private
	 */
	onFolderAdd: function(store, records, index)
	{
		this.fireEvent('addFolder', this, store.getParentRecord(), records, index);
	},

	/**
	 * Event handler fired when a folder in one of the {@link Zarafa.hierarchy.data.MAPIStoreRecord stores}
	 * in this store has been {@link Ext.data.Store#remove removed}. This will fire the
	 * {@link #removeFolder} event.
	 *
	 * @param {Zarafa.hierarchy.data.IPFSubStore} substore
	 * @param {Zarafa.hierarchy.data.IPFRecord} folder which is removed from the hierarchy
	 * @param {Number} index The index in the substore from where the folder was removed
	 * @private
	 */
	onFolderRemove: function(store, record, index)
	{
		// If the removed folder is a shared folder and not favorites marked then we have to check
		// if this is the last shared folder for the store. Because
		// that means we have to remove the entire store.
		if (record.isSharedFolder() && !record.isFavoritesFolder()) {
			var isEmpty = true;

			// Check if there are any shared folders left in the hierarchy...
			store.each(function(r) {
				if (r.isSharedFolder()) {
					isEmpty = false;
					return false;
				}
			}, this);

			// No shared folders remain, time to cleanup the entire store
			if (isEmpty) {
				// Reject all changes made to the IPFSubStore,
				// we are deleting the entire parent record, and thus
				// any changes made inside the SubStore are irrelevant.
				store.rejectChanges();

				// Remove the store from the Hierarchy
				var root = store.getParentRecord();
				this.remove(root);
				this.save(root);
				return;
			}
		}

		this.fireEvent('removeFolder', this, store.getParentRecord(), record, index);
	},

	/**
	 * Event handler fired when a folder in one of the {@link Zarafa.hierarchy.data.MAPIStoreRecord stores}
	 * in this store has been {@link Ext.data.Store#update updated}. This will fire the
	 * {@link #updateFolder} event.
	 *
	 * @param {Zarafa.hierarchy.data.IPFSubStore} store
	 * @param {Zarafa.hierarchy.data.IPFRecord} record which is updated in the hierarchy.
	 * @param {String} operation The update operation being performed. Value may be one of
	 * {@link Ext.data.Record#EDIT}, {@link Ext.data.Record#REJECT}, {@link Ext.data.Record#COMMIT}.
	 * @private
	 */
	onFolderUpdate: function(store, record, operation)
	{
		this.fireEvent('updateFolder', this, store.getParentRecord(), record, operation);

		// Update title counter for webapp tab.
		Zarafa.hierarchy.Actions.setTitleCounter(this);
	},

	/**
	 * @return {Array} list of all {@link Zarafa.hierarchy.data.MAPIStoreRecord Store}
	 */
	getStores: function()
	{
		return this.getRange();
	},

	/**
	 * @return {Zarafa.hierarchy.data.MAPIStoreRecord} default store
	 */
	getDefaultStore: function()
	{
		var index = this.findExact('mdb_provider', Zarafa.core.mapi.MDBProvider.ZARAFA_SERVICE_GUID);

		if (index !== -1) {
			return this.getAt(index);
		}
	},

	/**
	 * @return {Zarafa.hierarchy.data.MAPIStoreRecord} public store
	 */
	getPublicStore: function()
	{
		var index = this.findExact('mdb_provider', Zarafa.core.mapi.MDBProvider.ZARAFA_STORE_PUBLIC_GUID);

		if (index !== -1) {
			return this.getAt(index);
		}
	},

	/**
	 * Function is used get default {@link Zarafa.hierarchy.data.MAPIFolderRecord MAPIFolderRecord}
	 * from {@link Zarafa.hierarchy.data.MAPIStoreRecord MAPIStoreRecord},
	 * if {@link Zarafa.hierarchy.data.MAPIStoreRecord MAPIStoreRecord} is not passed then
	 * it will by default take default store.
	 *
	 * @param {String} name name of the default folder (i.e. 'inbox' or 'contacts')
	 * @param {Zarafa.hierarchy.data.MAPIStoreRecord} mapiStoreRecord (optional) {@link Zarafa.hierarchy.data.MAPIStoreRecord MAPIStoreRecord}
	 * whose default folder we should return, if not passed then it will use default store.
	 * @return {Zarafa.hierarchy.data.MAPIFolderRecord} a folder if a default folder with the given name was found, or undefined otherwise.
	 */
	getDefaultFolder: function(name, mapiStoreRecord)
	{
		if(!mapiStoreRecord) {
			mapiStoreRecord = this.getDefaultStore();

			if(!mapiStoreRecord) {
				// hierarchy store is empty
				return;
			}
		}

		return mapiStoreRecord.getDefaultFolder(name);
	},

	/**
	 * Retrieves a {@link Zarafa.hierarchy.data.MAPIStoreRecord MAPIStoreRecord} by owner entryid.
	 * @param {String} ownerEntryId entry id of the owner of the store.
	 * @return {Zarafa.hierarchy.data.MAPIStoreRecord} store owned by user.
	 */
	getStoreByOwnerEntryId: function(ownerEntryId)
	{
		for(var i = 0; i < this.getCount(); i++) {
			var folderStore = this.getAt(i);

			if(Zarafa.core.EntryId.compareABEntryIds(folderStore.get('mailbox_owner_entryid'), ownerEntryId)) {
				return folderStore;
			}
		}
	},

	/**
	 * Retrieves a folder by MAPI ID from list of stores
	 * @param {String} id MAPI ID of the folder to search for.
	 * @return {Zarafa.hierarchy.data.MAPIFolderRecord} a folder matching the given ID, or undefined if not found.
	 */
	getFolder: function(id)
	{
		for(var i = 0, len = this.getCount(); i < len; i++) {
			var folderStore = this.getAt(i).getFolderStore();

			if(folderStore) {
				var folder = folderStore.getById(id);
				if(folder) {
					return folder;
				}
			}
		}
	},

	/**
	 * Function will return default folder from the hierarchy
	 * for the supplied message_class to the function.
	 *
	 * @param {String} messageClass The message_class of the mapi record.
	 * @return {Zarafa.hierarchy.data.MAPIFolderRecord} a folder
	 * default folder for the supplied message_class
	 */
	getDefaultFolderFromMessageClass: function(messageClass)
	{
		var folderType = Zarafa.core.MessageClass.getDefaultFolderTypeFromMessageClass(messageClass);
		if (!Ext.isEmpty(folderType)) {
			return this.getDefaultStore().getDefaultFolder(folderType);
		}
	},

	/**
	 * Function will return default folder from the hierarchy
	 * for the supplied container_class to the function.
	 *
	 * @param {String} containerClass The container_class of the mapi record.
	 * @return {Zarafa.hierarchy.data.MAPIFolderRecord} a folder
	 * default folder for the supplied message_class
	 */
	getDefaultFolderFromContainerClass: function(containerClass)
	{
		var folderType = Zarafa.core.ContainerClass.getDefaultFolderTypeFromContainerClass(containerClass);
		if (!Ext.isEmpty(folderType)) {
			return this.getDefaultStore().getDefaultFolder(folderType);
		}
	},

	/**
	 * Helper method for getSortedFolders.
	 * @private
	 */
	collectFolders: function(arr, match, scope, folder)
	{
		if (!Ext.isFunction(match) || match.call(scope || this, folder)) {
			arr.push(folder);
		}

		Ext.each(folder.getChildren(), function(childFolder) {
			this.collectFolders(arr, match, scope, childFolder);
		}, this);
	},

	/**
	 * Retrieves a sorted list of folders. The folders will appear in the same order as they do in the hierarchy tree on screen.
	 * This order is partially dictated by the order in which the folders are returned by the server, and the hierarchy of folders.
	 * @param {Function} match (optional) a function that can be used to filter out undesired folders. Will be called with a folder object
	 * as parameter and should return true if that folder should appear in the output.
	 * @return {Array} array of matched folders, in the same order as they appear on screen.
	 */
	getSortedFolders: function(match, scope)
	{
		var allFolders = [];

		var stores = this.getStores();
		for (var i = 0; i < stores.length; i++) {
			this.collectFolders(allFolders, match, scope, stores[i].getSubtreeFolder());
		}

		return allFolders;
	},

	/**
	 * Checks whether any of the stores that were included in the parameters during the last load,
	 * matches the supplied entryid argument.
	 *
	 * Although the HierarchyStore can return true for all storeentryids (it contains the entire
	 * hierarchy after all), it will return false. This ensures that all IPFSubStores which are
	 * found within the HierarchyStore can check if they want to have the notification themself.
	 *
	 * @param {String|Array} entryidList Entryid of the folder
	 * @return {Boolean} Returns true when entryid matches, false when it does not.
	 */
	containsStoreInLastLoad: function(entryidList)
	{
		return false;
	},

	/**
	 * Notification handler called by {@link #onNotify} when
	 * a {@link Zarafa.core.data.Notifications#newMail newMail}
	 * notification has been received.
	 *
	 * This will update all stores in which the new mail was delivered.
	 *
	 * @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 received 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 received.
	 * @private
	 */
	onNotifyNewmail: function(action, records, data, timestamp, success)
	{
		if (!Array.isArray(records)) {
			records = [ records ];
		}

		for (var i = 0, len = records.length; i < len; i++) {
			// If this notification came at the same time as we reloaded this folder,
			// we already obtained created item, and we don't need to reload.
			if (!timestamp || records[i].lastExecutionTime(Zarafa.core.Actions['list']) < timestamp) {
				/*
				 * If this notification come at the time while user live scrolling
				 * then we have to reload the mail store with all mails(limit should
				 * be total loaded mails in grid).
				 */
				if(records[i].lastOptions.actionType === Zarafa.core.Actions['updatelist']) {
					delete records[i].lastOptions.add;
					Ext.apply(records[i].lastOptions.params.restriction,{
						start: 0,
						limit: records[i].getCount()
					});
					records[i].reload(records[i].lastOptions);
				} else {
					records[i].reload();
				}
			}
		}

		// Exclude these folders for notifications.
		var folder_keys = ['wastebasket', 'sent', 'drafts', 'outbox', 'junk', 'journal'];

		//notify user about new mail
		if(!Ext.isEmpty(data)){
			Ext.each(data.item, function(folder){
				var folderStore = this.getFolder(folder.entryid);
				if (!folderStore) {
					return;
				}

				var folderKey = folderStore.getDefaultFolderKey();
				var newmail = folder.content_unread !== 0
					&& folder.content_unread > folderStore.get('content_unread');

				if (newmail && folder_keys.indexOf(folderKey) === -1 && folderStore.isContainerClass('IPF.Note')) {
					var notificationMessage;
					if ( folder.user_display_name ) {
						// This is in a shared store. We have to show the shared store's user's name too
						notificationMessage = String.format(
							ngettext('There is {0} unread message in the folder {1} of {2}', 'There are {0} unread messages in the folder {1} of {2}', folder.content_unread),
							folder.content_unread,
							folder.display_name,
							folder.user_display_name
						);
					} else {
						notificationMessage = String.format(
							ngettext('There is {0} unread message in the folder {1}', 'There are {0} unread messages in the folder {1}', folder.content_unread),
							folder.content_unread,
							folder.display_name
						);
					}

					// New mail notification should show only on main browser window.
					var mainBrowserWindowBody;
					if (!Zarafa.core.BrowserWindowMgr.isMainWindowActive()) {
						mainBrowserWindowBody = Zarafa.core.BrowserWindowMgr.getMainBrowserWindowBody();
					}

					container.getNotifier().notify('info.newmail', _('New Mail'), notificationMessage, {
						container: mainBrowserWindowBody
					});
				}

				folderStore.set('content_unread', folder.content_unread);
				folderStore.set('content_count', folder.content_count);
			}, this);
		}
	},

	/**
	 * Initialize the keepalive requests to the server. Listen to the aftersend event in the
	 * {@link Zarafa.core.Request Request} object to reset the counter every time the clients sends a
	 * request to the server.
	 */
	startKeepAlive: function()
	{
		var interval = container.getSettingsModel().get('zarafa/v1/contexts/hierarchy/polling_interval');

		// add a listener event that will fire automatically after specified interval
		if (Ext.isNumber(interval) && interval > 0) {
			container.getRequest().on('aftersend', this.sendKeepAlive, this, { buffer: interval * 1000});
		}
	},

	/**
	 * Sends a request to the server with only an action type and no other parameters. It will also not
	 * wait for / handle a response.
	 * @param {Zarafa.core.Actions} action The action type that will be send to the server
	 * @param {Function} callback call back function to call when the request has finished successfully.
	 * @param {Array} params The request params.
	 */
	sendSimpleActionToServer: function(action, callback, params)
	{
		var options = {
			params: Ext.isDefined(params)? params : {},
			actionType: action
		};

		// fire request
		this.proxy.request(Ext.data.Api.actions['read'], null, options.params, this.reader, Ext.isFunction(callback) ? callback : Ext.emptyFn, this, options);
	},

	/**
	 * Function will be used to send <b>keepalive</b> requests to the server so we will be always logged
	 * on server and don't need to relogin after some time even if user doesn't perform any action.
	 * function is created on a fingerprint of {@link #load} method, which performs a 'read' operation
	 * for {@link Zarafa.hierarchy.data.HierarchyStore HierarchyStore}.
	 * Reason to create this function was we don't have any operation type for keepalive requests
	 * other than CRUD so what we do is call {@link Zarafa.hierarchy.data.HierarchyProxy#request}
	 * method of {@link Zarafa.hierarchy.data.HierarchyProxy HierarchyProxy} with 'read' operation
	 * and 'keepalive' as {@link #actionType} in options, we also need to pass Ext.emptyFn as callback function
	 * and this way don't interfere with the working of {@link #load} and {@link #reload} functionalities.
	 * @private
	 */
	sendKeepAlive: function()
	{
		this.sendSimpleActionToServer(Zarafa.core.Actions['keepalive']);
	},

	/**
	 * Send a <b>sharestoreupdate</b> request to make the backend check if there are unread emails
	 * in one of the opened shared stores.
	 * @private
	 */
	sendSharedStoreHierarchyUpdate: function()
	{
		this.sendSimpleActionToServer(Zarafa.core.Actions['sharedstoreupdate']);
	},

	/**
	 * Function will be used to send {Zarafa.core.Actions#destroysession destroysession} requests to the server. This will be used when
	 * CLIENT_TIMEOUT has been set in the configuration and the user has been idle for this time.
	 * Function is created on a fingerprint of {@link #load} method, which performs a 'read' operation
	 * for {@link Zarafa.hierarchy.data.HierarchyStore HierarchyStore}.
	 * Reason to create this function was we don't have any operation type for destroysession requests
	 * other than CRUD so what we do is call {@link Zarafa.hierarchy.data.HierarchyProxy#request}
	 * method of {@link Zarafa.hierarchy.data.HierarchyProxy HierarchyProxy} with 'read' operation
	 * and {Zarafa.core.Actions#destroysession destroysession} as {@link #actionType} in options,
	 * we also need to pass Ext.emptyFn as callback function and this way don't interfere with
	 * the working of {@link #load} and {@link #reload} functionalities.
	 * @private
	 */
	sendDestroySession: function()
	{
		this.sendSimpleActionToServer(Zarafa.core.Actions['destroysession']);
	},

	/**
	 * Intialialize the {@link #state state component}
	 * @private
	 */
	initState: function()
	{
		if (!this.state) {
			this.state = new Zarafa.hierarchy.data.HierarchyState();
		}
	},

	/**
	 * Obtain the path in which the {@link #getState state} must be saved.
	 * This option is only used when the {@link Zarafa.core.data.SettingsStateProvider SettingsStateProvider} is
	 * used in the {@link Ext.state.Manager}.
	 * This calls {@link Zarafa.hierarchy.data.HierarchyState#getStateNameForFolder getStateNameForFolder} on the {@link #state}.
	 *
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder The folder for which the statename is requested
	 * @param {String} type The category for the folder for which the statename is requested (e.g. 'list' or 'tree').
	 * @return {String} The unique name for this component by which the {@link #getState state} must be saved.
	 */
	getStateName: function(folder, type)
	{
		if (this.stateful !== false) {
			return this.state.getStateNameForFolder(folder, type);
		}
	},

	/**
	 * When {@link #stateful} the State object which should be saved into the
	 * {@link Ext.state.Manager}.
	 * This calls {@link Zarafa.hierarchy.data.HierarchyState#getStateForFolder getStateForFolder} on the {@link #state}.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder The folder for which the state is requested
	 * @param {String} type The category for the folder for which the state is requested (e.g. 'list' or 'tree').
	 * @return {Object} The state object
	 */
	getState: function(folder, type)
	{
		if (this.stateful !== false) {
			return this.state.getStateForFolder(folder, type);
		}
	},

	/**
	 * Apply the given state to this object activating the properties which were previously
	 * saved in {@link Ext.state.Manager}.
	 * This calls {@link Zarafa.hierarchy.data.HierarchyState#applyStateForFolder applyStateForFolder} on the {@link #state}.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder The folder for which the state is provided
	 * @param {String} type The category for the folder for which the state is provided (e.g. 'list' or 'tree').
	 * @param {Object} state The state object
	 */
	applyState: function(folder, type, state)
	{
		if (this.stateful !== false) {
			this.state.applyStateForFolder(folder, type, state);
		}
	},

	/**
	 * Notification handler called by {@link #onNotify} when
	 * a {@link Zarafa.core.data.Notifications#objectModified objectModified}
	 * notification has been received.
	 *
	 * This will update the mapistore's information in hierarhcyStore.
	 *
	 * @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 received 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 received.
	 * @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);
		}
	},

	/**
	 * Returns folder(s) which has the provided container-class except folders
	 * in "deleted items" or "Junk Email".
	 * @param {String} containerClass The container_class of the folders.
	 * @return {Zarafa.core.data.IPFRecord[]} An array of folders
	 */
	getByContainerClass: function(containerClass)
	{
		var finalResult = [];

		this.getStores().forEach( function(store) {
			var subFolders = store.getFolderStore().getRange();
			var subResult = subFolders.filter( function(folder) {
				return folder.get('container_class') === containerClass && !folder.isInDeletedItems();
			});

			finalResult = finalResult.concat(subResult);
		});

		return finalResult;
	}
});

Ext.reg('zarafa.hierarchystore', Zarafa.hierarchy.data.HierarchyStore);
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 occurred 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 received,
	 * '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 received.
	 *
	 * 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 received 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 received.
	 * @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 received.
	 *
	 * 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 received 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 received.
	 * @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 received.
	 *
	 * 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 received 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 received.
	 * @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/' + Zarafa.core.Util.bin2hex(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);
Ext.namespace('Zarafa.hierarchy.data');

/**
 * @class Zarafa.hierarchy.data.MAPIFolderPermissionsSubStore
 * @extends Zarafa.core.data.MAPISubStore
 */
Zarafa.hierarchy.data.MAPIFolderPermissionsSubStore = Ext.extend(Zarafa.core.data.MAPISubStore, {
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

		var recordType = Zarafa.core.data.RecordFactory.getRecordClassByCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_USER_PERMISSION);

		Ext.applyIf(config, {
			writer: new Zarafa.hierarchy.data.JsonPermissionsWriter(),
			// provide a default reader
			reader: new Zarafa.core.data.JsonReader({
				root: 'item',
				dynamicRecord: false
			}, recordType)
		});

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

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

/**
 * @class Zarafa.hierarchy.data.MAPIFolderSubStore
 * @extends Zarafa.core.data.MAPISubStore
 *
 * Substore which contains {@link Zarafa.hierarchy.data.MAPIFolderRecord folder} records,
 * which should not be serialized back to the server as they are only used for display
 * purposes in the {@link Zarafa.hierarchy.dialog.FolderSizeContentPanel}.
 */
Zarafa.hierarchy.data.MAPIFolderSubStore = Ext.extend(Zarafa.core.data.MAPISubStore, {
	/**
	 * @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, {
			reader: new Zarafa.core.data.JsonReader({ dynamicRecord: false }, recordType)
		});

		Zarafa.hierarchy.data.MAPIFolderSubStore.superclass.constructor.call(this, config);
	}
});
/*
 * #dependsFile client/zarafa/core/mapi/Rights.js
 */
Ext.namespace('Zarafa.hierarchy.data');

/**
 * @class Zarafa.hierarchy.data.PermissionProfiles
 * @singleton
 */
Zarafa.hierarchy.data.PermissionProfiles = [{
	value: Zarafa.core.mapi.Rights.RIGHTS_OWNER,
	name: _('Owner')
},{
	value: Zarafa.core.mapi.Rights.RIGHTS_PUBLISHINGEDITOR,
	name: _('Publishing Editor')
},{
	value: Zarafa.core.mapi.Rights.RIGHTS_EDITOR,
	name: _('Editor')
},{
	value: Zarafa.core.mapi.Rights.RIGHTS_PUBLISHINGAUTHOR,
	name: _('Publishing Author')
},{
	value: Zarafa.core.mapi.Rights.RIGHTS_AUTHOR,
	name: _('Author')
},{
	value: Zarafa.core.mapi.Rights.RIGHTS_NONEDITINGAUTHOR,
	name: _('Nonediting Author')
},{
	value: Zarafa.core.mapi.Rights.RIGHTS_REVIEWER,
	name: _('Reviewer')
},{
	value: Zarafa.core.mapi.Rights.RIGHTS_CONTRIBUTOR,
	name: _('Contributor')
},{
	value: Zarafa.core.mapi.Rights.RIGHTS_NO_RIGHTS,
	name: _('None')
},{
	value: null,
	name: _('Custom')
}];

/**
 * @class Zarafa.hierarchy.data.PermissionProfilesCalendar
 * @singleton
 */
Zarafa.hierarchy.data.PermissionProfilesCalendar = [{
	value: Zarafa.core.mapi.Rights.RIGHTS_CAL_OWNER,
	name: _('Owner')
},{
	value: Zarafa.core.mapi.Rights.RIGHTS_CAL_PUBLISHINGEDITOR,
	name: _('Publishing Editor')
},{
	value: Zarafa.core.mapi.Rights.RIGHTS_CAL_EDITOR,
	name: _('Editor')
},{
	value: Zarafa.core.mapi.Rights.RIGHTS_CAL_PUBLISHINGAUTHOR,
	name: _('Publishing Author')
},{
	value: Zarafa.core.mapi.Rights.RIGHTS_CAL_AUTHOR,
	name: _('Author')
},{
	value: Zarafa.core.mapi.Rights.RIGHTS_CAL_NONEDITINGAUTHOR,
	name: _('Nonediting Author')
},{
	value: Zarafa.core.mapi.Rights.RIGHTS_CAL_REVIEWER,
	name: _('Reviewer')
},{
	value: Zarafa.core.mapi.Rights.RIGHTS_CAL_CONTRIBUTOR,
	name: _('Contributor')
},{
	value: Zarafa.core.mapi.Rights.RIGHTS_CAL_FBDETAILED,
	name: _('Free/Busy time, subject, location')
},{
	value: Zarafa.core.mapi.Rights.RIGHTS_CAL_FBSIMPLE,
	name: _('Free/Busy time')
},{
	value: Zarafa.core.mapi.Rights.RIGHTS_NO_RIGHTS,
	name: _('None')
},{
	value: null,
	name: _('Custom')
}];
/*
 * #dependsFile client/zarafa/common/data/FolderContentTypes.js
 */
Ext.namespace('Zarafa.common.data');

/**
 * @class Zarafa.hierarchy.data.SharedFolderTypes
 * @singleton
 * This class can be used to set folder types.
 */
Zarafa.hierarchy.data.SharedFolderTypes = {
	/**
	 * Used for opening the Default Calendar folder
	 * @property
	 * @type String
	 */
	'APPOINTMENT': 'calendar',

	/**
	 * Used for opening the Default Contact folder
	 * @property
	 * @type String
	 */
	'CONTACT': 'contact',

	/**
	 * Used for opening the Entire Store
	 * @property
	 * @type String
	 */
	'ALL': 'all',

	/**
	 * Used for opening the Inbox folder
	 * @property
	 * @type String
	 */
	'MAIL': 'inbox',

	/**
	 * Used for opening the StickyNote folder
	 * @property
	 * @type String
	 */
	'NOTE': 'note',

	/**
	 * Used for opening the Task folder
	 * @property
	 * @type String
	 */
	'TASK': 'task',

	/**
	 * Array containing the {@link Zarafa.hierarchy.data.SharedFolderTypes Shared Folder Types} combined
	 * with the DisplayName. This array can be directly used as data in {@link Ext.ComboBox#store}.
	 * @property
	 * @type Array
	 */
	folders: [{
		value: 'calendar',
		name: Zarafa.common.data.FolderContentTypes.getFolderName(Zarafa.common.data.FolderContentTypes['appointment'])
	},{
		value: 'contact',
		name: Zarafa.common.data.FolderContentTypes.getFolderName(Zarafa.common.data.FolderContentTypes['contact'])
	},{
		value: 'all',
		name: Zarafa.common.data.FolderContentTypes.getFolderName(Zarafa.common.data.FolderContentTypes['ipmsubtree'])
	},{
		value: 'inbox',
		name: Zarafa.common.data.FolderContentTypes.getFolderName(Zarafa.common.data.FolderContentTypes['mail'])
	},{
		value: 'note',
		name: Zarafa.common.data.FolderContentTypes.getFolderName(Zarafa.common.data.FolderContentTypes['note'])
	},{
		value: 'task',
		name: Zarafa.common.data.FolderContentTypes.getFolderName(Zarafa.common.data.FolderContentTypes['task'])
	}]
};
/*
 * #dependsFile client/zarafa/core/mapi/ObjectType.js
 * #dependsFile client/zarafa/core/data/RecordCustomObjectType.js
 */
Ext.namespace('Zarafa.hierarchy.data');

/**
 * @class Zarafa.hierarchy.data.UserPermissionsFields
 *
 * Array of {@link Ext.data.Field field} configurations for the
 * {@link Zarafa.hierarchy.data.UserPermissionsFields PermissionsRecord} object.
 */
Zarafa.hierarchy.data.UserPermissionsFields = [
	{name: 'entryid'},
	{name: 'display_name'},
	{name: 'object_type', type: 'int', defaultValue: Zarafa.core.mapi.ObjectType.MAPI_MAILUSER},
	{name: 'rights', type: 'int', defaultValue: Zarafa.core.mapi.Rights.RIGHTS_NONE}
];

/**
 * @class Zarafa.hierarchy.data.UserPermissionsRecord
 * @extends Zarafa.core.data.MAPIRecord
 *
 * An extension to the {@link Zarafa.core.data.MAPIRecord MAPIRecord} specific to records which are
 * used as user permissions record
 */
Zarafa.hierarchy.data.UserPermissionsRecord = Ext.extend(Zarafa.core.data.MAPIRecord, {

	/**
	 * Copy the {@link Zarafa.core.data.MAPIRecord Record} to a new instance
	 * @param {String} newId (optional) A new Record id, defaults to the id of the record being copied. See id.
	 * @return {Zarafa.core.data.MAPIRecord} The copy of the record.
	 */
	copy: function(newId)
	{
		var copy = Zarafa.core.data.RecordFactory.createRecordObjectByCustomType(
			Zarafa.core.data.RecordCustomObjectType.ZARAFA_USER_PERMISSION,
			this.data,
			newId || this.id
		);

		copy.idProperties = this.idProperties.clone();
		copy.phantom = this.phantom;

		return copy.applyData(this, true);
	}
});

// Register a custom type to be used by the Record Factory
Zarafa.core.data.RecordCustomObjectType.addProperty('ZARAFA_USER_PERMISSION');

Zarafa.core.data.RecordFactory.setBaseClassToCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_USER_PERMISSION, Zarafa.hierarchy.data.UserPermissionsRecord);
Zarafa.core.data.RecordFactory.addFieldToCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_USER_PERMISSION, Zarafa.hierarchy.data.UserPermissionsFields);
Ext.namespace('Zarafa.hierarchy.dialogs');

/**
 * @class Zarafa.hierarchy.dialogs.FolderPropertiesContentPanel
 * @extends Zarafa.core.ui.RecordContentPanel
 * @xtype folderzarafa.propertiescontentpanel
 *
 * This will display a {@link Zarafa.core.ui.ContentPanel contentpanel}
 * for general properties of {@link Zarafa.hierarchy.data.MAPIFolderRecord folder} and set permissions on same.
 */
Zarafa.hierarchy.dialogs.FolderPropertiesContentPanel = Ext.extend(Zarafa.core.ui.RecordContentPanel, {
	/**
	 * @constructor
	 * @param config Configuration structure
	 */

	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			// Override from Ext.Component
			xtype: 'zarafa.folderpropertiescontentpanel',
			layout: 'fit',
			title: Ext.isDefined(config.title) ? config.title : _('Properties'),
			recordComponentPluginConfig: Ext.applyIf(config.recordComponentPluginConfig || {}, {
				allowWrite: true,
				useShadowStore: true
			}),
			closeOnSave: true,
			width: 425,
			height: 550,
			items: [{
				xtype: 'zarafa.folderpropertiespanel',
				activeTab: Ext.isDefined(config.activeTab) ? config.activeTab : 0,
				emptyText: config.emptyText,
				isAppointmentDialog: config.isAppointmentDialog || false,
				buttons: [{
					text: _('Ok'),
					handler: this.onOk,
					scope: this
				},{
					text: _('Cancel'),
					handler: this.onCancel,
					scope: this
				}]
			}]
		});

		Zarafa.hierarchy.dialogs.FolderPropertiesContentPanel.superclass.constructor.call(this, config);
	},

		/**
	 * Save all changes made to the {@link #record} to the server.
	 * @param {Boolean} storeSave (optional) False to only update the record,
	 * but not save the changes to the store.
	 * @return {Boolean} false if the record could not be saved
	 * @protected
	 */
		saveRecord: function(storeSave)
		{
			// Check if saving is allowed, and if by chance we aren't
			// saving already.
			if (this.recordComponentPlugin.allowWrite === false || this.isSaving === true) {
				return false;
			}
	
			if (this.fireEvent('beforesaverecord', this, this.record) === false) {
				return false;
			}
	
			// Check if the record is valid before saving.
			if (!this.record.isValid()) {
				return false;
			}
	
			this.fireEvent('saverecord', this, this.record);
	
			if (Ext.isDefined(this.modalRecord)) {
				this.modalRecord.applyData(this.record);
			}
	
			if (storeSave !== false) {
				var record = this.modalRecord || this.record;
	
				// Check if the record has actual modifications which
				// we can save to the server. record.save() will do
				// nothing if the store doesn't have the record in
				// the modifications array. So we must prevent going
				// any further here when there are not modifications.
				if (record.getStore().modified.indexOf(record) < 0) {
					if (this.closeOnSave === true) {
						this.close();
					}
					return;
				}
	
				// When the HTML body has been modified we must also send the isHTML property
				// with the save request because otherwise the backend will think this is an
				// plaintext record. (See Conversion::mapXML2MAPI())
				if ( record.isModified('html_body') ){
					record.set('isHTML', record.get('isHTML'), true);
				}
	
				record.save();
			}
			container.getHierarchyStore().reload();
		}
});

Ext.reg('zarafa.folderpropertiescontentpanel', Zarafa.hierarchy.dialogs.FolderPropertiesContentPanel);
Ext.namespace('Zarafa.hierarchy.dialogs');

/**
 * @class Zarafa.hierarchy.dialogs.FolderSizeContentPanel
 * @extends Zarafa.core.ui.RecordContentPanel
 * @xtype folderzarafa.sizecontentpanel
 *
 * This will display a {@link Zarafa.core.ui.ContentPanel contentpanel}
 * for displaying the size of the {@link Zarafa.hierarchy.data.MAPIFolderRecord folder} and the subfolders.
 */
Zarafa.hierarchy.dialogs.FolderSizeContentPanel = Ext.extend(Zarafa.core.ui.RecordContentPanel, {
	/**
	 * @constructor
	 * @param config Configuration structure
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			// Override from Ext.Component
			xtype: 'zarafa.foldersizecontentpanel',
			layout: 'fit',
			title: _('Folder Size'),
			recordComponentPluginConfig: Ext.applyIf(config.recordComponentPluginConfig || {}, {
				allowWrite: true,
				useShadowStore: true
			}),
			closeOnSave: true,
			width: 360,
			height: 360,
			items: [{
				xtype: 'zarafa.foldersizepanel',
				buttons: [{
					text: _('Close'),
					handler: this.close,
					scope: this
				}]
			}]
		});

		Zarafa.hierarchy.dialogs.FolderSizeContentPanel.superclass.constructor.call(this, config);
	}
});

Ext.reg('zarafa.foldersizecontentpanel', Zarafa.hierarchy.dialogs.FolderSizeContentPanel);
Ext.namespace('Zarafa.hierarchy.ui');

/**
 * @class Zarafa.hierarchy.ui.MultiSelectHierarchyTree
 * @extends Zarafa.hierarchy.ui.HierarchyTreePanel
 * @xtype zarafa.multiselecthierarchytree
 *
 * Subclass of {@link Zarafa.hierarchy.ui.HierarchyTreePanel HierarchyTree} which will
 * allow multiple selection of folders. This requires the {@link Zarafa.core.Context Context} to fully
 * support showing data from multiple folders at the same time.
 */
Zarafa.hierarchy.ui.MultiSelectHierarchyTree = Ext.extend(Zarafa.hierarchy.ui.HierarchyTreePanel, {
	/**
	 * @cfg {Boolean} Whether to apply colors to this tree's nodes
	 * This is useful when multiple {@link Zarafa.hierarchy.data.MAPIFolderRecord} folders are allowed to be selected
	 */
	colored: false,

	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		// Default Node Config
		config.nodeConfig = Ext.applyIf(config.nodeConfig || {}, {
			checked: false
		});

		Zarafa.hierarchy.ui.MultiSelectHierarchyTree.superclass.constructor.call(this, config);

		if (Ext.isDefined(this.model)) {
			this.on('checkchange', this.onTreeNodeCheckChange, this);
			this.on('click', this.onTreeNodeClick, this);
			this.mon(this.model, 'folderchange', this.onFolderChange, this);
			this.mon(this.model, 'activate', this.onCalendarActivate, this);
		}
	},

	/**
	 * Called after the tree has been {@link #render rendered} This will initialize
	 * Remove listeners on Zarafa.hierarchy.ui.Tree click events
	 * @private
	 */
	initEvents: function ()
	{
		Zarafa.hierarchy.ui.MultiSelectHierarchyTree.superclass.initEvents.call(this);
		this.un('click', this.onFolderClicked, this);
	},

	/**
	 * Called when a treeNode is click in tree. The corresponding folder is added to,
	 * or removed from the active folder list depending on the state of the check box.
	 * @param {Ext.tree.TreeNode} treeNode tree node.
	 * @private
	 */
	onTreeNodeClick: function(treeNode)
	{
		var treeNodeui = treeNode.getUI();
		if (treeNodeui.checkbox.checked && treeNode.isNodeSelected) {
			treeNodeui.toggleCheck(false);
			return false;
		}
		var folder = treeNode.getFolder();
		this.model.addFolder(folder);
		treeNode.isNodeSelected = true;
		treeNodeui.toggleCheck(true);
	},

	/**
	 * Manual selection of the treeNode to which the folder is attached in the tree.
	 * This will first ensure the given folder {@link #ensureFolderVisible is visible}
	 * and will then {@link Ext.tree.DefaultSelectionModel#select select the given node} in the tree.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder The folder to select
	 * @return {Boolean} True when the TreeNode for the given folder existed, and could be selected.
	 */
	selectFolderInTree: function(folder)
	{
		var treeNode = this.ensureFolderVisible(folder);
		if (treeNode) {
			this.getSelectionModel().select(treeNode);
			return true;
		} else {
			return false;
		}
	},

	/**
	 * Called when a check box in the calendar tree is toggled. The corresponding folder is added to,
	 * or removed from the active folder list depending on the state of the check box.
	 * @param {Ext.tree.TreeNode} node tree node.
	 * @param {Boolean} checked indicates whether the box is checked.
	 * @private
	 */
	onTreeNodeCheckChange: function(node, checked)
	{
		var folder = node.getFolder();
		if (checked) {
			if (!node.isNodeSelected) {
				this.fireEvent('click', node);
			}
		} else {
			node.isNodeSelected = false;
			this.model.removeFolder(folder);
		}
	},

	/**
	 * Handles a folderchange event from the model. This occurs when the user selects or deselects a folder from the folder hierarchy.
	 * @param {Zarafa.core.ContextModel} model Context model that fired the event.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord[]} folders list of currently selected folders.
	 * @private
	 */
	onFolderChange: function(model, folders)
	{
		this.updateAll();

		// It is possible to choose color from simple HierarchyTree as well.
		// MultiSelectHierarchyTree should sync the color chosen from other tree, so let us do that.
		for (var i = 0; i < folders.length; i++) {
			// traverse through all the available folders, and set the color if changed in other tree.
			var folderEntryid = folders[i].get('entryid');
			var node = this.getNodeById(folderEntryid);
			if (Ext.isDefined(node)) {
				var colorScheme = model.getColorScheme(folderEntryid);
				Ext.get(node.getUI().iconNode).setStyle('color', colorScheme.base);
			}
		}
	},

	/**
	 * Handles a activate event from the model.This occurs when the user select calendar by clicking on calendar tab.
	 * Set the corresponding folder as active folder in hierarchy.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord[]} folder which will mark as selected
	 */
	onCalendarActivate: function(folder)
	{
		var selectedNode = this.getNodeById(folder.get('entryid'));
		if (selectedNode) {
			selectedNode.select();
		}
	}
});

Ext.reg('zarafa.multiselecthierarchytree', Zarafa.hierarchy.ui.MultiSelectHierarchyTree);
Ext.namespace('Zarafa.mail');

/**
 * @class Zarafa.mail.MailContextModel
 * @extends Zarafa.core.ContextModel
 */
Zarafa.mail.MailContextModel = Ext.extend(Zarafa.core.ContextModel, {
	/**
	 * When searching, this property marks the {@link Zarafa.core.ContextModel#getCurrentDataMode datamode}
	 * which was used before {@link #onSearchStart searching started} the datamode was switched to
	 * {@link Zarafa.mail.data.DataModes#SEARCH}.
	 * @property
	 * @type Mixed
	 * @private
	 */
	oldDataMode: undefined,

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

		if(!Ext.isDefined(config.store)) {
			config.store = new Zarafa.mail.MailStore();
		}

		Ext.applyIf(config, {
			statefulRecordSelection: true,
			current_data_mode: Zarafa.mail.data.DataModes.ALL
		});

		Zarafa.mail.MailContextModel.superclass.constructor.call(this, config);

		this.on({
			'searchstart': this.onSearchStart,
			'searchstop': this.onSearchStop,
			scope: this
		});
	},

	/**
	 * Create a new {@link Zarafa.core.data.IPMRecord IPMRecord} which must be used within
	 * the {@link Zarafa.mail.dialogs.MailCreateContentPanel MailCreateContentPanel}.
	 * @param {Zarafa.core.data.MAPIFolder} folder folder in which new record should be created.
	 * @return {Zarafa.core.data.IPMRecord} The new {@link Zarafa.core.data.IPMRecord IPMRecord}.
	 */
	createRecord: function(folder)
	{
		folder = folder || container.getHierarchyStore().getDefaultFolder('drafts');

		var signatureId = this.getSignatureId();

		var record = Zarafa.core.data.RecordFactory.createRecordObjectByMessageClass('IPM.Note', {
			store_entryid: folder.get('store_entryid'),
			parent_entryid: folder.get('entryid'),
			body: this.getSignatureData(false, signatureId),
			html_body: this.getSignatureData(true, signatureId),
			isHTML: container.getSettingsModel().get('zarafa/v1/contexts/mail/dialogs/mailcreate/use_html_editor')
			// @todo should set From properties differently if replying for someone else's store
		});

		return record;
	},

	/**
	 * Function will create a new {@link Zarafa.core.data.IPMRecord IPMRecord} for responding to an original
	 * {@link Zarafa.core.data.IPMRecord IPMRecord}. This will also set subject, body, attachment, recipient
	 * properties based on {@link Zarafa.mail.data.ActionTypes ActionType} provided.
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The original {@link Zarafa.core.data.IPMRecord IPMRecord}.
	 * @param {String} actionType The action type for the given {@link Zarafa.core.data.IPMRecord record}.
	 * Can be any of the values of {@link Zarafa.mail.data.ActionTypes ActionTypes}.
	 * @param {Zarafa.core.data.IPMRecord} responseRecord The new {@link Zarafa.core.data.IPMRecord IPMRecord}.
	 * @param {Object} (Optional) config The optional configuration.
	 * @private
	 */
	createResponseRecord: function(record, actionType, responseRecord, config)
	{
		// FIXME: Error message?
		if (Ext.isEmpty(actionType) || !record) {
			return;
		}

		var isMultipleItems = false;
		if (Ext.isDefined(responseRecord)) {
			isMultipleItems = true;
		}
		else {
			responseRecord = this.createRecord();
		}

		// Set the Message action for the record. This will instruct
		// the server side to update the original message accordingly.
		responseRecord.addMessageAction('action_type', actionType);

		// By copying the reference to the original mail,
		// the server is able to update that mail and add
		// reply/forward flags to it.
		responseRecord.addMessageAction('source_entryid', record.get('entryid'));
		responseRecord.addMessageAction('source_store_entryid', record.get('store_entryid'));

		this.setSourceMessageInfo(record, actionType, responseRecord);

		var attachNum = record.get('attach_num');
		if(!Ext.isEmpty(attachNum) && actionType!==Zarafa.mail.data.ActionTypes.EDIT_AS_NEW) {
			responseRecord.addMessageAction('source_attach_num', attachNum);
		}

		// initialize properties of response record
		if (actionType === Zarafa.mail.data.ActionTypes.EDIT_AS_NEW){
			this.copyRecordRecipients(responseRecord, record);
			this.copySendAsIdentity(responseRecord, record);
		} else {
			var mapiFolderStore = this.getDefaultFolder().getMAPIFolderStore();
			var folderIndex = mapiFolderStore.find('entryid', record.get('parent_entryid'));
			var folder = mapiFolderStore.getAt(folderIndex);
			var isSentFolder = folder ? folder.getDefaultFolderKey() === 'sent' : false;
			this.initRecordRecipients(responseRecord, record, actionType, isSentFolder);
		}

		this.initRecordSubject(responseRecord, record, actionType);

		if (actionType === Zarafa.mail.data.ActionTypes.FORWARD_ATTACH) {
			responseRecord.getAttachmentStore().addAsAttachment(record, config);
		} else if (actionType === Zarafa.mail.data.ActionTypes.EDIT_AS_NEW) {
			this.copyRecordBody(responseRecord, record);
			this.initRecordAttachments(responseRecord, record, actionType);
		} else {
			this.initRecordBody(responseRecord, record, actionType);
			this.initRecordAttachments(responseRecord, record, actionType);
		}

		if (isMultipleItems) {
			if (container.getSettingsModel().get('zarafa/v1/contexts/mail/use_english_abbreviations')) {
				responseRecord.set('subject', ('Fwd') + ': ');
			} else {
				responseRecord.set('subject', _('Fwd') + ': ');
			}
		}

		// If the record we are replying is in other user's store then set delegator info.
		if (!Ext.isFunction(record.userIsStoreOwner) || !record.userIsStoreOwner()) {
			var storeOwner = container.getHierarchyStore().getById(record.get('store_entryid'));

			if(storeOwner) {
				responseRecord.setDelegatorInfo(storeOwner);
			}
		}

		return responseRecord;
	},

	/**
	 * Function is used to set the source message action type.
	 * @param {Zarafa.core.data.IPMRecord} record The original {@link Zarafa.core.data.IPMRecord IPMRecord}.
	 * @param {String} actionType The action type for the given {@link Zarafa.core.data.IPMRecord record}.
	 * @param {Zarafa.core.data.IPMRecord} responseRecord The new {@link Zarafa.core.data.IPMRecord IPMRecord}.
	 * Can be any of the values of {@link Zarafa.mail.data.ActionTypes ActionTypes}.
	 */
	setSourceMessageInfo: function(record, actionType, responseRecord)
	{
		// Hack alert !
		// we are not able to identify the 0x85CE named property, So here we hardcode first 24byte
		// value of the record, based on action type (reply, replyall, forward) and add 48byte
		// entryid at the end.
		var sourceMessageAction;
		switch(actionType) {
			case 'reply':
					sourceMessageAction = "0501000066000000";
				break;
			case 'replyall':
					sourceMessageAction = "0501000067000000";
				break;
			case 'forward':
			case 'forward_attach':
					sourceMessageAction = "0601000068000000";
				break;
		}
		if (sourceMessageAction) {
			var sourceMessageInfo = "01000E000C000000" + sourceMessageAction + "0200000030000000" + record.get('entryid');
			responseRecord.set('source_message_info', sourceMessageInfo);
		}
	},

	/**
	 * Initialize the {@link Zarafa.core.data.IPMRecord record} with an updated
	 * subject. This will prefix the previous subject with 'Re' or 'Fwd',
	 * depending on the given action type.
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record to initialize
	 * @param {Zarafa.core.data.IPMRecord} origRecord The original record
	 * to which the respond is created
	 * @param {Zarafa.mail.data.ActionTypes} actionType The actionType used
	 * for this response.
	 * @private
	 */
	initRecordSubject: function(record, origRecord, actionType)
	{
		var subjectPrefix;
		var english_abb = container.getSettingsModel().get('zarafa/v1/contexts/mail/use_english_abbreviations');

		switch (actionType)
		{
			case Zarafa.mail.data.ActionTypes.REPLY:
			case Zarafa.mail.data.ActionTypes.REPLYALL:
				if (english_abb) {
					subjectPrefix = ('Re') + ': ';
				} else {
					subjectPrefix = _('Re') + ': ';
				}
				break;
			case Zarafa.mail.data.ActionTypes.FORWARD:
			case Zarafa.mail.data.ActionTypes.FORWARD_ATTACH:
				if (english_abb) {
					subjectPrefix = ('Fwd') + ': ';
				} else {
					subjectPrefix = _('Fwd') + ': ';
				}
				break;
			case Zarafa.mail.data.ActionTypes.EDIT_AS_NEW:
				subjectPrefix = '';
				break;
			default:
				// FIXME: Error message?
				subjectPrefix = _('Re') + ': ';
				break;
		}

		var normalizedSubject = origRecord.get('normalized_subject');
		record.set('subject', subjectPrefix + normalizedSubject);
		record.set('normalized_subject', normalizedSubject);
	},

	/**
	 * Get the best available plain-text body from a record.
	 * @param {Zarafa.core.data.IPMRecord} record
	 * @return {String}
	 * @private
	 */
	getRecordPlainBody: function(record)
	{
		return record.getBody(false) || record.get('body') || '';
	},

	/**
	 * Get the best available HTML body from a record with robust fallbacks.
	 * @param {Zarafa.core.data.IPMRecord} record
	 * @param {String} htmlBody preferred HTML body candidate (used only when the
	 * record has no raw html_body of its own)
	 * @param {String} plainBody plain-text fallback body
	 * @return {String}
	 * @private
	 */
	getBestEffortHtmlBody: function(record, htmlBody, plainBody)
	{
		htmlBody = htmlBody || '';

		// Prefer the record's own html_body whenever it exists, even if isHTML is
		// stale-false (e.g. a body-less notifier merged isHTML=false onto an
		// already-opened record). The supplied candidate may be plain-text derived.
		var rawHtmlBody = record.get('html_body') || '';
		if (!Ext.isEmpty(rawHtmlBody)) {
			if (Ext.isFunction(record.inlineImgOutlookToZarafa)) {
				rawHtmlBody = record.inlineImgOutlookToZarafa(rawHtmlBody);
			}
			if (container.getServerConfig().getDOMPurifyEnabled()) {
				rawHtmlBody = DOMPurify.sanitize(rawHtmlBody);
			}
			if (Ext.isFunction(record.cleanupOutlookStyles)) {
				rawHtmlBody = record.cleanupOutlookStyles(rawHtmlBody);
			}
			htmlBody = rawHtmlBody;
		}

		if (Ext.isEmpty(htmlBody)) {
			htmlBody = Zarafa.core.HTMLParser.convertPlainToHTML(plainBody || this.getRecordPlainBody(record));
		}

		if (!Ext.isEmpty(htmlBody) && container.getServerConfig().getDOMPurifyEnabled()) {
			htmlBody = DOMPurify.sanitize(htmlBody);
		}

		return String(htmlBody);
	},

	/**
	 * Initialize the {@link Zarafa.core.data.IPMRecord record} with an updated
	 * body. This will quote the previous body as plain-text or html depending
	 * on the editors preferences.
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record to initialize
	 * @param {Zarafa.core.data.IPMRecord} origRecord The original record
	 * to which the respond is created
	 * @param {Zarafa.mail.data.ActionTypes} actionType The actionType used
	 * for this response.
	 * @private
	 */
	initRecordBody: function(record, origRecord, actionType)
	{
		var signatureId = this.getSignatureId(actionType);

		// Create a copy of the original data, the body has changed,
		// and we don't want to change the original record.
		var respondData = Ext.apply({}, origRecord.data);

		/**
		 * here we go through all the recipients in recipientStore and build the username <user@abc.com> format
		 * recipient for to and cc fields, and add then in respondData display_to and display_cc field.
		 * and we don't want to change original record,
		 */
		if(origRecord.isOpened()){
			var recipientStore = origRecord.getRecipientStore();
			var to = [];
			var cc = [];

			if (recipientStore.getCount() > 0) {
				recipientStore.each(function(recipient) {
					switch(recipient.get('recipient_type')){
						case Zarafa.core.mapi.RecipientType.MAPI_TO:
							to.push(recipient.formatRecipient());
							break;
						case Zarafa.core.mapi.RecipientType.MAPI_CC:
							cc.push(recipient.formatRecipient());
							break;
					}
				},this);

				respondData.display_to = to.join('; ');
				respondData.display_cc = cc.join('; ');
			}
		}

		var plainBody = this.getRecordPlainBody(origRecord);
		// Only seed from getSanitizedHtmlBody() when the record is genuinely HTML;
		// when isHTML is stale-false it returns a plain-text-derived body. The real
		// html_body is recovered by getBestEffortHtmlBody(). Mirrors MessageBody.js.
		var quotedHtmlBody = '';
		if (origRecord.get('isHTML') === true && Ext.isFunction(origRecord.getSanitizedHtmlBody)) {
			quotedHtmlBody = origRecord.getSanitizedHtmlBody() || '';
		}
		quotedHtmlBody = this.getBestEffortHtmlBody(origRecord, quotedHtmlBody, plainBody);

		respondData.body = quotedHtmlBody;
		respondData.signatureData = this.getSignatureData(true, signatureId);
		respondData.fontFamily = container.getSettingsModel().get('zarafa/v1/main/default_font');
		respondData.fontSize = Zarafa.common.ui.htmleditor.Fonts.getDefaultFontSize();

		record.set('html_body', Zarafa.mail.data.Templates.htmlQuotedTemplate.apply(respondData));

		// Initialize plain-text body
		respondData.body = plainBody;
		respondData.signatureData = this.getSignatureData(false, signatureId);

		// Prefix each line with the '> ' sign to indicate
		// it is being quoted. Wrap the text around 72 chars.
		const text = respondData.body;
		let newText = "> ";
		let count = 0;
		for (let i=0; i < text.length; ++i) {
			if ((text[i] === " " && count > 72) || text[i] === "\n") {
				newText += "\n> ";
				count = 0;
			} else {
				newText += text[i];
				count++;
			}
		}
		respondData.body = newText;

		record.set('body', Zarafa.mail.data.Templates.plaintextQuotedTemplate.apply(respondData));
	},

	/**
	 * Initialize the {@link Zarafa.core.data.IPMRecord record} with updated
	 * recipients. This will possibly copy all recipients, or will copy the
	 * sender recipient into a To recipient depending on the given actionType.
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record to initialize
	 * @param {Zarafa.core.data.IPMRecord} origRecord The original record
	 * to which the respond is created
	 * @param {Zarafa.mail.data.ActionTypes} actionType The actionType used
	 * for this response.
	 * @param {Boolean} isSentFolder it should be true if {@link Zarafa.core.data.IPMRecord record} belong in sent folder.
	 * @private
	 */
	initRecordRecipients: function(record, origRecord, actionType, isSentFolder)
	{
		// When forwarding, we don't need to copy any recipients
		if (actionType === Zarafa.mail.data.ActionTypes.FORWARD || actionType === Zarafa.mail.data.ActionTypes.FORWARD_ATTACH) {
			return;
		}

		var store = record.getRecipientStore();

		// To prevent duplicates to be added, we keep a list of
		// all recipients which are added. Note that the contents
		// of reply-to is unconditional, and we will only be using
		// this list for the REPLYALL case.
		var addedRecipientEntryids = [];
		var addedRecipientSmtpAddrs = [];

		// Simply, Don't use reply-to information in case of "sent items"
		if(!isSentFolder) {
			// This line will prevent logged-in user from recipients,
			// When we are in 'sent items', we want to include ourself in TO,CC,BCC
			var loggedInEntryId = container.getUser().getEntryId();
			addedRecipientEntryids.push(loggedInEntryId);
			var loggedInSmtp = container.getUser().getSMTPAddress();
			if (loggedInSmtp) {
				addedRecipientSmtpAddrs.push(loggedInSmtp.toLowerCase());
			}

			// We always need to add the reply-to recipients except "sent items"
			var replyTo = origRecord.getSubStore('reply-to');

			replyTo.each(function(recipient) {
				this.addRecipientToStore(store, recipient, true);

				var recipEntryid = recipient.get('entryid');
				var recipSmtp = recipient.get('smtp_address');

				// Store entryid and smtp address of added recipient to prevent doubles
				addedRecipientEntryids.push(recipEntryid);
				if (recipSmtp) {
					addedRecipientSmtpAddrs.push(recipSmtp.toLowerCase());
				}
			}, this);
		}

		// When Replying to all recipients, start adding the originals as well
		// If we are replying from "Inbox" then skip this whole logic, as we need only "reply-to" information
		if (actionType === Zarafa.mail.data.ActionTypes.REPLYALL || (actionType === Zarafa.mail.data.ActionTypes.REPLY && isSentFolder)) {
			var origStore = origRecord.getRecipientStore();

			origStore.each(function(recipient) {
				// In case where we are replying from "sent items" folder then
				// we only skip the CC/BCC recipients, TO recipient needs to be carried forward.
				if (actionType === Zarafa.mail.data.ActionTypes.REPLY && recipient.get('recipient_type') !== Zarafa.core.mapi.RecipientType.MAPI_TO) {
					return;
				}

				var recipEntryid = recipient.get('entryid');
				var recipSmtp = recipient.get('smtp_address');

				// Check if recipient was already added by comparing
				// entryids first, then fall back to SMTP address
				// comparison.  The entryid check alone is not enough
				// because the logged-in user's AB entryid can differ
				// in type from the recipient entryid in the message.
				var recipDuplicate = false;
				if (recipEntryid) {
					for (var i = 0; i < addedRecipientEntryids.length; i++) {
						if (Zarafa.core.EntryId.compareABEntryIds(addedRecipientEntryids[i], recipEntryid)) {
							recipDuplicate = true;
							break;
						}
					}
				}
				if (!recipDuplicate && recipSmtp) {
					recipDuplicate = addedRecipientSmtpAddrs.indexOf(recipSmtp.toLowerCase()) >= 0;
				}

				if (!recipDuplicate) {
					this.addRecipientToStore(store, recipient, false);

					// Store entryid and smtp address of added recipient to prevent doubles
					addedRecipientEntryids.push(recipEntryid);
					if (recipSmtp) {
						addedRecipientSmtpAddrs.push(recipSmtp.toLowerCase());
					}
				}
			}, this);

			// Add myself back as recipient if I am replying in the sent folder and there are no recipients in the TO field.
			if(isSentFolder && store.find('recipient_type', Zarafa.core.mapi.RecipientType.MAPI_TO) === -1) {
				this.addRecipientToStore(store, origRecord.getSender(), true);
			}
		}
	},

	/**
	 * Helper function for {@link Zarafa.mail.MailContextModel#initRecordRecipients}, adds a recipient to the store.
	 *
	 * @param {Zarafa.core.data.IPMRecipientStore} store recipient store
	 * @param {Zarafa.core.data.IPMRecipientRecord} recipient which should be added to store
	 * @param {boolean} to specifies if recipient should be TO or not
	 * @private
	 */
	addRecipientToStore: function(store, recipient, to)
	{
		var recipData = Ext.apply({}, recipient.data);

		// Create a new recipient containing all data from the original.
		recipient = Zarafa.core.data.RecordFactory.createRecordObjectByCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_RECIPIENT, recipData);

		if (to) {
			recipient.set('recipient_type', Zarafa.core.mapi.RecipientType.MAPI_TO);
		}

		// We have copied the 'rowid' as well, but new recipients
		// shouldn't have this property as it will be filled in by PHP.
		recipient.set('rowid', undefined);

		store.add(recipient);

	},

	/**
	 * Copy the body (both plain text and html) of the {@link Zarafa.core.data.IPMRecord original record}
	 * to the {@link Zarafa.core.data.IPMRecord new record}.
	 * The html body will be cleaned, meaning the wrapping div that was added by
	 * the grommunio Web backend will be removed. This is necessary because it introduces
	 * problems when we paste it in TinyMCE.
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The new record
	 * @param {Zarafa.core.data.IPMRecord} origRecord The original record
	 * @private
	 */
	copyRecordBody: function(record, origRecord)
	{
		// We can simply copy the contents of the plain text body
		var plainBody = this.getRecordPlainBody(origRecord);
		record.set('body', plainBody);

		var htmlBody = origRecord.getBody(true);
		htmlBody = this.getBestEffortHtmlBody(origRecord, htmlBody, plainBody);

		// Remove the comments
		htmlBody = htmlBody.replace(/<\!\-\-.*?\-\->/gi, '');

		// Remove the wrapping div
		htmlBody = htmlBody.replace(/^\s*<div\s+class=['"]bodyclass['"]\s*>/gi, '');
		htmlBody = htmlBody.replace(/\s*<\/div\s*>\s*$/gi, '');
		record.set('html_body', htmlBody);
	},

	/**
	 * Copy the recipients of the {@link Zarafa.core.data.IPMRecord original record}
	 * to the {@link Zarafa.core.data.IPMRecord new record}.
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record to initialize
	 * @param {Zarafa.core.data.IPMRecord} origRecord The original record
	 * to which the respond is created
	 * @private
	 */
	copyRecordRecipients: function(record, origRecord)
	{
		var recipientStore = record.getRecipientStore();
		var origRecipientStore = origRecord.getRecipientStore();

		origRecipientStore.each(function(recipient) {
			var recipData = Ext.apply({}, recipient.data);

			// Create a new recipient containing all data from the original.
			recipient = Zarafa.core.data.RecordFactory.createRecordObjectByCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_RECIPIENT, recipData);

			// We have copied the 'rowid' as well, but new recipients
			// shouldn't have this property as it will be filled in by PHP.
			recipient.set('rowid', undefined);

			recipientStore.add(recipient);
		}, this);
	},

	/**
	 * Copy the SendAs / on-behalf identity (the sent_representing_* properties)
	 * of the {@link Zarafa.core.data.IPMRecord original record} to the
	 * {@link Zarafa.core.data.IPMRecord new record} for "Edit as New".
	 *
	 * Only done for a message in the user's own Sent or Drafts folder which
	 * was sent under another identity. Received messages also carry
	 * sent_representing_* (the original author), which must not become the
	 * From; shared store messages get their identity from setDelegatorInfo.
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record to initialize
	 * @param {Zarafa.core.data.IPMRecord} origRecord The original record
	 * to which the respond is created
	 * @private
	 */
	copySendAsIdentity: function(record, origRecord)
	{
		if (!Ext.isFunction(origRecord.userIsStoreOwner) || !origRecord.userIsStoreOwner()) {
			return;
		}

		var reprEntryId = origRecord.get('sent_representing_entryid');
		if (Ext.isEmpty(reprEntryId)) {
			return;
		}

		// An alias identity carries the user's own addressbook entryid with a
		// different SMTP address, so a message only counts as sent-as-self
		// when the address does not differ either.
		if (Zarafa.core.EntryId.compareABEntryIds(reprEntryId, container.getUser().getEntryId())) {
			var reprAddress = origRecord.get('sent_representing_smtp_address') ||
				origRecord.get('sent_representing_email_address');
			var ownAddress = container.getUser().getSMTPAddress();
			if (!Zarafa.core.Util.validateEmailAddress(reprAddress) ||
				String(reprAddress).toLowerCase() === String(ownAddress).toLowerCase()) {
				return;
			}
		}

		var folder = container.getHierarchyStore().getFolder(origRecord.get('parent_entryid'));
		if (!folder || (folder.getDefaultFolderKey() !== 'sent' && folder.getDefaultFolderKey() !== 'drafts')) {
			return;
		}

		record.set('sent_representing_name', origRecord.get('sent_representing_name'));
		record.set('sent_representing_email_address', origRecord.get('sent_representing_email_address'));
		record.set('sent_representing_address_type', origRecord.get('sent_representing_address_type'));
		record.set('sent_representing_entryid', reprEntryId);
		record.set('sent_representing_search_key', origRecord.get('sent_representing_search_key'));
		var smtpAddress = origRecord.get('sent_representing_smtp_address');
		if (Zarafa.core.Util.validateEmailAddress(smtpAddress)) {
			record.set('sent_representing_smtp_address', smtpAddress);
		}
	},

	/**
	 * Initialize the {@link Zarafa.core.data.IPMRecord record} with attachments
	 * in case of forward it the attachments will be copied to the record.
	 * For reply it will be added if it is a inline image.
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record to initialize
	 * @param {Zarafa.core.data.IPMRecord} origRecord The original record
	 * to which the respond is created
	 * @param {Zarafa.mail.data.ActionTypes} actionType The actionType used
	 * for this response.
	 * @private
	 */
	initRecordAttachments: function(record, origRecord, actionType)
	{
		var store = record.getAttachmentStore();

		switch (actionType)
		{
			case Zarafa.mail.data.ActionTypes.FORWARD:
			case Zarafa.mail.data.ActionTypes.FORWARD_ATTACH:
			case Zarafa.mail.data.ActionTypes.EDIT_AS_NEW:
				var origStore = origRecord.getAttachmentStore();
				origStore.each(function(attach) {
					store.add(attach.copy());
				}, this);

				// Check record store or so
				record.set('hasattach', origRecord.get('hasattach'));
			/* falls through */
			case Zarafa.mail.data.ActionTypes.REPLYALL:
			case Zarafa.mail.data.ActionTypes.REPLY:
				// TODO: handle inline image attachments
				break;
		}
	},

	/**
	 * Function is used to get signature id of the signature which should be added to the
	 * body of {@link Zarafa.core.data.IPMRecord IPMRecord} based on passed actionType.
	 * If no action type is passed then it should be considered as new mail.
	 * @param {Zarafa.mail.data.ActionTypes} actionType one of 'reply', 'forward', 'replyall'.
	 * @return {Number} signature of signature that should be added to the body.
	 */
	getSignatureId: function(actionType)
	{
		var signatureId;

		// get signature id based on action type passed
		switch (actionType) {
			case Zarafa.mail.data.ActionTypes.FORWARD:
			case Zarafa.mail.data.ActionTypes.FORWARD_ATTACH:
			case Zarafa.mail.data.ActionTypes.REPLYALL:
			case Zarafa.mail.data.ActionTypes.REPLY:
				signatureId = container.getSettingsModel().get('zarafa/v1/contexts/mail/signatures/replyforward_message', true);
				break;
			default:
				signatureId = container.getSettingsModel().get('zarafa/v1/contexts/mail/signatures/new_message', true);
				break;
		}

		return parseInt(signatureId, 10);
	},

	/**
	 * Function is used to get signature data based on passed signature id
	 * from {@link Zarafa.settings.SettingsModel SettingsModel}. It also does conversion of signature data
	 * when it needs to be converted from plain to html or vice versa.
	 * @param {Boolean} preferHTML True if the signature should be returned in HTML format else in plain format.
	 * @param {Number} signatureId id of the signature to get the data, this id can be get using {@link #getSignatureId}.
	 * @param {Boolean} withEmptyLines True (default) to add empty lines before the signature, false otherwise.
	 * @return {String} signature data that should be added to body of the {@link Zarafa.core.data.IPMRecord IPMRecord}.
	 */
	getSignatureData: function(preferHtml, signatureId, withEmptyLines)
	{
		if(!signatureId) {
			return '';
		}

		var sigDetails = container.getSettingsModel().get('zarafa/v1/contexts/mail/signatures/all/' + signatureId, true);

		if(Ext.isEmpty(sigDetails)) {
			return '';
		}

		// Create a copy of the original data
		sigDetails = Ext.apply({}, sigDetails);

		if(!Ext.isDefined(sigDetails['content'])) {
			return '';
		}

		var sigIsHtml = sigDetails['isHTML'];

		if(preferHtml === false) {
			// we want signature in plain format, so if signature is in html format then convert it to plain format
			if(sigIsHtml === true) {
				sigDetails['content'] = Zarafa.core.HTMLParser.convertHTMLToPlain(sigDetails['content']);
			}

			// Prefix the signature with two newlines
			if ( withEmptyLines !== false ) {
				sigDetails['content'] = '\n\n' + sigDetails['content'];
			}
		} else {
			if (sigIsHtml === false) {
				// we want signature in html format, so if signature is in plain format then convert it to html
				sigDetails['content'] = Zarafa.core.HTMLParser.convertPlainToHTML(sigDetails['content']);
			}

			// Prefix the signature with two newlines
			if ( withEmptyLines !== false ) {
				sigDetails['content'] = this.wrapSignature(sigDetails['content']);
			}
		}

		// Parse the signature to replace the templates
		sigDetails['content'] = this.replaceSignatureTemplates(sigDetails['content'], preferHtml);

		return sigDetails['content'];
	},

	/**
	 * Function is used to wrap the signature with div 'signatureContainer'
	 * along with two empty lines.
	 *
	 * @param {String} signature The signature which is going to wrapped.
	 * @return {String} Wrapped signature.
	 */
	wrapSignature: function(signature)
	{
		var fontFamily = container.getSettingsModel().get('zarafa/v1/main/default_font');
		var fontSize = Zarafa.common.ui.htmleditor.Fonts.getDefaultFontSize();

		var lineStyle = 'font-family:'+fontFamily+'; font-size:'+fontSize+'; padding: 0; margin: 0;';
		var emptyLine = '<p style="' + lineStyle + '"><span><br/></span></p>';

		return emptyLine + emptyLine + '<div class="signatureContainer">'+signature+'</div>';
	},

	/**
	 * Replaces the templates in a signature
	 * @param {String} signatureContent The text of the signature (can be html or plain text)
	 * @param {Boolean} preferHTML True if the signature should be returned in HTML format else in plain format.
	 * @return {String} The text of the signature with template holders replaced by their value
	 */
	replaceSignatureTemplates: function(signatureContent, preferHtml)
	{
		// First check if there are template holders in the signature
		// otherwise we can return immediately
		if ( !/{%.*}/gi.test(signatureContent) ){
			return signatureContent;
		}

		// TODO: The user information should be updated, so we will always have
		// the latest data

		// Get the user information
		var user = container.getUser();
		// Map the template holders to their data
		var map = {
			firstname		: user.getFirstName(),
			initials		: user.getInitials(),
			lastname		: user.getLastName(),
			displayname		: user.getDisplayName(),
			title			: user.getTitle(),
			company			: user.getCompany(),
			department		: user.getDepartment(),
			office			: user.getOffice(),
			assistant		: user.getAssistant(),
			phone			: user.getPhone(),
			primary_email	: user.getSMTPAddress(),
			address			: user.getAddress(),
			city			: user.getCity(),
			state			: user.getState(),
			zipcode			: user.getZipCode(),
			country			: user.getCountry(),
			phone_business	: user.getPhoneBusiness(),
			phone_business2	: user.getPhoneBusiness2(),
			phone_fax		: user.getFax(),
			phone_assistant	: user.getPhoneAssistant(),
			phone_home		: user.getPhoneHome(),
			phone_home2		: user.getPhoneHome2(),
			phone_mobile	: user.getPhoneMobile(),
			phone_pager		: user.getPhonePager()
		};

		Ext.iterate(map, function(key, value){
			if ( !Ext.isDefined(value) ){
				value = '';
			} else if ( preferHtml ){
				// Let's replace newlines with br's, to make sure that info that was entered
				// on multiple lines in ldap will also be displayed on multiple lines.
				value = Zarafa.core.HTMLParser.nl2br(Ext.util.Format.htmlEncode(value));
			}
			signatureContent = signatureContent.replace(new RegExp('{%'+key+'}', 'gi'), value);
		});

		return signatureContent;
	},

	/**
	 * Load the store using the given (optional) restriction. if
	 * {@link Zarafa.core.data.ListModuleStore#hasFilterApplied} then
	 * set the filter restriction in params and if context is going to switch
	 * then clear the filter.
	 * @param {Object} options The options object to load the store with
	 * @private
	 */
	load: function(options)
	{
		var store = this.getStore();
		// suspended is only true when context is going to switch
		// on context witch if filter is enabled then clear the filter.
		if (this.suspended && store.hasFilterApplied) {
			store.stopFilter();
		} else {
			// If user switch folder within the context and Datamode is {@link Zarafa.mail.data.DataModes.UNREAD UNREAD}
			// then we have to persist the filter so, set the filter restriction
			// in restriction/params object.
			options = Ext.applyIf(options || {}, {
				params: {
					restriction: {
						filter: this.getFilterRestriction(this.getFilterTypeFromDataMode())
					}
				}
			});
		}
		Zarafa.mail.MailContextModel.superclass.load.call(this, options);
	},

	/**
	 * Function will provide the filter type based on the {@link #this.current_data_mode current_data_mode}
	 * when {@link #this.current_data_mode current_data_mode} is available.
	 *
	 * @return {Zarafa.common.data.Filters} filterType to get the restriction.
	 */
	getFilterTypeFromDataMode: function()
	{
		if (Ext.isDefined(this.current_data_mode)) {
			if (this.current_data_mode === Zarafa.mail.data.DataModes.UNREAD) {
				return Zarafa.common.data.Filters.UNREAD;
			}
		}
	},

	/**
	 * Function which provide the restriction based on the given {@link Zarafa.common.data.Filters.UNREAD Filter}
	 *
	 * @param {Zarafa.common.data.Filters} filterType The filterType which needs to perform on store.
	 * @return {Array|false} RES_BITMASK restriction else false.
	 */
	getFilterRestriction: function(filterType)
	{
		var store = this.getStore();
		return store.getFilterRestriction(filterType);
	},

	/**
	 * Event handler which is executed right before the {@link #datamodechange}
	 * event is fired. This allows subclasses to initialize the {@link #store}.
	 * This will apply a restriction to the {@link #store} if needed.
	 *
	 * @param {Zarafa.contact.ContactContextModel} model The model which fired the event.
	 * @param {Zarafa.contact.data.DataModes} newMode The new selected DataMode.
	 * @param {Zarafa.contact.data.DataModes} oldMode The previously selected DataMode.
	 * @private
	 */
	onDataModeChange: function(model, newMode, oldMode)
	{
		Zarafa.mail.MailContextModel.superclass.onDataModeChange.call(this, model, newMode, oldMode);

		if (newMode !== oldMode && oldMode === Zarafa.mail.data.DataModes.SEARCH) {
			this.stopSearch();
			// stop the live scroll after the search gets stopped.
			this.stopLiveScroll();
		}
		switch (newMode) {
			case Zarafa.mail.data.DataModes.SEARCH:
			case Zarafa.mail.data.DataModes.LIVESCROLL:
				break;
			case Zarafa.mail.data.DataModes.ALL:
				this.load();
				break;
			case Zarafa.mail.data.DataModes.UNREAD:
				this.load({
					params: {
						restriction: {
							filter: this.getFilterRestriction(Zarafa.common.data.Filters.UNREAD)
						}
					}
				});
				break;
			default: break;
		}
	},


	/**
	 * Event handler which is executed right before the {@link #folderchange}
	 * event is fired. This allows subclasses to update the folders.
	 * Also apply the default sorting on mail grid as per the folder type.
	 *
	 * @param {Zarafa.core.ContextModel} model The model which fired the event.
	 * @param {Array} folders selected folders as an array of {@link Zarafa.hierarchy.data.MAPIFolderRecord Folder} objects.
	 * @private
	 */
	onFolderChange: function(model, folders)
	{
		if(!Ext.isEmpty(folders)) {
			var folder = folders[0];
			var folderKey = folder.getDefaultFolderKey();
			var field = 'message_delivery_time';

			if(folderKey === 'drafts') {
				field = 'last_modification_time';
			} else if(folderKey === 'sent' ) {
				field = 'client_submit_time';
			} else if(folderKey === 'outbox') {
				field = 'deferred_send_time';
			}

			this.store.defaultSortInfo.field = field;
		}

		Zarafa.mail.MailContextModel.superclass.onFolderChange.call(this, model, folders);
	},

	/**
	 * Event handler for the {@link #searchstart searchstart} event.
	 * This will {@link #setDataMode change the datamode} to {@link Zarafa.mail.data.DataModes#SEARCH search mode}.
	 * The previously active {@link #getCurrentDataMode view} will be stored in the {@link #oldDataMode} and will
	 * be recovered when the {@link #onSearchStop search is stopped}.
	 * @param {Zarafa.core.ContextModel} model The model which fired the event
	 * @private
	 */
	onSearchStart: function(model)
	{
		if(this.getCurrentDataMode() != Zarafa.mail.data.DataModes.SEARCH){
			this.oldDataMode = this.getCurrentDataMode();
			this.setDataMode(Zarafa.mail.data.DataModes.SEARCH);
		}
	},

	/**
	 * Event handler for the {@link #searchstop searchstop} event.
	 * This will {@link #setDataMode change the datamode} to the {@link #oldDataMode previous datamode}.
	 * @param {Zarafa.core.ContextModel} model The model which fired the event
	 * @private
	 */
	onSearchStop: function(model)
	{
		if (this.getCurrentDataMode() === Zarafa.mail.data.DataModes.SEARCH) {
			this.setDataMode(this.oldDataMode);
		}
		delete this.oldDataMode;
	}

});
/*
 * #dependsFile client/zarafa/core/mapi/Flags.js
 */
Ext.namespace('Zarafa.mail.data');

/**
 * @class Zarafa.mail.data.MailFlags
 * @singleton
 */
Zarafa.mail.data.MailFlags = {
	colors: [{
		name: _('Red'),
		iconCls: 'icon_flag_red',
		flagStatus: Zarafa.core.mapi.FlagStatus.flagged,
		flagColor: Zarafa.core.mapi.FlagIcon.red
	},{
		name: _('Blue'),
		iconCls: 'icon_flag_blue',
		flagStatus: Zarafa.core.mapi.FlagStatus.flagged,
		flagColor: Zarafa.core.mapi.FlagIcon.blue
	},{
		name: _('Yellow'),
		iconCls: 'icon_flag_yellow',
		flagStatus: Zarafa.core.mapi.FlagStatus.flagged,
		flagColor: Zarafa.core.mapi.FlagIcon.yellow
	},{
		name: _('Green'),
		iconCls: 'icon_flag_green',
		flagStatus: Zarafa.core.mapi.FlagStatus.flagged,
		flagColor: Zarafa.core.mapi.FlagIcon.green
	},{
		name: _('Orange'),
		iconCls: 'icon_flag_orange',
		flagStatus: Zarafa.core.mapi.FlagStatus.flagged,
		flagColor: Zarafa.core.mapi.FlagIcon.orange
	},{
		name: _('Purple'),
		iconCls: 'icon_flag_purple',
		flagStatus: Zarafa.core.mapi.FlagStatus.flagged,
		flagColor: Zarafa.core.mapi.FlagIcon.purple
	}],

	state: [{
		name: _('Complete'),
		iconCls: 'icon_flag_complete',
		flagStatus: Zarafa.core.mapi.FlagStatus.completed,
		flagColor: Zarafa.core.mapi.FlagIcon.clear
	},{
		name: _('None'),
		iconCls: 'icon_flag',
		flagStatus: Zarafa.core.mapi.FlagStatus.cleared,
		flagColor: Zarafa.core.mapi.FlagIcon.clear
	}]
};
/*
 * #dependsFile client/zarafa/core/mapi/Sensitivity.js
 */
Ext.namespace('Zarafa.mail.data');

/**
 * @class Zarafa.mail.data.SensitivityFlags
 * @singleton
 */
Zarafa.mail.data.SensitivityFlags = {
	flags: [{
		value: Zarafa.core.mapi.Sensitivity['NONE'],
		name: Zarafa.core.mapi.Sensitivity.getDisplayName(Zarafa.core.mapi.Sensitivity['NONE'])
	},{
		value: Zarafa.core.mapi.Sensitivity['PERSONAL'],
		name: Zarafa.core.mapi.Sensitivity.getDisplayName(Zarafa.core.mapi.Sensitivity['PERSONAL'])
	},{
		value: Zarafa.core.mapi.Sensitivity['PRIVATE'],
		name: Zarafa.core.mapi.Sensitivity.getDisplayName(Zarafa.core.mapi.Sensitivity['PRIVATE'])
	},{
		value:  Zarafa.core.mapi.Sensitivity['COMPANY_CONFIDENTIAL'],
		name: Zarafa.core.mapi.Sensitivity.getDisplayName(Zarafa.core.mapi.Sensitivity['COMPANY_CONFIDENTIAL'])
	}]
};
Ext.namespace('Zarafa.mail.dialogs');

/**
 * @class Zarafa.mail.dialogs.MailCreateOptionsContentPanel
 * @extends Zarafa.core.ui.RecordContentPanel
 * @xtype zarafa.mailcreateoptionscontentpanel
 *
 * Content panel for users for setting the options on a {@link Zarafa.mail.MailRecord record}
 */
Zarafa.mail.dialogs.MailCreateOptionsContentPanel = Ext.extend(Zarafa.core.ui.RecordContentPanel, {
	/**
	 * @constructor
	 * @param config Configuration structure
	 */
	constructor: function(config)
	{
		config = config || {};

		config = Ext.applyIf(config, {
			// Override from Ext.Component
			xtype: 'zarafa.mailcreateoptionscontentpanel',
			// Override from Ext.Component
			layout: 'fit',
			title: _('Message Options'),
			recordComponentPluginConfig: Ext.applyIf(config.recordComponentPluginConfig || {}, {
				allowWrite: true
			}),
			autoSave: config.modal ? false : true,
			width: 320,
			height: 330,
			items: [{
				xtype: 'zarafa.mailcreateoptionspanel',
				buttons: [{
					text: _('Ok'),
					handler: this.onOk,
					scope: this
				},{
					text: _('Cancel'),
					handler: this.onCancel,
					scope: this
				}]
			}]
		});

		Zarafa.mail.dialogs.MailCreateOptionsContentPanel.superclass.constructor.call(this, config);
	}
});

Ext.reg('zarafa.mailcreateoptionscontentpanel', Zarafa.mail.dialogs.MailCreateOptionsContentPanel);
Ext.namespace('Zarafa.mail.dialogs');

/**
 * @class Zarafa.mail.dialogs.MailCreateToolbar
 * @extends Zarafa.core.ui.ContentPanelToolbar
 * @xtype zarafa.mailcreatetoolbar
 */
Zarafa.mail.dialogs.MailCreateToolbar = Ext.extend(Zarafa.core.ui.ContentPanelToolbar, {
	// Insertion points for this class
	/**
	 * @insert context.mail.mailcreatecontentpanel.toolbar.actions
	 * Insertion point for the Actions buttons in the Create Mail Toolbar
	 * @param {Zarafa.mail.dialogs.MailCreateToolbar} toolbar This toolbar
	 */
	/**
	 * @insert context.mail.mailcreatecontentpanel.toolbar.options
	 * Insertion point for the Options buttons in the Create Mail Toolbar
	 * @param {Zarafa.mail.dialogs.MailCreateToolbar} toolbar This toolbar
	 */
	/**
	 * @insert context.mail.showmailcontentpanel.toolbar.options.right
	 * Insertion point for the Options Right buttons which will show at right last in Mail Toolbar
	 * @param {Zarafa.mail.dialogs.ShowMailToolbar} toolbar This toolbar
	 */

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

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

		Ext.applyIf(config, {
			xtype: 'zarafa.mailcreatetoolbar',
			insertionPointBase: 'context.mail.mailcreatecontentpanel',

			actionItems: this.createActionButtons(),
			optionItems: this.createOptionButtons(),
			rightAlignedItems: this.createRightAlignedOptionButtons()
		});

		Zarafa.mail.dialogs.MailCreateToolbar.superclass.constructor.call(this, config);
	},

	/**
	 * Called automatically when the {@link Zarafa.mail.dialogs.MailCreateContentPanel content panel}
	 * is being rendered. This will add a listener to to the {@link Zarafa.mail.dialogs.MailCreateContentPanel#bcctoggle} button.
	 * @private
	 */
	onRender: function ()
	{
		Zarafa.mail.dialogs.MailCreateToolbar.superclass.onRender.apply(this, arguments);

		if (this.dialog) {
			this.mon(this.dialog, 'bcctoggle', this.onContentPanelBccToggle, this);
			this.mon(this.dialog, 'fromtoggle', this.onContentPanelFromToggle, this);
		}

		var settingsModel = container.getSettingsModel();
		this.mon(settingsModel, 'set', this.onSettingsChange, this);
		this.mon(settingsModel, 'remove', this.onSettingsChange, this);

		// also call it for the first time
		this.onSettingsChange(settingsModel);
	},

	/**
	 * Create all buttons which should be added by default the the `Actions` buttons.
	 * These buttons are used to send, save and add attachments to the message. And it contains
	 * also buttons to check the recipient names or add a signature.
	 *
	 * @return {Array} The {@link Ext.Button Button} elements which should be added in the Actions section of the {@link Ext.Toolbar}.
	 * @private
	 */
	createActionButtons: function ()
	{
		return [{
			xtype: 'splitbutton',
			text: _('Send'),
			overflowText: _('Send'),
			ref: 'sendButton',
			tooltip: _('Send') + Zarafa.core.KeyMapMgr.formatShortcutHint('Ctrl + Enter', true),
			menu: {
				defaults: {
					plugins: 'zarafa.menuitemtooltipplugin'
				},
				items: [{
					text: _('Send'),
					iconCls: 'icon_send_black',
					handler: this.onSendButton,
					tooltip: _('Send') + Zarafa.core.KeyMapMgr.formatShortcutHint('Ctrl + Enter', true),
					scope: this
				}, {
					text: _('Send Later'),
					tooltip: _('Schedule your mail to be sent on a specific date and time'),
					iconCls: 'icon_send_later_black',
					handler: this.onSendLaterButton,
					scope: this
				}]
			},
			cls: 'zarafa-action',
			iconCls: 'icon_send_white',
			handler: this.onSendButton,
			scope: this
		}, {
			xtype: 'button',
			ref: 'saveBtn',
			overflowText: _('Save'),
			tooltip: _('Save') + Zarafa.core.KeyMapMgr.formatShortcutHint('Ctrl + S', true),
			iconCls: 'icon_floppy',
			handler: this.onSaveButton,
			scope: this
		},{
			xtype: 'button',
			ref: 'deleteBtn',
			overflowText: _('Delete'),
			tooltip: _('Delete'),
			iconCls: 'icon_delete',
			handler: this.onDeleteButton,
			scope: this
		},{
			xtype: 'zarafa.attachmentbutton',
			plugins: ['zarafa.recordcomponentupdaterplugin'],
			overflowText: _('Add attachment'),
			tooltip: _('Add attachments to this email'),
			iconCls: 'icon_paperclip',
			ref: 'attachmentButton',
			// Add a listener to the component added event to set use the correct update function when the toolbar overflows
			// (i.e. is too wide for the panel) and Ext moves the button to a menuitem.
			listeners: {
				added: this.onAttachmentButtonAdded,
				scope: this
			}
		},{
			xtype: 'tbseparator'
		},{
			xtype: 'button',
			overflowText: _('Check names'),
			tooltip: _('Check names'),
			iconCls: 'icon_checknames',
			handler: this.onCheckNamesButton,
			scope: this
		},{
			xtype: 'button',
			overflowText: _('Addressbook'),
			tooltip: _('Open addressbook'),
			iconCls: 'icon_small_addressbook',
			handler: function() {
					Zarafa.mail.Actions.openRecipientSelectionContent(this.record, {
						defaultRecipientType: Zarafa.core.mapi.RecipientType.MAPI_TO
					});
			},
			scope: this
		},{
			xtype: 'button',
			overflowText: _('Add signature'),
			tooltip: _('Add signature'),
			iconCls: 'icon_signature',
			ref: 'signatureButton',
			scope: this
		}];
	},

	/**
	 * Event listener for the added event of the {@link Zarafa.common.attachment.ui.AttachmentButton attachmentButton}
	 * Adds the update function to the item when Ext converts the button to a menu item
	 * (which happens when the toolbar overflows, i.e. is too wide for the containing panel)
	 *
	 * @param {Ext.Component} item The item that was added. This can be a {@link Zarafa.common.attachment.ui.AttachmentButton}
	 * or a {@link Ext.menu.Item}
	 */
	onAttachmentButtonAdded: function(item)
	{
		if ( item.isXType('menuitem') ){
			// Set the update function to the update function of the original button
			// otherwise the Ext.Component.update function would be called by the recordcomponentupdaterplugin
			item.update = Zarafa.common.attachment.ui.AttachmentButton.prototype.update.createDelegate(this.attachmentButton);
		}
	},

	/**
	 * Create all buttons which should be added by default the the `Options` Buttons.
	 * This contains the buttons to set the message options like priority and read receipt.
	 *
	 * @return {Array} The {@link Ext.Button Button} elements which should be added in the Options section of the {@link Ext.Toolbar}.
	 * @private
	 */
	createOptionButtons: function ()
	{
		return [{
			xtype: 'button',
			overflowText: _('Options'),
			tooltip: _('Open options dialog'),
			iconCls: 'icon_cogwheel',
			handler: this.onMailOptionsButton,
			scope: this
		},{
			xtype: 'button',
			overflowText: _('Set flag'),
			tooltip: _('Set flag on this email'),
			iconCls: 'icon_flag_red',
			handler: this.onSetFlagButton,
			scope: this
		},{
			xtype: 'button',
			overflowText: _('High priority'),
			tooltip: _('Mark this mail as high priority'),
			iconCls: 'icon_priority_high',
			ref: 'highPriority',
			toggleGroup: 'priorityGroup',
			importance: Zarafa.core.mapi.Importance.URGENT,
			enableToggle: true,
			toggleHandler: this.onPriorityGroupToggle,
			scope: this
		},{
			xtype: 'button',
			overflowText: _('Low priority'),
			tooltip: _('Mark this mail as low priority'),
			iconCls: 'icon_priority_low',
			ref: 'lowPriority',
			toggleGroup: 'priorityGroup',
			importance: Zarafa.core.mapi.Importance.NONURGENT,
			enableToggle: true,
			toggleHandler: this.onPriorityGroupToggle,
			scope: this
		},{
			xtype: 'button',
			overflowText: _('Set read receipt'),
			tooltip: _('Request read receipt from recipients'),
			iconCls: 'icon_read_receipt',
			enableToggle: true,
			ref: 'readReceiptField',
			toggleHandler: this.onReadReceiptToggle,
			scope: this
		},{
			xtype: 'button',
			overflowText: _('Show Bcc'),
			tooltip: _('Show Bcc field'),
			iconCls: 'icon_showbcc',
			ref: 'showBcc',
			enableToggle: true,
			toggleHandler: this.onBccMenuToggle,
			scope: this
		},{
			xtype: 'button',
			overflowText: _('Show From field'),
			tooltip: _('Show From field'),
			iconCls: 'icon_showfrom',
			ref: 'showFrom',
			enableToggle: true,
			toggleHandler: this.onFromMenuToggle,
			scope: this
		}];
	},

	/**
	 * Create buttons which needs to be rendered on the right side of the toolbar.
	 * This contains the popout button if main webapp window is active.
	 *
	 * @return {Array} The {@link Ext.Button} elements which should be added in the Right Options section of the {@link Ext.Toolbar}.
	 */
	createRightAlignedOptionButtons: function()
	{
		// Display the popout button if supported.
		if (Zarafa.supportsPopOut() && Zarafa.core.BrowserWindowMgr.isMainWindowActive()) {
			return [{
				xtype: 'zarafa.toolbarbutton',
				tooltip: _('Pop-out'),
				overflowText: _('Pop-out'),
				iconCls: 'icon_popout',
				ref: 'popOutBtn',
				handler: this.onPopoutButton,
				scope: this
			}];
		}
	},

	/**
	 * Function generates menu item for signature button.
	 * @param {Zarafa.settings.SettingsModel} settingsModel The settings which contains signature information
	 * @param {Object} changedSettings The object of changed settings
	 */
	onSettingsChange: function (settingsModel, changedSettings)
	{
		var changeSignatures = true;
		if (!Ext.isEmpty(changedSettings)) {
			changeSignatures = false;
			// Check whether signatures settings are changed or not.
			for (var i = 0; i < changedSettings.length; i++) {
				if (changedSettings[i].path && changedSettings[i].path.indexOf('zarafa/v1/contexts/mail/signatures/all') >= 0) {
					changeSignatures = true;
					break;
				}
			}
		}

		if (!changeSignatures) {
			return;
		}

		// generate menu for signature button
		var signatures = settingsModel.get('zarafa/v1/contexts/mail/signatures/all', true);

		var sigItems = [];
		for (var key in signatures) {
			sigItems.push({
				text: Ext.util.Format.htmlEncode(signatures[key].name),
				iconCls: 'icon_signature',
				signatureId: parseInt(key, 10)
			});
		}

		if (!Ext.isEmpty(sigItems)){
			sigItems.push({
				xtype: 'menuseparator'
			});
		}
		sigItems.push({
			text: _('Add signature'),
			iconCls: 'plus',
			handler: this.onClickAddSignatureHandler,
			signatureId: false
		});

		if (this.signatureButton.menu) {
			// Remove list of old signatures and add all the signatures again
			var signatureMenu = this.signatureButton.menu;
			signatureMenu.removeAll();
			signatureMenu.add(sigItems);
		} else {
			// Create menu for the first time.
			// instance creation of menu will be handled by MenuMgr
			this.signatureButton.menu = Ext.menu.MenuMgr.get({
				xtype: 'menu',
				listeners: {
					click: this.onSignatureSelect,
					scope: this
				},
				items: sigItems
			});
		}
	},

	/**
	 * Handler which triggered when 'Add signature' button clicked.
	 * This will call the {@link Zarafa.mail.Actions#redirectToSignatureWidget}.
	 */
	onClickAddSignatureHandler: function()
	{
		Zarafa.mail.Actions.redirectToSignatureWidget();
	},

	/**
	 * Function will be called whenever selection of signature in menu selected.
	 * @param {Ext.menu.Menu} Menu button manu
	 * @param {Ext.menu.CheckItem} CheckItem menu item that is selected
	 * @param {Ext.EventObject} EventObjectt event object
	 * @private
	 */
	onSignatureSelect: function (menu, menuItem, eventObj)
	{
		if (Ext.isNumber(menuItem.signatureId)) {
			// Place the signature in editor.
			this.dialog.mainPanel.setSignatureInEditor(menuItem.signatureId);
		}
	},

	/**
	 * Event handler when the "Send" button has been pressed.
	 * This will {@link Zarafa.core.ui.MessageContentPanel#sendRecord send} the given record.
	 *
	 * @param {Ext.Button} button The button which has been pressed
	 * @private
	 */
	onSendButton: function (button)
	{
    if (this.record.get('deferred_send_time') !== null) {
      // If the mail is scheduled mail(in outbox) and user try to directly send it this will not send
      // Because of it has 'deferred_send_time' ,So by setting null into 'deferred_send_time' we can send the mail
      this.record.set('deferred_send_time', null);
    }
		this.dialog.sendRecord();
	},

	/**
	 * Event handler when the "Send Later" button has been pressed.
	 * This will call the {@link Zarafa.mail.Actions#openDelayedDeliveryContent}.
	 * @param {Ext.Button} button The button which was clicked
	 */
	onSendLaterButton: function (button)
	{
		this.dialog.sendLaterRecord();
	},

	/**
	 * Event handler when the "Save" button has been pressed.
	 * This will {@link Zarafa.core.ui.RecordContentPanel#saveRecord save} the given record.
	 *
	 * @param {Ext.Button} button The button which has been pressed
	 * @private
	 */
	onSaveButton: function (button)
	{
		this.dialog.saveRecord();
	},

	/**
	 * Event handler when the "Delete" button has been pressed.
	 * This will {@link Zarafa.core.ui.RecordContentPanel#deleteRecord delete} the given record.
	 *
	 * @param {Ext.Button} button The button which has been pressed
	 * @private
	 */
	onDeleteButton: function (button)
	{
		this.dialog.deleteRecord();
	},

	/**
	 * Event handler when the "Check Names" button has been pressed.
	 * This will {@link Zarafa.core.data.IPMRecipient#resolve resolve} all recipients.
	 *
	 * @param {Ext.Button} button The button which has been pressed
	 * @private
	 */
	onCheckNamesButton: function (button)
	{
		this.record.getRecipientStore().resolve(undefined, { cancelPreviousRequest: true });
	},

	/**
	 * Event handler when the "Message Options" button has been pressed.
	 * This will call the {@link Zarafa.mail.Actions#openMailOptionsContent}.
	 *
	 * @param {Ext.Button} button The button which has been pressed
	 * @private
	 */
	onMailOptionsButton: function (button)
	{
		Zarafa.mail.Actions.openMailOptionsContent(this.record, {
			autoSave: false
		});
	},

	/**
	 * Event handler when the "Set Flag" button has been pressed.
	 * This will call the {@link Zarafa.common.Actions#openFlagsMenu}.
	 *
	 * @param {Ext.Button} button The button which has been pressed
	 * @param {Ext.EventObject} eventObject event object
	 * @private
	 */
	onSetFlagButton: function (button, eventObject)
	{
		Zarafa.common.Actions.openFlagsMenu(this.record, eventObject.getXY(), false);
	},

	/**
	 * Event handler which is called when one of the PriorityGroup buttons
	 * have been toggled. If this is the case, the importance is updated,
	 * if the button is untoggled, then all buttons in the prioritygroup
	 * are untoggled and the normal importance is applied. Otherwise the
	 * importance which belongs to the button is applied.
	 *
	 * @param {Ext.Button} button The button from the PriorityGroup which was pressed
	 * @param {Boolean} pressed true if the button pressed, false otherwise.
	 * @private
	 */
	onPriorityGroupToggle: function (button,pressed)
	{
		if (pressed) {
			this.record.set('importance', button.importance);
		} else {
			this.record.set('importance', Zarafa.core.mapi.Importance.NORMAL);
		}
	},

	/**
	 * Event handler which is called when show Bcc recipientfield buttons have been
	 * toggled. If this is the case, the Bcc recipeint field is visible in dialog
	 * if the button is untoggled, then showBcc buttons is untoggled and
	 * the bcc field is hidden from the dailog.
	 * @param {Ext.Button} button showBcc recipient button is pressed
	 * @param {Boolean} state The state in which the button currently is.
	 * @private
	 */
	onBccMenuToggle: function (button, state)
	{
		this.dialog.toggleBccState(state);
	},

	/**
	 * Event handler which is called when show Bcc recipientfield buttons have been
	 * toggled. If this is the case, the Bcc recipeint field is visible in dialog
	 * if the button is untoggled, then showBcc buttons is untoggled and
	 * the bcc field is hidden from the dailog.
	 * @param {Ext.Button} button showBcc recipient button is pressed
	 * @param {Boolean} state The state in which the button currently is.
	 * @private
	 */
	onFromMenuToggle: function (button, state)
	{
		this.dialog.toggleFromState(state);
	},

	/**
	 * Event that is fired when visibility of the BCC field must be changed.
	 * This will update UI of the {@link Zarafa.mail.dialogs.MailCreateContentPanel}
	 * @param {Zarafa.core.ui.ContentPanel} contentpanel
	 * @param {Boolean} enabled true if the the BCC field should be shown
	 * @private
	 */
	onContentPanelBccToggle: function (contentpanel, pressed)
	{
		this.showBcc.toggle(pressed, true);
	},

	/**
	 * Event that is fired when visibility of the From field must be changed.
	 * This will update UI of the {@link Zarafa.mail.dialogs.MailCreateContentPanel}
	 * @param {Zarafa.core.ui.ContentPanel} contentpanel
	 * @param {Boolean} enabled true if the the From field should be shown
	 * @private
	 */
	onContentPanelFromToggle: function (contentpanel, pressed)
	{
		this.showFrom.toggle(pressed, true);
	},

	/**
	 * Event handler which is called when read receipt button have been
	 * toggled.
	 *
	 * @param {Ext.Button} button The button which was pressed
	 * @private
	 */
	onReadReceiptToggle: function (button)
	{
		this.record.set('read_receipt_requested', button.pressed);
	},

	/**
	 * Load record into form
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record to load
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 */
	update: function (record, contentReset)
	{
		// Add event listener when record is available with this toolbar.
		if (!Ext.isDefined(this.record) && Ext.isDefined(record)) {
			// Listen to the 'add', 'update' and 'remove' events on the attachment-sub-store
			var attachmentStore = record.getAttachmentStore();
			if (attachmentStore) {
				this.mon(attachmentStore, {
					'update': this.onAttachmentChange,
					'add': this.onAttachmentChange,
					'remove': this.onAttachmentChange,
					scope: this
				});
			}
		}

		this.record = record;

		// Only enable Disabled button when it is not a phantom
		this.deleteBtn.setDisabled(record.phantom === true);

		if (contentReset === true || record.isModifiedSinceLastUpdate('importance')) {
			switch (record.get('importance')) {
				case Zarafa.core.mapi.Importance.URGENT:
					this.highPriority.toggle(true, false);
					break;
				case Zarafa.core.mapi.Importance.NONURGENT:
					this.lowPriority.toggle(true, false);
					break;
				default:
					// No priority buttons will be selected if user choose normal priority
					// from Zarafa.mail.dialogs.MailViewOptionsContentPanel dialog.
					this.highPriority.toggle(false, true);
					this.lowPriority.toggle(false, true);
					break;
			}
		}

		if (contentReset === true || record.isModifiedSinceLastUpdate('read_receipt_requested')) {
			this.readReceiptField.toggle(this.record.get('read_receipt_requested'), true);
		}
	},

	/**
	 * Event handler called when attachment store gets updated.
	 * This will prevent save operation if attachments are still
	 * being uploaded.
	 *
	 * @param {Zarafa.core.data.IPMAttachmentStore} attachmentStore The store which fired the event
	 * @param {Ext.data.Record} record The record which was updated
	 * @private
	 */
	onAttachmentChange: function(attachmentStore, record)
	{
		var isAllAttachUploaded = true;

		// Loop over all the attachments and check if all attachments gets uploaded or not
		attachmentStore.each(function(attach) {
			if(!attach.isUploaded()) {
				isAllAttachUploaded = false;
				// stop further iterations
				return false;
			}
		}, this);

		// stop the autosave and disable the save button
		this.dialog.autoSave = isAllAttachUploaded;
		this.saveBtn.setDisabled(!isAllAttachUploaded);

		// Change the tooltip accordingly
		if (isAllAttachUploaded) {
			this.saveBtn.setTooltip(_('Save') + Zarafa.core.KeyMapMgr.formatShortcutHint('Ctrl + S', true));
		} else {
			this.saveBtn.setTooltip(_('Cannot save while attachment is being uploaded'));
		}
	},

	/**
	 * Event handler called when the "PopOut" button has been pressed.
	 * This will call the {@link Zarafa.mail.Actions#openMailContent}
	 * with record and its containing {@link Zarafa.core.ui.MessageContentPanel dialog}.
	 *
	 * @param {Ext.Button} button The button which has been pressed
	 * @private
	 */
	onPopoutButton: function(button)
	{
		Zarafa.mail.Actions.popoutMailContent(this.record, this.dialog);
	}
});
Ext.reg('zarafa.mailcreatetoolbar', Zarafa.mail.dialogs.MailCreateToolbar);
Ext.namespace('Zarafa.mail.dialogs');

/**
 * @class Zarafa.mail.dialogs.MailViewOptionsContentPanel
 * @extends Zarafa.core.ui.RecordContentPanel
 * @xtype zarafa.mailviewoptionscontentpanel
 *
 * Content panel for users for setting the options on a {@link Zarafa.mail.MailRecord record}
 */
Zarafa.mail.dialogs.MailViewOptionsContentPanel = Ext.extend(Zarafa.core.ui.RecordContentPanel, {
	/**
	 * @constructor
	 * @param config Configuration structure
	 */
	constructor: function(config)
	{
		config = config || {};

		config = Ext.applyIf(config, {
			// Override from Ext.Component
			xtype: 'zarafa.mailviewoptionscontentpanel',
			// Override from Ext.Component
			layout: 'fit',
			title: _('Message Options'),
			recordComponentPluginConfig: Ext.applyIf(config.recordComponentPluginConfig || {}, {
				allowWrite: true
			}),
			autoSave: config.modal ? false : true,
			width: 650,
			height: 550,
			items: [{
				xtype: 'zarafa.mailviewoptionspanel',
				buttons: [{
					text: _('Ok'),
					handler: this.onOk,
					scope: this
				},{
					text: _('Cancel'),
					handler: this.onCancel,
					scope: this
				}]
			}]
		});

		Zarafa.mail.dialogs.MailViewOptionsContentPanel.superclass.constructor.call(this, config);
	}
});

Ext.reg('zarafa.mailviewoptionscontentpanel', Zarafa.mail.dialogs.MailViewOptionsContentPanel);
Ext.namespace('Zarafa.mail.dialogs');

/**
 * @class Zarafa.mail.dialogs.ShowMailToolbar
 * @extends Zarafa.core.ui.ContentPanelToolbar
 * @xtype mail.showmailtoolbar
 */
Zarafa.mail.dialogs.ShowMailToolbar = Ext.extend(Zarafa.core.ui.ContentPanelToolbar, {
	// Insertion points for this class
	/**
	 * @insert context.mail.showmailcontentpanel.toolbar.actions
	 * Insertion point for the Actions buttons in the Show Mail Toolbar
	 * @param {Zarafa.mail.dialogs.ShowMailToolbar} toolbar This toolbar
	 */
	/**
	 * @insert context.mail.showmailcontentpanel.toolbar.options
	 * Insertion point for the Options buttons in the Show Mail Toolbar
	 * @param {Zarafa.mail.dialogs.ShowMailToolbar} toolbar This toolbar
	 */
	/**
	 * @insert context.mail.showmailcontentpanel.toolbar.options.right
	 * Insertion point for the Options Right buttons which will show at right last in Mail Toolbar
	 * @param {Zarafa.mail.dialogs.ShowMailToolbar} toolbar This toolbar
	 */

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

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

		var insertionPointBase = 'context.mail.showmailcontentpanel';

		Ext.applyIf(config, {
			xtype: 'mail.showmailtoolbar',
			insertionPointBase: insertionPointBase,

			actionItems: this.createActionButtons(insertionPointBase),
			optionItems: this.createOptionButtons(),
			rightAlignedItems: this.createRightAlignedOptionButtons()
		});

		Zarafa.mail.dialogs.ShowMailToolbar.superclass.constructor.call(this, config);
	},

	/**
	 * Create all buttons which should be added by default the the `Action` buttons
	 * This contains the buttons to reply and forward the message.
	 *
	 * @return {Array} The {@link Ext.Button} elements which should be added in the Action section of the {@link Ext.Toolbar}.
	 * @private
	 */
	createActionButtons: function(insertionPointBase)
	{
		var preActionButtonsInsert = container.populateInsertionPoint(insertionPointBase + '.toolbar.actions.first');

		return preActionButtonsInsert.concat([{
			xtype: 'button',
			rowspan: 2,
			text: _('Reply'),
			tooltip: _('Reply') + Zarafa.core.KeyMapMgr.formatShortcutHint('Ctrl + R', false),
			overflowText: _('Reply'),
			cls: 'zarafa-action',
			iconCls: 'icon_reply',
			ref: 'replyBtn',
			actionType: Zarafa.mail.data.ActionTypes.REPLY,
			handler: this.onMailResponseButton,
			scope: this,
			listeners: {
				beforeshow: this.onBeforeShowReplyButton,
				scope: this
			}
		},{
			xtype: 'button',
			text: _('Reply All'),
			tooltip: _('Reply All') + Zarafa.core.KeyMapMgr.formatShortcutHint('Ctrl + Alt + R', false),
			overflowText: _('Reply All'),
			iconCls: 'icon_reply_all',
			ref: 'replyAllBtn',
			actionType: Zarafa.mail.data.ActionTypes.REPLYALL,
			handler: this.onMailResponseButton,
			scope: this
		},{
			xtype: 'button',
			text: _('Forward'),
			tooltip: _('Forward') + Zarafa.core.KeyMapMgr.formatShortcutHint('Ctrl + F', false),
			overflowText: _('Forward'),
			iconCls: 'icon_forward',
			ref: 'forwardBtn',
			actionType: Zarafa.mail.data.ActionTypes.FORWARD,
			handler: this.onMailResponseButton,
			scope: this
		},{
			xtype: 'button',
			ref: 'deleteBtn',
			overflowText: _('Delete'),
			tooltip: {
				text: _('Delete')
			},
			iconCls: 'icon_delete',
			handler: this.onDeleteButton,
			scope: this
		}]);
	},

	/**
	 * Handler for the onbeforeshow event of the reply button. It will make
	 * sure that it is not an 'action' button when the record is a meeting
	 * request.
	 * @param {Ext.Button} btn The reply button
	 */
	onBeforeShowReplyButton: function(btn)
	{
		var messageClass = this.record.get('message_class');
		if ( messageClass.substring(0, 20) === 'IPM.Schedule.Meeting' ){
			btn.getEl().removeClass('zarafa-action');
		} else {
			btn.getEl().addClass('zarafa-action');
		}
	},

	/**
	 * Create all buttons which should be added by default the the `Options` buttons.
	 *
	 * @return {Array} The {@link Ext.Button} elements which should be added in the Options section of the {@link Ext.Toolbar}.
	 * @private
	 */
	createOptionButtons: function()
	{
		return [{
			xtype: 'button',
			ref: 'optionsBtn',
			overflowText: _('Options'),
			tooltip: {
				text: _('Open options dialog')
			},
			iconCls: 'icon_cogwheel',
			handler: this.onMailOptionsButton,
			scope: this
		},{
			xtype: 'button',
			overflowText: _('Set Category'),
			tooltip: {
				text: _('Open the categories dialog')
			},
			iconCls: 'icon_categories',
			handler: this.onOpenCategories,
			scope: this
		},{
			xtype: 'button',
			ref: 'setFlagBtn',
			overflowText: _('Set flag'),
			tooltip: {
				text: _('Set flag on this email')
			},
			iconCls: 'icon_flag_red',
			handler: this.onSetFlagButton,
			scope: this
		},{
			xtype: 'splitbutton',
			cls: 'zarafa-more-options-btn',
			tooltip: _('More options'),
			splitOnMoreMenu: true,
			overflowText: _('More options'),
			iconCls: 'icon_more',
			menu: this.moreMenuButtons(this),
			handler: function() {
				this.showMenu();
			}
		}];
	},

	/**
	 * Create buttons which needs to be rendered on the right side of the toolbar.
	 * This contains the popout button if main webapp window is active.
	 *
	 * @return {Array} The {@link Ext.Button} elements which should be added in the Right Options section of the {@link Ext.Toolbar}.
     */
	createRightAlignedOptionButtons: function ()
	{
		// Display the popout button only if supported.
		if (Zarafa.supportsPopOut() && Zarafa.core.BrowserWindowMgr.isMainWindowActive()) {
			return [{
				xtype: 'zarafa.toolbarbutton',
				tooltip: _('Open in new browser window'),
				overflowText: _('Pop-Out'),
				iconCls: 'icon_popout',
				ref: 'popOutBtn',
				handler: this.onPopoutButton,
				scope: this
			}];
		}
	},

	/**
	 * Load record into toolbar
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record to load
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 */
	update: function(record, contentReset)
	{
		this.record = record;

		// disable some buttons for sub messages
		var isSubMessage = false;
		var isFaultyMessage = false;

		if(record) {
			isSubMessage = record.isSubMessage();
			isFaultyMessage = record.isFaultyMessage();
		}

		this.deleteBtn.setVisible(!isSubMessage);
		this.setFlagBtn.setVisible(!isSubMessage);
		this.optionsBtn.setVisible(!isSubMessage && !isFaultyMessage);

		this.replyBtn.setVisible(!isFaultyMessage);
		this.replyAllBtn.setVisible(!isFaultyMessage);
		this.forwardBtn.setVisible(!isFaultyMessage);
	},

	/**
	 * The menu items of the more button.
	 *
	 * @param {Zarafa.mail.dialogs.ShowMailToolbar} scope The scope for the menu items
	 * @return {Zarafa.core.ui.menu.ConditionalMenu} the dropdown menu for the more button
	 */
	moreMenuButtons: function(scope)
	{
		return {
			xtype: 'zarafa.conditionalmenu',
			items: [{
				xtype: 'zarafa.conditionalitem',
				text: _('Mark Read'),
				iconCls: 'icon_mail icon_mail_read',
				hideMode: 'offsets',
				readState: true,
				beforeShow: this.onBeforeShowMoreMenu,
				handler: this.onReadFlagMenuItemClicked,
				scope: scope
			}, {
				xtype: 'zarafa.conditionalitem',
				text: _('Mark Unread'),
				hideMode: 'offsets',
				iconCls: 'icon_mail icon_mail_unread',
				readState: false,
				beforeShow: this.onBeforeShowMoreMenu,
				handler: this.onReadFlagMenuItemClicked,
				scope: scope
			}, {
				text: _('Copy/Move'),
				iconCls: 'icon_copy',
				handler: this.onCopyMove,
				scope: scope
			}, {
				text: _('Print'),
				iconCls: 'icon_print',
				handler: this.onPrintButton,
				scope: this
			}, {
				text: _('Edit as New'),
				iconCls: 'icon_edit_as_new_mail',
				actionType: Zarafa.mail.data.ActionTypes.EDIT_AS_NEW,
				handler: this.onMailResponseButton,
				scope: scope
			}, {
				xtype: 'zarafa.conditionalitem',
				text: _('Download'),
				iconCls: 'icon_download',
				hideOnDisabled: false,
				handler: this.onDownloadMailButton,
				beforeShow: this.onBeforeShowDownloadButton,
				scope: scope
			}]
		};
	},

	/**
	 * Handler for the beforeshow event of the {@link Zarafa.core.ui.menu.ConditionalItem menu item}. Will
	 * show and hide the menu item depends on record read status.
	 *
	 * @param {Zarafa.core.ui.menu.ConditionalItem} item The item to enable/disable
	 * @private
	 */
	onBeforeShowMoreMenu: function (item)
	{
		// show and hide the 'Mark Read' and 'Mark Unread' if record read status is unread or read respectively.
		item.setVisible(this.record.isRead() ? !item.readState : item.readState);
	},

	/**
	 * Event handler which is called when the item has been clicked.
	 * This will mark record as read or unread.
	 *
	 * @param {Zarafa.core.ui.menu.ConditionalItem} item The item which has been clicked.
	 * @private
	 */
	onReadFlagMenuItemClicked: function (item)
	{
		Zarafa.common.Actions.markAsRead(this.record, item.readState);
	},

	/**
	 * Event handler when the "Message Options" button has been pressed.
	 * This will call the {@link Zarafa.mail.Actions#openMailOptionsContent}.
	 *
	 * @param {Ext.Button} button The button which has been pressed
	 * @private
	 */
	onMailOptionsButton: function(button)
	{
		Zarafa.mail.Actions.openMailOptionsContent(this.record, {
			autoSave: true
		});
	},

	/**
	 * Event handler when the "Set Flag" button has been pressed.
	 * This will call the {@link Zarafa.common.Actions#openFlagsMenu}.
	 *
	 * @param {Ext.Button} button The button which has been pressed
	 * @param {Ext.EventObject} eventObject event object
	 * @private
	 */
	onSetFlagButton: function (button, eventObject)
	{
		Zarafa.common.Actions.openFlagsMenu(this.record, eventObject.getXY(), false);
	},

	/**
	 * Event handler when the "Delete" button has been pressed.
	 * This will {@link Zarafa.core.data.RecordContentPanel#deleteRecord delete} the given record.
	 *
	 * @param {Ext.Button} button The button which has been pressed
	 * @private
	 */
	onDeleteButton: function(button)
	{
		this.dialog.deleteRecord();
	},

	/**
	 * Handler for the Categories toolbar button
	 * This will call {@link Zarafa.common.Actions#openCategoriesContent}.
	 * @private
	 */
	onOpenCategories: function()
	{
		Zarafa.common.Actions.openCategoriesContent(this.record);
	},

	/**
	 * Open the {@link Zarafa.common.dialogs.CopyMoveContent CopyMoveContent} for copying
	 * or moving the given record.
	 * @private
	 */
	onCopyMove: function()
	{
		Zarafa.common.Actions.openCopyMoveContent(this.record);
	},

	/**
	 * Event handler when the "Print" button has been pressed.
	 * This will call the {@link Zarafa.common.Actions#openPrintDialog}.
	 *
	 * @param {Ext.Button} button The button which has been pressed
	 * @private
	 */
	onPrintButton: function(button)
	{
		Zarafa.common.Actions.openPrintDialog(this.record);
	},

	/**
	 * Event handler called when the "PopOut" button has been pressed.
	 * This will call the {@link Zarafa.mail.Actions#openMailContent}
	 * with record and its containing {@link Zarafa.core.ui.MessageContentPanel dialog}.
	 *
	 * @param {Ext.Button} button The button which has been pressed
	 * @private
	 */
	onPopoutButton: function(button)
	{
		Zarafa.mail.Actions.popoutMailContent(this.record, this.dialog);
	},

	/**
	 * Event handler when the "Reply/ Reply All/ Forward/ Edit as New" button has been pressed.
	 * This will call the {@link Zarafa.mail.Actions#openCreateMailResponseContent}.
	 *
	 * @param {Ext.Button} button The button which has been pressed
	 * @private
	 */
	onMailResponseButton: function(button)
	{
		if (button.actionType === Zarafa.mail.data.ActionTypes.FORWARD) {
			var messageClass = this.record.get('message_class') || '';
			if (messageClass.indexOf('IPM.Schedule.Meeting') === 0 &&
				messageClass.indexOf('IPM.Schedule.Meeting.Notification') !== 0) {
				Zarafa.calendar.Actions.openForwardMeetingRequestContent(this.record);
				return;
			}
		}

		var model = this.dialog.getContextModel();
		var isOwnedByMainWindow = Zarafa.core.BrowserWindowMgr.isOwnedByMainWindow(button);
		var configObject = !isOwnedByMainWindow ? {layerType: 'separateWindows'} : undefined;

		Zarafa.mail.Actions.openCreateMailResponseContent(this.record, model, button.actionType, configObject);
		if (container.getSettingsModel().get('zarafa/v1/contexts/mail/close_on_respond') === true) {
			this.dialog.close();
		}
	},

	/**
	 * Event handler when the "Download" button has been pressed.
	 * This will call the {@link Zarafa.common.Actions#openSaveEmlDialog} function.
	 *
	 * @param {Ext.Button} button The button which has been pressed
	 * @private
	 */
	onDownloadMailButton: function(button)
	{
		Zarafa.common.Actions.openSaveEmlDialog(this.record);
	},

	/**
	 * Handler for the beforeshow event of the {@link Zarafa.core.ui.menu.ConditionalItem menuitem}. Will
	 * disable the menu item if the record is embedded message.
	 *
	 * @param {Zarafa.core.ui.menu.ConditionalItem} item The item to enable/disable
	 * @private
	 */
	onBeforeShowDownloadButton: function(item)
	{
		// embedded messages can not be downloaded
		item.setDisabled(this.record.isSubMessage());
	}
});
Ext.reg('zarafa.showmailtoolbar', Zarafa.mail.dialogs.ShowMailToolbar);
// -*- coding: utf-8; indent-tabs-mode: nil -*-
Ext.namespace('Zarafa.mail.printer');

/**
 * @class Zarafa.mail.printer.MailRenderer
 * @extends Zarafa.common.printer.renderers.RecordRenderer
 *
 * Prints a single email
 */
Zarafa.mail.printer.MailRenderer = Ext.extend(Zarafa.common.printer.renderers.RecordRenderer, {

	/**
	 * Returns a dynamic title derived from the email metadata so
	 * the browser suggests a meaningful filename when printing to PDF.
	 * @param {Zarafa.core.data.MAPIRecord} record The mail record
	 * @return {String} The title for the print document
	 */
	getTitle: function(record)
	{
		var date = record.get('message_delivery_time');
		var subject = record.get('subject') || '';
		var sender = record.get('sender_name') || '';

		subject = subject.substring(0, 32).replace(/[\/\\:*?"<>|]/g, '_').trim();
		sender = sender.replace(/[\/\\:*?"<>|]/g, '_').trim();

		var parts = [];
		if (Ext.isDate(date)) {
			parts.push(date.format('Ymd'));
		}
		if (subject) {
			parts.push(subject);
		}
		if (sender) {
			// TRANSLATORS: prefix before sender name in print-to-PDF filename
			parts.push(_('FROM') + '_' + sender);
		}

		return parts.length > 0 ? parts.join('_') : Zarafa.mail.printer.MailRenderer.superclass.getTitle.call(this, record);
	},

	/**
	 * Generates a template on which prepareData() will be applied to create the HTML body.
	 * @param {zarafa.core.data.MAPIRecord} record the email to print
	 * @return {String} The HTML for the XTemplate to print
	 */
	generateBodyTemplate: function(record) {
		var html = '';
		html += '<b>{fullname}</b>\n';
		html += '<hr>\n';
		html += '<table>\n';
		html += this.addRow(_('From'), '{formatted_from}');
		// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
		html += this.addRow(_('Sent'), '{message_delivery_time:formatDefaultTimeString("' + _("l jS F Y {0}") + '")}');
		html += this.optionalRow(_('To'), 'display_to', '{formatted_to}');
		html += this.optionalRow(_('Cc'), 'display_cc', '{formatted_Cc}');
		html += this.optionalRow(_('Bcc'), 'display_bcc', '{formatted_Bcc}');
		html += this.addRow(_('Subject'), '{subject}');
		html += this.addRow('', '');   // separator
		html += this.optionalRow(_('Sensitivity'), 'sensitivity', '{sensitivity:sensitivityString}');
		html += this.optionalRow(_('Importance'), 'importance', '{importance:importanceString}');
		html += '<tpl if="!Ext.isEmpty(values.sensitivity) || !Ext.isEmpty(values.importance)">';
		html += this.addRow('', '');   // separator
		html += '</tpl>';
		html += this.optionalRow(_('Attachments'), 'attachment_names', '{attachment_names}');
		html += '</table><br><p>\n';
		// Strip tags such as {foo.bar}, otherwise they will be interpreted as XTemplate tag.
		// Strip line separator and paragraph separator, since they break JavaScript strings which breaks XTemplate's eval().
		var body = record.getBody(true) || '';
		if (!Ext.isEmpty(body) && container.getServerConfig().getDOMPurifyEnabled()) {
			body = DOMPurify.sanitize(body);
		}
		html += body.replace(/{/g, '&#123;').replace(/}/g, '&#125;').replace(/\u2028/g, '').replace(/\u2029/g, '');
		html += '</p>\n';
		// Remove inline images when that are not downloaded.
		return record.isExternalContentBlocked() ? html.replace(/<img[^>]*>/g,"") : html;
	},

	/**
	 * Prepares data for any record for use in the XTemplate
	 * @param {Zarafa.core.data.MAPIRecord} record The mapi record to print
	 * @return {Array} Data suitable for use in the XTemplate
	 */
	prepareData: function(record) {
		var data = Zarafa.mail.printer.MailRenderer.superclass.prepareData(record);

		if (!Ext.isEmpty(record.get('sender_entryid')) && !Ext.isEmpty(record.get('sent_representing_entryid')) 
		&& !Zarafa.core.EntryId.compareABEntryIds(record.get('sent_representing_entryid'), record.get('sender_entryid'))) {
			data['formatted_from'] = Ext.util.Format.htmlEncode(record.get('sender_name') +' <'+ record.get('sender_email_address') +'> ');
			data['formatted_from'] += _('on behalf of') + ' ';
			data['formatted_from'] += Ext.util.Format.htmlEncode(record.get('sent_representing_name') +' <'+ record.get('sent_representing_email_address') +'>;');
			
		} else {
			// add printer specific tags
			data['formatted_from'] = Ext.util.Format.htmlEncode(record.get('sender_name') +' <'+ record.get('sender_email_address') +'>;');
		}

		var recipientStore = record.getSubStore('recipients');
		data['formatted_to'] = "";
		data['formatted_Cc'] = "";
		data['formatted_Bcc'] = "";
		recipientStore.each(function (recipient) {
			var name = recipient.get('display_name');
			var email = recipient.get('smtp_address');
			var formattedInfo = Ext.util.Format.htmlEncode(name + ' <' + email +'>; ');
			switch (recipient.get('recipient_type')) {
				case Zarafa.core.mapi.RecipientType.MAPI_CC:
					data['formatted_Cc'] += formattedInfo;
					break;
				case Zarafa.core.mapi.RecipientType.MAPI_BCC:
					data['formatted_Bcc'] += formattedInfo;
					break;
				case Zarafa.core.mapi.RecipientType.MAPI_TO:
				default:
					data['formatted_to'] += formattedInfo;
					break;
			}
		}, this);

		return data;
	}
});

Ext.namespace('Zarafa.mail.ui');

/**
 * @class Zarafa.mail.ui.MailGrid
 * @extends Zarafa.common.ui.grid.MapiMessageGrid
 * @xtype zarafa.mailgrid
 */
Zarafa.mail.ui.MailGrid = Ext.extend(Zarafa.common.ui.grid.MapiMessageGrid, {
	/**
	 * @cfg {Zarafa.mail.MailContext} context The context to which this panel belongs
	 */
	context: undefined,

	/**
	 * The {@link Zarafa.mail.MailContextModel} which is obtained from the {@link #context}.
	 * @property
	 * @type Zarafa.mail.MailContextModel
	 */
	model: undefined,

	/**
	 * @cfg {Array} sentFolderTypes List of strings of the
	 * {@link Zarafa.hierarchy.data.MAPIFolderRecord#getDefaultFolderKey folder key}
	 * for the {@link Zarafa.hierarchy.data.MAPIFolderRecord folders} which contain
	 * messages where the Sender is the Store Owner. For these kinds of
	 * folders a different column set must be loaded.
	 *
	 * By default these will be the 'outbox' and 'sent'
	 * folders.
	 */
	sentFolderTypes: undefined,

	/**
	 * @cfg {Array} unSentFolderTypes List of strings of the
	 * {@link Zarafa.hierarchy.data.MAPIFolderRecord#getDefaultFolderKey folder key}
	 * for the {@link Zarafa.hierarchy.data.MAPIFolderRecord folders} which contain
	 * messages where the Sender is the Store Owner. For these kinds of
	 * folders a different column set must be loaded.
	 *
	 * By default these will be the 'drafts' folders.
	 */
	unSentFolderTypes: undefined,

	/**
	 * @cfg {Boolean} expandSingleConversation True if {@link Zarafa.mail.settings.SettingsConversationWidget#enableConversations enabled conversation view }
	 * and {@link Zarafa.mail.settings.SettingsConversationWidget#singleExpand single expand conversation} user settings enabled False otherwise
	 *
	 * By default this will be True.
	 */
	expandSingleConversation: true,

	/**
	 * Delay in milliseconds before updating the preview record after a selection change.
	 * This prevents rapid navigation (for example by holding the arrow keys) from
	 * triggering excessive preview loads.
	 * @property
	 * @type Number
	 */
	previewRecordDelay: 50,

	/**
	 * Helper task which delays updating the preview panel for the currently selected record.
	 * @property
	 * @type Ext.util.DelayedTask
	 * @private
	 */
	previewRecordTask: undefined,

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

		if (!Ext.isDefined(config.sentFolderTypes)) {
			config.sentFolderTypes = ['outbox', 'sent'];
		}

		if (!Ext.isDefined(config.unSentFolderTypes)){
			config.unSentFolderTypes = ['drafts'];
		}

		if (!Ext.isDefined(config.model) && Ext.isDefined(config.context)) {
			config.model = config.context.getModel();
		}

		if (!Ext.isDefined(config.store) && Ext.isDefined(config.model)) {
			config.store = config.model.getStore();
		}
		config.store = Ext.StoreMgr.lookup(config.store);

		Ext.applyIf(config, {
			xtype: 'zarafa.mailgrid',
			ariaLabel: _('Message list'),
			border: false,
			stateful: true,
			statefulRelativeDimensions: false,
			loadMask: this.initLoadMask(),
			supportLiveScroll: true,
			view: new Zarafa.mail.ui.MailGridView(this.initViewConfig()),
			sm: this.initSelectionModel(),
			cm: this.initColumnModel(),
			enableDragDrop: true,
			expandSingleConversation: this.allowExpandOneConversation(),
			ddGroup: 'dd.mapiitem'
		});

		Zarafa.mail.ui.MailGrid.superclass.constructor.call(this, config);
	},

	/**
	 * Initialize event handlers
	 * @private
	 */
	initEvents: function()
	{
		Zarafa.mail.ui.MailGrid.superclass.initEvents.call(this);

		this.on({
			'cellclick': this.onCellClick,
			//'rowclick': this.onRowClick,
			'rowclick': {
				fn: this.onRowClick,
				buffer: 1,
				scope: this
			},
			'rowbodycontextmenu': this.onRowBodyContextMenu,
			'rowdblclick': this.onRowDblClick,
			scope: this
		});

		this.mon(this.getView(), 'beforelivescrollstart', this.onBeforeLiveScrollStart, this);
		this.mon(this.getView(), 'livescrollstart', this.onLiveScrollStart, this);
		this.mon(this.getView(), 'beforesort', this.onBeforeSort, this);

		this.mon(this.getSelectionModel(), 'beforerowselect', this.onBeforeRowSelect, this);
		// Add a buffer to the following 2 event handlers. These are influenced by Extjs when a record
		// is removed from the store. However removing of records isn't performed in batches. This means
		// that we need to offload the event handlers attached to removing of records in case that
		// a large batch of records is being removed.
		this.mon(this.getSelectionModel(), 'rowselect', this.onRowSelect, this, { buffer: 1 });
		this.mon(this.getSelectionModel(), 'selectionchange', this.onSelectionChange, this, { buffer: 1 });

		this.mon(container, 'afterrendercontentpanel', function(tabPanel){
			this.mon(tabPanel, {
				'tabchange': this.onTabChange,
				'close': this.onTabClose,
				scope: this
			});
		}, this);

		this.mon(this.context, 'viewmodechange', this.onContextViewModeChange, this);
		this.mon(this.context, 'viewchange', this.onContextViewChange, this);
		this.onContextViewModeChange(this.context, this.context.getCurrentViewMode());

		// The conversation structure itself is maintained by the store
		// (see Zarafa.mail.MailStore#regroupConversations).
		this.mon(this.getView(), 'rowupdated', function(view, rowIndex, record) {
			if (record.get('depth') === 0) {
				return;
			}

			// Update the conversation header (to reflect read/unread status and flags)
			var headerStore = record.getStore();
			var i = rowIndex;
			var r;
			do {
				i--;
				r = headerStore.getAt(i);
			} while (r && r.get('depth') > 0);

			if (r) {
				view.refreshRow(r);
			}
		}, this);
	},

	/**
	 * Initialize the {@link Ext.grid.GridPanel.loadMask} field
	 *
	 * @return {Ext.LoadMask} The configuration object for {@link Ext.LoadMask}
	 * @private
	 */
	initLoadMask: function()
	{
		return {
			msg: _('Loading mail') + '...'
		};
	},

	/**
	 * Initialize the {@link Ext.grid.GridPanel#viewConfig} field
	 *
	 * @return {Ext.grid.GridView} The configuration object for {@link Ext.grid.GridView}
	 * @private
	 */
	initViewConfig: function()
	{
		return {
			// enableRowBody is used for enabling the rendering of
			// the second row in the compact view model. The actual
			// rendering is done in the function getRowClass.
			//
			// NOTE: Even though we default to the extended view,
			// enableRowBody must be enabled here. We disable it
			// later in onContextViewModeChange(). If we set false
			// here, and enable it later then the row body will never
			// be rendered. So disabling after initializing the data
			// with the rowBody works, but the opposite will not.
			enableRowBody: true,
			getRowClass: this.viewConfigGetRowClass,

			// The mail grid is not editable; without this, records that get
			// updated in the store (e.g. after opening a mail merges the full
			// item back into the list record) would show the red dirty-cell
			// marker in their cells.
			markDirty: false,

			// We need a rowselector depth of 15 because of the nested
			// table in the rowBody.
			rowSelectorDepth: 15,
			enableGrouping: this.hasEnabledGrouping(),
			enableGroupingMenu: true
		};
	},

	/**
	 * Apply custom style and content for the row body. This will always
	 * apply the Read/Unread style to the entire row. Optionally it will
	 * enable the row body containing the subject.
	 *
	 * @param {Ext.data.Record} record The {@link Ext.data.Record Record} corresponding to the current row.
	 * @param {Number} rowIndex The row index
	 * @param {Object} rowParams A config object that is passed to the row template during
	 * rendering that allows customization of various aspects of a grid row.
	 * If enableRowBody is configured true, then the following properties may be set by this function,
	 * and will be used to render a full-width expansion row below each grid row.
	 * @param {Ext.data.Store} store The Ext.data.Store this grid is bound to
	 * @return {String} a CSS class name to add to the row
	 * @private
	 */
	viewConfigGetRowClass : function(record, rowIndex, rowParams, store)
	{
		var cssClass = this.grid.getConversationCssClasses(record, rowIndex, store);

		if (this.enableRowBody) {
			var meta = {}; // Metadata object for Zarafa.common.ui.grid.Renderers.
			var value = ''; // The value which must be rendered
			rowParams.body = '<div class="zarafa-grid-body-container">';

			if (!record.isConversationHeaderRecord()) {
				// Render the categories
				cssClass += ' with-categories';
				var categories = Zarafa.common.categories.Util.getCategories(record);
				var categoriesHtml = Zarafa.common.categories.Util.getCategoriesHtml(categories);
				rowParams.body += '<div class="k-category-add-container"><span class="k-category-add"></span></div><div class="k-category-container">' + categoriesHtml + '</div>';
			}

			// Render the subject (also for conversation items: consistent with
			// normal mails, and it allows the list request to skip the body).
			meta = {};
			value = Zarafa.common.ui.grid.Renderers.subject(record.get('subject'), meta, record);
			rowParams.body += String.format('<div class="grid_compact grid_compact_left grid_compact_subject_cell {0}">{1}</div>', meta.css, value);

			rowParams.body += '</div>';
			cssClass += ' k-compact-row';

			return 'x-grid3-row-expanded ' + cssClass;
		}

		return 'x-grid3-row-collapsed ' + cssClass;
	},

	/**
	 * Helper function to get css classes related to conversation view.
	 *
	 * @param {Ext.data.Record} record The {@link Ext.data.Record Record} corresponding to the current row.
	 * @param {Number} rowIndex The row index
	 * @param {Ext.data.Store} store The Ext.data.Store this grid is bound to
	 * @return {String} css classes related to conversation item.
	 */
	getConversationCssClasses: function(record, rowIndex, store)
	{
		var isConversationHeader = record.isConversationHeaderRecord();
		var depth = record.get('depth');
		var cssClass = (Ext.isFunction(record.isRead) && !record.isRead() ? 'mail_unread' : 'mail_read');

		// Conversation headers should get the read/unread info from the items in the conversation
		if (isConversationHeader) {
			cssClass = 'mail_read';
			var conversationRecords = store.getConversationItemsFromHeaderRecord(record);
			conversationRecords.every(function(r) {
				if (Ext.isFunction(r.isRead) && !r.isRead()) {
					cssClass = 'mail_unread';
					return false;
				}
				return true;
			});

			cssClass += ' k-conversation-header';

			// The thread line of an expanded conversation starts at the header,
			// right below the expand arrow.
			if (store.isConversationOpened(record)) {
				cssClass += ' k-conversation-open';
			}
		} else if (record.isConversationRecord()) {
			// If its last item of the conversation.
			if (store.getCount() === rowIndex + 1 ||
				store.getAt(rowIndex + 1).get('depth') === 0) {
				cssClass += ' line_circle_end k-last-conversation-item';
			} else {
				cssClass += ' line_circle_l';
			}
		}

		cssClass += ' k-depth-' + depth;

		return cssClass;
	},

	/**
	 * Initialize the {@link Ext.grid.GridPanel.sm SelectionModel} field
	 *
	 * @return {Ext.grid.RowSelectionModel} The subclass of {@link Ext.grid.AbstractSelectionModel}
	 * @private
	 */
	initSelectionModel: function()
	{
		return new Zarafa.mail.ui.MailRowSelectionModel({
			singleSelect: false
		});
	},

	/**
	 * Initialize the {@link Ext.grid.GridPanel.cm ColumnModel} field.
	 *
	 * @return {Ext.grid.ColumnModel} The {@link Ext.grid.ColumnModel} for this grid
	 * @private
	 */
	initColumnModel: function()
	{
		return new Zarafa.mail.ui.MailGridColumnModel();
	},

	/**
	 * Event handler which is fired when the currently active view inside the {@link #context}
	 * has been updated. This will update the call
	 * {@link #viewPanel}#{@link Zarafa.core.ui.SwitchViewContentContainer#switchView}
	 * to make the requested view active.
	 *
	 * @param {Zarafa.core.Context} context The context which fired the event.
	 * @param {Zarafa.mail.data.Views} newView The ID of the selected view.
	 * @param {Zarafa.mail.data.Views} oldView The ID of the previously selected view.
	 */
	onContextViewChange: function(context, newView, oldView)
	{
		if(oldView === Zarafa.mail.data.Views.LIVESCROLL) {
			this.getView().resetScroll();
		}
	},

	/**
	 * Event handler which is fired when the {@link Zarafa.core.Context} fires the
	 * {@link Zarafa.core.Context#viewmodechange viewmodechange} event. This will check
	 * where the preview panel is located, and if needed change the
	 * {@link Ext.grid.Column columns} inside the {@link Ext.grid.ColumnModel ColumnModel}
	 * of the {@link Zarafa.mail.ui.MailGrid MailGrid}. Either use the extended (and more flexible)
	 * set or the more compact set.
	 *
	 * @param {Zarafa.core.Context} context The context which fired the event
	 * @param {Zarafa.mail.data.ViewModes} newViewMode The new active mode
	 * @param {Zarafa.mail.data.ViewModes} oldViewMode The previous mode
	 * @private
	 */
	onContextViewModeChange: function(context, newViewMode, oldViewMode)
	{
		switch(newViewMode){
			case Zarafa.mail.data.ViewModes.RIGHT_PREVIEW:
			case Zarafa.mail.data.ViewModes.NO_PREVIEW:
			case Zarafa.mail.data.ViewModes.BOTTOM_PREVIEW:
				var compact = newViewMode === Zarafa.mail.data.ViewModes.RIGHT_PREVIEW;
				//The row body must only be enabled in compact view.
				this.getView().enableRowBody = compact;
				this.getColumnModel().setCompactView(compact);
				break;
			case Zarafa.mail.data.ViewModes.SEARCH:
			case Zarafa.mail.data.ViewModes.LIVESCROLL:
				break;
		}
	},

	/**
	 * Event handler for the {@link #beforeconfigchange} event which is fired at the start of
	 * {@link Zarafa.common.ui.grid.ColumnModel#setConfig}. At this time we can
	 * check which columns should be activated based on the {@link #currentEntryId}.
	 *
	 * @param {Ext.gridColumnModel} columnModel The model which is being configured
	 * @param {Object} config The configuration object
	 * @private
	 */
	onBeforeConfigChange: function(columnModel, config)
	{
		if (!this.currentEntryId) {
			return;
		}

		var store = container.getHierarchyStore().getById(this.currentStoreEntryId);
		var folder = store.getFolder(this.currentEntryId);
		var folderKey = folder.getDefaultFolderKey();

		// It will return true if selected folder is 'Outbox' or 'Sent' folder else return false.
		var isSentFolderType = this.sentFolderTypes.indexOf(folderKey) >= 0;
		// It will return true if selected folder is 'Drafts' folder else return false.
		var isUnsentFolderType = this.unSentFolderTypes.indexOf(folderKey) >= 0;
		// it will return true if selected folder is other then 'Outbox', 'Sent', and 'Drafts' else return false.
		var isReceivedFolderType = (!isSentFolderType && !isUnsentFolderType);

		for (var i = 0, len = config.length; i < len; i++) {
			var column = config[i];

			if (column.dataIndex === 'sent_representing_name') {
				// Here it will visible the 'From' column in mail grid,
				// if selected folder is Received folder type.
				column.hidden = !isReceivedFolderType;
			} else if (column.dataIndex === 'display_to') {
				// Here it will visible the 'To' column in mail grid,
				// if selected folder is sent or unsent folder type.
				column.hidden = !(isSentFolderType || isUnsentFolderType);
			} else if (column.dataIndex === 'message_delivery_time') {
				// Here it will visible the 'Received' column in mail grid,
				// if selected folder is Received folder type.
				column.hidden = !isReceivedFolderType;
			} else if (column.dataIndex === 'client_submit_time') {
				// Here it will visible the 'Sent' column in mail grid,
				// if selected folder is Sent folder type.
				column.hidden = !isSentFolderType;
			} else if(column.dataIndex === 'last_modification_time'){
				// Here it will visible the 'Modified' column in mail grid,
				// if selected folder is unsent folder type.
				column.hidden = !isUnsentFolderType;
			} else if (column.dataIndex === 'deferred_send_time') {
				// Here it will visible the 'Scheduled' column in mail grid,
				// if selected folder is Sent folder type.
				column.hidden = !isSentFolderType;
			}
		}

		Zarafa.mail.ui.MailGrid.superclass.onBeforeConfigChange.apply(this, arguments);
	},

	/**
	 * Event handler for the cellclick event of the grid. Will mark/unmark a mail
	 * as read when the user clicked on the icon column
	 *
	 * @param {Zarafa.mail.ui.MailGrid} grid The mail grid
	 * @param {Number} rowIndex The index number of the row that was clicked
	 * @param {Number} columnIndex The index number of the column that was clicked
	 * @param {Ext.EventObject} event The event object
	 * @private
	 */
	onCellClick: function(grid, rowIndex, columnIndex, e)
	{
		var record = this.store.getAt(rowIndex);
		if (!Ext.isDefined(record)) {
			return;
		}

		// Toggle the conversation when the arrow (icon) column of a header row
		// is clicked. The icon column prevents row selection, so this click is
		// not handled by onBeforeRowSelect and must be handled here.
		if (record.isConversationHeaderRecord()) {
			if (columnIndex === 0) {
				this.toggleConversation(record, grid.selModel, rowIndex);
			}
			return false;
		}

		var cm = this.getColumnModel();
		var column = cm.config[columnIndex];
		if (
			column.dataIndex === 'icon_index' && !(Ext.isFunction(record.isConversationRecord) && record.isConversationRecord()) ||
			column.dataIndex === 'sent_representing_name' && Ext.isFunction(record.isConversationRecord) && record.isConversationRecord() && Ext.fly(e.target).hasClass('k-icon')
		) {
			Zarafa.common.Actions.markAsRead(record, !record.isRead());
		} else if(column.dataIndex === 'flag_due_by') {
			Zarafa.common.Actions.openFlagsMenu(record, e.getXY());
		}
	},

	/**
	 * Event handler that opens the categories dialog when the user clicks on the
	 * add category icon.
	 *
	 * @param {Zarafa.mail.ui.MailGrid} grid The grid which was right clicked
	 * @param {Number} rowIndex The index number of the row which was right clicked
	 * @param {Ext.EventObject} event The event structure
	 * @private
	 */
	onRowClick : function(grid, rowIndex, event)
	{
		var store = this.store;
		var keepExisting = event.ctrlKey || event.shiftKey;
		// Get the record from the rowIndex
		var record = store.getAt(rowIndex);
		if (!Ext.isDefined(record)) {
			return;
		}

		// Conversation header rows are toggled by onBeforeRowSelect (mousedown
		// anywhere on the row) or onCellClick (arrow column). Handling them
		// here as well would toggle the conversation twice per click.
		if (record.isConversationHeaderRecord()) {
			return;
		}

		if (this.expandSingleConversation && !(keepExisting)) {
			// close all opened conversation when user click on non conversation item.
			var headerRecord = record.isConversationRecord() ? store.getHeaderRecordFromItem(record) : false;
			store.collapseAllConversation(headerRecord);
		}

		if ( Ext.get(event.target).hasClass('k-category-add') ){
			Zarafa.common.Actions.openCategoriesMenu([record], event.getXY());
		}
	},


	/**
	 * Event handler which is triggered when the user opens the context menu.
	 * Overridden to show the standard (multi selection) context menu with all
	 * items of the conversation when a conversation header is right-clicked, so
	 * actions like delete, mark (un)read, categorize and move act on the entire
	 * conversation.
	 *
	 * @param {Zarafa.mail.ui.MailGrid} grid The grid which was right clicked
	 * @param {Number} rowIndex The index number of the row which was right clicked
	 * @param {Number} cellIndex The index number of the column which was right clicked
	 * @param {Ext.EventObject} event The event structure
	 * @private
	 */
	onCellContextMenu: function(grid, rowIndex, cellIndex, event)
	{
		var record = this.store.getAt(rowIndex);
		if (record && record.isConversationHeaderRecord()) {
			// Note: header rows cannot be selected, so the conversation items
			// are passed directly instead of going through the selection model.
			var items = this.store.getConversationItemsFromHeaderRecord(record);
			if (!Ext.isEmpty(items)) {
				Zarafa.core.data.UIFactory.openDefaultContextMenu(items, {
					position: event.getXY(),
					context: this.context,
					actsOnTodoListFolder: this.model.getDefaultFolder().isTodoListFolder(),
					// Single-message actions (reply, forward, open, ...) act on
					// the newest message of the conversation, bulk actions
					// (delete, mark read, ...) on all of them.
					conversationHeader: true
				});
			}

			return false;
		}

		return Zarafa.mail.ui.MailGrid.superclass.onCellContextMenu.call(this, grid, rowIndex, cellIndex, event);
	},

	/**
	 * Event handler which is triggered when the user opens the context menu.
	 *
	 * This will call {@link onCellContextMenu} and pass -1 for the column index to prevent it
	 * showing a special context menu if one would be set for specific columns.

	 *
	 * @param {Zarafa.mail.ui.MailGrid} grid The grid which was right clicked
	 * @param {Number} rowIndex The index number of the row which was right clicked
	 * @param {Ext.EventObject} event The event structure
	 * @private
	 */
	onRowBodyContextMenu: function(grid, rowIndex, event)
	{
		var record = this.store.getAt(rowIndex);
		if (record && record.isConversationHeaderRecord()) {
			return this.onCellContextMenu(grid, rowIndex, -1, event);
		}

		this.onCellContextMenu(grid, rowIndex, -1, event);
	},

	/**
	 * Event handler which is triggered when the user double-clicks on a particular item in the grid.
	 * This will check if existing record is opened in browser window then set focus on it otherwise,
	 * Open a {@link Zarafa.mail.dialogs.ShowMailContentPanel contentpanel} which contains the selected item.
	 *
	 * @param {Grid} grid The Grid on which the user double-clicked
	 * @param {Number} rowIndex The Row number on which was double-clicked.
	 * @param {Ext.EventObject} e The event object
	 * @private
	 */
	onRowDblClick: function(grid, rowIndex, e)
	{
		var record = this.getSelectionModel().getSelected();

		var recordClick = grid.getStore().getAt(rowIndex);
		if (!Ext.isDefined(record) || recordClick.isConversationHeaderRecord()) {
			return false;
		}

		// Switch focus to the browser window is record is already opened in browser window.
		var browserWindow = Zarafa.core.BrowserWindowMgr.getOpenedWindow(record);
		if (browserWindow) {
			browserWindow.focus();
		} else {
			Zarafa.common.Actions.openMessageContent(record, undefined, true);
		}
	},

	/**
	 * Event handler before the row selection performs. If selected record is
	 * conversation header then cancel the selection.
	 *
	 * @param {Zarafa.mail.ui.MailRowSelectionModel} selectionModel The selectionModel
	 * @param {Number} rowIndex Then index to be selected.
	 * @param {Boolean} keepExisting False if other selections will be cleared
	 * @param {Zarafa.core.data.IPMRecord} record The record to be selected
	 */
	onBeforeRowSelect: function(selectionModel, rowIndex, keepExisting, record)
	{
		if (record.isConversationHeaderRecord()) {
			if (keepExisting === true) {
				// Range or multi selections may only ever expand a conversation:
				// collapsing would remove rows from the selection range.
				this.openConversation(record, selectionModel, rowIndex, keepExisting);
			} else {
				// A plain click on a header row toggles the conversation.
				this.toggleConversation(record, selectionModel, rowIndex);
			}
			return false;
		}
	},

	/**
	 * Function will toggle the conversation: expand it when it is collapsed and
	 * select its first item, collapse it when it is expanded.
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The header record of the conversation.
	 * @param {Zarafa.mail.ui.MailRowSelectionModel} selectionModel The selectionModel
	 * @param {Number} rowIndex The index of the header row.
	 */
	toggleConversation: function(record, selectionModel, rowIndex)
	{
		var store = this.getStore();
		var wasOpened = store.isConversationOpened(record);
		store.toggleConversation(record);

		if (!wasOpened) {
			if (selectionModel) {
				selectionModel.selectRow(rowIndex + 1, false);
			}
			if (this.expandSingleConversation) {
				store.collapseAllConversation(record);
			}
		}
	},

	/**
	 * Function will expand the conversation if closed and select the first item of it.
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record needs to expand.
	 * @param {Zarafa.mail.ui.MailRowSelectionModel} selectionModel The selectionModel
	 * @param {Number} rowIndex Then index to be selected.
	 * @param {Boolean} keepExisting False if other selections will be cleared
	 */
	openConversation: function(record, selectionModel, rowIndex, keepExisting)
	{
		var store = this.getStore();
		if (!store.isConversationOpened(record)) {
			store.expandConversation(record);
			if (selectionModel) {
				selectionModel.selectRow(rowIndex+1, keepExisting);
			}
			if (this.expandSingleConversation) {
				store.collapseAllConversation(record);
			}
		}
	},

	/**
	 * Helper function used to check {@link Zarafa.mail.settings.SettingsConversationWidget#enableConversations enabled conversation view }
	 * and {@link Zarafa.mail.settings.SettingsConversationWidget#singleExpand single expand conversation} user setting enabled.
	 *
	 * @return {Boolean} True if {@link Zarafa.mail.settings.SettingsConversationWidget#enableConversations enabled conversation view } and
	 * {@link Zarafa.mail.settings.SettingsConversationWidget#singleExpand expand single conversation} is enabled else false.
	 */
	allowExpandOneConversation: function()
	{
		var settingsModel = container.getSettingsModel();
		return settingsModel.get("zarafa/v1/contexts/mail/enable_conversation_view") && settingsModel.get("zarafa/v1/contexts/mail/expand_single_conversation");
	},

	/**
	 * Event handler which is triggered when the user selects a row from the {@link Ext.grid.GridPanel}.
	 * This will updates the {@link Zarafa.mail.MailContextModel MailContextModel} with the record which
	 * was selected in the grid for preview
	 *
	 * @param {Ext.grid.RowSelectionModel} selectionModel The selection model used by the grid.
	 * @param {Integer} rowNumber The row number which is selected in the selection model
	 * @param {Ext.data.Record} record The record which is selected for preview.
	 * @private
	 */
	onRowSelect: function(selectionModel, rowNumber, record)
	{
		var count = selectionModel.getCount();

		if (Ext.isFunction(record.isConversationHeaderRecord) && record.isConversationHeaderRecord()) {
			this.cancelPreviewRecordTask();
			this.model.setPreviewRecord(undefined);
		} else if (count === 0) {
			this.cancelPreviewRecordTask();
			this.model.setPreviewRecord(undefined);
		} else if (count === 1 && selectionModel.getSelected() === record) {
			this.queuePreviewRecord(record);
		}
	},

	/**
	 * Event handler which is triggered when the {@link Zarafa.mail.ui.MailGrid grid}
	 * {@link Zarafa.core.data.IPMRecord record} selection is changed. This will inform
	 * the {@link Zarafa.mail.MailContextModel contextmodel} about the change.
	 *
	 * @param {Ext.grid.RowSelectionModel} selectionModel The selection model used by the grid.
	 * @private
	 */
	onSelectionChange: function(selectionModel)
	{
		var selections = selectionModel.getSelections();

		this.model.setSelectedRecords(selections);
		if (Ext.isEmpty(selections)) {
		        this.cancelPreviewRecordTask();
		        this.model.setPreviewRecord(undefined);
		}
	},

	/**
	 * Ensure the delayed task for updating the preview record exists.
	 * @return {Ext.util.DelayedTask}
	 * @private
	 */
	getPreviewRecordTask: function()
	{
		if (!this.previewRecordTask) {
		        this.previewRecordTask = new Ext.util.DelayedTask(this.applyPreviewRecord, this);
		}

		return this.previewRecordTask;
	},

	/**
	 * Queue updating the preview panel for the provided record.
	 * @param {Ext.data.Record} record The record to preview.
	 * @private
	 */
	queuePreviewRecord: function(record)
	{
		var task = this.getPreviewRecordTask();
		task.cancel();
		task.delay(this.previewRecordDelay, this.applyPreviewRecord, this, [record]);
	},

	/**
	 * Cancel the delayed preview update task when no longer needed.
	 * @private
	 */
	cancelPreviewRecordTask: function()
	{
		if (this.previewRecordTask) {
		        this.previewRecordTask.cancel();
		}
	},

	/**
	 * Apply the record to the preview panel.
	 * @param {Ext.data.Record} record The record to preview.
	 * @private
	 */
	applyPreviewRecord: function(record)
	{
		this.model.setPreviewRecord(record);
	},

	/**
	 * Cleanup any pending tasks before destroying the grid.
	 * @private
	 */
	beforeDestroy: function()
	{
		this.cancelPreviewRecordTask();
		Zarafa.mail.ui.MailGrid.superclass.beforeDestroy.apply(this, arguments);
	},

	/**
	 * Event handler triggered before the live scroll start. In conversation view the
	 * store contains more rows than inbox items (headers, sent items), so the default
	 * cursor-based check of the view is not usable and we compare the number of real
	 * inbox items with the total count instead.
	 */
	onBeforeLiveScrollStart: function()
	{
		if (this.store.containsConversations() && this.store.getStoreLength() >= this.store.getTotalCount()) {
			return false;
		}
	},

	/**
	 * Event handler which triggered when scrollbar gets scrolled more then 90% of it`s height.
	 * it will be used to start live scroll on {@link Zarafa.core.data.ListModuleStore ListModuleStore}.
	 * also it will register event on {@link Zarafa.core.data.ListModuleStore ListModuleStore} to get
	 * updated batch of mails status.
	 *
	 * @param {Number} cursor the cursor contains the last index of record in grid.
	 * @private
	 */
	onLiveScrollStart: function(cursor)
	{
		this.model.startLiveScroll(cursor);
	},

	/**
	 * Event handler which triggered when header of grid was clicked to apply the sorting
	 * on {@link Zarafa.mail.ui.MailGrid mailgrid}. it will first stop the
	 * {@link Zarafa.core.ContextModel#stopLiveScroll live scroll} and then apply the sorting.
	 * @private
	 */
	onBeforeSort: function()
	{
		// Sorting works together with the conversation view: the store only
		// groups conversations for the default (newest first) sorting and shows
		// a flat, sorted list for any other sorting. Announce the switch to the
		// flat list, otherwise it looks like the conversation view got lost.
		if (this.getStore().containsConversations()) {
			container.getNotifier().notify(
				'info.conversations',
				_('Conversation view'),
				_('Sorting shows the flat message list. Sort by "Received" (newest first) to show conversations again.')
			);
		}

		this.model.stopLiveScroll();
	},

	/**
	 * Event handler triggers when the configuration of {@link Zarafa.mail.ui.MailGridColumnModel MailGridColumnModel}
	 * is changed.
	 */
	onConfigChange: function ()
	{
		if (this.getView().enableGrouping) {
			var store = this.getStore();
			var sortInfo = this.getStore().sortInfo;
			if (this.getView().isAllowGrouping(sortInfo.field)) {
				store.groupField = sortInfo.field;
			}
		}
	},

	/**
	 * Function which is used to check that grouping has enabled.
	 * @return {Boolean} true if grouping is enabled else return false.
	 */
	hasEnabledGrouping: function ()
	{
		return container.getSettingsModel().get('zarafa/v1/contexts/mail/enable_grouping');
	},

	/**
	 * Called after a row has been removed for the GridView.
	 * This will be called from {@link Zarafa.common.ui.grid.GridPanel#onWriteRecord onWriteRecord}.
	 * Function will check whether the {@link Zarafa.mail.ui.MailRowSelectionModel#updatedLast updatedLast} config is available
	 * and will select row accordingly.
	 *
	 * @param rowIndex {Number} rowIndex the index of row was deleted from grid.
	 * @private
	 */
	onRowRemoved: function(rowIndex)
	{
		var rowSelectionModel= this.getSelectionModel();
		var lastActiveIndex = rowSelectionModel.updatedLast;

		if (Ext.isDefined(lastActiveIndex)) {
			rowIndex = lastActiveIndex;
			rowSelectionModel.clearUpdatedLast();
		}

		Zarafa.mail.ui.MailGrid.superclass.onRowRemoved.call(this, rowIndex);
	},

	/**
	 * Event handler triggers when content tab panel is changed, it's used to
	 * set the focus on selected row.
	 *
	 * @param {Ext.TabPanel} tabPanel the tab panel which contains tabs.
	 * @param {Ext.Panel} activeTab the activeTab from tab panel
	 */
	onTabChange: function(tabPanel, activeTab)
	{
		if (activeTab.id === "zarafa-mainpanel-content") {
			this.getView().setFocus();
		}
	},

	/**
	 * Event handler triggers when tab was close. Which call {@link Zarafa.mail.ui.MailGridView#setFocus setFocus} function
	 * to set the focus on mail grid. 
	 */
	onTabClose: function()
	{
		if (container.getTabPanel().getActiveTab().id === "zarafa-mainpanel-content") {
			this.getView().setFocus();
		}
	}
});

Ext.reg('zarafa.mailgrid', Zarafa.mail.ui.MailGrid);
Ext.namespace('Zarafa.mail.widget');

/**
 * @class Zarafa.mail.widget.MailWidget
 * @extends Zarafa.core.ui.widget.AbstractFolderWidget
 *
 * Widget that shows the unread mail.
 */
Zarafa.mail.widget.MailWidget = Ext.extend(Zarafa.core.ui.widget.AbstractFolderWidget, {
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

		var store = new Zarafa.mail.MailStore();

		// The store already has the default sort info, but we
		// must apply it still. (Bug in the ListModuleStore?).
		store.setDefaultSort(store.defaultSortInfo.field, store.defaultSortInfo.direction);

		// Create a restriction, we only want unread mails, so mails which
		// do not have the MSGFLAG_READ flag on the PR_MESSAGE_FLAGS
		store.setRestriction({
			'search': Zarafa.core.data.RestrictionFactory.dataResBitmask(
				'0x00E070003', /* PR_MESSAGE_FLAGS */
				Zarafa.core.mapi.Restrictions.BMR_EQZ,
				Zarafa.core.mapi.MessageFlags.MSGFLAG_READ
			)
		});
		// If we want to use a search restriction we must say that this store is an advanceSearchStore
		// or it will be removed.
		store.isAdvanceSearchStore = function(){
			return true;
		};

		Ext.applyIf(config, {
			autoScroll: true,
			layout: 'fit',
			folderType: 'inbox',
			store: store,
			items: [{
				xtype: 'zarafa.gridpanel',
				id: 'unread-mail-widget',
				store: store,
				border: true,
				loadMask: {
					msg: _('Loading mail') + '...'
				},
				sm: new Zarafa.common.ui.grid.RowSelectionModel({
					singleSelect: true
				}),
				viewConfig: {
					deferEmptyText: false,
					emptyText: '<div class="emptytext">' + _('No unread mail.') + '</div>',
					forceFit: true,

					// Enable the row body in which we can render
					// the subject of the mail and some icons
					// for the attachment and importance.
					enableRowBody: true,
					rowSelectorDepth: 15,
					getRowClass: this.viewConfigGetRowClass
				},
				colModel: new Ext.grid.ColumnModel({
					columns: [{
						header: _('From'),
						dataIndex: 'sent_representing_name',
						menuDisabled: true,
						renderer: Ext.util.Format.htmlEncode
					},{
						header: _('Received'),
						dataIndex: 'message_delivery_time',
						editable: false,
						menuDisabled: true,
						renderer: Zarafa.common.ui.grid.Renderers.datetime
					}]
				}),
				listeners: {
					'rowcontextmenu': this.onRowContextMenu,
					'rowdblclick': this.onRowDblClick,
					scope: this
				}
			}]
		});

		Zarafa.mail.widget.MailWidget.superclass.constructor.call(this, config);
	},

	/**
	 * Update the filter. This will filter the records using
	 * {@link Zarafa.core.data.IPMRecord#isRead} and {@link Zarafa.core.data.IPMRecord#isConversationHeaderRecord}.
	 * @private
	 */
    updateFilter: function()
	{
		this.store.filterBy(function(record) {
			return !record.isRead() && !record.isConversationHeaderRecord();
		}, this);
	},

	/**
	 * Apply custom style and content for the row body. This will always
	 * apply the Read/Unread style to the entire row. Optionally it will
	 * enable the row body containing the subject and icons for attachment
	 * and priority.
	 *
	 * @param {Ext.data.Record} record The {@link Ext.data.Record Record} corresponding to the current row.
	 * @param {Number} rowIndex The row index
	 * @param {Object} rowParams A config object that is passed to the row template during
	 * rendering that allows customization of various aspects of a grid row.
	 * If enableRowBody is configured true, then the following properties may be set by this function,
	 * and will be used to render a full-width expansion row below each grid row.
	 * @param {Ext.data.Store} store The Ext.data.Store this grid is bound to
	 * @return {String} a CSS class name to add to the row
	 * @private
	 */
	viewConfigGetRowClass: function(record, rowIndex, rowParams, store)
	{
		var cssClass = (record.isRead() ? 'mail_read' : 'mail_unread');

		var meta = {}; // Metadata object for Zarafa.common.ui.grid.Renderers.
		var value = ''; // The value which must be rendered
		rowParams.body = '<table cellspacing="0" cellpadding="0" border="0" style="width: 100%;">';
		rowParams.body += '<tr>';

		// Render the subject
		meta = {};
		value = Zarafa.common.ui.grid.Renderers.subject(record.get('subject'), meta, record);
		rowParams.body += String.format('<td style="width: 100%"><div class="grid_compact grid_compact_left grid_compact_subject_cell {0}" style="height: 24px;">{1}</div></td>', meta.css, value);

		// Render the attachment icon (always aligned to the right)
		meta = {};
		value = Zarafa.common.ui.grid.Renderers.attachment(record.get('hasattach'), meta, record);
		rowParams.body += String.format('<td style="width: 24px"><div class="grid_compact {0}" style="height: 24px; width: 24px;">{1}</div></td>', meta.css, value);

		// Render the importance icon (always aligned to the right)
		meta = {};
		value = Zarafa.common.ui.grid.Renderers.importance(record.get('importance'), meta, record);
		rowParams.body += String.format('<td style="width: 24px"><div class="grid_compact {0}" style="height: 24px; width: 24px;">{1}</div></td>', meta.css, value);

		rowParams.body += '</tr></table>';
		return 'x-grid3-row-expanded ' + cssClass;
	},

	/**
	 * Event handler which is triggered when user opens context menu
	 * @param {Ext.grid.GridPanel} grid grid panel object
	 * @param {Number} rowIndex index of row
	 * @param {Ext.EventObject} eventObj eventObj object of the event
	 * @private
	 */
	onRowContextMenu: function(grid, rowIndex, event)
	{
		// check row is already selected or not, if its not selected then select it first
		var selectionModel = grid.getSelectionModel();
		if (!selectionModel.isSelected(rowIndex)) {
			selectionModel.selectRow(rowIndex);
		}

		// The ContextMenu needs the ContextModel for cases where we want to reply the mail.
		var model;
		if (this.folder) {
			var context = container.getContextByFolder(this.folder);
			if (context) {
				model = context.getModel();
			}
		}

		Zarafa.core.data.UIFactory.openDefaultContextMenu(selectionModel.getSelections(), { position: event.getXY(), model: model });
	},

	/**
	 * Called when the user double-clicks on a mail.
	 * @param {Ext.grid.GridPanel} grid The grid which fired the event
	 * @param {Number} rowIndex The row which was double clicked
	 * @param {Ext.EventObject} event The event object
	 * @private
	 */
	onRowDblClick: function(grid, rowIndex, event)
	{
		Zarafa.core.data.UIFactory.openViewRecord(grid.getSelectionModel().getSelected());
	}
});

Zarafa.onReady(function() {
	container.registerWidget(new Zarafa.core.ui.widget.WidgetMetaData({
		name: 'mail',
		iconCls: 'icon_widget_unread_mail',
		displayName: _('Unread Mail'),
		widgetConstructor: Zarafa.mail.widget.MailWidget
	}));
});
Ext.namespace('Zarafa.mail.widgets');

/**
 * @class Zarafa.mail.widgets.QuickMailWidget
 * @extends Zarafa.core.ui.widget.AbstractQuickItemWidget
 *
 * Widget for creating a mail quickly with a minimum set of
 * input fields
 */
Zarafa.mail.widgets.QuickMailWidget = Ext.extend(Zarafa.core.ui.widget.AbstractQuickItemWidget, {

	/**
	 * @cfg {Boolean} Enable the HTML editor
	 */
	useHtml: false,

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

		Ext.applyIf(config, {
			wrapCfg: {
				recordComponentPluginConfig: Ext.applyIf(config.recordComponentPluginConfig || {}, {
					allowWrite: true
				}),
				layout: 'fit',
				items: [{
					xtype: 'form',
					ref: 'formPanel',
					layout: {
						type: 'vbox',
						align: 'stretch'
					},
					border: false,
					bodyStyle: 'background-color: inherit; padding: 5px;',
					defaults: {
						border: false,
						labelLength: 100,
						style: 'padding-bottom: 2px'
					},
					items: [{
						xtype: 'zarafa.resizablecompositefield',
						hideLabel: true,
						anchor: '100%',
						autoHeight: true,
						items: [{
							xtype: 'label',
							width: 20,
							autoHeight: true,
							text: _('To') + ':'
						},{
							xtype: 'zarafa.recipientfield',
							ref: '../toRecipientField',
							fieldLabel: _('To'),
							flex: 1,
							autoHeight: true,
							defaultRecipientType: Zarafa.core.mapi.RecipientType.MAPI_TO
						}]
					},{
						xtype: 'zarafa.compositefield',
						hideLabel: true,
						anchor: '100%',
						autoHeight: true,
						items: [{
							xtype: 'textfield',
							flex: 1,
							name: 'subject',
							emptyText: _('Subject') + ':',
							listeners: {
								change: this.onChange,
								scope: this
							}
						}]
					},{
						xtype: 'zarafa.editorfield',
						ref: '../editorField',
						htmlName: 'html_body',
						plaintextName: 'body',
						hideLabel: true,
						flex: 1,
						useHtml: config.useHtml,
						defaultValue: '',
						listeners: {
							change: this.onBodyChange,
							scope: this
						}
					}]
				}]
			},
			buttons: [{
				text: _('Send'),
				cls: 'zarafa-action',
				style: 'padding-bottom: 5px',
				handler: this.onSend,
				scope: this
			},{
				text: _('Discard'),
				style: 'padding-bottom: 5px',
				handler: this.onDiscard,
				scope: this
			}]
		});

		Zarafa.mail.widgets.QuickMailWidget.superclass.constructor.call(this, config);
	},

	/**
	 * Create a new record which must be edited by this widget.
	 * @return {Ext.data.Record} record The record to load into the {@link #wrap}
	 * @protected
	 */
	createRecord: function()
	{
		var folder = container.getHierarchyStore().getDefaultFolder('drafts');
		var context = container.getContextByName('mail');
		var model = context.getModel();

		return model.createRecord(folder);
	},

	/**
	 * Updates the widget by loading data from the record into the {@link #wrap}.
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record update the panel with.
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 * @protected
	 */
	update: function(record, contentReset)
	{
		this.wrap.formPanel.getForm().loadRecord(record);
		if (contentReset) {
			this.wrap.formPanel.toRecipientField.setRecipientStore(record.getSubStore('recipients'));
		}
	},

	/**
	 * Updates the widget by loading data from the record into the {@link #wrap}.
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record to update
	 * @protected
	 */
	updateRecord: function(record)
	{
		record.beginEdit();
		this.wrap.formPanel.getForm().updateRecord(record);
		this.onBodyChange(this.wrap.editorField, this.wrap.editorField.getValue());
		record.endEdit();
	},

       /**
        * Event handler which is fired when the user pressed the 'Send' button.
        * This will call {@link Zarafa.core.ui.MessageContentPanel#sendRecord} to start
        * sending the mail.
        * @private
        */
       onSend: function()
       {
               this.wrap.sendRecord();
       }
});

Zarafa.onReady(function() {
	container.registerWidget(new Zarafa.core.ui.widget.WidgetMetaData({
		name: 'quickmail',
		iconCls: 'icon_widget_email',
		displayName: _('Quick Mail'),
		widgetConstructor: Zarafa.mail.widgets.QuickMailWidget
	}));
});
Ext.namespace('Zarafa.note');

/**
 * @class Zarafa.note.NoteContextModel
 * @extends Zarafa.core.ContextModel
 * class will instantiate {@link Zarafa.note.NoteStore NoteStore} object
 */
Zarafa.note.NoteContextModel = Ext.extend(Zarafa.core.ContextModel, {
	/**
	 * When searching, this property marks the {@link Zarafa.core.ContextModel#getCurrentDataMode datamode}
	 * which was used before {@link #onSearchStart searching started} the datamode was switched to
	 * {@link Zarafa.note.data.DataModes#SEARCH}.
	 * @property
	 * @type Mixed
	 * @private
	 */
	oldDataMode: undefined,

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

		if(!Ext.isDefined(config.store)) {
			config.store = new Zarafa.note.NoteStore();
		}

		Ext.applyIf(config, {
			statefulRecordSelection: true,
			current_data_mode: Zarafa.note.data.DataModes.ALL
		});

		Zarafa.note.NoteContextModel.superclass.constructor.call(this, config);

		this.on({
			'searchstart': this.onSearchStart,
			'searchstop': this.onSearchStop,
			scope: this
		});
	},

	/**
	 * Create a new {@link Zarafa.core.data.IPMRecord record}
	 * @param {Zarafa.core.IPMFolder} folder (optional) The target folder in which the new record must be
	 * created. If this is not provided the default folder will be used.
	 * @return {Zarafa.core.data.IPMRecord} The new {@link Zarafa.core.data.IPMRecord IPMRecord}.
	 */
	createRecord: function(folder)
	{
		folder = folder || this.getDefaultFolder();

		return Zarafa.core.data.RecordFactory.createRecordObjectByMessageClass('IPM.StickyNote', {
			store_entryid: folder.get('store_entryid'),
			parent_entryid: folder.get('entryid')
		});
	},

	/**
	 * Event handler which is executed right before the {@link #datamodechange}
	 * event is fired. This allows subclasses to initialize the {@link #store}.
	 * This will apply filtering to the {@link #store} if needed.
	 *
	 * @param {Zarafa.note.NoteContextModel} model The model which fired the event.
	 * @param {Zarafa.note.data.DataModes} newMode The new selected DataMode.
	 * @param {Zarafa.note.data.DataModes} oldMode The previously selected DataMode.
	 * @private
	 */
	onDataModeChange: function(model, newMode, oldMode)
	{
		Zarafa.note.NoteContextModel.superclass.onDataModeChange.call(this, model, newMode, oldMode);

		if (newMode !== oldMode && oldMode === Zarafa.note.data.DataModes.SEARCH) {
			this.stopSearch();
		}

		switch(newMode) {
			case Zarafa.note.data.DataModes.SEARCH:
				this.store.clearFilter();
				break;
			case Zarafa.note.data.DataModes.ALL:
				this.store.clearFilter();
				this.load();
				break;
			case Zarafa.note.data.DataModes.LAST_7_DAYS:
				var now = new Date();
				// Set time to 12:00 to prevent problems when
				// calling Date.add(Date.DAY, ...) when the DST
				// switch is at 00:00 like in Brasil.
				now.setHours(12);
				var lastSevenDay = (now.add(Date.DAY, -7).clearTime()).getTime() / 1000;

				this.load({
					params: {
						restriction: {
							note: Zarafa.core.data.RestrictionFactory.dataResProperty(
								'last_modification_time',
								Zarafa.core.mapi.Restrictions.RELOP_GT,
								lastSevenDay
							)
						}
					}
				});
				break;
		}
	},

	/**
	 * Event handler for the {@link #searchstart searchstart} event.
	 * This will {@link #setDataMode change the datamode} to {@link Zarafa.note.data.DataModes#SEARCH search mode}.
	 * The previously active {@link #getCurrentDataMode view} will be stored in the {@link #oldDataMode} and will
	 * be recovered when the {@link #onSearchStop search is stopped}.
	 * @param {Zarafa.core.ContextModel} model The model which fired the event
	 * @private
	 */
	onSearchStart: function(model)
	{
		if(this.getCurrentDataMode() != Zarafa.note.data.DataModes.SEARCH){
			this.oldDataMode = this.getCurrentDataMode();
			this.setDataMode(Zarafa.note.data.DataModes.SEARCH);
		}
	},

	/**
	 * Event handler for the {@link #searchstop searchstop} event.
	 * This will {@link #setDataMode change the datamode} to the {@link #oldDataMode previous datamode}.
	 * @param {Zarafa.core.ContextModel} model The model which fired the event
	 * @private
	 */
	onSearchStop: function(model)
	{
		if (this.getCurrentDataMode() === Zarafa.note.data.DataModes.SEARCH) {
			this.setDataMode(this.oldDataMode);
		}
		delete this.oldDataMode;
	}
});
Ext.namespace('Zarafa.note.dialogs');

/**
 * @class Zarafa.note.dialogs.NoteEditContentPanel
 * @extends Zarafa.core.ui.RecordContentPanel
 * @xtype zarafa.noteeditcontentpanel
 *
 * this class is used to create/view note edit content panel
 */
Zarafa.note.dialogs.NoteEditContentPanel = Ext.extend(Zarafa.core.ui.RecordContentPanel, {
	/**
	 * @constructor
	 * @param {Object} config Configuration structure
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			xtype	: 'zarafa.noteeditcontentpanel',
			layout	: 'fit',
			title: _('Note'),
			recordComponentPluginConfig: Ext.applyIf(config.recordComponentPluginConfig || {}, {
				allowWrite: true
			}),
			confirmClose: true,
			items: [ this.createPanel() ]
		});

		Zarafa.note.dialogs.NoteEditContentPanel.superclass.constructor.call(this,config);
	},

	/**
	 * Add the main Window Panel to the content panel. This will contain
	 * a {@link Zarafa.core.ui.ContentPanelToolbar ContentPanelToolbar} and a {@link Zarafa.note.dialogs.NoteEditPanel NoteEditPanel}.
	 * @return {Object} The configuration object for the main panel.
	 * @private
	 */
	createPanel: function()
	{
		//Add toolbar and text area in window panel
		return {
			iconCls		: 'icon_folder_note',
			border		: false,
			tbar		: {
				xtype: 'zarafa.noteedittoolbar'
			},
			xtype		: 'zarafa.noteeditpanel',
			layout		: 'fit'
		};
	},

	/**
	 * Update the components with the given record.
	 *
	 * @param {Zarafa.core.data.MAPIRecord} record The record to update in this component
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 */
	update: function(record, contentReset)
	{
		if(contentReset === true) {
			this.inputAutoFocusPlugin.setAutoFocus('zarafa.editorfield');
		}
		this.updateIconFromRecord(record);
		this.updateTitleFromRecord(record);
	},

	/**
	 * Update this panel's icon class from the record that it contains
	 * First obtains the icon class from a mapping, then calls {@link #setIcon}
	 *
	 * @param {Zarafa.core.data.MAPIRecord} record The record bound to this component
	 * @private
	 */
	updateIconFromRecord: function(record)
	{
		//TODO: create a new icon mapping for tabs
		var iconCls = Zarafa.common.ui.IconClass.getIconClass(record);
		this.setIcon(iconCls);
	},

	/**
	 * When record has been updated, title also has to be - for instance if we have the subject
	 * in the title and the subject changes
	 * Calls {@link #setTitle} this.setTitle in order to update
	 * @param {Zarafa.core.data.MAPIRecord} record The record that has been updated
	 */
	updateTitleFromRecord: function(record)
	{
		var subject = record.get('subject');
		if(!Ext.isEmpty(subject)){
			this.setTitle(subject);
		} else {
			this.setTitle(this.initialConfig.title);
		}
	}
});

//Register note content panel
Ext.reg('zarafa.noteeditcontentpanel', Zarafa.note.dialogs.NoteEditContentPanel);
Ext.namespace('Zarafa.note.dialogs');

/**
 * @class Zarafa.note.dialogs.NoteEditToolbar
 * @extends Zarafa.core.ui.ContentPanelToolbar
 * @xtype zarafa.noteedittoolbar
 *
 * this class is used to create/view note edit panel
 */
Zarafa.note.dialogs.NoteEditToolbar = Ext.extend(Zarafa.core.ui.ContentPanelToolbar, {
	// Insertion points for this class
	/**
	 * @insert context.note.noteeditcontentpanel.toolbar.actions
	 * Insertion point for the Actions buttons in the Note Edit Toolbar
	 * @param {Zarafa.note.dialogs.NoteEditToolbar} toolbar This toolbar
	 */
	/**
	 * @insert context.note.noteeditcontentpanel.toolbar.options
	 * Insertion point for the Options buttons in the Note Edit Toolbar
	 * @param {Zarafa.note.dialogs.NoteEditToolbar} toolbar This toolbar
	 */

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

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

		Ext.applyIf(config, {
			insertionPointBase: 'context.note.noteeditcontentpanel',

			actionItems: this.createActionButtons()
		});

		Zarafa.note.dialogs.NoteEditToolbar.superclass.constructor.call(this, config);
	},

	/**
	 * Function will create buttons for top toolbar and it will be
	 * attached to zarafa.contentpaneltoolbar
	 * @return {Array} array consist of buttons for note edit dialog
	 */
	createActionButtons: function()
	{
		return [{
			//Save button
			xtype: 'button',
			ref: 'saveBtn',
			text: _('Save'),
			overflowText: _('Save'),
			tooltip: _('Save') + Zarafa.core.KeyMapMgr.formatShortcutHint('Ctrl + S', true),
			cls: 'zarafa-action',
			iconCls: 'icon_save_white',
			handler: this.onSaveButton,
			scope: this
		}, {
			//Delete button
			xtype: 'button',
			ref: 'deleteBtn',
			overflowText: _('Delete'),
			tooltip: _('Delete'),
			cls: 'tb-notes-btn-delete',
			iconCls: 'icon_delete',
			handler: this.onDeleteButton,
			scope: this
		},{
			//Categories button
			xtype: 'button',
			overflowText: _('Set Category'),
			tooltip: _('Open the categories dialog'),
			cls: 'tb-notes-btn-categories',
			iconCls: 'icon_categories',
			handler: this.onOpenCategories,
			scope: this
		}, {
			xtype: 'button',
			overflowText: _('Print'),
			tooltip: _('Print note'),
			cls: 'tb-notes-btn-print',
			iconCls: 'icon_print',
			handler: this.onPrintRecord,
			scope: this
		}, {
			xtype: 'combo',
			width: 75,
			fieldLabel: _('Color'),
			overflowText: _('Color'),
			ref: 'colorCombo',
			cls: 'tb-notes-combo-color',
			store: {
				xtype: 'arraystore',
				fields: ['icon_index', 'name'],
				data: [ [768, _("Blue")],[769, _("Green")],[770, _("Pink")],[771, _("Yellow")],[772, _("White")]]
			},
			displayField: 'name',
			valueField: 'icon_index',
			mode: 'local',
			editable: false,
			triggerAction: 'all',
			listeners: {
				select: this.onComboSelect,
				scope: this
			},
			plugins: [ 'zarafa.fieldlabeler' ]
		}];
	},

	/**
	 * Event handler when the "Save" button has been pressed.
	 * This will {@link Zarafa.core.data.RecordContentPanel#saveRecord save} the given record.
	 *
	 * @param {Ext.Button} button The button which has been pressed
	 * @private
	 */
	onSaveButton: function()
	{
		this.dialog.saveRecord();
	},

	/**
	 * Event handler when the "Delete" button has been pressed.
	 * This will {@link Zarafa.core.data.RecordContentPanel#deleteRecord delete} the given record.
	 *
	 * @param {Ext.Button} button The button which has been pressed
	 * @private
	 */
	onDeleteButton: function()
	{
		this.dialog.deleteRecord();
	},

	/**
	 * Handler for Note dialogbox's toolbar's comboxe's select event
	 * @param {Object} form UI form object
	 * @param {Object} record object. this is not selected record.
	 * @param {Number} selected index of combo box
	 * @private
	 */
	onComboSelect: function(form, record, selectedIndex)
	{
		// set new icon index value for record
		this.record.set('icon_index', record.get('icon_index'));

		var colorIndex = Zarafa.core.mapi.NoteColor.getColorValue(record.get('icon_index'));
		this.record.set('color', colorIndex);
	},

	/**
	 * Handler for the Categories toolbar button
	 * This will call {@link Zarafa.common.Actions#openCategoriesContent}.
	 * @private
	 */
	onOpenCategories: function()
	{
		Zarafa.common.Actions.openCategoriesContent(this.record, {autoSave: false});
	},

	/**
	 * Handler for the Print toolbar buttoon
	 * This will call {@link Zarafa.common.Actions#openPrintDialog}.
	 * @private
	 */
	onPrintRecord: function()
	{
		Zarafa.common.Actions.openPrintDialog(this.record);
	},

	/**
	 * Load record into form
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record to load
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 * @private
	 */
	update: function(record, contentReset)
	{
		this.record = record;

		if(record.isSubMessage()) {
			this.deleteBtn.setVisible(false);
			this.saveBtn.setVisible(false);
		} else {
			this.saveBtn.setVisible(true);

			// Only enable Disabled button when it is not a phantom
			this.deleteBtn.setDisabled(record.phantom === true);
		}

		// set combobox selected value as record's color
		this.colorCombo.setValue(record.get('icon_index'));

		this.doLayout();
	},

	/**
	 * Function to update the record whenever form fields are modified
	 * @param {Zarafa.core.data.IPMRecord} the record to update
	 * @private
	 */
	updateRecord: function(record)
	{
		record.set('icon_index', this.colorCombo.getValue());
	}
});
Ext.reg('zarafa.noteedittoolbar', Zarafa.note.dialogs.NoteEditToolbar);
Ext.namespace('Zarafa.note.dialogs');

/**
 * @class Zarafa.note.dialogs.NoteOptionsContentPanel
 * @extends Zarafa.core.ui.RecordContentPanel
 * @xtype zarafa.noteoptionscontentpanel
 */
Zarafa.note.dialogs.NoteOptionsContentPanel = Ext.extend(Zarafa.core.ui.RecordContentPanel, {
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

		config = Ext.applyIf(config, {
			xtype: 'zarafa.noteoptionscontentpanel',
			layout: 'fit',
			title: _('Message Options'),
			recordComponentPluginConfig: Ext.applyIf(config.recordComponentPluginConfig || {}, {
				allowWrite: true
			}),
			autoSave: config.modal ? false : true,
			width: 360,
			height: 220,
			items: [{
				xtype: 'zarafa.noteoptionspanel',
				buttons: [{
					text: _('Ok'),
					handler: this.onOk,
					scope: this
				}]
			}]
		});

		Zarafa.note.dialogs.NoteOptionsContentPanel.superclass.constructor.call(this, config);
	}
});

Ext.reg('zarafa.noteoptionscontentpanel', Zarafa.note.dialogs.NoteOptionsContentPanel);
Ext.namespace('Zarafa.note.printer');

/**
 * @class Zarafa.note.printer.NoteRenderer
 * @extends Zarafa.common.printer.renderers.RecordRenderer
 *
 * A printer for notes using the same layout as for emails
 *
 * Prints a single note request or note item
 */
Zarafa.note.printer.NoteRenderer = Ext.extend(Zarafa.common.printer.renderers.RecordRenderer, {

	/**
	 * Generate the XTemplate HTML text for printing a single note item or note request.
	 * @param {Zarafa.core.data.MAPIRecord} record The note item to print
	 * @return {String} The HTML for the XTemplate to print
	 */
	generateBodyTemplate: function(record) {
		var html = '';
		html += '<b>{fullname}</b>\n';
		html += '<hr>\n';
		html += '<table>\n';
		html += this.addRow(_('Modified'), '{last_modification_time:formatDefaultTimeString("' + _("l jS F Y {0}") + '")}');
		html += this.optionalRow(_('Categories'), 'categories', '{categories}');
		html += '</table><br><p>\n';
		html += record.getBody(true);
		html += '</p>\n';
		return html;
	}
});

/*
 * #dependsFile client/zarafa/note/ui/NoteGridColumnModel.js
 */
Ext.namespace('Zarafa.note.ui');

/**
 * @class Zarafa.note.ui.NoteGrid
 * @extends Zarafa.common.ui.grid.MapiMessageGrid
 * @xtype zarafa.notegrid
 *
 * This GridView extends the {@link Zarafa.common.ui.grid.MapiMessageGrid GridPanel} and
 * implements custom context menus.
 */
Zarafa.note.ui.NoteGrid = Ext.extend(Zarafa.common.ui.grid.MapiMessageGrid, {
	/**
	 * @cfg {Zarafa.note.NoteContext} context The context to which this panel belongs
	 */
	context: undefined,

	/**
	 * The {@link Zarafa.note.NoteContextModel} which is obtained from
	 * the {@link #context}.
	 *
	 * @property
	 * @type Zarafa.note.NoteContextModel
	 */
	model: undefined,

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

		if (!Ext.isDefined(config.model) && Ext.isDefined(config.context)) {
			config.model = config.context.getModel();
		}
		if (!Ext.isDefined(config.store) && Ext.isDefined(config.model)) {
			config.store = config.model.getStore();
		}
		config.store = Ext.StoreMgr.lookup(config.store);

		Ext.applyIf(config, {
			id: 'note-gridview',
			ariaLabel: _('Notes list'),
			border: false,
			autoExpandColumn: 'subject',
			stateful: true,
			statefulRelativeDimensions: false,
			loadMask: this.initLoadMask(),
			viewConfig: this.initViewConfig(),
			selModel: this.initSelectionModel(),
			colModel: this.initColumnModel(),
			enableDragDrop: true,
			ddGroup: 'dd.mapiitem'
		});

		Zarafa.note.ui.NoteGrid.superclass.constructor.call(this, config);
	},

	/**
	 * @return {Object} view config object
	 * @private
	 */
	initViewConfig: function()
	{
		return {
		};
	},

	/**
	 * creates and returns a selection model object, used in {@link Ext.grid.GridPanel.selModel selModel} config
	 * @return {Ext.grid.RowSelectionModel} selection model object
	 * @private
	 */
	initSelectionModel: function()
	{
		return new Ext.grid.RowSelectionModel({
			singleSelect: false,
			listeners: {
				scope: this,
				selectionchange: this.onSelectionChange
			}
		});
	},

	/**
	 * creates and returns a column model object, used in {@link Ext.grid.EditorGridPanel.colModel colModel} config
	 * @return {Ext.grid.ColumnModel} column model object
	 * @private
	 */
	initColumnModel: function()
	{
		return new Zarafa.note.ui.NoteGridColumnModel();
	},

	/**
	 * @return {Object} loas mask config object
	 * @private
	 */
	initLoadMask: function()
	{
		return {
			msg: _('Loading Notes') + '...'
		};
	},

	/**
	 * initialize events for the grid panel
	 * @private
	 */
	initEvents: function()
	{
		Zarafa.note.ui.NoteGrid.superclass.initEvents.call(this);

		this.on({
			'rowdblclick': this.onNoteRowDblClick,
			scope: this
		});
	},

	/**
	 *@Private
	 *Event handler which is triggered when user double click on row
	 *@param {Ext.grid.GridPanel} grid grid panel object
	 *@param {Number} rowIndex	index of row
	 *@param {Ext.event} eventObj eventObj object of the event
	 */
	onNoteRowDblClick: function(grid, rowIndex, event)
	{
		Zarafa.note.Actions.openNoteContent(grid.getSelectionModel().getSelected());
	},

	/**
	 * Event handler which is triggered when the {@link Zarafa.note.ui.NoteGrid NoteGrid}
	 * {@link Zarafa.core.data.IPMRecord record} selection is changed. This will inform
	 * the {@link Zarafa.note.NoteModel contextmodel} about the change.
	 *
	 * @param {Ext.grid.RowSelectionModel} selectionModel The selection model used by the grid.
	 * @private
	 */
	onSelectionChange: function(selectionModel)
	{
		this.model.setSelectedRecords(selectionModel.getSelections());
	}
});

Ext.reg('zarafa.notegrid',Zarafa.note.ui.NoteGrid);
Ext.namespace('Zarafa.note.widgets');

/**
 * @class Zarafa.note.widgets.QuickNoteWidget
 * @extends Zarafa.core.ui.widget.AbstractQuickItemWidget
 *
 * Widget for creating a Sticky note quickly with a minimum set of
 * input fields
 */
Zarafa.note.widgets.QuickNoteWidget = Ext.extend(Zarafa.core.ui.widget.AbstractQuickItemWidget, {

	/**
	 * The Color CSS class which currently has been applied to the Text area.
	 * @property
	 * @type String
	 */
	currentColorCls: undefined,

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

		Ext.applyIf(config, {
			wrapCfg: {
				recordComponentPluginConfig: Ext.applyIf(config.recordComponentPluginConfig || {}, {
					allowWrite: true
				}),
				layout: 'fit',
				items: [{
					xtype: 'form',
					ref: 'formPanel',
					layout: 'fit',
					items: [{
						xtype: 'textarea',
						ref: '../editorField',
						name: 'body',
						listeners: {
							change: this.onChange,
							scope: this
						}
					}]
				}]
			},
			buttons: [{
				text: _('Save'),
				cls: 'zarafa-action',
				style: 'padding-bottom: 5px',
				handler: this.onSave,
				scope: this
			},{
				text: _('Discard'),
				style: 'padding-bottom: 5px',
				handler: this.onDiscard,
				scope: this
			}]
		});

		Zarafa.note.widgets.QuickNoteWidget.superclass.constructor.call(this, config);
	},

	/**
	 * Create a new record which must be edited by this widget.
	 * @return {Ext.data.Record} record The record to load into the {@link #wrap}
	 * @protected
	 */
	createRecord: function()
	{
		var folder = container.getHierarchyStore().getDefaultFolder('note');
		var context = container.getContextByName('note');
		var model = context.getModel();

		return model.createRecord(folder);
	},

	/**
	 * Updates the widget by loading data from the record into the {@link #wrap}.
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record update the panel with.
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 * @protected
	 */
	update: function(record, contentReset)
	{
		this.wrap.formPanel.getForm().loadRecord(record);

		if (contentReset || record.isModifiedSinceLastUpdate('icon_index')) {
			var iconIndex = record.get('icon_index');
			var textCls;

			switch (iconIndex) {
				case Zarafa.core.mapi.IconIndex['note_blue']:
					textCls = 'stickynote_dialog_blue';
					break;
				case Zarafa.core.mapi.IconIndex['note_green']:
					textCls = 'stickynote_dialog_green';
					break;
				case Zarafa.core.mapi.IconIndex['note_pink']:
					textCls= 'stickynote_dialog_pink';
					break;
				case Zarafa.core.mapi.IconIndex['note_yellow']:
				default:
					textCls = 'stickynote_dialog_yellow';
					break;
				case Zarafa.core.mapi.IconIndex['note_white']:
					textCls = 'stickynote_dialog_white';
					break;
			}

			this.wrap.editorField.removeClass(this.currentColorCls);
			this.currentColorCls = textCls;
			this.wrap.editorField.addClass(this.currentColorCls);
		}
	},

	/**
	 * Updates the widget by loading data from the record into the {@link #wrap}.
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record to update
	 * @protected
	 */
	updateRecord: function(record)
	{
		record.beginEdit();
		this.wrap.formPanel.getForm().updateRecord(record);
		this.record.generateSubject();
		record.endEdit();
	}
});

Zarafa.onReady(function() {
	container.registerWidget(new Zarafa.core.ui.widget.WidgetMetaData({
		name: 'quicknote',
		iconCls: 'icon_widget_new_note',
		displayName: _('Quick Note'),
		widgetConstructor: Zarafa.note.widgets.QuickNoteWidget
	}));
});
Ext.namespace('Zarafa.settings');

/**
 * @class Zarafa.settings.SettingsContextModel
 * @extends Zarafa.core.ContextModel
 */
Zarafa.settings.SettingsContextModel = Ext.extend(Zarafa.core.ContextModel, {

	/**
	 * The settings model of the active settings, this is a reference
	 * to {@link Zarafa.core.Container#getSettingsModel}. This model is
	 * used to save the changes to the server. However
	 * {@link Zarafa.settings.ui.SettingsCategory categories} should not
	 * edit to this settings model directly, instead they should use
	 * {@link #editModel}.
	 * @property
	 * @type Zarafa.settings.SettingsModel
	 * @private
	 */
	realModel: undefined,

	/**
	 * Copy of {@link #realModel}. This model can be obtained by
	 * {@link Zarafa.settings.ui.SettingsCategory categories} using
	 * {@link #getEditableSettingsModel}. This model can be used
	 * for editing the settings which should later be saved to
	 * the server.
	 * @property
	 * @type Zarafa.settings.SettingsModel
	 * @private
	 */
	editModel: undefined,

	/**
	 * The list of all settings which were changes in the {@link #editModel}.
	 * When {@link #applyChanges} this list will be applied to {@link #realModel}
	 * and saved to the server. To discard these changes {@link #discardChanges}
	 * is used.
	 * @property
	 * @type Array
	 * @private
	 */
	setlist: undefined,

	/**
	 * The list of all settings which were deleted in the {@link #editModel}.
	 * When {@link #applyChanges} this list will be applied to {@link #realModel}
	 * and saved to the server. To discard these changes {@link #discardChanges}
	 * is used.
	 * @property
	 * @type Array
	 * @private
	 */
	dellist: undefined,

	/**
	 * Indicates if any changes are pending. This is updated as soon as
	 * {@link #setlist} or {@link #dellist} are updated. This might be
	 * updated manually by calling {@link #setDirty}.
	 * This value might be obtained by calling {@link #hasChanges}.
	 * @property
	 * @type Boolean
	 * @private
	 */
	dirty: false,

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

		// Obtain reference to active settings model
		this.realModel = container.getSettingsModel();

		// Create an editing model, this is initialized in #enable.
		this.editModel = new Zarafa.settings.SettingsModel({
			autoSave: false
		});

		Ext.applyIf(config, {
			stateful: false
		});

		this.addEvents(
			/**
			 * @event beforesavesettings
			 *
			 * Event which is fired just before the settings are being
			 * {@link Zarafa.settings.ContextModel#save saved}. At this
			 * time the settings might still be changed.
			 * @param {Zarafa.settings.SettingsContextModel} model This model
			 * @param {Zarafa.settings.SettingsModel} settingsModel The
			 * settingsModel which is about to be saved
			 * @return {Boolean} false to cancel the 'save' event
			 */
			'beforesavesettings',
			/**
			 * @event savesettings
			 *
			 * Event which is fired when the settings are being
			 * {@link Zarafa.settings.ContextModel#save saved}.
			 * @param {Zarafa.settings.SettingsContextModel} model This model
			 * @param {Zarafa.settings.SettingsModel} settingsModel The
			 * settingsModel which is being saved
			 */
			'savesettings',
			/**
			 * @event discardsettings
			 *
			 * Event which is fired when the changes made in the
			 * {@link #editModel} are being discarded.
			 * @param {Zarafa.settings.SettingsContextModel} model This model
			 * @param {Zarafa.settings.SettingsModel} settingsModel The
			 * model for which the settings are just discarded.
			 */
			'discardsettings'
		);

		Zarafa.settings.SettingsContextModel.superclass.constructor.call(this, config);

		this.editModel.on({
			set: this.onSetSetting,
			remove: this.onRemoveSetting,
			scope: this
		});
	},

	/**
	 * Called during the {@link Zarafa.core.Context#enable enabling} of the {@link Zarafa.core.Context context}.
	 * Secondly it will {@link #setFolders set the} {@link #folders folder} to this object to {@link #load} the {@link #store}.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder MAPI folder to show.
	 * @param {Boolean} suspended True to enable the ContextModel {@link #suspendLoading suspended}
	 */
	enable: function(folder, suspended)
	{
		Zarafa.settings.SettingsContextModel.superclass.enable.apply(this, arguments);

		var defaults = this.realModel.get(undefined, true);
		this.editModel.initialize(defaults);
	},

	/**
	 * Called during the {@link Zarafa.core.Context#disable disabling} of the {@link Zarafa.core.Context context}.
	 */
	disable: Ext.emptyFn,

	/**
	 * Obtain the reference to the {@link #editModel} this can be used by
	 * {@link Zarafa.settings.ui.SettingsCategory categories} for loading
	 * and editing settings.
	 * @return {Zarafa.settings.SettingsModel} The settings model to be
	 * used for editing.
	 */
	getEditableSettingsModel: function()
	{
		return this.editModel;
	},

	/**
	 * Obtain the reference to the {@link #realModel}.
	 * @return {Zarafa.settings.SettingsModel} The settings model to be
	 * used for saving changes to the server .
	 */
	getRealSettingsModel: function()
	{
		return this.realModel;
	},

	/**
	 * True when there are {@link #dirty unsaved changes} pending in the {@link #editModel}
	 * @return {Boolean} True when there are pending changes.
	 */
	hasChanges: function()
	{
		return this.dirty;
	},

	/**
	 * Mark the Model as dirty, this will make {@link #hasChanges} return true.
	 * This should only be used if the SettingsWidget isn't touching the Settings
	 * themselves, but does require a forced save from the user.
	 */
	setDirty: function()
	{
		this.dirty = true;
	},

	/**
	 * Call to save all changes made in {@link #editModel} to the server,
	 * this will also update {@link #realModel} and clear the
	 * {@link #setlist set} and {@link #dellist deleted} lists.
	 * Calling this function makes only sense when {@link #hasChanges}
	 * returned true.
	 */
	applyChanges: function()
	{
		if (this.fireEvent('beforesavesettings', this, this.editModel) !== false) {
			// During the 'beforesavesettings' event handler, the model
			// might have received changes. If we haven't, then there is
			// nothing to save and we can exit here now.
			if (!this.hasChanges()) {
				return;
			}

			this.fireEvent('savesettings', this, this.editModel);

			this.realModel.requiresReload = this.editModel.requiresReload;

			this.realModel.beginEdit();

			if (!Ext.isEmpty(this.setlist)) {
				for (var i = 0, len = this.setlist.length; i < len; i++) {
					var setting = this.setlist[i];
					if (Ext.isDefined(setting['value'])) {
						this.realModel.set(setting['path'], setting['value']);
					}
				}
			}
			if (!Ext.isEmpty(this.dellist)) {
				for (var i = 0, len = this.dellist.length; i < len; i++) {
					var setting = this.dellist[i];
					this.realModel.remove(setting);
				}
			}
			this.realModel.endEdit();

			// now we have synchronized changes from edit model to real model, its time to
			// remove modifications from edit model
			this.editModel.commit();

			this.dirty = false;
			delete this.setlist;
			delete this.dellist;
		}
	},

	/**
	 * Discard any changes made to the {@link #editModel} this will not
	 * save anything to the server and will reload all settings from
	 * {@link #realModel} into the {@link #editModel} and clear the
	 * {@link #setlist set} and {@link #dellist deleted} lists.
	 * Calling this function makes only sense when {@link #hasChanges}
	 * returned true.
	 */
	discardChanges: function()
	{
		var defaults = this.realModel.get(undefined, true);
		this.editModel.initialize(defaults);

		this.editModel.commit();

		this.dirty = false;
		delete this.setlist;
		delete this.dellist;

		this.fireEvent('discardsettings', this, this.editModel);
	},

	/**
	 * Sets the selected folder list directly.
	 * Fires the {@link #folderchange} event.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord[]} folders selected folders as an array of
	 * {@link Zarafa.hierarchy.data.MAPIFolderRecord MAPIFolder} objects.
	 */
	setFolders: Ext.emptyFn,

	/**
	 * Event handler which is fired when the {@link #editModel} fires
	 * the {@link Zarafa.settings.SettingsModel#setsetting} event.
	 * This will add the changed settings to the {@link #setlist}
	 * array.
	 * @param {Zarafa.settings.SettingsModel} model The model which fired the event
	 * @param {Array} settings The settings which were set
	 * @private
	 */
	onSetSetting: function(model, settings)
	{
		this.dirty = true;
		this.setlist = Ext.value(this.setlist, []);
		this.setlist = this.setlist.concat(settings);
	},

	/**
	 * Event handler which is fired when the {@link #editModel} fires
	 * the {@link Zarafa.settings.SettingsModel#removesetting} event.
	 * This will add the changed settings to the {@link #dellist}
	 * array.
	 * @param {Zarafa.settings.SettingsModel} model The model which fired the event
	 * @param {Array} settings The settings which were deleted
	 * @private
	 */
	onRemoveSetting: function(mode, settings)
	{
		this.dirty = true;
		this.dellist = Ext.value(this.dellist, []);
		this.dellist = this.dellist.concat(settings);
	}
});
Ext.namespace('Zarafa.settings.ui');

/**
 * @class Zarafa.settings.ui.SettingsTreePanel
 * @extends Zarafa.common.ui.EditorTreeGrid
 * @xtype zarafa.settingstreepanel
 *
 * A special {@link Ext.Panel panel} which contains a {@link Ext.ux.tree.TreeGrid TreeGrid}
 * which represents the settings hierarchy.
 */
Zarafa.settings.ui.SettingsTreePanel = Ext.extend(Zarafa.common.ui.EditorTreeGrid, {
	/**
	 * @cfg {Object} editors Object containing the editors which must be use for editing
	 * the Setting Value property. The keys used in this object are the 'typeof' results
	 * of the value type.
	 */
	editors: undefined,

	/**
	 * @cfg {Zarafa.settings.SettingsModel} model The model which should be displayed
	 * in the grid. This can be configured later using {@link #bindModel}.
	 */
	model: undefined,

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

		Ext.applyIf(config, {
			xtype: 'zarafa.editortreegrid',
			ref: 'treeGrid',
			border: false,
			rootVisible: true,
			enableHdMenu: false,
			enableSort: true,
			forceFit: true,
			loader: new Zarafa.settings.data.SettingsTreeLoader({
				model: config.model,
				autoExpandLevel: 4,
				autoExpandFilter: /^\/*(zarafa.*)?$/
			}),
			root: new Zarafa.settings.ui.SettingsTreeNode({
				text: _('Settings'),
				id: '/',
				expanded: false
			}),
			editors: {
				'boolean': { xtype: 'checkbox' },
				'number': { xtype: 'zarafa.spinnerfield', plugins: [ 'zarafa.numberspinner' ] },
				'string': { xtype: 'textfield' }
			},
			columns: [{
				id: 'setting',
				header: _('Setting'),
				dataIndex: 'text',
				tpl: '{text:htmlEncodeUndef}'
			},{
				id: 'value',
				header: _('Value'),
				dataIndex: 'value',
				tpl: '{value:htmlEncodeUndef}',
				width: 250,
				editable: true
			}]
		});

		Zarafa.settings.ui.SettingsTreePanel.superclass.constructor.call(this, config);
		
		if (this.treeFilter && !(this.treeFilter instanceof Ext.tree.TreeFilter)) {
			this.treeFilter = new Zarafa.common.ui.TreeFilter(this, {autoClear: true});
		}
	},

	/**
	 * Called by Extjs to initialize all event listeners.
	 * @private
	 */
	initEvents: function()
	{
		Zarafa.settings.ui.SettingsTreePanel.superclass.initEvents.call(this);

		this.on('afteredit', this.onAfterEdit, this);
		this.on('contextmenu', this.onContextMenu, this);

		if (this.model) {
			this.bindModel(this.model, true);
		}
	},

	/**
	 * Initialize a new {@link Zarafa.settings.SettingsModel Settings Model}.
	 * @param {Zarafa.settings.SettingsModel} model The model to load
	 * @param {Boolean} initialize (optional) True if this function is called
	 * during initialization
	 */
	bindModel: function(model, initialize)
	{
		if (initialize || this.model !== model) {
			if (this.model) {
				this.mun(this.model, 'set', this.onSettingsChange, this);
				this.mun(this.model, 'remove', this.onSettingsRemove, this);

				this.root.collapse();
			}

			this.model = model;

			if (this.model) {
				this.loader.bindModel(this.model);
				this.mon(this.model, {
					set: this.onSettingsChange,
					remove: this.onSettingsRemove,
					scope: this
				});

				this.root.expand();
			}
		} else {
			this.root.reload();
		}
	},

	/**
	 * Event handler which is fired when the {@link Zarafa.settings.SettingsModel settingsModel}
	 * has fired the {@link Zarafa.settings.SettingsModel#set} event to indicate that one or
	 * more settings have been changed. This will look up the corresponding nodes and
	 * changes the value accordingly.
	 * @param {Zarafa.settings.SettingsModel} model The model which fired the event
	 * @param {Array} settings The array of settings which has been changed
	 * @private
	 */
	onSettingsChange: function(model, settings)
	{
		if (!Array.isArray(settings)) {
			settings = [ settings ];
		}

		for (var i = 0, len = settings.length; i < len; i++) {
			var setting = settings[i];
			var node = this.getNodeById(setting.path);

			if (node) {
				// The node is visible for the user, update the value
				if (node.attributes['value'] !== setting.value) {
					if (this.rendered) {
						node.setValue(setting.value);
					}
				}
			} else {
				// THe node is not visible for the user, lets see if the
				// parent is, and has already been loaded by the user,
				// but is either collapsed, or this is a new setting, which
				// requires the parent to be reloaded.
				var index = setting.path.lastIndexOf('/');
				var path = setting.path.substring(0, index) || '/';
				var p = this.getNodeById(path);
				if (p && p.isLoaded()) {
					p.reload();
				}
			}
		}
	},

	/**
	 * Event handler which is fired when the {@link Zarafa.settings.SettingsModel settingsModel}
	 * has fired the {@link Zarafa.settings.SettingsModel#remove} event to indicate that one or
	 * more settings have been removed. This will look up the corresponding nodes and
	 * delete them from the tree.
	 * @param {Zarafa.settings.SettingsModel} model The model which fired the event
	 * @param {Array} settings The array of settings which has been deleted
	 * @private
	 */
	onSettingsRemove: function(model, settings)
	{
		if (!Array.isArray(settings)) {
			settings = [ settings ];
		}

		for (var i = 0, len = settings.length; i < len; i++) {
			var path = settings[i];
			var node = this.getNodeById(path);
			if (node) {
				node.remove(true);
			}
		}
	},

	/**
	 * Event handler which is fired when the {@link #afteredit} event has been fired.
	 * This will {@link Zarafa.settings.SettingsModel#set update} the setting in the
	 * {@link Zarafa.settings.SettingsModel}.
	 * @param {Zarafa.common.ui.EditorTreeGrid} grid The grid which fired the event
	 * @param {Ext.tree.TreeNode} node The node which was being edited
	 * @param {Number} column The column which has been edited
	 * @param {Mixed} value The value which was applied to the node
	 * @private
	 */
	onAfterEdit: function(grid, node, column, value)
	{
		this.model.set(node.id, value);
	},

	/**
	 * Event handler which is fired when the {@link #contextmenu} event has been fired.
	 * This will open the contextmenu for this node.
	 * @param {Ext.tree.TreeNode} node The node on which the contextmenu was requested
	 * @param {Ext.EventObject} event The event object
	 * @private
	 */
	onContextMenu: function(node, event)
	{
		node.select();
		Zarafa.core.data.UIFactory.openDefaultContextMenu(node, { position: event.getXY(), settingsModel: this.model });
	},

	/**
	 * Obtain the {@link Ext.form.Field} which should be used for editing the cell
	 * indicated by the given node and column. This will check which property is being
	 * edited and will look up the most appropriate editor for the value.
	 * @param {Ext.tree.TreeNode} node The node for which the required editor is looked up
	 * @param {Number} column The column index for which the editor is looked up
	 * @return {Ext.form.Field} The editor used to edit the given cell
	 */
	getEditor: function(node, column)
	{
		var field = this.getColumn(column).dataIndex;
		var value = node.attributes[field];
		var type = typeof value;

		if (Ext.isDefined(this.editors[type])) {
			return Ext.create(this.editors[type]);
		} else {
			Ext.MessageBox.show({
				title: _('Edit setting'),
				msg: String.format(_('The setting \'{0}\' can not be edited'), Ext.util.Format.htmlEncode(node.text)),
				buttons: Ext.MessageBox.OK
			});
		}
	},

	/**
	 * Called when this component is being destroyed. This will automatically destroy
	 * all {@link #editors} as well.
	 * @private
	 */
	onDestroy: function()
	{
		Zarafa.settings.ui.SettingsTreePanel.superclass.onDestroy.call(this);

		for (var key in this.editors) {
			var editor = this.editors[key];
			if (Ext.isFunction(editor.destroy)) {
				editor.destroy();
			}
		}
	}
});

Ext.reg('zarafa.settingstreepanel', Zarafa.settings.ui.SettingsTreePanel);
Ext.namespace('Zarafa.task');

/**
 * @class Zarafa.task.TaskContextModel
 * @extends Zarafa.core.ContextModel
 */
Zarafa.task.TaskContextModel = Ext.extend(Zarafa.core.ContextModel, {
	/**
	 * When searching, this property marks the {@link Zarafa.core.ContextModel#getCurrentDataMode datamode}
	 * which was used before {@link #onSearchStart searching started} the datamode was switched to
	 * {@link Zarafa.task.data.DataModes#SEARCH}.
	 * @property
	 * @type Mixed
	 * @private
	 */
	oldDataMode: undefined,

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

		if(!Ext.isDefined(config.store)) {
			config.store = new Zarafa.task.TaskStore();
		}

		Ext.applyIf(config, {
			statefulRecordSelection: true,
			current_data_mode: Zarafa.task.data.DataModes.ALL
		});

		Zarafa.task.TaskContextModel.superclass.constructor.call(this, config);

		this.on({
			'searchstart': this.onSearchStart,
			'searchstop': this.onSearchStop,
			scope: this
		});
	},

	/**
	 * Create a new {@link Zarafa.core.data.IPMRecord IPMRecord} which must be used within
	 * this {@link Zarafa.task.dialogs.TaskEditContentPanel TaskEditContentPanel}.
	 * @param {Zarafa.core.IPMFolder} folder (optional) The target folder in which the new record must be
	 * created. If this is not provided the default folder will be used.
	 * @return {Zarafa.core.data.IPMRecord} The new {@link Zarafa.core.data.IPMRecord IPMRecord}.
	 */
	createRecord: function(folder)
	{
		folder = folder || this.getDefaultFolder();

		if (folder.isTodoListFolder()) {
			folder = container.getHierarchyStore().getDefaultFolderFromMessageClass('IPM.Task');
		}

		var defaultStore = folder.getMAPIStore();

		var record = Zarafa.core.data.RecordFactory.createRecordObjectByMessageClass('IPM.Task', {
			store_entryid: folder.get('store_entryid'),
			parent_entryid: folder.get('entryid'),
			icon_index: Zarafa.core.mapi.IconIndex['task'],
			owner: defaultStore.isPublicStore() ? container.getUser().getFullName() : defaultStore.get('mailbox_owner_name')
		});

		return record;
	},

	/**
	 * Event handler which is executed right before the {@link #datamodechange}
	 * event is fired. This allows subclasses to initialize the {@link #store}.
	 * This will apply filtering to the {@link #store} if needed.
	 *
	 * @param {Zarafa.task.TaskContextModel} model The model which fired the event.
	 * @param {Zarafa.task.data.DataModes} newMode The new selected DataMode.
	 * @param {Zarafa.task.data.DataModes} oldMode The previously selected DataMode.
	 * @private
	 */
	onDataModeChange: function(model, newMode, oldMode)
	{
		Zarafa.task.TaskContextModel.superclass.onDataModeChange.call(this, model, newMode, oldMode);

		if (newMode !== oldMode && oldMode === Zarafa.task.data.DataModes.SEARCH) {
			this.stopSearch();
		}

		this.load({
			params: {
				restriction: {
					task: this.getFilterRestriction(newMode)
				}
			}
		});
	},

	/**
	 * Event handler for the {@link #searchstart searchstart} event.
	 * This will {@link #setDataMode change the datamode} to {@link Zarafa.task.data.DataModes#SEARCH search mode}.
	 * The previously active {@link #getCurrentDataMode view} will be stored in the {@link #oldDataMode} and will
	 * be recovered when the {@link #onSearchStop search is stopped}.
	 * @param {Zarafa.core.ContextModel} model The model which fired the event
	 * @private
	 */
	onSearchStart: function(model)
	{
		if(this.getCurrentDataMode() != Zarafa.task.data.DataModes.SEARCH){
			this.oldDataMode = this.getCurrentDataMode();
			this.setDataMode(Zarafa.task.data.DataModes.SEARCH);
		}
	},

	/**
	 * Event handler for the {@link #searchstop searchstop} event.
	 * This will {@link #setDataMode change the datamode} to the {@link #oldDataMode previous datamode}.
	 * @param {Zarafa.core.ContextModel} model The model which fired the event
	 * @private
	 */
	onSearchStop: function(model)
	{
		if (this.getCurrentDataMode() === Zarafa.task.data.DataModes.SEARCH) {
			this.setDataMode(this.oldDataMode);
		}
		delete this.oldDataMode;
	},

	/**
	 * Function will check if last used folders is empty then
	 * Sets todolist folder as an {@link #defaultFolder default folder} for the
	 * {@link Zarafa.task.TaskContext task context}.
	 *
	 * @param {Zarafa.core.hierarchyStore} hierarchyStore that holds hierarchy data.
	 * @private
	 */
	onHierarchyLoad: function(hierarchyStore)
	{
		// only continue when hierarchyStore has data
		if (hierarchyStore.getCount() === 0) {
			return;
		}

		if (Ext.isEmpty(this.last_used_folders)) {
			var folder = hierarchyStore.getDefaultFolder('todolist');
			if(folder) {
				var entryid = folder.get('entryid');
				var storeentryid = folder.get('store_entryid');
				this.last_used_folders = {};
				this.last_used_folders[storeentryid] = [entryid];
			}
		}

		Zarafa.task.TaskContextModel.superclass.onHierarchyLoad.call(this, hierarchyStore);

	},

	/**
	 * Function which provides the restriction based on the given {@link Zarafa.task.data.DataModes datemode}
	 *
	 * @param {Zarafa.task.data.DataModes} datamode The datamode based on which restriction is prepared.
	 * @return {Array|false} returns restriction according to filter else false.
	 */

	getFilterRestriction: function(datamode)
	{
		var store = this.getStore();
		return store.getFilterRestriction(datamode);
	},

	/**
	 * Function will load the store with given options.
	 *
	 * @param {object} options Object containing options to load
	 */
	load: function(options)
	{
		var store = this.getStore();
		if (this.suspended && store.hasFilterApplied) {
			store.stopFilter();
		} else {
			options = Ext.apply(options || {}, {
				params: {
					restriction: {
						filter: this.getFilterRestriction(this.current_data_mode)
					}
				}
			});
		}
		Zarafa.task.TaskContextModel.superclass.load.call(this, options);
	}
});
/*
 * #dependsFile client/zarafa/core/mapi/TaskStatus.js
 */
Ext.namespace('Zarafa.task.data');

/**
 * @class Zarafa.task.data.TaskStatus
 * @singleton
 */
Zarafa.task.data.TaskStatus = {
	status: [{
		value: Zarafa.core.mapi.TaskStatus['NOT_STARTED'],
		name: Zarafa.core.mapi.TaskStatus.getDisplayName(Zarafa.core.mapi.TaskStatus['NOT_STARTED'])
	},{
		value: Zarafa.core.mapi.TaskStatus['IN_PROGRESS'],
		name: Zarafa.core.mapi.TaskStatus.getDisplayName(Zarafa.core.mapi.TaskStatus['IN_PROGRESS'])
	},{
		value: Zarafa.core.mapi.TaskStatus['COMPLETE'],
		name: Zarafa.core.mapi.TaskStatus.getDisplayName(Zarafa.core.mapi.TaskStatus['COMPLETE'])
	},{
		value: Zarafa.core.mapi.TaskStatus['WAIT_FOR_OTHER_PERSON'],
		name: Zarafa.core.mapi.TaskStatus.getDisplayName(Zarafa.core.mapi.TaskStatus['WAIT_FOR_OTHER_PERSON'])
	},{
		value: Zarafa.core.mapi.TaskStatus['DEFERRED'],
		name: Zarafa.core.mapi.TaskStatus.getDisplayName(Zarafa.core.mapi.TaskStatus['DEFERRED'])
	}]
};
Ext.namespace('Zarafa.task.data');

/**
 * @class Zarafa.task.data.TodoTaskListNotificationResponseHandler
 * @extends Zarafa.core.data.AbstractNotificationResponseHandler
 *
 * The {@link Zarafa.task.data.TodoTaskListNotificationResponseHandler NotificationResponseHandler}
 * to notify To-Do task list folder. This can updates To-Do task list folder.
 */
Zarafa.task.data.TodoTaskListNotificationResponseHandler = Ext.extend(Zarafa.core.data.AbstractNotificationResponseHandler, {

	/**
	 * Handle the newtodotask action we receive as notification.
	 * This will contains to-do task list folder related information
	 * which used to generate the correct notification.
	 *
	 * @param {Object} data The entire response object which will be
	 * processed during this transaction.
	 * @private
	 */
	doNewtodotask: function (data)
	{
		var stores = data.item;
		var entryIds = Ext.pluck(stores, 'entryid');
		var folders = [];
		entryIds.forEach(function (entryId) {
			folders.push(container.getHierarchyStore().getFolder(entryId));
		});
		this.addNotification(Zarafa.core.data.Notifications.objectCreated, folders, null);
	}
});Ext.namespace('Zarafa.task.dialogs');

/**
 * @class Zarafa.task.dialogs.TaskOptionsContentPanel
 * @extends Zarafa.core.ui.RecordContentPanel
 * @xtype zarafa.taskoptionscontentpanel
 */
Zarafa.task.dialogs.TaskOptionsContentPanel = Ext.extend(Zarafa.core.ui.RecordContentPanel, {
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

		config = Ext.applyIf(config, {
			xtype: 'zarafa.taskoptionscontentpanel',
			layout: 'fit',
			title: _('Message Options'),
			recordComponentPluginConfig: Ext.applyIf(config.recordComponentPluginConfig || {}, {
				allowWrite: true
			}),
			autoSave: config.modal ? false : true,
			width: 360,
			height: 220,
			items: [{
				xtype: 'zarafa.taskoptionspanel',
				buttons: [{
					text: _('Ok'),
					handler: this.onOk,
					scope: this
				}]
			}]
		});

		Zarafa.task.dialogs.TaskOptionsContentPanel.superclass.constructor.call(this, config);
	}
});

Ext.reg('zarafa.taskoptionscontentpanel', Zarafa.task.dialogs.TaskOptionsContentPanel);
Ext.namespace('Zarafa.task.dialogs');

/**
 * @class Zarafa.task.dialogs.TaskToolbar
 * @extends Zarafa.core.ui.ContentPanelToolbar
 * @xtype zarafa.tasktoolbar
 */
Zarafa.task.dialogs.TaskToolbar = Ext.extend(Zarafa.core.ui.ContentPanelToolbar, {
	// Insertion points for this class
	/**
	 * @insert context.task.taskeditcontentpanel.toolbar.actions
	 * Insertion point for the Actions buttons in the Edit Task Toolbar
	 * @param {Zarafa.task.dialogs.TaskToolbar} toolbar This toolbar
	 */
	/**
	 * @insert context.task.taskeditcontentpanel.toolbar.options
	 * Insertion point for the Options buttons in the Edit Task Toolbar
	 * @param {Zarafa.task.dialogs.TaskToolbar} toolbar This toolbar
	 */

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

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

		Ext.applyIf(config, {
			insertionPointBase: 'context.task.taskcontentpanel',
			actionItems: this.createActionButtons(),
			optionItems: this.createOptionButtons()
		});

		Zarafa.task.dialogs.TaskToolbar.superclass.constructor.call(this, config);
	},

	/**
	 * Create all buttons which should be added by default to the 'Actions' buttons.
	 * These buttons are used to send, save, add attachments, delete, view the status of the message.
	 * It also contains buttons to check the recipient names.
	 *
	 * @return {Array} The {@link Ext.Button Button} elements which should be
	 * added in the Options section of the {@link Ext.Toolbar Toolbar}.
	 * @private
	 */
	createActionButtons: function()
	{
		return [{
			xtype: 'button',
			text: _('Send'),
			overflowText: _('Send'),
			tooltip: _('Send task to assignees') + Zarafa.core.KeyMapMgr.formatShortcutHint('Ctrl + Enter', true),
			cls: 'zarafa-action',
			iconCls: 'icon_send_white',
			ref: 'sendBtn',
			handler: this.onSendTask,
			scope: this
		},{
			xtype: 'button',
			overflowText: _('Save'),
			tooltip: _('Save') + Zarafa.core.KeyMapMgr.formatShortcutHint('Ctrl + S', true),
			iconCls: 'icon_floppy',
			ref: 'saveTaskRequest',
			handler: this.onSave,
			scope: this
		},{
			xtype: 'button',
			ref: 'saveBtn',
			text: _('Save')+' & '+_('Close'),
			overflowText: _('Save')+' & '+_('Close'),
			tooltip: _('Save')+' & '+_('Close') + Zarafa.core.KeyMapMgr.formatShortcutHint('Ctrl + S', true),
			cls: 'zarafa-action',
			iconCls: 'icon_save_white',
			handler: this.onSave,
			scope: this
		},{
			xtype: 'button',
			ref: 'restoreToTaskList',
			text: _('Restore to Task List'),
			overflowText: _('Restore to Task List'),
			hidden: true,
			handler: this.onRestoreToTaskList,
			scope: this
		},{
			xtype: 'button',
			ref: 'deleteBtn',
			overflowText: _('Delete'),
			tooltip: _('Delete this task'),
			iconCls: 'icon_delete',
			handler: this.onDelete,
			scope: this
		},{
			xtype: 'zarafa.attachmentbutton',
			plugins: [ 'zarafa.recordcomponentupdaterplugin' ],
			overflowText: _('Add Attachment'),
			tooltip: _('Add attachments to this task'),
			iconCls: 'icon_paperclip',
			ref: 'attachmentButton',
			// Add a listener to the component added event to set use the correct update function when the toolbar overflows
			// (i.e. is too wide for the panel) and Ext moves the button to a menuitem.
			listeners: {
				added: this.onAttachmentButtonAdded,
				scope: this
			}
		},{
			// Task Accept/Decline buttons.
			xtype: 'zarafa.taskrequestbutton',
			name: Zarafa.task.data.TaskRequestButtonNames.ACCEPT,
			text: _('Accept'),
			iconCls: 'icon_calendar_appt_accept',
			responseStatus: Zarafa.core.mapi.TaskMode.ACCEPT
		},{
			xtype: 'zarafa.taskrequestbutton',
			name: Zarafa.task.data.TaskRequestButtonNames.DECLINE,
			text: _('Decline'),
			iconCls: 'icon_calendar_appt_cancelled',
			responseStatus: Zarafa.core.mapi.TaskMode.DECLINE
		},{
			xtype: 'button',
			ref: 'markCompleteBtn',
			overflowText: _('Mark as Complete'),
			tooltip: _('Mark this task as complete'),
			iconCls: 'icon_task_complete',
			handler: this.onComplete,
			scope: this
		}, {
			xtype: 'button',
			overflowText: _('Print'),
			ref: 'printBtn',
			tooltip: _('Print this task'),
			iconCls: 'icon_print',
			handler: this.onPrint,
			scope: this
		},{
			xtype: 'button',
			overflowText: _('Check names'),
			tooltip: _('Check names'),
			iconCls: 'icon_checknames',
			ref: 'checkNamesBtn',
			handler: this.onCheckNames,
			scope: this
		},{
			xtype: 'button',
			overflowText: _('Addressbook'),
			tooltip: _('Open addressbook'),
			iconCls: 'icon_small_addressbook',
			ref: 'addressbookBtn',
			handler: function() {
				Zarafa.task.Actions.openRecipientSelectionContent(this.record, {
					defaultRecipientType: Zarafa.core.mapi.RecipientType.MAPI_TO
				});
			},
			scope: this
		}];
	},

	/**
	 * Event listener for the added event of the {@link Zarafa.common.attachment.ui.AttachmentButton attachmentButton}
	 * Adds the update function to the item when Ext converts the button to a menu item
	 * (which happens when the toolbar overflows, i.e. is too wide for the containing panel)
	 *
	 * @param {Ext.Component} item The item that was added. This can be a {@link Zarafa.common.attachment.ui.AttachmentButton}
	 * or a {@link Ext.menu.Item}
	 */
	onAttachmentButtonAdded: function(item)
	{
		if ( item.isXType('menuitem') ){
			// Set the update function to the update function of the original button
			// otherwise the Ext.Component.update function would be called by the recordcomponentupdaterplugin
			item.update = Zarafa.common.attachment.ui.AttachmentButton.prototype.update.createDelegate(this.attachmentButton);
		}
	},

	/**
	 * Create all buttons which should be added by default the the `Options` buttons.
	 * This contains the buttons to assign the task and set the task as a recurrence.
	 *
	 * @return {Array} The {@link Ext.Button Button} elements which should be
	 * added in the Options section of the {@link Ext.Toolbar Toolbar}.
	 * @private
	 */
	createOptionButtons: function()
	{
		return [{
			xtype: 'button',
			text: _('Assign Task'),
			overflowText: _('Assign Task'),
			iconCls: 'icon_invite_attendees',
			ref: 'assignTask',
			handler: this.onAssignment,
			scope: this
		},{
			xtype: 'button',
			text: _('Cancel Assignment'),
			overflowText: _('Cancel Assignment'),
			iconCls: 'icon_calendar_appt_cancelled',
			ref: 'cancelAssignmetBtn',
			handler: this.onCancelAssignment,
			scope: this
		},{
			xtype: 'button',
			overflowText: _('Categories'),
			tooltip: _('Open the categories dialog'),
			ref: 'categoriesBtn',
			iconCls: 'icon_categories',
			handler: this.onCategories,
			scope: this
		},{
			xtype: 'button',
			overflowText: _('Private'),
			tooltip: _('Mark this task as private'),
			iconCls: 'icon_private',
			ref: 'setPrivate',
			enableToggle: true,
			toggleHandler: this.onPrivateGroupToggle,
			scope: this
		},{
			xtype: 'button',
			overflowText: _('Set flag'),
			tooltip: _('Set flag on this email'),
			ref: 'setFlags',
			iconCls: 'icon_flag_red',
			handler: this.onSetFlagButton,
			scope: this
		}];
	},

	/**
	 * Event handler when the "Save" button has been pressed.
	 * This will {@link Zarafa.core.data.RecordContentPanel#saveRecord save} the given record.
	 * it also checks whether the message is a meeting, and if to send the changes to attendees as well
	 * @param {Ext.Button} button The button which has been pressed
	 * @private
	 */
	onSave: function(button)
	{
		this.dialog.saveRecord();
	},

	/**
	 * Event handler when the "Check Names" button has been pressed.
	 * This will {@link Zarafa.core.data.IPMRecipient#resolve resolve} all recipients.
	 *
	 * @param {Ext.Button} button The button which has been pressed
	 * @private
	 */
	onCheckNames: function(button)
	{
		this.record.getRecipientStore().resolve(undefined, { cancelPreviousRequest: true });
	},

	/**
	 * Event handler which is called when the "Recurrence" button has been pressed.
	 * This will open the {@link Zarafa.common.recurrence.dialogs.RecurrenceContentPanel RecurrenceContentPanel}.
	 * @param {Ext.Button} button The button which has been pressed
	 * @private
	 */
	onRecurrence: function(button)
	{
		Zarafa.common.Actions.openRecurrenceContent(this.record, false);
	},

	/**
	 * Event handler when the "Set Flag" button has been pressed.
	 * This will call the {@link Zarafa.task.Actions#openFlagsMenu}.
	 *
	 * @param {Ext.Button} button The button which has been pressed
	 * @param {Ext.EventObject} eventObject event object
	 * @private
	 */
	onSetFlagButton: function (button, eventObject)
	{
		var options = {
			position: eventObject.getXY(),
			shadowEdit: false,
			saveOnSetFlag: false
		};
		Zarafa.task.Actions.openFlagsMenu(this.record, options);
	},

	/**
	 * Event handler which is called when the "Assign Task" button has
	 * been pressed. This will update the "Assigning" state of the record which
	 * will trigger the update of all UI components.
	 * @param {Ext.Button} button The button which has been pressed
	 * @private
	 */
	onAssignment: function(button)
	{
		this.record.beginEdit();
		this.record.set('taskstate', Zarafa.core.mapi.TaskState.OWNER_NEW);
		this.record.set('taskmode', Zarafa.core.mapi.TaskMode.REQUEST);
		this.record.endEdit();
	},

	/**
	 * Event handler which is called when the "Cancel Assignment" button has
	 * been pressed. This will cancel the "assigned" state of the record which
	 * will trigger the update of all UI components.
	 * @param {Ext.Button} button The button which has been pressed
	 * @private
	 */
	onCancelAssignment: function(button)
	{
		this.record.beginEdit();
		this.record.set('taskstate', Zarafa.core.mapi.TaskState.NORMAL);
		this.record.set('taskmode', Zarafa.core.mapi.TaskMode.NOTHING);
		this.record.endEdit();
	},

	/**
	 * Event handler which is is called when the user presses the "Mark as Complete" button.
	 * This will mark the record as "complete".
	 * @param {Ext.Button} button The button which has been pressed
	 * @private
	 */
	onComplete: function(button)
	{
		this.record.beginEdit();
		this.record.set('complete', true);
		this.record.set('percent_complete', 1);
		this.record.set('status', Zarafa.core.mapi.TaskStatus.COMPLETE);
		this.record.set('date_completed', new Date());
		this.record.set('flag_icon', Zarafa.core.mapi.FlagIcon.red);
		this.record.set('flag_complete_time', new Date());
		this.record.set('flag_request', '');
		this.record.set('flag_status', Zarafa.core.mapi.FlagStatus.completed);
		this.record.endEdit();

		/**
		 * Send request for saving the record as after marking task as complete
		 * it should save all the properties and close the dialog.
		 */
		this.dialog.saveRecord();
	},

	/**
	 * Event handler which is called when the user pressed the 'Restore to task List' button.
	 * this will convert the assigned task to {@link Zarafa.core.mapi.TaskState#OWNER_NEW Normal task}.
	 *
	 * @param {Ext.Button} button The button which has been pressed
	 * @private
	 */
	onRestoreToTaskList: function (button)
	{
		var record = this.record;
		record.beginEdit();
		record.addMessageAction("action_type", "restoreToTaskList");
		record.set('taskstate', Zarafa.core.mapi.TaskState.OWNER_NEW);
		record.set('taskmode', Zarafa.core.mapi.TaskMode.NOTHING);
		record.set('taskhistory', Zarafa.core.mapi.TaskHistory.NONE);
		record.set('ownership', Zarafa.core.mapi.TaskOwnership.NEWTASK);
		record.set('icon_index', Zarafa.core.mapi.IconIndex['task']);
		record.set('updatecount', 2);
		record.set('taskfcreator', true);
		record.set('tasklastdelegate', '');
		record.set('tasklastuser', '');
		record.set('subject', record.get('conversation_topic'));
		var store = container.getHierarchyStore().getById(record.get('store_entryid'));
		if (store) {
			record.set('owner', store.get('display_name'));
		}
		record.getRecipientStore().removeAll();
		record.endEdit();
		record.save();
	},

	/**
	 * Event handler which is called when the user pressed the 'Categories' button.
	 * This will open the {@link Zarafa.common.categories.dialogs.CategoriesContentPanel CategoriesContentPanel}.
	 * @param {Ext.Button} button The button which has been pressed
	 * @private
	 */
	onCategories: function(button)
	{
		Zarafa.common.Actions.openCategoriesContent(this.record, {autoSave: false});
	},

	/**
	 * Event handler which is called when the user pressed the 'Delete' button.
	 * This will call {@link Zarafa.core.ui.RecordContentPanel#deleteRecord} to delete the record
	 * @param {Ext.Button} button The button which has been pressed
	 * @private
	 */
	onDelete: function(button)
	{
		this.dialog.deleteRecord();
	},
	/**
	 * Event handler which is called when the user pressed the 'Print' button.
	 * This will call {@link Zarafa.common.Actions.openPrintDialog} to print the record
	 * @param {Ext.Button} button The button which has been pressed
	 * @private
	 */
	onPrint: function()
	{
		Zarafa.common.Actions.openPrintDialog(this.record);
	},
	/**
	 * Event handler which is called when the PrivateGroup button
	 * has been toggled. If this is the case 'private' is updated.
	 *
	 * @param {Ext.Button} button The button which was toggled
	 * @private
	 */
	onPrivateGroupToggle: function(button)
	{
		this.record.beginEdit();
		this.record.set('private', button.pressed);
		if (button.pressed) {
			this.record.set('sensitivity', Zarafa.core.mapi.Sensitivity['PRIVATE']);
		} else {
			this.record.set('sensitivity', Zarafa.core.mapi.Sensitivity['NONE']);
		}
		this.record.endEdit();
	},

	/**
	 * Updates the toolbar by updating the Toolbar buttons based on the settings
	 * from the {@link Zarafa.core.data.IPMRecord record}.
	 *
	 * @param {Zarafa.core.data.IPMRecord} 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)
	{
		var layout = false;

		this.record = record;

		if(record.isSubMessage()) {
			this.deleteBtn.setVisible(false);
			this.sendBtn.setVisible(false);
			this.saveTaskRequest.setVisible(false);
			this.saveBtn.setVisible(false);
			this.checkNamesBtn.setVisible(false);
			this.addressbookBtn.setVisible(false);
			this.restoreToTaskList.setVisible(false);
			this.assignTask.setVisible(false);
			this.cancelAssignmetBtn.setVisible(false);
			this.markCompleteBtn.setVisible(false);
			this.setPrivate.setVisible(false);

			layout = true;
		} else {
			// Task which received by assignee and currently not accepted or decline the task.
			var isReceivedTask = record.isTaskReceived() && record.isTaskOwner() && !record.isTaskAccepted() && !record.isTaskUpdated();
			// isTaskDecline gets true if task state is Zarafa.core.mapi.TaskState.DECLINE;
			var isTaskDecline = record.get('taskstate') === Zarafa.core.mapi.TaskState.DECLINE;
			var isDelegatedTask = record.isTaskDelegated();
			var isModifiedTaskMode = record.isModified('taskmode');

			this.saveBtn.setVisible(!isDelegatedTask && !isTaskDecline && !isReceivedTask && !record.isTaskRequest());
			if(!isModifiedTaskMode) {
				var visible = false;
				if (record.isTaskReceived()) {
					if (record.get('task_not_found')) {
						visible = false;
					} else if(record.isTaskAccepted() || record.isTaskUpdated()) {
						visible = true;
					}
				} else if (record.isDraftAssignedTask() || record.isNormalTask()) {
					visible = true;
				}

				this.setFlags.setVisible(visible);
				this.categoriesBtn.setVisible(visible);
				this.markCompleteBtn.setVisible(visible);
				this.setPrivate.setVisible(visible);
				this.attachmentButton.setVisible(visible);
				this.printBtn.setVisible(!isReceivedTask);
			}
			this.restoreToTaskList.setVisible(record.isTaskOrganized() && isTaskDecline);
			if ( this.restoreToTaskList.isVisible() ) {
				this.restoreToTaskList.getEl().addClass('zarafa-action');
			}
			if (isReceivedTask) {
				this.deleteBtn.setVisible(isDelegatedTask);
			} else {
				// Only enable Disabled button when it is not a phantom
				this.deleteBtn.setDisabled(record.phantom === true);
			}

			if (contentReset === true || record.isModifiedSinceLastUpdate('complete')) {
				this.markCompleteBtn.setDisabled(record.get('complete'));
			}

			if (contentReset === true || record.isModifiedSinceLastUpdate('private')) {
				this.setPrivate.toggle(record.get('private'), true);
			}

			if (contentReset === true || isModifiedTaskMode) {
				switch (record.get('taskmode')) {
				case Zarafa.core.mapi.TaskMode.REQUEST:
					var isTaskRequestInstance = record instanceof Zarafa.task.TaskRequestRecord;
					var isVisible = (!record.isTaskDelegated() && !isTaskRequestInstance) || record.get('taskstate') === Zarafa.core.mapi.TaskState.OWNER_NEW;

					this.sendBtn.setVisible(isVisible);
					this.saveTaskRequest.setVisible(isVisible);
					this.saveBtn.setVisible(false);
					this.checkNamesBtn.setVisible(isVisible);
					this.addressbookBtn.setVisible(isVisible);
					this.assignTask.setVisible(false);
					this.cancelAssignmetBtn.setVisible(isVisible);
					break;
				case Zarafa.core.mapi.TaskMode.NOTHING:
				/* falls through */
				default:
					this.sendBtn.setVisible(false);
					this.saveTaskRequest.setVisible(false);
					this.checkNamesBtn.setVisible(false);
					this.addressbookBtn.setVisible(false);
					this.assignTask.setVisible(record.isNormalTask());
					this.cancelAssignmetBtn.setVisible(false);
					break;
				}

				layout = true;
			}

			if (!contentReset && record.isModifiedSinceLastUpdate('taskmode')) {
				layout = true;
			}

			if(!contentReset
				&& record.isModifiedSinceLastUpdate('taskhistory')
				&& record.get('taskhistory') === Zarafa.core.mapi.TaskHistory.NONE) {
				this.assignTask.setVisible(record.isNormalTask());
			}
		}

		if(layout) {
			this.doLayout();
		}
	},

	/**
	 * Send the {@link Zarafa.core.data.IPMRecord record} to the recipient
	 * @param {Ext.Button} button The button which has been pressed
	 * @private
	 */
	onSendTask: function(button)
	{
		this.dialog.sendRecord();
	}

});

Ext.reg('zarafa.tasktoolbar', Zarafa.task.dialogs.TaskToolbar);
// -*- coding: utf-8; indent-tabs-mode: nil -*-
Ext.namespace('Zarafa.task.printer');

/**
 * @class Zarafa.task.printer.TaskRenderer
 * @extends Zarafa.common.printer.renderers.RecordRenderer
 *
 * A printer for tasks using the same layout as for emails
 *
 * Prints a single task request or task item
 */
Zarafa.task.printer.TaskRenderer = Ext.extend(Zarafa.common.printer.renderers.RecordRenderer, {

	/**
	 * Generate the XTemplate HTML text for printing a single task item or task request.
	 * @param {Zarafa.core.data.MAPIRecord} record The task item to print
	 * @return {String} The HTML for the XTemplate to print
	 */
	generateBodyTemplate: function(record) {
		var html = '';
		html += '<b>{fullname}</b>\n';
		html += '<hr>\n';
		html += '<table>\n';
		html += this.addRow(_('Subject'), '{subject}');
		// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
		html += this.optionalRow(_('Start'), 'startdate', '{startdate:date("' + _('l d/m/Y') + '")}');
		// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
		html += this.optionalRow(_('Due'), 'duedate', '{duedate:date("' + _('l d/m/Y') + '")}');
		html += this.optionalRow(_('Priority'), 'importance', '{importance:importanceString}');
		html += this.addRow('', '');  // separator
		html += this.addRow(_('Status'), '{status:taskStatusString}');
		html += this.addRow(_('Percent complete'), '{percent_complete:percentage(0)}');
		html += this.addRow('', '');  // separator
		html += this.addRow(_('Total work'), _('{totalwork} hours'));
		html += this.addRow(_('Actual work'), _('{actualwork} hours'));
		html += this.addRow('', '');  // separator
		// outlook always prints Recurrence: (none) .. no clue what it means, and why.
		html += this.optionalRow(_('Recurrence pattern'), 'recurring_pattern', '{recurring_pattern}');
		html += this.optionalRow('', 'recurring_pattern', '');
		html += this.addRow(_('Owner'), '{owner}');
		html += this.addRow('', '');  // separator
		html += this.optionalRow(_('Categories'), 'categories', '{categories}');
		html += this.optionalRow(_('Attachments'), 'attachment_names', '{attachment_names}');
		html += '</table><br><p>\n';
		html += record.getBody(true);
		html += '</p>\n';
		return html;
	}
});

Ext.namespace('Zarafa.common.flags.ui');

/**
 * @class Zarafa.task.ui.TaskFlagsMenu
 * @extends Zarafa.common.flags.ui.FlagsMenu
 * @xtype zarafa.taskflagsmenu
 *
 * The TaskFlagsMenu is the menu that is shown for flags.
 */
Zarafa.task.ui.TaskFlagsMenu = Ext.extend(Zarafa.common.flags.ui.FlagsMenu, {
	/**
	 * @cfg {Boolean} saveOnSetFlag True to save record just
	 * after set flags properties in selected record else
	 * don't save record.
	 */
	saveOnSetFlag: true,

	/**
	 * Action should perform on selected record(s).
	 * default is undefined
	 * @property
	 * @type String
	 */
	action: undefined,

	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = Ext.applyIf(config || {}, {
			xtype: 'zarafa.taskflagsmenu'
		});

		Zarafa.task.ui.TaskFlagsMenu.superclass.constructor.call(this, config);
	},
	/**
	 * Event handler for the click event of the items in the flag menu. Will set the required properties
	 * on the selected records.
	 *
	 * @param {Zarafa.core.ui.menu.ConditionalItem} menuItem The menu item that was clicked
	 */
	setFlag: function(menuItem)
	{
		this.action = menuItem.action;
		Zarafa.task.ui.TaskFlagsMenu.superclass.setFlag.apply(this, arguments);
	},

	/**
	 * Set necessary flag related properties into given record(s).
	 *
	 * @param {Zarafa.core.data.IPMRecord} records The record(s) for which configured flag needs to be set.
	 * @param {Object} flagProperties Necessary flag properties
	 */
	setFlagProperties: function (records, flagProperties)
	{
		records.forEach(function(record){
			record.beginEdit();
			for ( var property in flagProperties ){
				if(property === 'startdate' || property === 'duedate' || property === 'reminder_time') {
					continue;
				}
				record.set(property, flagProperties[property]);
			}
			// - If action is 'complete' then mark all selected records
			// to complete.
			// - If action is other then complete and
			// from selected records some record are completed then
			// make only those records to incomplete.
			if (this.action === 'complete') {
				record.set('percent_complete', 1);
				record.set('status', Zarafa.core.mapi.TaskStatus.COMPLETE);
			} else if (record.get('complete')) {
				record.set('complete', false);
				record.set('percent_complete', 0);
				record.set('status', Zarafa.core.mapi.TaskStatus.NOT_STARTED);
			}

			var startDate = flagProperties['startdate'];
			var dueDate = flagProperties['duedate'];
			// Condition gets false only when action type no_date
			if (Ext.isDate(startDate) && Ext.isDate(dueDate)) {
				startDate = startDate.clearTime();
				dueDate = dueDate.clearTime();
				record.set('startdate', startDate.fromUTC());
				record.set('commonstart', startDate.clone());
				record.set('duedate', dueDate.fromUTC());
				record.set('commonend', dueDate.clone());
			} else if (this.action !== 'complete') {
				record.set('startdate', startDate);
				record.set('commonstart', startDate);
				record.set('duedate', dueDate);
				record.set('commonend', dueDate);
			}
			record.endEdit();
			if (this.saveOnSetFlag) {
				// Task is assigned task then send an update
				// to assigner when it's updated. Flag menu visible
				// only for received/assignee task copy.
				if (record.isMessageClass('IPM.Task') && !record.isNormalTask()) {
					record.respondToTaskRequest(Zarafa.core.mapi.TaskMode.UPDATE);
				} else {
					record.save();
				}
			}
		}, this);
	}
});

Ext.reg('zarafa.taskflagsmenu', Zarafa.task.ui.TaskFlagsMenu);
Ext.namespace('Zarafa.task.ui');

/**
 * @class Zarafa.task.ui.TaskGridView
 * @extends Zarafa.common.ui.grid.MapiMessageGrid
 * @xtype zarafa.taskgrid
 *
 * this view is used to list all the tasks as a list view
 */
Zarafa.task.ui.TaskGridView = Ext.extend(Zarafa.common.ui.grid.MapiMessageGrid, {
	/**
	 * @cfg {Zarafa.task.TaskContextModel} model data handling part of context
	 */
	model: undefined,

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

		if (!Ext.isDefined(config.model) && Ext.isDefined(config.context)) {
			config.model = config.context.getModel();
		}
		if (!Ext.isDefined(config.store) && Ext.isDefined(config.model)) {
			config.store = config.model.getStore();
		}

		config.store = Ext.StoreMgr.lookup(config.store);

		Ext.applyIf(config, {
			xtype: 'zarafa.taskgrid',
			ariaLabel: _('Tasks list'),
			border: false,
			stateful: true,
			statefulRelativeDimensions: false,
			loadMask: this.initLoadMask(),
			viewConfig: this.initViewConfig(),
			selModel: this.initSelectionModel(),
			colModel: this.initColumnModel(config.model),
			enableDragDrop: true,
			ddGroup: 'dd.mapiitem'
		});

		Zarafa.task.ui.TaskGridView.superclass.constructor.call(this, config);
	},

	/**
	 * @return {Object} view config object
	 * @private
	 */
	initViewConfig: function()
	{
		return {
			autoExpandColumn: 'subject',
			enableRowBody: false,
			getRowClass: this.viewConfigGetRowClass
		};
	},

	/**
	 * @return {Object} loas mask config object
	 * @private
	 */
	initLoadMask: function()
	{
		return {
			msg: _('Loading tasks') + '...'
		};
	},

	/**
	 * creates and returns a column model object, used in {@link Ext.grid.EditorGridPanel.colModel colModel} config
	 *
	 * @param {Zarafa.task.TaskContextModel} model The {@link Zarafa.task.TaskContextModel}.
	 * @return {Ext.grid.ColumnModel} column model object
	 * @private
	 */
	initColumnModel: function(model)
	{
		return new Zarafa.task.ui.TaskGridColumnModel({model: model});
	},

	/**
	 * creates and returns a selection model object, used in {@link Ext.grid.GridPanel.selModel selModel} config
	 * @return {Ext.grid.RowSelectionModel} selection model object
	 * @private
	 */
	initSelectionModel: function()
	{
		return new Ext.grid.RowSelectionModel({
			singleSelect: false,
			listeners: {
				scope: this,
				selectionchange: this.onSelectionChange
			}
		});
	},

	/**
	 * initialize events for the grid panel
	 * @private
	 */
	initEvents: function()
	{
		Zarafa.task.ui.TaskGridView.superclass.initEvents.call(this);

		this.on({
			'cellclick': this.onCellClick,
			'rowdblclick': this.onRowDblClick,
			scope: this
		});

		this.mon(this.context, 'viewmodechange', this.onContextViewModeChange, this);

		// Call the handler manually as task-context-object isn't initialized yet,
		// while switching to task context for the first time.
		this.onContextViewModeChange(this.context, this.context.getCurrentViewMode());

		// Add an event handler that will reload the store when the flag status of
		// a record has been changed
		this.mon(this.getStore(), 'update', this.onStoreUpdate, this);
	},

	/**
	 * Event handler for the cellclick event of the grid. Will open the
	 * {@link Zarafa.common.flags.ui.FlagsMenu FlagContextMenu} if flag icon column
	 * was clicked by user.
	 *
	 * @param {Zarafa.task.ui.TaskGridView} grid The task grid
	 * @param {Number} rowIndex The index number of the row that was clicked
	 * @param {Number} columnIndex The index number of the column that was clicked
	 * @param {Ext.EventObject} event The event object
	 * @private
	 */
	onCellClick: function(grid, rowIndex, columnIndex, e)
	{
		var record = this.store.getAt(rowIndex);
		if (!Ext.isDefined(record)) {
			return;
		}

		var cm = this.getColumnModel();
		var column = cm.config[columnIndex];
		// show flag menu only for received/assignee task copy.
		var disabled = record.isMessageClass('IPM.Task', true) && (record.isTaskOrganized() || record.isTaskNotResponded());
		if(column.dataIndex === 'flag_due_by' && !disabled) {
			Zarafa.task.Actions.openFlagsMenu(record, {
				position: e.getXY()
			});
		}
	},

	/**
	 * Event handler which is triggered when user double clicks on a row.
	 * @param {Ext.grid.GridPanel} grid grid panel object
	 * @param {Number} rowIndex	index of row
	 * @param {Ext.event} eventObj object of the event
	 * @private
	 */
	onRowDblClick: function(grid, rowIndex, eventObj)
	{
		Zarafa.common.Actions.openMessageContent(this.getSelectionModel().getSelected());
	},

	/**
	 * Event handler for the update event of the store of the grid. Will reload the store (and thus update
	 * the view) when the flag status of a record has been changed.
	 * @param {Zarafa.core.data.IPMStore} store The store with the data that is being displayed in the grid
	 * @param {Zarafa.core.data.IPMRecord} record The Record that was updated
	 * @private
	 */
	onStoreUpdate: function(store, record)
	{
		var todoListEntryId = container.getHierarchyStore().getDefaultFolder('todolist').get('entryid');
		if ( !Zarafa.core.EntryId.compareEntryIds(todoListEntryId, store.entryId) ){
			// Only reload when watching the To-do list
			return;
		}

		if ( record.modified && (record.isModified('flag_due_by') || record.isModified('flag_request')) ){
			store.reload();
		}
	},

	/**
	 * Event handler which is triggered when the {@link Zarafa.task.ui.TaskGridView TaskGridView}
	 * {@link Zarafa.core.data.IPMRecord record} selection is changed. This will inform
	 * the {@link Zarafa.task.TaskContextModel contextmodel} about the change.
	 *
	 * @param {Ext.grid.RowSelectionModel} selectionModel The selection model used by the grid.
	 * @private
	 */
	onSelectionChange: function(selectionModel)
	{
		this.model.setSelectedRecords(selectionModel.getSelections());
	},

	/**
	 * Apply custom style and content for the row body. This will always
	 * apply the Complete/Incomplete and overdue style to the entire row.
	 *
	 * @param {Ext.data.Record} record The {@link Ext.data.Record Record} corresponding to the current row.
	 * @param {Number} rowIndex The row index
	 * @param {Object} rowParams A config object that is passed to the row template during
	 * rendering that allows customization of various aspects of a grid row.
	 * If enableRowBody is configured true, then the following properties may be set by this function,
	 * and will be used to render a full-width expansion row below each grid row.
	 * @param {Ext.data.Store} store The Ext.data.Store this grid is bound to
	 * @return {String} a CSS class name to add to the row
	 * @private
	 */
	viewConfigGetRowClass: function(record, rowIndex, rowParams, store)
	{
		var cssClass = '';
		var complete;

		// For TaskRecords we will use the complete field to style it strikethrough . For
		// other records (in the Todo-list) we will use the flag_status field.
		if ( record instanceof Zarafa.task.TaskRecord ){
			complete = record.get('complete');
		} else {
			complete = record.get('flag_status') === Zarafa.core.mapi.FlagStatus.completed;
		}

		var duedate = record.get('duedate');
		if (Ext.isDate(duedate) && duedate.getTime() < new Date().getTime()) {
			cssClass += 'zarafa-task-overdue ';
		}

		if (complete) {
			cssClass += 'zarafa-task-complete ';
		}

		return cssClass;
	},

	/**
	 * Event handler which is fired when the {@link Zarafa.core.Context} fires the
	 * {@link Zarafa.core.Context#viewmodechange viewmodechange} event.
	 * This will check the selected mode, and if needed change the
	 * {@link Ext.grid.Column columns} inside the {@link Ext.grid.ColumnModel ColumnModel}
	 * of this grid. Either use the simple set or the detailed set.
	 *
	 * @param {Zarafa.core.Context} context The context which fired the event
	 * @param {Zarafa.mail.data.ViewModes} newViewMode The new active mode
	 * @param {Zarafa.mail.data.ViewModes} oldViewMode The previous mode
	 * @private
	 */
	onContextViewModeChange: function(context, newViewMode, oldViewMode)
	{
		switch(newViewMode){
			case Zarafa.task.data.ViewModes.SIMPLE:
				this.getColumnModel().setSimpleView(true);
				break;
			case Zarafa.task.data.ViewModes.DETAILED:
				this.getColumnModel().setSimpleView(false);
				break;
		}
	}
});

Ext.reg('zarafa.taskgrid', Zarafa.task.ui.TaskGridView);
Ext.namespace('Zarafa.task.widgets');

/**
 * @class Zarafa.task.widgets.QuickTaskWidget
 * @extends Zarafa.core.ui.widget.AbstractQuickItemWidget
 *
 * Widget for creating a task quickly with a minimum set of
 * input fields
 */
Zarafa.task.widgets.QuickTaskWidget = Ext.extend(Zarafa.core.ui.widget.AbstractQuickItemWidget, {

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

		Ext.applyIf(config, {
			wrapCfg: {
				recordComponentPluginConfig: Ext.applyIf(config.recordComponentPluginConfig || {}, {
					allowWrite: true
				}),
				layout: 'fit',
				items: [{
					xtype: 'form',
					ref: '../formPanel',
					layout: {
						type: 'vbox',
						align: 'stretch'
					},
					border: false,
					bodyStyle: 'background-color: inherit; padding: 5px;',
					defaults: {
						border: false,
						labelLength: 100,
						style: 'padding-bottom: 2px'
					},
					items: [{
						xtype: 'zarafa.compositefield',
						hideLabel: true,
						anchor: '100%',
						items: [{
							xtype: 'textfield',
							flex: 1,
							name: 'subject',
							emptyText: _('Subject') + ':',
							listeners: {
								change: this.onChange,
								scope: this
							}
						}]
					},{
						xtype: 'zarafa.compositefield',
						hideLabel: true,
						anchor: '100%',
						items: [{
						},{
							xtype: 'datefield',
							ref: '../dueDateField',
							emptyText: _('End date') + ': ' + _('none'),
							flex: 1,
							name: 'commonend',
							utcname: 'duedate',
							// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
							format: _('d/m/Y'),
							minValue: new Date(),
							listeners: {
								change: this.onDueDateChange,
								scope: this
							}
						}]
					},{
						xtype: 'zarafa.editorfield',
						ref: 'editorField',
						htmlName: 'html_body',
						plaintextName: 'body',
						hideLabel: true,
						flex: 1,
						useHtml: true,
						defaultValue: '',
						listeners: {
							change: this.onBodyChange,
							scope: this
						}
					}]
				}]
			},
			buttons: [{
				text: _('Save'),
				cls: 'zarafa-action',
				style: 'padding-bottom: 5px',
				handler: this.onSave,
				scope: this
			},{
				text: _('Discard'),
				style: 'padding-bottom: 5px',
				handler: this.onDiscard,
				scope: this
			}]
		});

		Zarafa.task.widgets.QuickTaskWidget.superclass.constructor.call(this, config);
	},

	/**
	 * Event handler which is triggered when one of the Input fields
	 * has been changed by the user. It will validate the new value,
	 * and if correct, will apply it to the {@link Zarafa.core.data.IPMRecord record}.
	 * @param {Ext.form.Field} field The {@link Ext.form.Field field} which was changed.
	 * @param {Mixed} newValue The new value
	 * @param {Mixed} oldValue The old value
	 * @private
	 */
	onDueDateChange: function(field, newValue, oldValue)
	{
		if (Ext.isDate(newValue)) {
			this.record.set(field.name, newValue.clone());
			this.record.set(field.utcname, newValue.fromUTC());
		} else {
			this.record.set(field.name, null);
			this.record.set(field.utcname, null);
		}
	},

	/**
	 * Create a new record which must be edited by this widget.
	 * @return {Ext.data.Record} record The record to load into the {@link #wrap}
	 * @protected
	 */
	createRecord: function()
	{
		var folder = container.getHierarchyStore().getDefaultFolder('task');
		var context = container.getContextByName('task');
		var model = context.getModel();

		return model.createRecord(folder);
	},

	/**
	 * Updates the widget by loading data from the record into the {@link #wrap}.
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record update the panel with.
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 * @protected
	 */
	update: function(record, contentReset)
	{
		this.formPanel.getForm().loadRecord(record);
	},

	/**
	 * Updates the widget by loading data from the record into the {@link #wrap}.
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record to update
	 * @protected
	 */
	updateRecord: function(record)
	{
		record.beginEdit();
		this.formPanel.getForm().updateRecord(record);
		this.onDueDateChange(this.formPanel.dueDateField, this.formPanel.dueDateField.getValue());
		this.onBodyChange(this.formPanel.editorField, this.formPanel.editorField.getValue());
		record.endEdit();
	}
});

Zarafa.onReady(function() {
	container.registerWidget(new Zarafa.core.ui.widget.WidgetMetaData({
		name: 'quicktask',
		iconCls: 'icon_widget_new_task',
		displayName: _('Quick Task'),
		widgetConstructor: Zarafa.task.widgets.QuickTaskWidget
	}));
});
Ext.namespace('Zarafa.task.widgets');

/**
 * @class Zarafa.task.widgets.TasksWidget
 * @extends Zarafa.core.ui.widget.AbstractFolderWidget
 *
 * Widget that shows current, non-completed tasks incl. its due date, importance,
 * % completed, owner and categories
 */
Zarafa.task.widgets.TasksWidget = Ext.extend(Zarafa.core.ui.widget.AbstractFolderWidget, {
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function (config)
	{
		config = config || {};

		// FIXME - add something to find mails marked for follow-up as well
		var store = new Zarafa.task.TaskStore();

		// sort by duedate
		store.setDefaultSort('duedate', 'asc');

		// Create a restriction, we only want incomplete tasks, so tasks which
		// do not have the status flag set to Zarafa.core.mapi.TaskStatus.COMPLETE or
		// Zarafa.core.mapi.FlagStatus.completed
		store.setRestriction({
			'task': Zarafa.core.data.RestrictionFactory.createResAnd([Zarafa.core.data.RestrictionFactory.dataResProperty(
				'status',
				Zarafa.core.mapi.Restrictions.RELOP_NE,
				Zarafa.core.mapi.TaskStatus.COMPLETE
			),Zarafa.core.data.RestrictionFactory.dataResProperty(
				'flag_status',
				Zarafa.core.mapi.Restrictions.RELOP_NE,
				Zarafa.core.mapi.FlagStatus.completed
			)])
		});

		Ext.applyIf(config, {
			autoScroll: true,
			layout: 'fit',
			folderType: 'todolist',
			store: store,
			items: [{
				xtype: 'zarafa.gridpanel',
				cls: 'k-taskwidget',
				store: store,
				loadMask: {
					msg: _('Loading tasks') + '...'
				},
				sm: new Zarafa.common.ui.grid.RowSelectionModel({
					singleSelect: true
				}),
				viewConfig: {
					deferEmptyText: false,
					emptyText: '<div class="emptytext">' + _('No tasks.') + '</div>',
					forceFit: true,
					enableRowBody: true,
					rowSelectorDepth: 15,
					getRowClass: this.viewConfigGetRowClass

				},
				colModel: new Ext.grid.ColumnModel({
					columns: [{
						header: _("Due"),
						tooltip: _('Sort by: Due Date'),
						dataIndex: "duedate",
						renderer: Zarafa.common.ui.grid.Renderers.utcdate
					}, {
						header: "<p class='icon_importance'>&nbsp;</p>",
						tooltip: _('Sort by: Priority'),
						dataIndex: "importance",
						width: 24,
						fixed: true,
						renderer: Zarafa.common.ui.grid.Renderers.importance
					}, {
						header: "%",
						tooltip: _('Sort by: Percentage Completed'),
						dataIndex: "percent_complete",
						width: 75,
						renderer: Zarafa.common.ui.grid.Renderers.percentage
					}, {
						header: _('Owner'),
						tooltip: _('Sort by: Owner'),
						dataIndex: 'owner',
						renderer: this.ownerRenderer
					}],
					defaults: {
						sortable: true,
						menuDisabled: true
					}
				}),
				listeners: {
					'rowcontextmenu': this.onRowContextMenu,
					'rowdblclick': this.onRowDblClick,
					scope: this
				}
			}]
		});

		Zarafa.task.widgets.TasksWidget.superclass.constructor.call(this, config);
	},

	/**
	 * Update the filter to make sure only non-completed tasks are shown
	 * @private
	 */
	updateFilter: function ()
	{
		this.store.filterBy(function (record) {
			return (record.get('status') != Zarafa.core.mapi.TaskStatus.COMPLETE);
		}, this);
	},

	/**
	 * Renders the owner of the task as its initials and adds its full
	 * form as a tooltip
	 *
	 * @param {Mixed} value The subject of the appointment
	 * @param {Object} metaData Used to set style information to gray out appointments that occur now
	 * @param {Ext.data.Record} record The record being displayed, used to retrieve the start and end times
	 * @private
	 */
	ownerRenderer: function (value, metaData, record)
	{
		var owner = record.get("owner");
		if (!Ext.isString(owner)) {
			return '';
		}

		var ownerNames = owner.split(" "); // array of all parts of the owner's name
		var initials = '';

		for (var i = 0, len = ownerNames.length; i < len; i++) {
			initials += ownerNames[i].substring(0, 1);
		}

		return '<span ext:qtip="' + Ext.util.Format.htmlEncode(owner) + '" ext:qwidth="100%">' + Ext.util.Format.htmlEncode(initials) + '</span>';
	},

	/**
	 * Event handler which is triggered when user opens context menu
	 * @param {Ext.grid.GridPanel} grid grid panel object
	 * @param {Number} rowIndex index of row
	 * @param {Ext.EventObject} event event object of the event
	 * @private
	 */
	onRowContextMenu: function (grid, rowIndex, event)
	{
		// check row is already selected or not, if its not selected then select it first
		var selectionModel = grid.getSelectionModel();
		if (!selectionModel.isSelected(rowIndex)) {
			selectionModel.selectRow(rowIndex);
		}

		// The ContextMenu needs the ContextModel for cases where we want to reply the mail.
		var model;
		if (this.folder) {
			var context = container.getContextByFolder(this.folder);
			if (context) {
				model = context.getModel();
				Zarafa.core.data.UIFactory.openDefaultContextMenu(selectionModel.getSelections(), {
					position: event.getXY(),
					model: model
				});
			}
		}
	},

	/**
	 * Called when the user double-clicks on a task.
	 * @param {Ext.grid.GridPanel} grid The grid which fired the event
	 * @param {Number} rowIndex The row which was double clicked
	 * @param {Ext.EventObject} event The event object
	 * @private
	 */
	onRowDblClick: function (grid, rowIndex, event)
	{
		Zarafa.core.data.UIFactory.openViewRecord(grid.getSelectionModel().getSelected());
	}
});

Zarafa.onReady(function () {
	container.registerWidget(new Zarafa.core.ui.widget.WidgetMetaData({
		name: 'tasks',
		iconCls: "icon_widget_task",
		displayName: _('Tasks / To-Do'),
		widgetConstructor: Zarafa.task.widgets.TasksWidget
	}));
});
Ext.namespace('Zarafa.today');

/**
 * @class Zarafa.today.TodayContextModel
 * @extends Zarafa.core.ContextModel
 *
 * The aim of this model is to cause the root folder in the hierarchy to be selected when switching to the TodayContext
 * This is done by setting {@link Zarafa.core.ContextModel#defaultFolder}
 */
Zarafa.today.TodayContextModel = Ext.extend(Zarafa.core.ContextModel, {
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			stateful: false
		});
		Zarafa.today.TodayContextModel.superclass.constructor.call(this, config);
	},

	/**
	 * Called during the {@link Zarafa.core.Context#disable disabling} of the {@link Zarafa.core.Context context}.
	 * This will {@link #stopSearch stop the search} and clear all data in the {@link #store}.
	 */
	disable: Ext.emptyFn,

	/**
	 * Sets the selected folder list directly.
	 * Fires the {@link #folderchange} event.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord[]} folders selected folders as an array of
	 * {@link Zarafa.hierarchy.data.MAPIFolderRecord MAPIFolder} objects.
	 */
	setFolders: Ext.emptyFn,

	/**
	 * Returns the default {@link Zarafa.hierarchy.data.MAPIFolderRecord folder} which is
	 * used within the current selection of folders.
	 * @return {Zarafa.hierarchy.data.MAPIFolderRecord} The default folder
	 */
	getDefaultFolder: function()
	{
		if (!this.defaultFolder) {
			var store = container.getHierarchyStore().getDefaultStore();
			if (store) {
				this.defaultFolder = store.getSubtreeFolder();
			}
		}
		return this.defaultFolder;
	}
});
/*
 * 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,

	/**
	 * @cfg {Boolean} preferDisplayNameForResolve True to prefer display_name based resolving for
	 * name-like input. This is useful in dialogs where users are expected to type a GAL display name.
	 * Defaults to false.
	 */
	preferDisplayNameForResolve: false,

	/**
	 * 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();
			}

			var preferDisplayNameLookup = this.preferDisplayNameForResolve &&
				!Ext.isEmpty(displayName) &&
				!Zarafa.core.Util.validateEmailAddress(displayName);

			// Prefer resolving by display_name in name-centric dialogs.
			if (preferDisplayNameLookup) {
				emailAddress = '';
			}

			// 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 (!preferDisplayNameLookup && 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);
	},

	/**
	 * Handle a silent single-recipient resolve request that should not open the check names dialog.
	 *
	 * @param {Ext.data.Record/Array} records The resolve response records.
	 * @param {Object} options The options object used for the request.
	 * @param {Boolean} success True if the request was successful.
	 * @private
	 */
	onResolveRecipientByEmailAddressResult: function(records, options, success)
	{
		var record = options.record;
		var wasResolved = false;

		if (success && record) {
			if (!Array.isArray(records)) {
				records = [ records ];
			}

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

				if (resolveResult && resolveResult.id === record.id && resolveResult.result.totalRecords == 1) {
					record.applyResolveRecord(resolveResult.result.records[0], true);
					record.resolveAttempted = true;
					record.resolveAttemptAmbiguous = false;
					wasResolved = true;
					break;
				}
			}
		}

		Ext.callback(options.callback, options.scope || this, [ record, wasResolved ]);
	},

	/**
	 * Resolve a recipient by SMTP address without opening the check names dialog.
	 * The callback receives the original record after any unique match has been applied.
	 *
	 * @param {Zarafa.core.data.IPMRecipientRecord} record The recipient to resolve.
	 * @param {Function} callback The callback that should receive the record.
	 * @param {Object} scope The scope for the callback.
	 */
	resolveRecipientByEmailAddress: function(record, callback, scope)
	{
		var smtpAddress = Ext.value(record && record.get('smtp_address'), '').trim();

		if (Ext.isEmpty(smtpAddress) || !Zarafa.core.Util.validateEmailAddress(smtpAddress)) {
			Ext.callback(callback, scope || this, [ record, false ]);
			return;
		}

		var parameters = {
			resolverequests: [{
				id: record.id,
				display_name: Ext.value(record.get('display_name'), '').trim(),
				email_address: smtpAddress,
				address_type: record.get('address_type')
			}],
			exclude_local_contacts: !this.allowResolvingToLocalContacts,
			exclude_gab_groups: !this.allowResolvingToGABGroups
		};

		this.resolveProxy.request(
			Zarafa.core.Actions['checknames'],
			[ record ],
			parameters,
			this.resolveReader,
			this.onResolveRecipientByEmailAddressResult,
			this,
			{
				actionType: Zarafa.core.Actions['checknames'],
				listRequest: true,
				pendingRecords: [],
				params: parameters,
				record: record,
				callback: callback,
				scope: scope
			}
		);
	},

	/**
	 * 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 successful.
	 * @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 successful.
	 * @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;
	},

	/**
	 * Normalize an email address so recipient comparisons are case-insensitive.
	 *
	 * @param {String} emailAddress The email address to normalize.
	 * @return {String} The normalized email address.
	 */
	normalizeRecipientEmailAddress: function(emailAddress)
	{
		return !Ext.isString(emailAddress) ? '' : emailAddress.trim().toLocaleLowerCase();
	},

	/**
	 * Extract the comparable email address from a recipient-like object.
	 *
	 * @param {Zarafa.core.data.IPMRecipientRecord/Object} record The record or object to inspect.
	 * @return {String} The normalized email address.
	 */
	getRecipientEmailAddress: function(record)
	{
		if (!record) {
			return '';
		}

		var emailAddress = record instanceof Ext.data.Record ?
			record.get('smtp_address') || record.get('email_address') :
			record.smtp_address || record.email_address;

		return this.normalizeRecipientEmailAddress(emailAddress);
	},

	/**
	 * 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'];
		var emailAddress = this.getRecipientEmailAddress(record);
		var matchingEmails = true; // a recipient might have aliases

		return records.some(function(recipient) {
			if (recipient === record) {
				return false;
			}

			if (!Ext.isEmpty(emailAddress)) {
				if (this.getRecipientEmailAddress(recipient) === emailAddress) {
					return true;
				}
				else {
					matchingEmails = false;
				}
			}

			if (Ext.isEmpty(entryid) || Ext.isEmpty(recipient.get('entryid'))) {
				return false;
			}

			if (recipient.isOneOff()) {
				return recipient.get('entryid') === entryid;
			}

			return Zarafa.core.EntryId.compareEntryIds(recipient.get('entryid'), entryid) && matchingEmails;
		}, scope || this);
	}
});
Ext.namespace('Zarafa.addressbook');

/**
 * @class Zarafa.addressbook.AddressBookContext
 * @extends Zarafa.core.Context
 */
Zarafa.addressbook.AddressBookContext = Ext.extend(Zarafa.core.Context, {
	/**
	 * @constructor
	 * @param config
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			hasToolbar: false,
			hasContentPanel: false
		});

		Zarafa.addressbook.AddressBookContext.superclass.constructor.call(this, config);

		// Register addressbook specific dialog types
		Zarafa.core.data.SharedComponentType.addProperty('addressbook.dialog.addressbook');
		Zarafa.core.data.SharedComponentType.addProperty('addressbook.dialog.abuserselection');
		Zarafa.core.data.SharedComponentType.addProperty('addressbook.dialog.abmultiuserselection');
	},

	/**
	 * Bid for the type of shared component and the given record.
	 * This will bid on a dialog.crate or dialog.view for a record with a
	 * message class set to IPM or IPM.Note.
	 * @param {Zarafa.core.data.SharedComponentType} type Type of component a context can bid for.
	 * @param {Ext.data.Record} record Optionally passed record.
	 * @return {Number} The bid for the shared component
	 */
	bidSharedComponent: function(type, record)
	{
		var bid = -1;

		if (Array.isArray(record)) {
			record = record[0];
		}

		switch (type) {
			case Zarafa.core.data.SharedComponentType['addressbook.dialog.addressbook']:
			case Zarafa.core.data.SharedComponentType['addressbook.dialog.abuserselection']:
			case Zarafa.core.data.SharedComponentType['addressbook.dialog.abmultiuserselection']:
				bid = 1;
				break;
			case Zarafa.core.data.SharedComponentType['common.contextmenu']:
				if (record instanceof Zarafa.addressbook.AddressBookRecord) {
					bid = 1;
				}
				break;
			case Zarafa.core.data.SharedComponentType['common.view']:
				if (record instanceof Zarafa.addressbook.AddressBookRecord) {
					var objType = record.get('object_type');
					var biddableObjectTypes = [
						Zarafa.core.mapi.ObjectType.MAPI_MAILUSER,
						Zarafa.core.mapi.ObjectType.MAPI_DISTLIST,
						Zarafa.core.mapi.ObjectType.MAPI_ABCONT
					];
					// We also check whether the entryid does not indicate this entry comes from the Contact Provider
					if(biddableObjectTypes.indexOf(objType) >= 0 && !Zarafa.core.EntryId.hasContactProviderGUID(record.get('entryid'))){
						bid = 1;
					}
				}
				break;
		}
		return bid;
	},

	/**
	 * Will return the reference to the shared component.
	 * Based on the type of component requested a component is returned.
	 * @param {Zarafa.core.data.SharedComponentType} type Type of component a context can bid for.
	 * @param {Ext.data.Record} record Optionally passed record.
	 * @return {Ext.Component} Component
	 */
	getSharedComponent: function(type, record)
	{
		var component;
		switch (type) {
			case Zarafa.core.data.SharedComponentType['addressbook.dialog.addressbook']:
				component = Zarafa.addressbook.dialogs.AddressBookContentPanel;
				break;
			case Zarafa.core.data.SharedComponentType['common.view']:
				switch (record.get('object_type')) {
					case Zarafa.core.mapi.ObjectType.MAPI_MAILUSER:
						component = Zarafa.addressbook.dialogs.ABUserDetailContentPanel;
						break;
					case Zarafa.core.mapi.ObjectType.MAPI_DISTLIST:
						component = Zarafa.addressbook.dialogs.ABGroupDetailContentPanel;
						break;
					case Zarafa.core.mapi.ObjectType.MAPI_ABCONT:
						component = Zarafa.addressbook.dialogs.ABGroupDetailContentPanel;
						break;
				}
				break;
			case Zarafa.core.data.SharedComponentType['addressbook.dialog.abuserselection']:
				component = Zarafa.addressbook.dialogs.ABUserSelectionContentPanel;
				break;
			case Zarafa.core.data.SharedComponentType['addressbook.dialog.abmultiuserselection']:
				component = Zarafa.addressbook.dialogs.ABMultiUserSelectionContentPanel;
				break;
			case Zarafa.core.data.SharedComponentType['common.contextmenu']:
				component = Zarafa.addressbook.ui.AddressBookContextMenu;
				break;
		}
		return component;
	}

});

Zarafa.onReady(function() {
	container.registerContext(new Zarafa.core.ContextMetaData({
		name: 'addressbook',
		allowUserVisible: false,
		pluginConstructor: Zarafa.addressbook.AddressBookContext
	}));
});
/*
 * #dependsFile client/zarafa/addressbook/AddressBookHierarchyProxy.js
 */
Ext.namespace('Zarafa.addressbook');

/**
 * @class Zarafa.addressbook.AddressBookHierarchyStore
 * @extends Zarafa.core.data.ListModuleStore
 * @xtype zarafa.addressbookhierarchystore
 * this will contain all records {@link Zarafa.addressbook.AddressbookHierchyRecord}
 * fetched from the server side, stores all available addressbooks from all
 * available server side MAPI stores
 */
Zarafa.addressbook.AddressBookHierarchyStore = Ext.extend(Zarafa.core.data.ListModuleStore, {
	/**
	 * @constructor
	 * @param config Configuration structure
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			preferredMessageClass: 'addressbook',
			standalone: true,
			proxy: new Zarafa.addressbook.AddressBookHierarchyProxy(),
			sortInfo: {
				field: 'display_name',
				direction: 'desc'
			}
		});

		Zarafa.addressbook.AddressBookHierarchyStore.superclass.constructor.call(this, config);
	},

	/**
	 * Compare a {@link Ext.data.Record#id ids} to determine if they are equal.
	 * This will apply the {@link Zarafa.core.EntryId#compareABEntryIds compareABEntryIds} function
	 * on both ids, as all records in this store will have a Address Book EntryId as unique key.
	 * @param {String} a The first id to compare
	 * @param {String} b The second id to compare
	 * @protected
	 */
	idComparison: function(a, b)
	{
		return Zarafa.core.EntryId.compareABEntryIds(a, b);
	},

	/**
	 * Function which is use to load {@link Zarafa.addressbook.AddressBookHierarchyStore store}
	 */
	loadAddressBookHierarchy: function ()
	{
		Zarafa.addressbook.AddressBookHierarchyStore.load({
			actionType: Zarafa.core.Actions.list,
			params: {
				subActionType: Zarafa.core.Actions.hierarchy,
				gab: 'all'
			}
		});
	},

	/**
	 * Notification handler called by {@link #onNotify} when
	 * a {@link Zarafa.core.data.Notifications#objectModified objectModified}
	 * notification has been received.
	 *
	 * This will update the address book hierarchy 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 received 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 received.
	 * @private
	 */
	onNotifyObjectmodified: function(action, records, data, timestamp, success)
	{
		this.loadAddressBookHierarchy();
	}
});

Ext.reg('zarafa.addressbookhierarchystore', Zarafa.addressbook.AddressBookHierarchyStore);

Zarafa.onUIReady(function(){
	// Make a singleton of the address book store and load it immediately
	// Note: The typeof check is necessary for the js tests
	if ( typeof Zarafa.addressbook.AddressBookHierarchyStore === 'function' ){
		Zarafa.addressbook.AddressBookHierarchyStore = new Zarafa.addressbook.AddressBookHierarchyStore();
		Zarafa.addressbook.AddressBookHierarchyStore.on('load', function(){
			// Filter out shared contact folders from delegate stores that are
			// no longer open in the hierarchy (e.g. auto-hooked stores that
			// the user has closed within this session). After a full reload
			// the stores reappear in the hierarchy, so they pass the filter.
			var hierarchyStore = container.getHierarchyStore();
			if (hierarchyStore) {
				var closedStoreRecords = [];
				Zarafa.addressbook.AddressBookHierarchyStore.each(function(record) {
					var storeEntryId = record.get('store_entryid');
					if (record.get('type') === 'sharedcontacts' &&
						record.get('depth') > 0 && storeEntryId) {
						var storeFound = false;
						hierarchyStore.each(function(mapiStore) {
							if (Zarafa.core.EntryId.compareEntryIds(
								mapiStore.get('store_entryid'), storeEntryId)) {
								storeFound = true;
								return false;
							}
						});
						if (!storeFound) {
							closedStoreRecords.push(record);
						}
					}
				});
				Ext.each(closedStoreRecords, function(record) {
					Zarafa.addressbook.AddressBookHierarchyStore.remove(record);
				});
			}

			// Add a property to identify group headers and remove group headers that don't
			// have any group members (e.g. All Address Lists)
			var removeRecords = [];
			var storeCount = Zarafa.addressbook.AddressBookHierarchyStore.getCount();
			Zarafa.addressbook.AddressBookHierarchyStore.each(function(record, index){
				// The GAB has index 0
				if ( index>0 && record.get('depth')===0 ){
					if (
						index === storeCount - 1 ||
						Zarafa.addressbook.AddressBookHierarchyStore.getAt(index+1).get('depth') === 0
					){
						removeRecords.push(record);
					} else {
						record.set('group_header', true);
					}
				} else {
					record.set('group_header', false);
				}
			});

			Ext.each(removeRecords, function(record){
				Zarafa.addressbook.AddressBookHierarchyStore.remove(record);
			});
		});

		Zarafa.addressbook.AddressBookHierarchyStore.loadAddressBookHierarchy();
	}
});
Ext.namespace('Zarafa.addressbook');

/**
 * @class Zarafa.addressbook.AddressBookResponseHandler
 * @extends Zarafa.core.data.IPMResponseHandler
 *
 * Small extension to the main {@link Zarafa.core.data.IPMResponseHandler IPMResponseHandler},
 * to add support for the {@link #doHierarchy 'hierarchy'} action.
 */
Zarafa.addressbook.AddressBookResponseHandler = Ext.extend(Zarafa.core.data.IPMResponseHandler, {
	/**
	 * Handles the 'list' response. Gathers the records from the response data,
	 * converts each entry into a record (using {@link #readRecordsFromResponse}
	 * and pushes them into the {@link #receivedRecords records list}.
	 * @param {Object} data The response object belonging to the given command.
	 * @return {Boolean} False when action could not be handled successfully. This will
	 * not cancel the transaction itself, but rather causes the 'success' argument for the
	 */
	doList: function(response)
	{
		this.receivedRecords = this.readRecordsFromResponse(response, 'item');
	}
});
Ext.namespace('Zarafa.addressbook');

/**
 * @class Zarafa.addressbook.AddressBookStore
 * @extends Zarafa.core.data.ListModuleStore
 * @xtype zarafa.addressbookstore
 *
 * this will contain all records fetched from the server side code
 */
Zarafa.addressbook.AddressBookStore = Ext.extend(Zarafa.core.data.ListModuleStore, {
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

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

		Ext.applyIf(config, {
			preferredMessageClass: 'addressbook',
			standalone: true,
			sortInfo: {
				field: 'full_name',
				direction: 'asc'
			},
			reader: new Zarafa.core.data.JsonReader({}, recordType)
		});

		Zarafa.addressbook.AddressBookStore.superclass.constructor.call(this, config);
	},

	/**
	 * Compare a {@link Ext.data.Record#id ids} to determine if they are equal.
	 * This will apply the {@link Zarafa.core.EntryId#compareABEntryIds compareABEntryIds} function
	 * on both ids, as all records in this store will have a Address Book EntryId as unique key.
	 * @param {String} a The first id to compare
	 * @param {String} b The second id to compare
	 * @protected
	 */
	idComparison: function(a, b)
	{
		return Zarafa.core.EntryId.compareABEntryIds(a, b);
	},

	/**
	 * For contacts with multiple email address, we show create multiple records for a single contact
	 * to show multiple entries with different email address for a single contact. But the entryid will
	 * be same for all these contacts so we can't use it as uniqueid, so we create uniqueid by appending
	 * email_index after entryid.
	 * @param {Ext.data.Record} o The record for which the key is requested
	 * @return {String} The key by which the record must be saved into the {@link Ext.util.MixedCollection}.
	 * @protected
	 */
	getRecordKey: function(o)
	{
		if (o.get('email_index') && o.get('email_index') !== -1) {
			return o.id + '_' + o.get('email_index');
		} else {
			return o.id;
		}
	}
});

Ext.reg('zarafa.addressbookstore', Zarafa.addressbook.AddressBookStore);
Ext.namespace('Zarafa.addressbook.ui');

/**
 * @class Zarafa.addressbook.ui.AddressBookBoxField
 * @extends Zarafa.common.recipientfield.ui.RecipientField
 * @xtype zarafa.addressbookboxfield
 *
 * Special {@link Zarafa.common.ui.BoxField boxfield} which is used
 * for displaying AddressBook records. This works similar to the
 * {@link Zarafa.common.recipientfield.ui.RecipientField} regarding
 * resolving, but it will mark any non-AddressBook user as invalid.
 *
 * If the {@link Zarafa.core.plugins.RecordComponentUpdaterPlugin} is installed
 * in the {@link #plugins} array of this component, this component will automatically
 * load the {@link Zarafa.core.data.IPMRecipientStore RecipientStore} into the component.
 * Otherwise the user of this component needs to call {@link #setRecipientStore}.
 */
Zarafa.addressbook.ui.AddressBookBoxField = Ext.extend(Zarafa.common.recipientfield.ui.RecipientField, {
	/**
	 * @cfg {Zarafa.core.mapi.RecipientType} defaultRecipientType
	 * @hide
	 */

	/**
	 * @cfg {Zarafa.core.mapi.RecipientType} filterRecipientType
	 * @hide
	 */

	/**
	 * @cfg {String} delimiterCharacter
	 * @hide
	 */

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

		Ext.applyIf(config, {
			boxType: 'zarafa.addressbookbox',
			enableComboBox: false,
			editable: true,
			height: 24
		});

		Zarafa.addressbook.ui.AddressBookBoxField.superclass.constructor.call(this, config);
	}

});

Ext.reg('zarafa.addressbookboxfield', Zarafa.addressbook.ui.AddressBookBoxField);
Ext.namespace('Zarafa.advancesearch');

/**
 * @class Zarafa.advancesearch.AdvanceSearchContext
 * @extends Zarafa.core.Context
 */
Zarafa.advancesearch.AdvanceSearchContext = Ext.extend(Zarafa.core.Context, {
	/**
	 * @constructor
	 * @param config
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			hasToolbar: false,
			hasContentPanel: false,
			current_view: Zarafa.common.data.Views.LIST,
			current_view_mode: Zarafa.common.data.ViewModes.RIGHT_PREVIEW
		});

		Zarafa.advancesearch.AdvanceSearchContext.superclass.constructor.call(this, config);

		Zarafa.core.data.SharedComponentType.addProperty('search.dialog.selectfolder');
		Zarafa.core.data.SharedComponentType.addProperty('search.dialog.searchcategory');
	},

	/**
	 * Returns the buttons for the dropdown list of the Print button in the main toolbar.
	 *
	 * @return {Ext.Component[]} an array of components
	 */
	getMainToolbarPrintButtons: function()
	{
		var defaultItems = [{
			xtype: 'zarafa.conditionalitem',
			id: 'zarafa-maintoolbar-print-singleitem',
			overflowText: _('Print single item'),
			iconCls: 'icon_print',
			tooltip: _('Print selected item') + Zarafa.core.KeyMapMgr.formatShortcutHint('Ctrl + P', true),
			plugins: 'zarafa.menuitemtooltipplugin',
			text: _('Print single item'),
			hideOnDisabled: false,
			singleSelectOnly: true,
			handler: this.onPrintSelected,
			scope: this
		}];

		return defaultItems;
	},

	/**
	 * Event handler which is fired when the 'print single' item in the dropdown has been pressed
	 * This calls {@link Zarafa.common.Actions.openPrintDialog} with the previewed {@link Zarafa.core.data.MAPIRecord} record.
	 *
	 * @param {Object} button The button which user pressed.
	 * @param {Ext.EventObject} evt The mouse event
	 * @private
	 */
	onPrintSelected: function (button, evt)
	{
		var records = this.getModel().getSelectedRecords();
		if (Ext.isEmpty(records)) {
			Ext.MessageBox.alert(_('Print'), _('No item selected'));
			return;
		}

		Zarafa.common.Actions.openPrintDialog(records);
	},

	/**
	 * Returns the buttons for the dropdown list of the VIEW-button in the main toolbar. It will use the
	 * main.maintoolbar.view.advancesearch insertion point to allow other plugins to add their items at the end.
	 *
	 * @return {Ext.Component[]} an array of components
	 */
	getMainToolbarViewButtons: function(){
		var items = container.populateInsertionPoint('main.maintoolbar.view.advancesearch') || [];

		var defaultItems = [{
			id: 'zarafa-maintoolbar-view-advancesearch-nopreview',
			overflowText: _('No preview'),
			iconCls: 'icon_previewpanel_off',
			text: _('No preview'),
			valueView: Zarafa.common.data.Views.LIST,
			valueViewMode: Zarafa.common.data.ViewModes.NO_PREVIEW,
			valueDataMode: Zarafa.common.data.DataModes.ALL,
			handler: this.onContextSelectView,
			scope: this
		},{
			id: 'zarafa-maintoolbar-view-advancesearch-previewright',
			overflowText: _('Right preview'),
			iconCls: 'icon_previewpanel_right',
			text: _('Right preview'),
			valueView: Zarafa.common.data.Views.LIST,
			valueViewMode: Zarafa.common.data.ViewModes.RIGHT_PREVIEW,
			valueDataMode: Zarafa.common.data.DataModes.ALL,
			handler: this.onContextSelectView,
			scope: this
		},{
			id: 'zarafa-maintoolbar-view-advancesearch-previewbottom',
			overflowText: _('Bottom preview'),
			iconCls: 'icon_previewpanel_bottom',
			text: _('Bottom preview'),
			valueView: Zarafa.common.data.Views.LIST,
			valueViewMode: Zarafa.common.data.ViewModes.BOTTOM_PREVIEW,
			valueDataMode: Zarafa.common.data.DataModes.ALL,
			handler: this.onContextSelectView,
			scope: this
		}];

		return defaultItems.concat(items);
	},

	/**
	 * Event handler which is fired when one of the View buttons
	 * has been pressed. This will call {@link #setView setView}
	 * to update the view.
	 * @param {Ext.Button} button The button which was pressed
	 * @private
	 */
	onContextSelectView: function(button)
	{
		this.getModel().setDataMode(button.valueDataMode);
		this.switchView(button.valueView, button.valueViewMode);
	},

	/**
	 * Function was used to get the {@link Zarafa.advancesearch.AdvanceSearchContextModel advance search model}.
	 * @param {Object} config the configuration object which contains the parent context model.
	 * @return {Zarafa.advancesearch.AdvanceSearchContextModel} the advance search context model
	 */
	getModel: function(config)
	{
		if (!Ext.isDefined(this.model)) {
			this.model = new Zarafa.advancesearch.AdvanceSearchContextModel();
		}
		return this.model;
	},

	/**
	 * Bid for the type of shared component and the given record.
	 * This will bid on a dialog.crate or dialog.view for a record with a
	 * message class set to IPM or IPM.Note.
	 * @param {Zarafa.core.data.SharedComponentType} type Type of component a context can bid for.
	 * @param {Ext.data.Record} record Optionally passed record.
	 * @return {Number} The bid for the shared component
	 */
	bidSharedComponent: function(type, record)
	{
		var bid = -1;

		switch (type) {
			case Zarafa.core.data.SharedComponentType['common.search']:
				bid = 1;
				break;
			case Zarafa.core.data.SharedComponentType['search.dialog.selectfolder']:
				bid = 1;
				break;
			case Zarafa.core.data.SharedComponentType['common.create']:
				if(record.get('folder_type') === Zarafa.core.mapi.MAPIFolderType.FOLDER_SEARCH) {
					bid = 1;
				}
				break;
			case Zarafa.core.data.SharedComponentType['common.contextmenu']:
				if (record instanceof Zarafa.core.data.MAPIRecord) {
					if (record.get('object_type') == Zarafa.core.mapi.ObjectType.MAPI_FOLDER && record.isSearchFolder()) {
						bid = 1;
					}
				}
				break;
			case Zarafa.core.data.SharedComponentType['search.dialog.searchcategory']:
				bid = 1;
				break;
		}
		return bid;
	},

	/**
	 * Will return the reference to the shared component.
	 * Based on the type of component requested a component is returned.
	 * @param {Zarafa.core.data.SharedComponentType} type Type of component a context can bid for.
	 * @param {Ext.data.Record} record Optionally passed record.
	 * @return {Ext.Component} Component
	 */
	getSharedComponent: function(type, record)
	{
		var component;
		switch (type) {
			case Zarafa.core.data.SharedComponentType['common.search']:
				component = Zarafa.advancesearch.dialogs.SearchContentPanel;
				break;
			case Zarafa.core.data.SharedComponentType['search.dialog.selectfolder']:
				component = Zarafa.advancesearch.dialogs.SelectFolderContentPanel;
				break;
			case Zarafa.core.data.SharedComponentType['common.create']:
				if(record.get('folder_type') === Zarafa.core.mapi.MAPIFolderType.FOLDER_SEARCH) {
					component = Zarafa.advancesearch.dialogs.CreateSearchFolderContentPanel ;
				}
				break;
			case Zarafa.core.data.SharedComponentType['common.contextmenu']:
				if (record instanceof Zarafa.core.data.MAPIRecord) {
					if (record.get('object_type') == Zarafa.core.mapi.ObjectType.MAPI_FOLDER && record.isSearchFolder()) {
						component = Zarafa.advancesearch.ui.SearchFolderContextMenu;
					}
				}
				break;
			case Zarafa.core.data.SharedComponentType['search.dialog.searchcategory']:
				component = Zarafa.advancesearch.dialogs.SearchCategoriesContentPanel;
				break;
		}
		return component;
	}

});

Zarafa.onReady(function() {
	container.registerContext(new Zarafa.core.ContextMetaData({
		name: 'advancesearch',
		allowUserVisible: false,
		pluginConstructor: Zarafa.advancesearch.AdvanceSearchContext
	}));
});
Ext.namespace('Zarafa.advancesearch');

/**
 * @class Zarafa.advancesearch.AdvanceSearchStore
 * @extends Zarafa.core.data.ListModuleStore
 * @xtype zarafa.advancesearchstore
 *
 * The AdvabceSearchStore class provides a way to connect the 'advancedsearchlistmodule' in the server back-end to an
 * Ext.grid.GridPanel object. It provides a means to retrieve search returl list asynchronously.
 * The advance search store object, once instantiated, will be able to retrieve and list search result from a
 * search folder only.
 */
Zarafa.advancesearch.AdvanceSearchStore = Ext.extend(Zarafa.core.data.ListModuleStore, {
	/**
	 * Read-only. entryid of the search folder.
	 * @property
	 * @type HexString
	 */
	searchFolderEntryId: undefined,

	/**
	 * Read-only. store entryId where search folder is belongs.
	 * @property
	 * @type HexString
	 */
	searchStoreEntryId: undefined,

	/**
	 * searchStoreUniqueId is represent the unique id of {@link Zarafa.advancesearch.AdvanceSearchStore AdvanceSearchStore}.
	 * searchStoreUniqueId and {@link Zarafa.advancesearch.dialogs.SearchContentPanel SearchContentPanel} name
	 * are similar, so we can easily map or manage the {@link Zarafa.advancesearch.AdvanceSearchStore AdvanceSearchStore}.
	 * @type Mixed
	 * @private
	 */
	searchStoreUniqueId: undefined,

	/**
	 * True if the model is currently busy searching. This is updated during
	 * {@link #startSearch} and {@link #stopSearch} and can be checked using
	 * {@link #isSearching}.
	 * @property
	 * @type Boolean
	 * @private
	 */
	isBusySearching: false,

	/**
	 * The search suggestion if one was given by the search engine.
	 * @property
	 * @type String
	 */
	suggestion: '',

	/**
	 *
	 */
	searchFolder: {},

	/**
	 * @constructor
	 * @param {Object} config configuration params that should be used to create instance of this store.
	 */
	constructor: function(config)
	{
		config = config || {};

		// Apply default settings.
		Ext.applyIf(config, {
			preferredMessageClass: 'IPM.Search'
		});

		Zarafa.advancesearch.AdvanceSearchStore.superclass.constructor.call(this, config);

		// Fold search results that belong to the same conversation into one
		// row (see #dedupeConversations). Rebuilt whenever the result set
		// changes.
		this.dedupeTask = new Ext.util.DelayedTask(this.dedupeConversations, this);
		this.on('load', this.dedupeConversations, this);
		this.on('add', this.scheduleDedupe, this);
		this.on('remove', this.scheduleDedupe, this);
	},

	/**
	 * Schedules a (buffered) {@link #dedupeConversations} run.
	 * @private
	 */
	scheduleDedupe: function()
	{
		this.dedupeTask.delay(10);
	},

	/**
	 * Returns the number of loaded records. The conversation dedupe hides
	 * records through a filter, so the (visible) count must not be used for
	 * live scroll paging: it would never reach the total row count and every
	 * scroll would keep requesting the same page.
	 *
	 * @return {Number} The number of loaded records.
	 */
	getStoreLength: function()
	{
		return (this.snapshot || this.data).getCount();
	},

	/**
	 * Folds search results that belong to the same conversation into a single
	 * row: the first (most relevant) hit stays visible, carries the number of
	 * hits and all messages of the conversation (newest first) for the
	 * conversation preview, and the other hits are hidden. The relevance
	 * ordering of the result list is unaffected.
	 * @private
	 */
	dedupeConversations: function()
	{
		if (container.isEnabledConversation() === false) {
			return;
		}

		var collection = this.snapshot || this.data;
		var groups = {};
		collection.each(function(record) {
			if (!Ext.isFunction(record.isMessageClass) || !record.isMessageClass('IPM.Note', true)) {
				return;
			}
			var conversationId = record.get('conversation_id');
			if (Ext.isEmpty(conversationId)) {
				return;
			}
			(groups[conversationId] = groups[conversationId] || []).push(record);
		});

		var recordTime = function(record) {
			var date = record.get('message_delivery_time') || record.get('client_submit_time');
			return Ext.isDate(date) ? date.getTime() : 0;
		};

		var hidden = {};
		var hasHidden = false;
		Ext.iterate(groups, function(conversationId, members) {
			var primary = members[0];
			if (members.length > 1) {
				primary.searchConversationRecords = members.slice().sort(function(a, b) {
					return recordTime(b) - recordTime(a);
				});
				primary.searchConversationCount = members.length;
				Ext.each(members, function(member) {
					if (member !== primary) {
						hidden[member.id] = true;
						hasHidden = true;
					}
				});
			} else {
				primary.searchConversationRecords = undefined;
				primary.searchConversationCount = 0;
			}
		});

		// Only touch the filter when the hidden set actually changed: filterBy
		// triggers a full grid refresh, and dedupe runs after every load.
		var signature = Object.keys(hidden).sort().join(',');
		if (hasHidden) {
			if (signature !== this.conversationDedupeSignature) {
				this.conversationDedupeSignature = signature;
				this.conversationDedupeActive = true;
				this.filterBy(function(record) {
					return hidden[record.id] !== true;
				});
			}
		} else if (this.conversationDedupeActive === true) {
			this.conversationDedupeActive = false;
			this.conversationDedupeSignature = undefined;
			this.clearFilter();
		}
	},

	/**
	 * Initialize all events which this {#Zarafa.advancesearch.AdvanceSearchStore AdvanceSearchStore} will listen to.
	 * @private
	 */
	initEvents: function()
	{
		this.on('load', this.onLoad, this);
	},

	/**
	 * Getter function which used to get the {@link #searchStoreUniqueId}.
	 * @return {String} return {@link #searchStoreUniqueId}
	 */
	getSearchStoreUniqueId: function()
	{
		return this.searchStoreUniqueId;
	},

	/**
	 * Function is used as a callback for 'read' action. It is overridden to be able to
	 * store the search suggestion in the AdvanceSearchStore if one is given.
	 * @param {Object} options options that are passed through {@link #load} event.
	 * @param {Boolean} success success status of request.
	 * @param {Object} metaData extra information that is received with response data.
	 */
	loadRecords: function(data, options, success, metaData)
	{
		// Only update the suggestion for the search action, not for the updatesearch action
		// because the suggestion is not send with that response
		if ( options.actionType === 'search' ){
			if ( success!==false && Ext.isObject(metaData) && Ext.isObject(metaData.search_meta) ) {
				this.suggestion = metaData.search_meta.suggestion;
			} else {
				this.suggestion = '';
			}
		}

		// Guard against undefined data from server errors
		if (!data || !data.records) {
			return;
		}

		Zarafa.advancesearch.AdvanceSearchStore.superclass.loadRecords.apply(this, arguments);
	},

	/**
	 * Event handler for the load event of this {#Zarafa.advancesearch.AdvanceSearchStore}
	 * @param {Zarafa.advancesearch.AdvanceSearchStore} store This store
	 * @param {Zarafa.core.data.IPMRecord[]} records loaded record set
	 * @param {Object} options the options (parameters) with which the load was invoked.
	 * @private
	 */
	onLoad: function(store, records, options)
	{
		// Set the searchdate field for each record, so Ext can use it for local sorting
		Ext.each(records, function(record){
			// update the record in the store, bypass setting dirty flag,
			// and do not store the change in the modified records
			record.data['searchdate'] = record.get('message_delivery_time') || record.get('last_modification_time');
			record.commit();
		});
	},

	/**
	 * Function will set entryid of search folder.
	 * @param {HexString} searchFolderEntryId entry id of search folder.
	 */
	setSearchEntryId: function(searchFolderEntryId)
	{
		this.searchFolderEntryId = searchFolderEntryId;
	},

	/**
	 * Function will set store entryid where search folder is belongs.
	 *
	 * @param {HexString} searchStoreEntryId entryId of store where search folder is belongs.
	 */
	setSearchStoreEntryId: function(searchStoreEntryId)
	{
		this.searchStoreEntryId = searchStoreEntryId;
	},

	/**
	 * Function will be used to issue a search request to server to start searching,
	 * this will internally call {@link #load} method but with some different options also it will
	 * cancel existing {@link Zarafa.core.Actions#updatesearch} or {@link Zarafa.core.Actions#search}
	 * request and send new {@link Zarafa.core.Actions#search} request with updated search restriction.
	 *
	 * @param {Object} options options object that must contain restriction to apply for search.
	 */
	search: function(options)
	{
		// When doing a new search we will have sorting done by the backend
		this.remoteSort = true;
		// Also remove the sortInfo because the backend will do sorting on relevance and not on a field
		delete this.sortInfo;

		if (this.isExecuting(Zarafa.core.Actions['updatesearch']) || this.isExecuting(Zarafa.core.Actions['search'])) {
			this.proxy.cancelRequests(Zarafa.core.Actions['updatesearch']);
			this.proxy.cancelRequests(Zarafa.core.Actions['search']);
		}

		this.hasSearchResults = false;

		/**
		 * userSearchFolder is only true if folder is not Public folder, shred folder or Favorite folder.
		 * These are the folders don't support search folder so we have to set search entryid to undefined.
		 */
		if(!options.useSearchFolder) {
			this.setSearchEntryId(undefined);
		}

		Zarafa.advancesearch.AdvanceSearchStore.superclass.search.apply(this, arguments);
	}
});

Ext.reg('zarafa.advancesearchstore', Zarafa.advancesearch.AdvanceSearchStore);
Ext.namespace('Zarafa.calendar');

/**
 * @class Zarafa.calendar.AppointmentStore
 * @extends Zarafa.core.data.ListModuleStore
 * @xtype zarafa.appointmentstore
 *
 * The AppointmentStore class provides a way to connect to the 'appointmentlistmodule' in the server back-end to an
 * Ext.grid.GridPanel object. It provides a means to retrieve appointment listings asynchronously.
 * The store has to be initialised with a store Id, which corresponds (somewhat confusingly) to
 * a MAPI store id. The AppointmentStore object, once instantiated, will be able to retrieve and list
 * appointments from a single specific store only.
 */
Zarafa.calendar.AppointmentStore = Ext.extend(Zarafa.core.data.ListModuleStore, {
	/**
	 * @constructor
	 * @param {Object} config configuration params that should be used to create instance of this store.
	 */
	constructor: function(config)
	{
		config = config || {};

		// Apply default settings.
		Ext.applyIf(config, {
			preferredMessageClass: 'IPM.Appointment',
			defaultSortInfo: {
				field: 'startdate',
				direction: 'desc'
			}
		});

		Zarafa.calendar.AppointmentStore.superclass.constructor.call(this, config);
		this.on('update', this.onUpdate, this);
	},

	/**
	 * With recurring appointments, each occurrence has the same entryid.
	 * For those cases, we cannot use the entryid as unique property, instead
	 * we combine it with the basedate.
	 * @param {Ext.data.Record} record The record for which the key is requested
	 * @return {String} The key by which the record must be saved into the {@link Ext.util.MixedCollection}.
	 * @protected
	 */
	getRecordKey: function(record)
	{
		// Check isRecurringOccurrence exists because the user could have moved a non-appointment message to
		// the calendar
		if ( Ext.isDefined(record.isRecurringOccurrence) && record.isRecurringOccurrence()) {
			return record.id + '' + record.get('basedate');
		} else {
			return record.id;
		}
	},

	/**
	 * Event handler fired by the {@link Ext.data.Store} when a record is being removed
	 * from the store. This adds a special case to the default behavior of the {@link Ext.data.Store#destroyRecord}
	 * especially for recurring appointments. When the appointment is recurring, and no basedate is provided, we
	 * manually fire a {@link Ext.data.Store#remove} event for each occurrence which is currently loaded. This will
	 * ensure that the UI can remove every occurrence from the UI while we send a single record to the server to
	 * remove the entire series from the server.
	 *
	 * @param {Ext.data.Store} store The store from where the record is removed
	 * @param {Ext.data.Record} record The record record which was removed
	 * @param {Number} index The index of the record where the record was removed
	 * @private
	 */
	destroyRecord: function(store, record, index)
	{
		// Don't remove a record which is already removed
		if (this.removed.indexOf(record) > -1) {
			return;
		}

		// Special case for deleting recurrences
		if (record.isRecurring && record.isRecurring()) {
			// Search for all occurrences which belong to this recurrence.
			// Note that 'record' is already removed from the store,
			// so we don't risk of adding it again into the array.
			var deleteOccurrences = [];
			this.each(function(r) {
				if (r.id === record.id) {
					deleteOccurrences.push(r);
				}
			});

			// Now remove every occurrence from the store, before removing we
			// push it into the 'removed' array to make sure that when we arrive
			// inside this function again, the first if-statement will return.
			for (var i = 0, len = deleteOccurrences.length; i < len; i++) {
				var occur = deleteOccurrences[i];
				this.removed.push(occur);
				this.remove(occur);
				this.removed.remove(occur);
			}

			// All occurrences have been deleted. Continue as usual...
		}

		Zarafa.calendar.AppointmentStore.superclass.destroyRecord.call(this, store, record, index);
	},

	/**
	 * Filter a list of {@link Zarafa.core.data.IPMRecord records} by checking if the record
	 * belongs to this Store. This comparison is based on checking if the entryid of the given
	 * records match the entryid of the records inside the store. If the records are being
	 * {@link Zarafa.core.data.JsonReader#realize realized} by the {@link Zarafa.core.data.JsonReader JsonReader}
	 * then we check if the parent_entryid matches this store.
	 *
	 * What will be returned is an object containing the records as present inside the store,
	 * and the data objects which should be applied to them (the data from the records of the
	 * store that triggered the event).
	 *
	 * @param {Zarafa.core.data.IPMRecord|Array} records The record or records to filter
	 * @param {Ext.data.Api.actions} action The API action for which the updates are looked for.
	 * @return {Object} Object containing the key 'records' containing the array of records inside
	 * this store, and the key 'updatedRecords' containing the array of records which should be
	 * applied to those records.
	 * @private
	 */
	getRecordsForUpdateData: function(records, action)
	{
		if (!Ext.isDefined(records) || action === Ext.data.Api.actions.create) {
			return Zarafa.calendar.AppointmentStore.superclass.getRecordsForUpdateData.apply(this, arguments);
		}

		var results = { records: [],  updatedRecords: [] };

		if (!Array.isArray(records)) {
			records = [ records ];
		}

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

			if (!record.isMessageClass('IPM.Appointment') && !record.isMessageClass('IPM.OLE.CLASS.{00061055-0000-0000-C000-000000000046}')) {
				continue;
			} else if (record.isRecurringOccurrence()) {
				// The appointment is an occurrence, this means we are
				// updating an exception. Find the exact occurrence which
				// was changed.
				var index = this.findBy(function(rec) {
					return Zarafa.core.EntryId.compareEntryIds(record.get('entryid'), rec.get('entryid')) &&
						   Ext.isDate(record.get('basedate')) && Ext.isDate(rec.get('basedate')) &&
						   (record.get('basedate').getTime() === rec.get('basedate').getTime());
				});

				if (index >= 0) {
					// The update is for an occurrence, make sure we add
					// the 'basedate' as identifier.
					results.records.push(this.getAt(index));
					results.updatedRecords.push(record);
				}

			} else if (record.isRecurring()) {
				// if the record is a series, we must search for
				// the occurrences (except for exceptions).
				var index = -1;
				do {
					index = this.findBy(function(rec) {
						return ((action === Ext.data.Api.actions.destroy) || !rec.isRecurringException()) &&
							   Zarafa.core.EntryId.compareEntryIds(record.get('entryid'), rec.get('entryid'));
					}, undefined, index + 1);

					if (index >= 0) {
						var update = this.getAt(index);
						var clonedRec = record.copy();

						// The 'basedate' property must not be altered by the series,
						// as this value will the different for each occurrence.
						var basedate = update.get('basedate');
						if (Ext.isDate(basedate)) {
							clonedRec.set('basedate', basedate.clone());
						}
						// The 'recurrence' property must not be altered by the series,
						// as occurrences have a different value for this property.
						clonedRec.set('recurrence', update.get('recurrence'));

						// Update time and duration properties correctly according to the series information.
						clonedRec.set('startdate', update.get('startdate').clearTime(true).add(Date.MINUTE, record.get('recurrence_startocc')));
						clonedRec.set('duedate', update.get('startdate').clearTime(true).add(Date.MINUTE, record.get('recurrence_endocc')));
						clonedRec.set('commonstart', clonedRec.get('startdate').clone());
						clonedRec.set('commonend', clonedRec.get('duedate').clone());
						clonedRec.set('duration', (clonedRec.get('duedate') - clonedRec.get('startdate')) / (60 * 1000));

						results.records.push(update);
						results.updatedRecords.push(clonedRec);
					}
				} while(index != -1);
			} else {
				// For non-recurring appointments we should simply look
				// for the appointment where the entyid matches.
				var storeRec = this.getById(record.get('entryid'));

				if (storeRec) {
					results.records.push(storeRec);
					results.updatedRecords.push(record);
				} else {
					// Record not found in store, reload to pick up
					// newly visible appointments (e.g. moved into view).
					this.reload();
				}
			}
		}

		return results;
	},

	/**
	 * Notification handler called by {@link #onNotify} when
	 * a {@link Zarafa.core.data.Notifications#objectDeleted objectDeleted}
	 * notification has been received. This will remove the
	 * {@link Zarafa.calendar.AppointmentRecord appointment} Or  {@link Zarafa.calendar.MeetingRequestRecord meeting}from the
	 * {@link Zarafa.calendar.AppointmentStore store} and after removing store gets reload.
	 *
	 * @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 received 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 received.
	 * @private
	 */
	onNotifyObjectdeleted: function(action, records, data, timestamp, success)
	{
		Zarafa.calendar.AppointmentStore.superclass.onNotifyObjectdeleted.apply(this, arguments);
	},

	/**
	 * Event handler triggered when store was updated also it should open the copied meeting record
	 * into tab for further adding.
	 *
	 * @param {Zarafa.calendar.AppointmentStore} store which gets updated.
	 * @param {Zarafa.calendar.AppointmentRecord} record which is created in store.
	 * @param {string} operation write Action that occurred. Can be one of
	 * {@link Ext.data.Record.EDIT EDIT}, {@link Ext.data.Record.REJECT REJECT} or
	 * {@link Ext.data.Record.COMMIT COMMIT}
	 */
	onUpdate: function (store, record, operation)
	{
		if(operation === Ext.data.Record.COMMIT && record.getActionResponse('resources_pasted') && record.isMeeting()) {
			Zarafa.core.data.UIFactory.openViewRecord(record);
		}
	}
});

Ext.reg('zarafa.appointmentstore', Zarafa.calendar.AppointmentStore);
Ext.namespace('Zarafa.calendar');

/**
 * @class Zarafa.calendar.CalendarContext
 * @extends Zarafa.core.Context
 *
 * Calendar context. This provides a tool bar and specialised tree view with selectable appointment folders. The folder hierarchy
 * can show either a full list of folders, or a list of appointment folders (when the user clicks the calendar icon at the bottom
 * of the folder hierarchy).
 */
Zarafa.calendar.CalendarContext = Ext.extend(Zarafa.core.Context, {
	// Insertion points for this class
	/**
	 * @insert main.maintoolbar.view.calendar
	 * Insertion point for populating the main toolbar with a View button. This item is only visible
	 * when this context is active.
	 * @param {Zarafa.mail.CalendarContext} context This context
	 */

	/**
	 * The currently active zoom level for the calendar.
	 * @property
	 * @type Number
	 */
	default_zoom_level: undefined,

	/**
	 * When searching, this property marks the {@link Zarafa.core.Context#getCurrentView view}
	 * which was used before {@link #onSearchStart searching started} the view was switched to
	 * {@link Zarafa.calendar.data.Views#SEARCH}.
	 * @property
	 * @type Mixed
	 * @private
	 */
	oldView: undefined,

	/**
	 * When searching, this property marks the {@link Zarafa.core.Context#getCurrentViewMode viewmode}
	 * which was used before {@link #onSearchStart searching started} the viewmode was switched to
	 * {@link Zarafa.calendar.data.ViewModes#SEARCH}.
	 * @property
	 * @type Mixed
	 * @private
	 */
	oldViewMode: undefined,

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

		Ext.applyIf(config, {
			groupViewBtns: false,
			current_view: Zarafa.calendar.data.Views.BLOCKS,
			current_view_mode: Zarafa.calendar.data.ViewModes.DAYS
		});

		// The "New appointment" button which is available in all contexts
		this.registerInsertionPoint('main.maintoolbar.new.item', this.createNewAppointmentButton, this);
		// The "New meeting request" button which is available in all contexts
		this.registerInsertionPoint('main.maintoolbar.new.item', this.createNewMeetingRequestButton, this);

		// The tab in the top tabbar
		this.registerInsertionPoint('main.maintabbar.left', this.createMainTab, this);

		// Add a date picker control to the navigation panel.
		this.registerInsertionPoint('navigation.north', this.createDatePicker, this);

		// Add a tree control showing a list of calendar folders to the navigation panel.
		// The control will be shown when the user selects the calendar context from the button panel.
		this.registerInsertionPoint('navigation.center', this.createCalendarNavigationPanel, this);

		// Adds convert mail to appointment contextmenu item in the mail contextmenu.
		this.registerInsertionPoint('context.mail.contextmenu.topoptions', this.convertToAppointment, this);

		// Register the calendar category for the settings
		this.registerInsertionPoint('context.settings.categories', this.createSettingCategories, this);

		this.registerInsertionPoint('previewpanel.toolbar.left', this.getMeetingRequestToolbarButtons, this);
		this.registerInsertionPoint('previewpanel.toolbar.detaillinks', this.getMeetingRequestDetailLinks, this);

		// Add meeting request related button in the showmeeting ContentPanel
		this.registerInsertionPoint('context.mail.showmailcontentpanel.toolbar.actions.first', this.getMeetingRequestToolbarButtons, this);

		this.addEvents([
			/**
			 * @event zoomchange
			 * Fired when the zoom level has changed, some views support a zoom level being configured.
			 * @param {Zarafa.calendar.CalendarContext} context The context which fired the event
			 * @param {Number} zoomLevel The new zoomLevel.
			 * @param {Number} oldZoomLevel The previously configured zoomlevel.
			 */
			'zoomchange'
		]);

		Zarafa.calendar.CalendarContext.superclass.constructor.call(this, config);

		// Register calendar specific dialog types
		Zarafa.core.data.SharedComponentType.addProperty('calendar.dialogs.proposenewtimecontentpanel');
		Zarafa.core.data.SharedComponentType.addProperty('calendar.dialogs.sendmeetingrequestconfirmation');
		Zarafa.core.data.SharedComponentType.addProperty('calendar.dialogs.sendmeetingrequestcancellation');
		Zarafa.core.data.SharedComponentType.addProperty('calendar.dialog.options');
	},

	/**
	 * Called before the context is switched in.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder MAPI folder to show.
	 * @param {Boolean} suspended True to enable the ContextModel {@link Zarafa.core.ContextModel#suspendLoading suspended}
	 */
	enable: function(folder, suspended)
	{
		this.default_zoom_level = container.getSettingsModel().get('zarafa/v1/contexts/calendar/default_zoom_level');

		Zarafa.calendar.CalendarContext.superclass.enable.apply(this, arguments);
	},

	/**
	 * @return Zarafa.calendar.CalendarContextModel the calendar context model
	 */
	getModel: function()
	{
		if (!Ext.isDefined(this.model)) {
			this.model = new Zarafa.calendar.CalendarContextModel();
			this.model.on({
				'searchstart': this.onModelSearchStart,
				'searchstop': this.onModelSearchStop,
				scope: this
			});
		}
		return this.model;
	},

	/**
	 * Event handler for the {@link #model}#{@link Zarafa.core.ContextModel#searchstart searchstart} event.
	 * This will {@link #switchView switch the view} to {@link Zarafa.calendar.data.Views#SEARCH search mode}.
	 * The previously active {@link #getCurrentView view} will be stored in the {@link #oldView} and will
	 * be recovered when the {@link #onModelSearchStop search is stopped}.
	 * @param {Zarafa.core.ContextModel} model The model which fired the event
	 * @private
	 */
	onModelSearchStart: function(model)
	{
		if(this.getCurrentView() != Zarafa.calendar.data.Views.SEARCH && this.getCurrentViewMode() != Zarafa.calendar.data.ViewModes.SEARCH){
			this.oldView = this.getCurrentView();
			this.oldViewMode = this.getCurrentViewMode();
			this.switchView(Zarafa.calendar.data.Views.SEARCH, Zarafa.calendar.data.ViewModes.SEARCH);
		}
	},

	/**
	 * Event handler for the {@link #model}#{@link Zarafa.core.ContextModel#searchstop searchstop} event.
	 * This will {@link #switchView switch the view} to the {@link #oldView previous view}.
	 * @param {Zarafa.core.ContextModel} model The model which fired the event
	 * @private
	 */
	onModelSearchStop: function(model)
	{
		this.switchView(this.oldView, this.oldViewMode);
		delete this.oldView;
		delete this.oldViewMode;
	},

	/**
	 * Update the zoomLevel {@link Zarafa.settings.SettingsModel setting}
	 * for the currently active {@link #current_view view}.
	 * @param {Number} zoomLevel The new zoomLevel.
	 * @param {Boolean} init (optional) True when this function is called during initialization
	 * and it should force the change of the zoom level
	 */
	setZoomLevel: function(zoomLevel, init)
	{
		if (init === true || this.default_zoom_level !== zoomLevel) {
			var oldZoomLevel = this.default_zoom_level;
			this.default_zoom_level = zoomLevel;

			this.fireEvent('zoomchange', this, this.default_zoom_level, oldZoomLevel);
		}
	},

	/**
	 * Update the zoomLevel {@link Zarafa.settings.SettingsModel setting}.
	 * @return {Number} the zoom level
	 */
	getZoomLevel: function(zoomLevel)
	{
		return this.default_zoom_level;
	},

	/**
	 * Enable the calendar panel when a context switch happens and the folder is an appointment folder.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder folder to bid on.
	 * @return {Number} returns a number which is used for bidding for a folder, highest bidder will be allowed to show the contents
	 */
	bid: function(folder)
	{
		// If the folder contains items of type IPF.Appointment then the calendar context applies
		if (folder.isContainerClass('IPF.Appointment', true)) {
			return 1;
		}

		// return -1, don't know this content types
		return -1;

	},

	/**
	 * Bid for the type of shared component and the given record.
	 * @param {Zarafa.core.data.SharedComponentType} type Type of component a context can bid for.
	 * @param {Ext.data.Record} record Optionally passed record.
	 * @return {Number} The bid for the shared component
	 */
	bidSharedComponent: function(type, record)
	{
		var bid = -1;

		if (Array.isArray(record)) {
			record = record[0];
		}

		switch(type) {
			case Zarafa.core.data.SharedComponentType['common.create']:
			case Zarafa.core.data.SharedComponentType['common.view']:
			case Zarafa.core.data.SharedComponentType['common.preview']:
				if (record instanceof Zarafa.core.data.IPMRecord && record.get('object_type') == Zarafa.core.mapi.ObjectType.MAPI_MESSAGE) {
					if (record.isMessageClass([ 'IPM.Appointment', 'IPM.OLE.CLASS.{00061055-0000-0000-C000-000000000046}' ], true)) {
						bid = 1;
					} else {
						bid = -1;
					}
				}
				break;
			case Zarafa.core.data.SharedComponentType['calendar.dialogs.sendmeetingrequestconfirmation']:
			case Zarafa.core.data.SharedComponentType['calendar.dialogs.sendmeetingrequestcancellation']:
			case Zarafa.core.data.SharedComponentType['calendar.dialogs.proposenewtimecontentpanel']:
			case Zarafa.core.data.SharedComponentType['calendar.dialog.options']:
				if (record instanceof Zarafa.core.data.IPMRecord && record.get('object_type') == Zarafa.core.mapi.ObjectType.MAPI_MESSAGE) {
					if (record.isMessageClass([ 'IPM.Appointment', 'IPM.Schedule.Meeting', 'IPM.OLE.CLASS.{00061055-0000-0000-C000-000000000046}' ], true)) {
						bid = 1;
					}
				}
				break;
			case Zarafa.core.data.SharedComponentType['common.printer.renderer']:
				if (record instanceof Zarafa.core.data.IPMRecord && record.get('object_type') == Zarafa.core.mapi.ObjectType.MAPI_MESSAGE) {
					if (record.isMessageClass([ 'IPM.Appointment', 'IPM.Schedule.Meeting', 'IPM.OLE.CLASS.{00061055-0000-0000-C000-000000000046}' ], true)) {
						bid = 1;
					}
				} else if (record instanceof Zarafa.calendar.CalendarContext) {
					bid = 1;
				}
				break;
			case Zarafa.core.data.SharedComponentType['common.contextmenu']:
				if (!record) {
					bid = 1;
				} else if (record instanceof Zarafa.core.data.MessageRecord && record.isMessageClass(['IPM.Appointment', 'IPM.OLE.CLASS.{00061055-0000-0000-C000-000000000046}'], true)) {
					bid = 1;
				}
				break;
			case Zarafa.core.data.SharedComponentType['common.attachment.dialog.attachitem.columnmodel']:
				if (record instanceof Zarafa.hierarchy.data.MAPIFolderRecord) {
					if (record.isContainerClass('IPF.Appointment', true)) {
						bid = 1;
					}
				}
				break;
			case Zarafa.core.data.SharedComponentType['common.attachment.dialog.attachitem.textrenderer']:
				if (record instanceof Zarafa.core.data.IPMRecord && record.get('object_type') == Zarafa.core.mapi.ObjectType.MAPI_MESSAGE) {
					if (record.isMessageClass([ 'IPM.Appointment', 'IPM.Schedule.Meeting' ], true)) {
						bid = 1;
					}
				}
				break;
		}
		return bid;
	},

	/**
	 * Will return the reference to the shared component.
	 * Based on the type of component requested a component is returned.
	 * @param {Zarafa.core.data.SharedComponentType} type Type of component a context can bid for.
	 * @param {Ext.data.Record} record Optionally passed record.
	 * @return {Ext.Component} Component
	 */
	getSharedComponent: function(type, record)
	{
		var component;
		switch(type) {
			case Zarafa.core.data.SharedComponentType['common.create']:
			case Zarafa.core.data.SharedComponentType['common.view']:
				component = Zarafa.calendar.dialogs.AppointmentContentPanel;
				break;
			case Zarafa.core.data.SharedComponentType['common.preview']:
				component = Zarafa.calendar.ui.AppointmentPreviewPanel;
				break;
			case Zarafa.core.data.SharedComponentType['common.contextmenu']:
				component = Zarafa.calendar.ui.CalendarContextMenu;
				break;
			case Zarafa.core.data.SharedComponentType['calendar.dialogs.proposenewtimecontentpanel']:
				component = Zarafa.calendar.dialogs.ProposeNewTimeContentPanel;
				break;
			case Zarafa.core.data.SharedComponentType['calendar.dialogs.sendmeetingrequestconfirmation']:
				component = Zarafa.calendar.dialogs.SendMeetingRequestConfirmationContentPanel;
				break;
			case Zarafa.core.data.SharedComponentType['calendar.dialogs.sendmeetingrequestcancellation']:
				component = Zarafa.calendar.dialogs.SendMeetingRequestCancellationContentPanel;
				break;
			case Zarafa.core.data.SharedComponentType['calendar.dialog.options']:
				component = Zarafa.calendar.dialogs.CalendarOptionsContentPanel;
				break;
			case Zarafa.core.data.SharedComponentType['common.printer.renderer']:
				if (record instanceof Zarafa.core.data.IPMRecord) {
					component = Zarafa.calendar.printer.MeetingRenderer;
				} else {
					// see if we have a view before getModel() ?
					switch (record.getModel().getCurrentDataMode()) {
						case Zarafa.calendar.data.DataModes.WORKWEEK:
							component = Zarafa.calendar.printer.WorkWeekViewRenderer;
							break;
						case Zarafa.calendar.data.DataModes.WEEK:
							component = Zarafa.calendar.printer.WeekViewRenderer;
							break;
						case Zarafa.calendar.data.DataModes.DAY:
							component = Zarafa.calendar.printer.DaysViewRenderer;
							break;
						case Zarafa.calendar.data.DataModes.MONTH:
							component = Zarafa.calendar.printer.MonthViewRenderer;
							break;
						case Zarafa.calendar.data.DataModes.ALL:
							component = Zarafa.calendar.printer.ListViewRenderer;
							break;
					}
				}
				break;
			case Zarafa.core.data.SharedComponentType['common.attachment.dialog.attachitem.columnmodel']:
				component = Zarafa.calendar.attachitem.AttachCalendarColumnModel;
				break;
			case Zarafa.core.data.SharedComponentType['common.attachment.dialog.attachitem.textrenderer']:
				component = Zarafa.calendar.attachitem.AttachCalendarRenderer;
				break;
		}
		return component;
	},

	/**
	 * Creates the calendar tree that is shown when the user selects the calendar context from the
	 * button panel. It shows a tree of available appointment folders that can be checked and unchecked,
	 * allowing the user to open multiple calendars side-by-side.
	 * @private
	 */
	createCalendarNavigationPanel: function()
	{
		return {
			xtype: 'zarafa.contextnavigation',
			context: this,
			items: [{
				xtype: 'panel',
				id: 'zarafa-navigationpanel-calendar-navigation',
				cls: 'zarafa-context-navigation-block',
				layout: 'fit',
				items: [{
					xtype: 'zarafa.multiselecthierarchytree',
					id: 'zarafa-navigationpanel-calendar-navigation-tree',
					ref: '../../multiSelectHierarchyTree',
					model: this.getModel(),
					IPMFilter: 'IPF.Appointment',
					hideDeletedFolders: true,
					multiSelect: true,
					enableDD: true,
					enableItemDrop: true,
					deferredLoading: true,
					colored: true,
					bbarConfig: {
						defaultSelectedSharedFolderType: Zarafa.hierarchy.data.SharedFolderTypes['APPOINTMENT'],
						buttonText: _('Add Shared Calendar')
					}
				}]
			}]
		};
	},

	/**
	 * Create a {@link Zarafa.calendar.ui.DatePicker DatePicker} panel which can
	 * be used by the user to select a particular date.
	 * @return {Zarafa.calendar.ui.DatePicker} The date picker.
	 */
	createDatePicker: function()
	{
		var picker = new Zarafa.calendar.ui.DatePicker({
			id: 'zarafa-navigationpanel-calendar-datepicker',
			navigationContext: this,
			showWeekNumber: true,
			showToday: true,
			width:'100%',
			handler: function (picker, date) {
				this.getModel().setDate(date);
			},
			scope: this
		});

		this.getModel().on('datechange', function(model, date) { this.setValue(date); }, picker);

		return picker;
	},

	/**
	 * @param {Ext.Component} component The component to which the buttons will be added
	 * @return {Array} Array containing meeting request buttons which should be
	 * added in the {@link Ext.Toolbar Toolbar}.
	 * @private
	 */
	getMeetingRequestToolbarButtons: function(component)
	{
		return [{
			xtype: 'zarafa.meetingrequestbutton',
			name: Zarafa.calendar.data.MeetingRequestButtonNames.REMOVEFROMCALENDAR,
			text: _('Remove From Calendar'),
			overflowText: _('Remove From Calendar'),
			tooltip: {
				title: _('Remove From Calendar'),
				text: _('Remove From Calendar')
			},
			cls: 'tb-calendar-btn-remove zarafa-action'
		}, {
			xtype: 'zarafa.meetingrequestbutton',
			name: Zarafa.calendar.data.MeetingRequestButtonNames.NORESPONSE,
			text: _('No Response Required'),
			overflowText: _('No Response Required'),
			tooltip: {
				title: _('No Response Required'),
				text: _('No Response Required')
			},
			cls: 'tb-calendar-btn-no-response-required',
			iconCls: 'icon_no_response_required'
		}, {
			xtype: 'zarafa.meetingrequestbutton',
			name: Zarafa.calendar.data.MeetingRequestButtonNames.ACCEPT,
			text: _('Accept'),
			overflowText: _('Accept'),
			parentScope: this,
			tooltip: {
				title: _('Accept'),
				text: _('Accept Meeting Request')
			},
			cls: 'zarafa-action',
			iconCls: 'icon_calendar_appt_accept',
			responseStatus: Zarafa.core.mapi.ResponseStatus.RESPONSE_ACCEPTED
		}, {
			xtype: 'zarafa.meetingrequestbutton',
			name: Zarafa.calendar.data.MeetingRequestButtonNames.TENTATIVE,
			text: _('Tentative'),
			overflowText: _('Tentative'),
			tooltip: {
				title: _('Tentative'),
				text: _('Tentatively Accept Meeting Request')
			},
			iconCls: 'icon_calendar_appt_tentative',
			responseStatus: Zarafa.core.mapi.ResponseStatus.RESPONSE_TENTATIVE
		}, {
			xtype: 'zarafa.meetingrequestbutton',
			name: Zarafa.calendar.data.MeetingRequestButtonNames.DECLINE,
			text: _('Decline'),
			overflowText: _('Decline'),
			tooltip: {
				title: _('Decline'),
				text: _('Decline Meeting Request')
			},
			iconCls: 'icon_calendar_appt_cancelled',
			responseStatus: Zarafa.core.mapi.ResponseStatus.RESPONSE_DECLINED
		}, {
			xtype: 'zarafa.meetingrequestbutton',
			name: Zarafa.calendar.data.MeetingRequestButtonNames.PROPOSENEWTIME,
			text: _('Propose New Time'),
			overflowText: _('Propose New Time'),
			tooltip: {
				title: _('Propose New Time'),
				text: _('Propose New Time for Meeting Request')
			},
			iconCls: 'icon_calendar_appt_newtime'
		}, {
			xtype: 'zarafa.meetingrequestbutton',
			name: Zarafa.calendar.data.MeetingRequestButtonNames.VIEWPROPOSALS,
			text: _('View All Proposals'),
			overflowText: _('View All Proposals'),
			tooltip: {
				title: _('View All Proposals'),
				text: _('View All Proposals')
			},
			iconCls: 'icon_calendar_appt_proposals'
		}, {
			xtype: 'zarafa.meetingrequestbutton',
			name: Zarafa.calendar.data.MeetingRequestButtonNames.ACCEPTPROPOSAL,
			text: _('Accept Proposal'),
			overflowText: _('Accept Proposal'),
			tooltip: {
				title: _('Accept Proposal'),
				text: _('Accept Proposed Time')
			},
			iconCls: 'icon_calendar_appt_accept'
		}, {
			xtype: 'zarafa.meetingrequestbutton',
			name: Zarafa.calendar.data.MeetingRequestButtonNames.CALENDAR,
			ref: 'calendarButton',
			text: _('View in calendar'),
			overflowText: _('View in calendar'),
			tooltip: {
				title: _('View in calendar'),
				text: _('View in calendar')
			},
			cls: 'tb-calendar-btn-calendar',
			iconCls: 'icon_calendar'
		}];
	},

	/**
	 * @param {Ext.Component} component The component to which the panel will be added
	 * @return {Object} Configuration object containing the details which should be
	 * added into the {@link Zarafa.core.ui.PreviewPanel}.
	 * @private
	 */
	getMeetingRequestDetailLinks: function(component)
	{
		return {
			xtype: 'zarafa.meetinginfo'
		};
	},

	/**
	 * Creates the main panel for this context.
	 * @return {Zarafa.calendar.ui.CalendarMainPanel} main panel.
	 */
	createContentPanel: function()
	{
		return {
			xtype: 'zarafa.calendarmainpanel',
			id: 'zarafa-mainpanel-contentpanel-calendar',
			context: this
		};
	},

	/**
	 * Returns the buttons for the dropdown list of the VIEW-button in the main toolbar. It will use the
	 * main.maintoolbar.view.calendar insertion point to allow other plugins to add their items at the end.
	 *
	 * @return {Ext.Component[]} an array of components
	 */
	getMainToolbarViewButtons: function()
	{
		var items = container.populateInsertionPoint('main.maintoolbar.view.calendar', this) || [];
		var defaultItems = [{
			id: 'zarafa-maintoolbar-view-calendar-day',
			text: _('Day'),
			iconCls: 'icon_large_calendar_view_day',
			ref: 'calendarDay',
			valueView: Zarafa.calendar.data.Views.BLOCKS,
			valueViewMode: Zarafa.calendar.data.ViewModes.DAYS,
			valueDataMode: Zarafa.calendar.data.DataModes.DAY,
			handler: this.onContextSelectView,
			scope: this
		},{
			id: 'zarafa-maintoolbar-view-calendar-workweek',
			text: _('Workweek'),
			iconCls: 'icon_large_calendar_view_workweek',
			ref: 'calendarWorkweek',
			valueView: Zarafa.calendar.data.Views.BLOCKS,
			valueViewMode: Zarafa.calendar.data.ViewModes.DAYS,
			valueDataMode: Zarafa.calendar.data.DataModes.WORKWEEK,
			handler: this.onContextSelectView,
			scope: this
		},{
			id: 'zarafa-maintoolbar-view-calendar-week',
			text: _('Week'),
			iconCls: 'icon_large_calendar_view_week',
			ref: 'calendarWeek',
			valueView: Zarafa.calendar.data.Views.BLOCKS,
			valueViewMode: Zarafa.calendar.data.ViewModes.DAYS,
			valueDataMode: Zarafa.calendar.data.DataModes.WEEK,
			handler: this.onContextSelectView,
			scope: this
		},{
			id: 'zarafa-maintoolbar-view-calendar-month',
			text: _('Month'),
			iconCls: 'icon_large_calendar_view_month',
			ref: 'calendarMonth',
			valueView: Zarafa.calendar.data.Views.BLOCKS,
			valueViewMode: Zarafa.calendar.data.ViewModes.BOX,
			valueDataMode: Zarafa.calendar.data.DataModes.MONTH,
			handler: this.onContextSelectView,
			scope: this
		},{
			id: 'zarafa-maintoolbar-view-calendar-list',
			text: _('List'),
			iconCls: 'icon_large_list_view',
			ref: 'calendarList',
			valueView: Zarafa.calendar.data.Views.LIST,
			valueViewMode: Zarafa.calendar.data.ViewModes.LIST,
			valueDataMode: Zarafa.calendar.data.DataModes.ALL,
			handler: this.onContextSelectView,
			scope: this
		}];

		return defaultItems.concat(items);
	},

	/**
	 * Event handler which is fired when one of the View buttons
	 * has been pressed. This will call {@link #switchView switchView}
	 * to update the view.
	 * @param {Ext.Button} button The button which was pressed
	 * @private
	 */
	onContextSelectView: function(button)
	{
		this.switchView(button.valueView, button.valueViewMode);
		this.getModel().setDataMode(button.valueDataMode);
	},

	/**
	 * Determines whether the passed button has to be shown or not based on what
	 * {@link Zarafa.core.Context Context} is active. If no Context is supplied as an argument it
	 * will get that from the {@link Zarafa.core.Container container}.
	 * @param {Ext.Button} btn The button
	 * @param {Zarafa.core.Context} activeContext (Optional} The active Context
	 * @private
	 */
	setVisiblityMainToolbarButton: function(btn, activeContext)
	{
		activeContext = activeContext || container.getCurrentContext();
		var viewId = activeContext.getCurrentView();
		if(activeContext === this && viewId == Zarafa.calendar.data.Views.BLOCKS){
			btn.show();
		} else {
			btn.hide();
		}
	},

	/**
	 * Create "New Appointment" button for the "New item" menu in the toolbar.
	 * @return {Object} The menu item for creating a new Appointment
	 * @static
	 */
	createNewAppointmentButton: function()
	{
		return {
			xtype: 'menuitem',
			id: 'zarafa-maintoolbar-newitem-appointment',
			tooltip: _('Appointment') + Zarafa.core.KeyMapMgr.formatShortcutHint('Ctrl + Alt + A', false),
			plugins: 'zarafa.menuitemtooltipplugin',
			text: _('Appointment'),
			handler: function()
			{
				Zarafa.calendar.Actions.openCreateAppointmentContent(this.getModel());
			},
			scope: this,
			iconCls: 'icon_new_appointment',
			newMenuIndex: 2,
			context: 'calendar'
		};
	},

	/**
	 * Create "New meeting request" button for the "New item" menu in the toolbar.
	 * @return {Object} The menu item for creating a new Meeting Request
	 * @static
	 */
	createNewMeetingRequestButton: function()
	{
		return {
			xtype: 'menuitem',
			id: 'zarafa-maintoolbar-newitem-meetingrequest',
			tooltip: _('Meeting request') + Zarafa.core.KeyMapMgr.formatShortcutHint('Ctrl + Alt + V', false),
			plugins: 'zarafa.menuitemtooltipplugin',
			text: _('Meeting request'),
			handler: function()
			{
				Zarafa.calendar.Actions.openCreateMeetingRequestContent(this.getModel());
			},
			scope: this,
			iconCls: 'icon_new_meeting_request',
			newMenuIndex: 2
		};
	},

	/**
	 * Populates the View button in the main.toolbar.print.calendar insertion point
	 * @return {Object[]} items The configuration objects of the menu items available for printing in this context
	 */
	getMainToolbarPrintButtons: function()
	{
		var items = container.populateInsertionPoint('main.toolbar.print.calendar', this) || [];

		var defaultItems = [{
			xtype: 'zarafa.conditionalitem',
			id: 'zarafa-maintoolbar-print-selectedappointment',
			overflowText: _('Print selected appointment'),
			iconCls: 'icon_print_appt',
			tooltip: _('Print selected appointment') + Zarafa.core.KeyMapMgr.formatShortcutHint('Ctrl + P', true),
			plugins: 'zarafa.menuitemtooltipplugin',
			text: _('Print selected appointment'),
			hideOnDisabled: false,
			singleSelectOnly: true,
			handler: this.onPrintSelected.createDelegate(this, [_('No appointment selected')], 2),
			scope: this
		},{
			xtype: 'zarafa.conditionalitem',
			id: 'zarafa-maintoolbar-print-calendaroverview',
			overflowText: _('Print overview'),
			iconCls: 'icon_print',
			text: _('Print overview'),
			tooltip: _('Print overview') + Zarafa.core.KeyMapMgr.formatShortcutHint('Ctrl + Alt + P', false),
			plugins: 'zarafa.menuitemtooltipplugin',
			handler: this.onPrintView,
			scope: this,
			hideOnDisabled: false
		}];

		return defaultItems.concat(items);
	},

	/**
	 * Handler for printing the entire view. The current context is passed to {@link Zarafa.common.Actions#openPrintDialog}
	 * @private
	 */
	onPrintView: function ()
	{
		Zarafa.common.Actions.openPrintDialog(this);
	},

	/**
	 * Create the mail {@link Zarafa.settings.ui.SettingsCategory Settings Category}
	 * to the {@link Zarafa.settings.SettingsContext}. This will create new
	 * {@link Zarafa.settings.ui.SettingsCategoryTab tabs} for the
	 * {@link Zarafa.calendar.ui.SettingsCalendarCategory Calendar}
	 * in the {@link Zarafa.settings.ui.SettingsCategoryWidgetPanel Widget Panel}.
	 * @param {String} insertionName insertion point name that is currently populated
	 * @param {Zarafa.settings.ui.SettingsMainPanel} settingsMainPanel settings main panel
	 * which is populating this insertion point
	 * @param {Zarafa.settings.SettingsContext} settingsContext settings context
	 * @return {Array} configuration object for the categories to register
	 * @private
	 */
	createSettingCategories: function(insertionName, settingsMainPanel, settingsContext)
	{
		return [{
			xtype: 'zarafa.settingscalendarcategory',
			settingsContext: settingsContext
		}];
	},

	/**
	 * Adds a button to the top tab bar for this context.
	 * @return {Object} The button for the top tabbar
	 * @private
	 */
	createMainTab: function()
	{
		return {
			text: this.getDisplayName(),
			tabOrderIndex: 3,
			context: this.getName(),
			id: 'mainmenu-button-calendar'
		};
	},

	/**
	 * Adds a new contextmenu item in the mail context, which converts an email to an appointment
	 * @return {Zarafa.core.ui.menu.ConditionalItem} The Action context menu item
	 * @private
	 */
	convertToAppointment: function()
	{
		return {
			xtype: 'zarafa.conditionalitem',
			text: _('Create Appointment'),
			iconCls: 'icon_new_appointment',
			hidden: true,
			handler: this.onContextItemCreateAppointment,
			beforeShow: this.onBeforeShowCreateAppointment,
			scope: this
		};
	},

	/**
	 * Event Handler triggered when {@link #convertToAppointment convert to
	 * appointment} context menu item is clicked.
	 *
	 * @param {Zarafa.core.ui.menu.ConditionalItem} item context menu item
	 * @private
	 */
	onContextItemCreateAppointment: function(menuItem)
	{
		Zarafa.calendar.Actions.createAppointmentFromMail(menuItem.getRecords(), this.getModel());
	},

	/**
	 * onBeforeShow handler which disables showing the convert to appointment menuitem
	 * when a single item is selected and it's a mail item.
	 *
	 * @param {Zarafa.core.ui.menu.ConditionalItem} item context menu item
	 * @private
	 */
	onBeforeShowCreateAppointment: function(menuItem)
	{
		var records = menuItem.getRecords();
		if (Array.isArray(records) && records.length === 1) {
			menuItem.setVisible(records[0].isMessageClass('IPM.Note'));
		} else {
			menuItem.setVisible(false);
		}
	}
});

Zarafa.onReady(function() {
	container.registerContext(new Zarafa.core.ContextMetaData({
		name: 'calendar',
		displayName: _('Calendar'),
		allowUserVisible: false,
		pluginConstructor: Zarafa.calendar.CalendarContext
	}));
});
Ext.namespace('Zarafa.calendar');

/**
 * @class Zarafa.calendar.CalendarContextModel
 * @extends Zarafa.core.MultiFolderContextModel
 *
 * Model class that keeps track of data in the calendar context that is being modified and read by various sources.
 * This includes the current selected date, visible date range (start, due), mode (day, work week, week, month) and
 * selected folder list.
 * <p>
 * The date range is recalculated when the active date is set. For instance, when the mode is set to 'week' and the
 * active date is changed to 17th of July, 2009 the range will be set to 13-19 July, 2009.
 */
Zarafa.calendar.CalendarContextModel = Ext.extend(Zarafa.core.MultiFolderContextModel, {
	/**
	 * The dateRange which is used as restriction for loading appointments
	 * from the selected {@link #folders}. The dateRange will overlap the {@link #date},
	 * as {@link #date} is used which date the user selected, while the {@link #dateRange}
	 * is used to indicate which corresponding range of dates has been loaded. For example,
	 * if the user clicked on January 11 2011, and the current DataMode is {@link Zarafa.calendar.data.DataMode.WEEK week}
	 * then the DateRange will be January 10 to January 16 2011.
	 *
	 * @property
	 * @type Zarafa.core.DateRange
	 */
	dateRange: undefined,

	/**
	 * True if the {@link #dateRange} will not be needed during {@link #load}. This means that the loaded
	 * appointments will not be restricted to a particular date range.
	 * @property
	 * @type Boolean
	 */
	ignoreDateRange: false,

	/**
	 * The {@link Date date} which has been selected by the user. This is used as base date
	 * for the {@link #dateRange} object. See the comment at {@link #dateRange} on how
	 * the values relate to each other.
	 *
	 * @property
	 * @type Date
	 */
	date: undefined,

	/**
	 * When searching, this property marks the {@link Zarafa.core.ContextModel#getCurrentDataMode datamode}
	 * which was used before {@link #onSearchStart searching started} the datamode was switched to
	 * {@link Zarafa.calendar.data.DataModes#SEARCH}.
	 * @property
	 * @type Mixed
	 * @private
	 */
	oldDataMode: undefined,

	/**
	 * The activeDateRange which contains the start and due date of selected month.For example,
	 * if the user clicked on November 27 2013, then the activeDateRange will be November 1 to November 30 2013.
	 *
	 * @property
	 * @type Zarafa.core.DateRange
	 */
	activeDateRange: undefined,

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

		if(!Ext.isDefined(config.store)) {
			config.store = new Zarafa.calendar.AppointmentStore();
		}

		Ext.applyIf(config, {
			statefulRecordSelection: true,
			colorScheme: Zarafa.core.ColorSchemes.getColorSchemes(),
			current_data_mode: Zarafa.calendar.data.DataModes.WORKWEEK
		});

		this.date = new Date().clearTime(true);
		this.dateRange = new Zarafa.core.DateRange();

		this.addEvents([
			/**
			 * @event datechange
			 * Fires when the selected date is changed.
			 * @param {Zarafa.calendar.CalandarContextModel} model this calendar context model.
			 * @param {Date} date New configured date.
			 * @param {Date} oldDate Previously configured date
			 */
			'datechange',
			/**
			 * @event daterangechange
			 * Fires when the date range is changed.
			 * @param {Zarafa.calendar.CalandarContextModel} model this calendar context model.
			 * @param {Zarafa.core.DateRange} newDateRange new date range.
			 * @param {Zarafa.core.DateRange} oldDateRange old date range.
			 */
			'daterangechange',

			/**
			 * @event activate
			 * Fires when select calendar by clicking on calendar tab.
			 * @param {Zarafa.hierarchy.data.MAPIFolderRecord[]} folder which will mark as selected
			 */
			'activate'

		]);

		Zarafa.calendar.CalendarContextModel.superclass.constructor.call(this, config);

		this.on({
			'searchstart': this.onSearchStart,
			'searchstop': this.onSearchStop,
			scope: this
		});

		this.calculateDateRange(this.date);
		this.pendingLoadTask = new Ext.util.DelayedTask(this.onPendingLoad, this);
	},

	/**
	 * Enables the context.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder folder to bid on.
	 * @param {Boolean} suspended True to enable the ContextModel {@link #suspendLoading suspended}
	 */
	enable: function(folder, suspended)
	{
		this.default_merge_state = container.getSettingsModel().get('zarafa/v1/contexts/calendar/default_merge_state');

		// Enable the superclass with suspended enabled, so we can safely change the date
		// after the superclass has been enabled.
		Zarafa.calendar.CalendarContextModel.superclass.enable.call(this, folder, true);
		this.setDate(this.date, true);

		// We enabled the superclass as suspended,
		// so time to resume it now.
		if (suspended !== true) {
			this.resumeLoading();
		}
	},

	disable: function()
	{
		if (this.pendingLoadTask) {
			this.pendingLoadTask.cancel();
			delete this._pendingLoadData;
		}

		Zarafa.calendar.CalendarContextModel.superclass.disable.apply(this, arguments);
	},

	/*
	 * Check if user has enough permissions to create record in given folder.
	 * A confirmation message will be raised when user don't have enough permissions
	 * asking to use default folder instead.
	 * @param {Function} callback The callback function which will be called once the record gets created.
	 * @param {Zarafa.core.IPMFolder} folder The target folder in which the new record will be created.
	 * @param {Zarafa.core.DateRange} dateRange The {@link Zarafa.core.DateRange DateRange} object
	 * @return {Boolean} true if user has enough permissions, false otherwise.
	 */
	checkCreateRights: function(callback, folder, dateRange)
	{
		if (folder.get('rights') & Zarafa.core.mapi.Rights.RIGHTS_CREATE) {
			return true;
		} else {
			Zarafa.common.dialogs.MessageBox.addCustomButtons({
				width: 400,
				title: _('Create in other calendar'),
				msg: _('The selected calendar has \"read-only\" permissions. Instead you can create an item in your own calendar.') + '<br><br>' + _('What would you like?'),
				fn: function(buttonName){
					if ( buttonName === 'own' ){
						this.createRecord(callback, this.defaultFolder, dateRange);
					}
				},
				customButton: [{
					name: 'own',
					text: _('Own calendar')
				}, {
					name: 'cancel',
					text: _('Cancel')
				}],
				scope: this
			});

			return false;
		}
	},

	/**
	 * Create a new {@link Zarafa.core.data.IPMRecord IPMRecord} record  which is associated to this context.
	 * this is a base function to create record. each context will overwrite this function to
	 * create record of that specific context.
	 * @param {Function} callback The callback function which will be called once the record gets created.
	 * @param {Zarafa.core.IPMFolder} folder (optional) The target folder in which the new record must be
	 * created. If this is not provided the default folder will be used.
	 * @param {Zarafa.core.DateRange} dateRange (optional) The {@link Zarafa.core.DateRange DateRange} object
	 * that should be used to get start and due dates for the appointment.
	 * @return {Zarafa.core.data.IPMRecord} record  which is associated to this context.
	 */
	createRecord: function(callback, folder, dateRange)
	{
		folder = folder || this.getDefaultFolder();

		// check for create permissions
		if (!this.checkCreateRights(callback, folder, dateRange)) {
			return;
		}

		var store;
		if (folder.existsInFavorites()) {
			store = container.getHierarchyStore().getById(folder.get('store_entryid'));
		} else {
			store = folder.getMAPIStore();
		}

		var zoomLevel = container.getSettingsModel().get('zarafa/v1/contexts/calendar/default_zoom_level');
		var defaultPeriod = container.getSettingsModel().get('zarafa/v1/contexts/calendar/default_appointment_period');

		// The default should be the next (rounded up) default Appointment time slot.
		// e.g. When creating a new appointment on 11:55, with a defaultPeriod of 30 minutes,
		// will create an appointment starting on 12:00.
		var startDate = new Date().ceil(Date.MINUTE, zoomLevel);
		// The default should be the default appointment time after the start date.
		var dueDate = startDate.add(Date.MINUTE, defaultPeriod);
		var allDay = false;
		var busyStatus = Zarafa.core.mapi.BusyStatus.BUSY;
		var duration = Date.diff(Date.SECOND, dueDate, startDate);

		// if daterange is provided then use start and due dates from that
		if(dateRange) {
			startDate = dateRange.getStartDate();
			dueDate = dateRange.getDueDate();
			duration = dateRange.getDuration(Date.MINUTE);

			if(dateRange.isAllDay()) {
				allDay = true;
				busyStatus = container.getSettingsModel().get('zarafa/v1/contexts/calendar/default_allday_busy_status');
			}
		}

		var record = Zarafa.core.data.RecordFactory.createRecordObjectByMessageClass('IPM.Appointment', {
			store_entryid: store.get('store_entryid'),
			parent_entryid: folder.get('entryid'),
			busystatus: busyStatus,
			startdate: startDate,
			duedate: dueDate,
			commonstart: startDate,
			commonend: dueDate,
			// Duration is in minutes, not seconds or millis.
			duration: duration,
			alldayevent: allDay
		});

		// call callback if provided, return newly created record otherwise
		if (Ext.isDefined(callback)) {
			callback.call(this, record);
		} else {
			return record;
		}
	},

	/**
	 * Loads the store with new data, based on the list of currently selected folders and selected date range.
	 */
	load: function(data, immediate)
	{
		if (immediate === true || !this.pendingLoadTask) {
			if (this.pendingLoadTask) {
				this.pendingLoadTask.cancel();
			}
			delete this._pendingLoadData;
			return this._loadInternal(data);
		}

		this._pendingLoadData = data;
		this.pendingLoadTask.cancel();
		this.pendingLoadTask.delay(50);
		return false;
	},

	_loadInternal: function(data)
	{
		if (Ext.isDefined(data)) {
			return Zarafa.calendar.CalendarContextModel.superclass.load.call(this, data);
		}

		if (this.ignoreDateRange) {
			return Zarafa.calendar.CalendarContextModel.superclass.load.call(this);
		}

		return Zarafa.calendar.CalendarContextModel.superclass.load.call(this, {
				params: {
					restriction: {
						startdate: this.dateRange.getStartTime() / 1000,
						duedate: this.dateRange.getDueTime() / 1000
					}
				}
			});
	},

	onPendingLoad: function()
	{
		var data = this._pendingLoadData;
		delete this._pendingLoadData;
		this._loadInternal(data);
	},

	/**
	 * Converts a date to a date range. The calendar context model has a mode that indicates the range
	 * type (day, workweek, week, month). This method calculates the appropriate date range to go with
	 * each mode. For example, if the mode is set to 'week', the range produced from a date is the range
	 * that starts on the first day of the week the input date is in, en ends on the first day of the next.
	 *
	 * This method is used in conjunction with date pickers. When a user selects a date from the date picker
	 * control, depending on the mode, the calendar should navigate to either the day, workweek, week, or month
	 * that date is in.
	 *
	 * If the newly calculated date range is different from the previous one, the store is automatically reloaded and
	 * the 'daterangechange' event is fired.
	 *
	 * TODO this method reloads the store automatically when needed. This behavior is different from adding/removing
	 * selected folders.
	 *
	 * @param {Date} date date to set.
	 * @private
	 */
	calculateDateRange: function(date)
	{
		var oldRange = this.dateRange.clone();
		var oldIgnore = this.ignoreDateRange;

		// Set the time to 12:00, this ensures that
		// we can safely use Date.add(Date.DAY, ..) without having
		// problems when the DST switch is at 00:00 (in Brasil).
		date = date.clone();
		date.setHours(12);

		switch (this.current_data_mode)
		{
			case Zarafa.calendar.data.DataModes.DAY:
				this.dateRange.set(date.clearTime(true), date.add(Date.DAY, 1).clearTime());
				this.ignoreDateRange = false;
				break;
			case Zarafa.calendar.data.DataModes.WORKWEEK:
				// get the first working day of the week.
				var weekStart = container.getSettingsModel().get('zarafa/v1/main/week_start');
				var workingDays = container.getSettingsModel().get('zarafa/v1/main/working_days');
				workingDays.sort();

				// If there are no working days selected, use the weekview like outlook does
				if (Ext.isEmpty(workingDays)) {
					this.setWeekView(date);
					break;
				}

				// The first day of the workweek is the first workday which is equal or higher
				// then the work day of the week (e.g. If my workweek is monday, tuesday, thursday,
				// and my first day of the week is wednesday, then the start of the workweek is
				// thursday). Determining the number of days which must be loaded is also never
				// and exact number, the workingDays array could contain gaps (the user can be
				// free on wednesday), in this case we do include the wednesday into the number
				// of days to load, but let the rendered filter those days out.
				if (weekStart <= workingDays[workingDays.length - 1]) {
					for (var i = 0, len = workingDays.length; i < len; i++) {
						if (workingDays[i] >= weekStart) {
							weekStart = workingDays[i];
							if (i === 0) {
								workingDays = 1 + workingDays[workingDays.length - 1] - workingDays[i];
							} else {
								workingDays = 1 + 7 - (workingDays[i] - workingDays[(i - 1)]);
							}
							break;
						}
					}
				} else {
					// Fix a corner case, if our weekStart is higher then the highest
					// working day, we have to wrap the entire week around to ensure
					// we still generate the full workweek.
					weekStart = workingDays[0];
					workingDays = workingDays[workingDays.length - 1];
				}

				// FIXME: Add exceptions to support the gaps in the workweek

				var start = date.getPreviousWeekDay(weekStart);
				this.dateRange.set(start.clearTime(true), start.add(Date.DAY, workingDays).clearTime());
				this.ignoreDateRange = false;
				break;
			case Zarafa.calendar.data.DataModes.WEEK:
				this.setWeekView(date);
				break;
			case Zarafa.calendar.data.DataModes.MONTH:
				var weekStart = container.getSettingsModel().get('zarafa/v1/main/week_start');

				// get the first and last date of month.
				var startDate = date.getFirstDateOfMonth();
				var dueDate = startDate.add(Date.MONTH, 1).clearTime();

				this.setActiveDateRange(startDate, dueDate);

				// get the visible date range of selected month.
				var visibleStartDate = startDate.getPreviousWeekDay(weekStart);
				var visibleDueDate = dueDate.getNextWeekDay(weekStart);

				this.dateRange.set(visibleStartDate, visibleDueDate);
				this.ignoreDateRange = false;
				break;
		}


		if (!oldRange.equals(this.dateRange) || oldIgnore !== this.ignoreDateRange) {
			this.load();
			this.fireEvent('daterangechange', this, this.dateRange, oldRange);
		}
	},

	/**
	 * Function is going to set the date range for week view.
	 * it will show all seven days in calendar.
	 *
	 * @param {Date} date date that has been selected by the user
	 */
	setWeekView: function(date)
	{
		// get the first working day of the week.
		var weekStart = container.getSettingsModel().get('zarafa/v1/main/week_start');
		var start = date.getPreviousWeekDay(weekStart);
		this.dateRange.set(start.clearTime(true), start.add(Date.DAY, 7).clearTime());
		this.ignoreDateRange = false;
	},

	/**
	 * It will set start and due date of selected month.
	 *
	 * @param {Date} start the range's start date.
	 * @param {Date} due the range's due date.
	 */
	setActiveDateRange: function(start, due)
	{
		if(!Ext.isObject(this.activeDateRange)) {
			this.activeDateRange = new Zarafa.core.DateRange();
		}
		this.activeDateRange.set(start, due);
	},

	/**
	 * It will return activeDateRange if is set else return dateRange.
	 * Here activeDateRange contains start date of month to last date of selected month as date range and
	 * dateRange contains all visible days in month view as date range.
	 *
	 * @return {Zarafa.core.DateRange} will return if activeDateRange is set, otherwise will return visibleDateRange.
	 */
	getActiveDateRange: function()
	{
		if(Ext.isDefined(this.activeDateRange)) {
			return this.activeDateRange;
		} else {
			return this.dateRange;
		}
	},

	/**
	 * @param {Date} date to set. The date range (start, due) will be calculated so that the day, work week,
	 * week, or month selected will contain the given date.
	 * @param {Boolean} init True if this function is called during {@link #enable}
	 */
	setDate: function(date, init)
	{
		var oldDate = this.date;

		// clear time (round to whole days)
		date = date.clearTime(true);

		if (!init && oldDate.getTime() === date.getTime()) {
			return;
		}

		// set date
		this.date = date;
		this.fireEvent('datechange', this, date, this.oldDate);

		this.calculateDateRange(this.date);
	},

	/**
	 * Moves the start date forwards or backwards by n days, weeks, or months depending on the current mode.
	 * Causes the 'datechange' event to be fired.
	 * @param {Number} direction if set to -1 the date will be moved backwards in time, if set to 1 the date will move forwards.
	 */
	moveDate: function(direction)
	{
		var date = this.date.clone();

		// Set the time to 12:00, this ensures that
		// we can safely use Date.add(Date.DAY, ..) without having
		// problems when the DST switch is at 00:00 (in Brasil).
		date.setHours(12);

		switch (this.current_data_mode) {
			case Zarafa.calendar.data.DataModes.DAY:
				date = date.add(Date.DAY, 1 * direction);
				break;
			case Zarafa.calendar.data.DataModes.WEEK:
			case Zarafa.calendar.data.DataModes.WORKWEEK:
				date = date.add(Date.DAY, 7 * direction);
				break;
			case Zarafa.calendar.data.DataModes.MONTH:
				date = date.add(Date.MONTH, 1 * direction);
				break;
		}

		this.setDate(date.clearTime());
	},

	/**
	 * Moves the current date range to the 'next date', which can be the next day, week, or month depending on the current mode.
	 * Causes the 'datechange' event to be fired.
	 */
	nextDate: function()
	{
		this.moveDate(1);
	},

	/**
	 * Moves the current date range to the 'previous date', which can be the previous day, week, or month depending on the current mode.
	 * Causes the 'datechange' event to be fired.
	 */
	previousDate: function()
	{
		this.moveDate(-1);
	},

	/**
	 * Event handler which is executed right before the {@link #datamodechange}
	 * event is fired. This allows subclasses to initialize the {@link #store}.
	 * This will recalculate the {@link #dateRange} based on the previously configured
	 * {@link #date}.
	 *
	 * @param {Zarafa.calendar.CalendarContextModel} model The model which fired the event.
	 * @param {Zarafa.calendar.data.DataModes} newMode The new selected DataMode.
	 * @param {Zarafa.calendar.data.DataModes} oldMode The previously selected DataMode.
	 * @private
	 */
	onDataModeChange: function(model, newMode, oldMode)
	{
		Zarafa.calendar.CalendarContextModel.superclass.onDataModeChange.call(this, model, newMode, oldMode);

		if (newMode !== oldMode && oldMode === Zarafa.calendar.data.DataModes.SEARCH) {
			this.stopSearch();
		}

		switch (newMode) {
			case Zarafa.calendar.data.DataModes.SEARCH:
				this.ignoreDateRange = true;
				break;
			case Zarafa.calendar.data.DataModes.ALL:
				this.ignoreDateRange = true;
				this.load();
				break;
			default:
				// call setDate to re-calculate the start and due dates
				this.calculateDateRange(this.date);
				break;
		}

	},

	/**
	 * Sets the current mode (one of {@link Zarafa.calendar.data.DataModes DataModes}) and date. This switches between view
	 * types (days, week, month) and sets the start date. The due date is calculated automatically.
	 *
	 * Fires the {@link #datamodechange} event (but not the {@link #datechange} event).
	 *
	 * @param {Zarafa.calendar.data.DataModes} mode view mode (days, week, month).
	 * @param {Data} date start date.
	 */
	setModeAndDate: function(mode, date)
	{
		this.date = date.clearTime(true);
		this.setDataMode(mode, true);
	},

	/**
	 * See getDateRangeText()
	 * @private
	 */
	getMonthRangeText: function()
	{
		var startDate = this.date.getFirstDateOfMonth();
		return startDate.format(_('F Y'));
	},

	/**
	 * Formats the current visible date range as human-readable text. When in day mode a simple date format is used,
	 * i.e. '19 September 2009'. When in month mode the returned text will show the month and year, i.e. 'September 2009'.
	 * <p>
	 * When showing a 'free' date range the formatter looks at which components the start and due dates have in common.
	 * For instance, the first and last days of a week range might lie in the same month (i.e. '13 - 19 July 2009'),
	 * or they might not (i.e. '28 September - 2 November 2009'). Finally a range may have the start and end dates
	 * in different years, i.e. '28 December 2009 - 1 January 2010'.
	 *
	 * @return {String} the current date range as text.
	 */
	getDateRangeText: function()
	{
		switch (this.current_data_mode) {
			case Zarafa.calendar.data.DataModes.MONTH:
				return this.getMonthRangeText();
			case Zarafa.calendar.data.DataModes.DAY:
				// For day view we add the current day and date. Ref KW-2818.
				return this.date.format(_('l')) + ' '+ this.dateRange.format();
			default:
				return this.dateRange.format();
		}
	},

	/**
	 * Event handler for the {@link #searchstart searchstart} event.
	 * This will {@link #setDataMode change the datamode} to {@link Zarafa.calendar.data.DataModes#SEARCH search mode}.
	 * The previously active {@link #getCurrentDataMode view} will be stored in the {@link #oldDataMode} and will
	 * be recovered when the {@link #onSearchStop search is stopped}.
	 * @param {Zarafa.core.ContextModel} model The model which fired the event
	 * @private
	 */
	onSearchStart: function(model)
	{
		if(this.getCurrentDataMode() != Zarafa.calendar.data.DataModes.SEARCH){
			this.oldDataMode = this.getCurrentDataMode();
			this.setDataMode(Zarafa.calendar.data.DataModes.SEARCH);
		}
	},

	/**
	 * Event handler for the {@link #searchstop searchstop} event.
	 * This will {@link #setDataMode change the datamode} to the {@link #oldDataMode previous datamode}.
	 * @param {Zarafa.core.ContextModel} model The model which fired the event
	 * @private
	 */
	onSearchStop: function(model)
	{
		if (this.getCurrentDataMode() === Zarafa.calendar.data.DataModes.SEARCH) {
			this.setDataMode(this.oldDataMode);
		}
		delete this.oldDataMode;
	},

	/**
	 * Set the corresponding folder as active folder in hierarchy.
	 * And will mark the given folder as {@link Zarafa.hierarchy.ui.Tree#selectFolderInTree selected}.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord[]} folder which will mark as selected
	 */
	setActiveFolder: function(folder)
	{
		this.fireEvent('activate', folder, this);
	}
});
Ext.namespace('Zarafa.calendar.dialogs');

/**
 * @class Zarafa.calendar.dialogs.AppointmentContentPanel
 * @extends Zarafa.core.ui.MessageContentPanel
 * @xtype zarafa.appointmentcontentpanel
 */
Zarafa.calendar.dialogs.AppointmentContentPanel = Ext.extend(Zarafa.core.ui.MessageContentPanel, {
	/**
	 * @cfg {Object} newAppointmentProps The object containing all properties which must be applied
	 * to the {@link #record} when it has been opened. These properties will be applied during {@link #update}.
	 */
	newAppointmentProps: undefined,

	/**
	 * @cfg {Number} activeTab number, the tab which should be active in {Zarafa.calendar.dialogs.AppointmentPanel tabs}
	 * 0 opens Appointment tab{@link Zarafa.calendar.dialogs.AppointmentTab}, this is defaultValue, we also want this tab when we accept any proposed time from attendee,
	 * 1 opens Freebusy tab{@link Zarafa.calendar.dialogs.FreebusyTab}, we want this tab to be opened when we view all propsed time from Attendees
	 * 2 opens Tracking tab{@link Zarafa.calendar.dialogs.TrackingTab}
	 */
	activeTab: undefined,

	/**
	 * True if record properties were changed. This is updated in {@link #onUpdateRecord},
	 * and used in {@link #validateBusyRecipients} to determine if a message should be send
	 * to all attendees or not.
	 * @property
	 * @type Boolean
	 * @private
	 */
	isPropertyChanged: false,

	/**
	 * True if record attendees were changed. This is updated in {@link #onUpdateRecord},
	 * and used in {@link #validateBusyRecipients} to determine if a message should be send
	 * to all attendees or not.
	 * @property
	 * @type Boolean
	 * @private
	 */
	isRecipientChanged: false,

	/**
	 * @constructor
	 * @param config Configuration structure
	 */
	constructor: function(config)
	{
		// Add in some standard configuration data.
		config = config || {};

		if (Ext.isDefined(config.activeTab)) {
			this.activeTab = config.activeTab;
		}

		Ext.applyIf(config, {
			// Override from Ext.Component
			xtype: 'zarafa.appointmentcontentpanel',
			// Override from Ext.Component
			layout: 'fit',
			cls: 'k-apptcreatepanel',
			recordComponentPluginConfig: Ext.applyIf(config.recordComponentPluginConfig || {}, {
				allowWrite: true
			}),
			// Override from Zarafa.core.ui.RecordContentPanel
			title: _('Appointment'),
			confirmClose: true,
			items: [{
				xtype: 'zarafa.appointmentpanel',
				activeTab: this.activeTab,
				tbar: {
					xtype: 'zarafa.appointmenttoolbar'
				}
			}]
		});

		// Call parent constructor
		Zarafa.calendar.dialogs.AppointmentContentPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Create and initialize the {@link #sendValidationQueue}. This will add various
	 * validation steps which must be executed to determine if the message can be send.
	 * @protected
	 */
	createSendValidationQueue: function()
	{
		Zarafa.calendar.dialogs.AppointmentContentPanel.superclass.createSendValidationQueue.apply(this, arguments);

		// Add a validation step when a meeting will be send,
		// it should check if only recipients were changed or not.
		// And ask if the user wants to send an update to all recipients.
		this.sendValidationQueue.add(this.validateSendUpdateToRecipients, this);

		// Add a validation step when a meeting will be send,
		// it should check if one of the recipients is busy
		this.sendValidationQueue.add(this.validateBusyRecipients, this);
	},

	/**
	 * Update the components with the given record.
	 *
	 * @param {Zarafa.core.data.MAPIRecord} record The record to update in this component
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 */
	update: function(record, contentReset)
	{
		this.updateTitleFromRecord(record, contentReset);
		if(contentReset){
			this.updateIconFromRecord(record);
		}

		if (record.isOpened()) {
			if (contentReset || record.isModifiedSinceLastUpdate('meeting')) {
				record.updateMeetingRecipients();

				switch (record.get('meeting')) {
					case Zarafa.core.mapi.MeetingStatus.MEETING:
						if(!Ext.isDefined(this.initialConfig.closeOnSave)) {
							this.closeOnSave = false;
						}
						if(!Ext.isDefined(this.initialConfig.closeOnSend)) {
							this.closeOnSend = true;
						}
						break;
					case Zarafa.core.mapi.MeetingStatus.MEETING_RECEIVED:
						if(!Ext.isDefined(this.initialConfig.closeOnSave)) {
							this.closeOnSave = true;
						}
						if(!Ext.isDefined(this.initialConfig.closeOnSend)) {
							this.closeOnSend = true;
						}
						break;
					case Zarafa.core.mapi.MeetingStatus.NONMEETING:
					/* falls through */
					default:
						if(!Ext.isDefined(this.initialConfig.closeOnSave)) {
							this.closeOnSave = true;
						}
						if(!Ext.isDefined(this.initialConfig.closeOnSend)) {
							this.closeOnSend = false;
						}
						break;
				}
			}

			// we need to set these properties only when record is opened
			// so server response will not overwrite these properties
			if (this.newAppointmentProps) {
				record.beginEdit();

				Ext.iterate(this.newAppointmentProps, record.set, record);

				// After applying these properties should be deleted
				// to prevent them from being reapplied.
				delete this.newAppointmentProps;

				record.endEdit();
			}

			var recipientStore = record.getSubStore('recipients');
			if (recipientStore && record.isMeetingOrganized()) {
				this.mon(recipientStore, 'resolved', this.setRecipientTypeOnResolve, this);
			}
		}
	},

	/**
	 * Update this panel's icon class from the record that it contains
	 * First obtains the icon class from a mapping, then calls {@link #setIcon}
	 *
	 * @param {Zarafa.core.data.MAPIRecord} record The record bound to this component
	 * @private
	 */
	updateIconFromRecord: function(record)
	{
		//TODO: create a new icon mapping for tabs
		var iconCls = Zarafa.common.ui.IconClass.getIconClass(record);
		this.setIcon(iconCls);
	},

	/**
	 * When record has been updated, title also has to be - for instance if we have the subject
	 * in the title and the subject changes
	 * Calls {@link #setTitle} this.setTitle in order to update
	 * @param {Zarafa.core.data.MAPIRecord} record The record that has been updated
	 */
	updateTitleFromRecord: function(record)
	{
		var subject = record.get('subject');
		if(!Ext.isEmpty(subject)){
			this.setTitle(subject);
		} else {
			if(record.get('meeting') == Zarafa.core.mapi.MeetingStatus.NONMEETING){
				this.setTitle(this.initialConfig.title);
			} else {
				this.setTitle(_('Meeting'));
			}
		}
	},

	/**
	 * Function is used to automatically set recipient_type property value to
	 * {@link Zarafa.core.mapi.RecipientType#MAPI_BCC} to mark it as a resource if it is a room or equipment.
	 * This can be checked by using display_type and display_type_ex properties. This function is called when
	 * {@link Zarafa.core.data.IPMRecipientStore IPMRecipientStore} fires 'resolved' event.
	 * @param {Zarafa.core.data.IPMRecipientStore} store store which fired the 'resolved' event.
	 * @param {Zarafa.core.data.IPMRecipientRecord[]} records records which are in the response of resolve request.
	 */
	setRecipientTypeOnResolve: function(store, records)
	{
		for(var index = 0; index < records.length; index++) {
			var record = records[index];
			var displayTypeEx = record.get('display_type_ex');

			/**
			 * user type --> PR_DISPLAY_TYPE_EX value
			 * active user --> DT_MAILUSER | DTE_FLAG_ACL_CAPABLE
			 * non-active user --> DT_MAILUSER
			 * room --> DT_ROOM
			 * equipment --> DT_EQUIPMENT
			 * contact --> DT_REMOTE_MAILUSER
			 */
			switch (displayTypeEx) {
				case Zarafa.core.mapi.DisplayTypeEx.DT_EQUIPMENT:
				case Zarafa.core.mapi.DisplayTypeEx.DT_ROOM:
					record.set('recipient_type', Zarafa.core.mapi.RecipientType.MAPI_BCC);
					break;
			}
		}
	},

	/**
	 * Fired when the {@link #updaterecord} event has been fired. If {@link #showSavingMask} is enabled,
	 * this will display the {@link #savingText} to indicate the saving is in progress.
	 *
	 * @param {Zarafa.core.ui.RecordContentPanel} contentpanel The record which fired the event
	 * @param {String} action write Action that occurred. Can be one of
	 * {@link Ext.data.Record.EDIT EDIT}, {@link Ext.data.Record.REJECT REJECT} or
	 * {@link Ext.data.Record.COMMIT COMMIT}
	 * @param {Zarafa.core.data.IPMRecord} record The record which was updated
	 * @private
	 * @overridden
	 */
	onUpdateRecord: function(contentpanel, action, record)
	{
		if (action === Ext.data.Record.COMMIT) {
			// If the record is a meeting request that has been sent successfully and contains resources
			// then we have to notify the user of the successfulness of the action.
			if(record.isMeetingOrganized() && this.isSending === true) {
				if(record.getRecipientStore().findExact('recipient_type', Zarafa.core.mapi.RecipientType.MAPI_BCC) !== -1){
					// If the server lets the client now the appointment has been booked (resources) then notify the user.
					if(record.getActionResponse('resources_booked')){
						container.getNotifier().notify('info.meeting', pgettext('calendar.dialog', 'Resources have been planned'));
					}
				}
			}

			// Reset the trackers for record modifications
			this.isPropertyChanged = false;
			this.isRecipientChanged = false;
		} else {
			// Determine which changes were made to the record,
			// we need this for validateSendUpdateToRecipients() which
			// needs to differentiate between changes made to only recipients
			// or properties.
			if (record.updateModifications && Object.keys(record.updateModifications).length > 0) {
				this.isPropertyChanged = true;
			}
			if (record.updateSubStoreModifications) {
				if (record.updateSubStoreModifications.recipients) {
					// If recipients object is present, recipients have changed.
					this.isRecipientChanged = true;
				} else if(record.updateSubStoreModifications.attachments) {
					// if attachment object is present, attachment have changed.
					this.isPropertyChanged = true;
				}

				// Recipient and another substore might have changed...
				if (Object.keys(record.updateSubStoreModifications).length > 1) {
					this.isPropertyChanged = true;
				}
			}
		}


		Zarafa.calendar.dialogs.AppointmentContentPanel.superclass.onUpdateRecord.apply(this, arguments);
	},

	/**
	 * Save all changes made to the {@link #record} to the server.
	 * @param {Boolean} storeSave (optional) False to only update the record,
	 * but not save the changes to the store.
	 * @return {Boolean} false if the record could not be saved
	 * @protected
	 */
	saveRecord: function(storeSave)
	{
		// Safety net: a read-only item must never reach the server (it would be
		// rejected with "Insufficient Privileges"). The editing controls are
		// already disabled, so this only triggers via a keyboard shortcut.
		if (this.record && this.record.isReadOnlyRecord()) {
			container.getNotifier().notify('error', _('Insufficient privileges'), _('You do not have permission to modify this item.'));

			return false;
		}

		// Before saving a meeting request, check if the attendees were already
		// invited. If they are, then we _must_ send a meeting update to those
		// attendees. If the organizer doesn't want that, we will not save
		// anything. Inform the user about this!
		//
		// TODO:also need to add check for any modification/changes in record data,
		// so that send is only done when there is any change in the data by the organizer
		var record = this.record;

		if (record.isMeetingSent() && !record.isAppointmentInPast() && record.getMessageAction('send') !== true) {

			 if (!this.isOnlyOrganizerPropertiesChanged(record)) {

				// Determine if organizer related properties were only changed
				// then no need to send message to all attendees
				Ext.MessageBox.show({
					title: _('Send update'),
					msg: _('Do you want to send an update to all recipients?'),
					cls: Ext.MessageBox.WARNING_CLS,
					fn: this.sendMeetingUpdate,
					scope: this,
					buttons: Ext.MessageBox.YESNO
				});
				// The user is confronted with a messagebox, lets not yet
				// save the appointment be wait for his answer.
				return;
			} else if(this.isRecipientChanged){
				// Inform the user about the update must be send to only the added and removed attendees or to everybody.
				// so,that send is only done when there is any change in attendees only.

				this.sendRecord();
				return;
			}
		}

		return Zarafa.calendar.dialogs.AppointmentContentPanel.superclass.saveRecord.apply(this, arguments);
	},

	/**
	 * Save all changes made to the {@link #record} and send
	 * the message to the specified recipients.
	 */
	sendRecord: function()
	{
		if (this.record && this.record.isReadOnlyRecord()) {
			container.getNotifier().notify('error', _('Insufficient privileges'), _('You do not have permission to modify this item.'));

			return false;
		}

		if(!this.record.isMeeting()) {
			// can not send a non meeting record
			return;
		}

		return Zarafa.calendar.dialogs.AppointmentContentPanel.superclass.sendRecord.apply(this, arguments);
	},

	/**
	 * Delete the {@link #record}, unless the user lacks delete rights on it.
	 * A safety net for the toolbar and keyboard delete actions so that a
	 * read-only item cannot trigger a server-side "Insufficient Privileges"
	 * error. New (phantom) records can always be discarded.
	 * @overridden
	 */
	deleteRecord: function()
	{
		if (this.record && !this.record.phantom && !this.record.hasDeleteAccess()) {
			container.getNotifier().notify('error', _('Insufficient privileges'), _('You do not have permission to delete this item.'));

			return;
		}

		return Zarafa.calendar.dialogs.AppointmentContentPanel.superclass.deleteRecord.apply(this, arguments);
	},

	/**
	 * Callback function from {@link Ext.MessageBox#show} which checks if the
	 * organizer wants to send an update to the attendees regarding the changes in meeting item
	 * If yes then send update to the attendees regarding the changes and
	 * If No then save changes without sending update to attendees.
	 * @param {String} button button text which was pressed.
	 * @private
	 */
	sendMeetingUpdate: function(button)
	{
		if (button == 'yes') {
			// The user wants to send the record
			this.sendRecord();
		}
	},

	/**
	 * Validation function for the {@link #sendValidationQueue} to check if the Meeting
	 * can be send to the attendees.
	 *
	 * This checks if the meeting has been sent to the attendees before, and will ask the user
	 * if the update must be send to only the added and removed attendees or to everybody.
	 *
	 * @param {Function} callback The callback to call to continue in the queue
	 * @private
	 */
	validateSendUpdateToRecipients: function(callback)
	{
		if (this.record.get('request_sent') && !this.isPropertyChanged && this.isRecipientChanged) {
			Zarafa.common.dialogs.MessageBox.select(
				_('Send update to attendees'),
				_('You have made changes to the list of attendees. What would you like to do?'),
				function(button, radio) {
					if (button === 'ok') {
						if (radio.id == 'sendModified') {
							this.record.addMessageAction('send_update', 'modified');
						} else {
							this.record.addMessageAction('send_update', 'all');
						}
						callback(true);
					} else {
						callback(false);
					}
				}, this,
				[{
					boxLabel: _('Send updates to only added or deleted attendees.'),
					id: 'sendModified',
					name: 'select',
					checked: true
				},{
					boxLabel: _('Send updates to all attendees.'),
					id: 'sendAll',
					name: 'select'
				}]
			);
		} else {
			callback(true);
		}
	},

	/**
	 * Validation function for the {@link #sendValidationQueue} to check if the Meeting
	 * can be send to the attendees.
	 *
	 * This validates if all attendees which were added into the FreebusyPanel
	 * are all available during the given start and duedate.
	 *
	 * @param {Function} callback The callback to call to continue in the queue
	 * @private
	 */
	validateBusyRecipients: function(callback)
	{
		var freebusyPanel = this.findByType('zarafa.freebusypanel')[0];
		var record = this.record;
		var busy = false;

		if (freebusyPanel) {
			busy = freebusyPanel.getModel().checkAttendeesBusyStatus(record.get('startdate'), record.get('duedate'), record.get('request_sent'));
		}

		if (busy) {
			Ext.MessageBox.show({
				title: _('Busy attendees'),
				msg: _('One or more attendees are busy. Are you sure to invite?'),
				buttons: Ext.MessageBox.YESNO,
				fn: function (buttonClicked) {
					// If the user pressed "yes" we can continue with the validation
					// If not, sending will be stopped.
					callback(buttonClicked == 'yes');
				},
				scope: this
			});
		} else {
			// The freebusy panel couldn't be found,
			// lets not make this block sending the meeting request.
			callback(true);
		}
	},

	/**
	 * Function which is use to determine that only organizer related properties of record were changed.
	 * Some set of properties like label,body,categories,etc.
	 * if those properties were only changed then no need to send message to all attendees
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record which was updated
	 * @returns {boolean} true if only organizer related properties changed, false otherwise
     */
	isOnlyOrganizerPropertiesChanged: function (record)
	{
		var isOrganizerPropertiesChanged = true;
		var organizerProperties = ['body' ,'html_body' , 'categories' , 'label' , 'busystatus', 'private', 'reminder', 'isHTML', 'sensitivity','importance','hasattach','attach_num','flagdueby','reminder_minutes'];
		Ext.iterate(record.modified, function (property) {
			if (organizerProperties.indexOf(property) < 0 && record.data[property] != record.modified[property]) {
				isOrganizerPropertiesChanged = false;
				return false;
			}
		});
		if(isOrganizerPropertiesChanged){
			// we need this for validateSendUpdateToRecipients() which
			// needs to differentiate between changes made to only organizer related properties

			this.isPropertyChanged = false;
		}
		return isOrganizerPropertiesChanged;
	}
});

Ext.reg('zarafa.appointmentcontentpanel', Zarafa.calendar.dialogs.AppointmentContentPanel);
// -*- coding: utf-8; indent-tabs-mode: nil -*-
Ext.namespace('Zarafa.calendar.printer');

/**
 * @class Zarafa.calendar.printer.WeekViewRenderer
 * @extends Zarafa.calendar.printer.DaysViewRenderer
 *
 * Prints a single week calendar overview
 */
Zarafa.calendar.printer.WeekViewRenderer = Ext.extend(Zarafa.calendar.printer.DaysViewRenderer, {

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

		Ext.applyIf(config, {
			timeStyle: 'width:33%;'
		});

		Zarafa.calendar.printer.WeekViewRenderer.superclass.constructor.call(this, config);
	},

	/**
	 * Returns the HTML that will be placed into the <body> part of the print window.
	 * @param {Ext.Component} component The component to render
	 * @return {String} The HTML fragment to place inside the print window's <body> element
	 */
	generateBodyTemplate: function(context) {
		/* +---------------------------------------------------------------+
		 * | Overview of {fullname}     | datepicker_left datepicker_right |
		 * | {foldernames}              | datepicker_left datepicker_right |
		 * | Start day - end day        |                                  |
		 * +---------------------------------------------------------------+
		 * +---------------------------------------------------------------+
		 * |             day 1             |             day 2             |
		 * +---------------------------------------------------------------+
		 * |                               |                               |
		 * |                               |                               |
		 * |                               |                               |
		 * +---------------------------------------------------------------+
		 * |             day 3             |             day 4             |
		 * +---------------------------------------------------------------+
		 * |                               |                               |
		 * |                               |                               |
		 * |                               |                               |
		 * +---------------------------------------------------------------+
		 * |             day 5             |             day 6             |
		 * +---------------------------------------------------------------+
		 * |                               |                               |
		 * |                               |                               |
		 * |                               |                               |
		 * +---------------------------------------------------------------+
		 * |             day 7             |                               |
		 * +---------------------------------------------------------------+
		 * |                               |                               |
		 * |                               |                               |
		 * |                               |                               |
		 * +---------------------------------------------------------------+
		 *
		 * Printed by {fullname} at {date}
		 */

		return '<table class="print-calendar" cellpadding=0 cellspacing=0>\n'
			+	'<tr style="height:10%;"><td colspan=2>'
			+		'<table id="top">\n'
			+			'<tr><td align="center">' +_('An overview of') + ' {fullname} {foldernames}</td>'
			+			'<td align="center" rowspan="2"valign="top" width="10%"><div id="datepicker_left"></div></td>'
			+			'<td align="center" rowspan="2" valign="top" width="10%"><div id="datepicker_right"></div></td></tr>\n'
			+			'<tr><td align="left" valign="top" style="font-size: large;">'
			+				'{startdate:date("' + _("l jS F Y") + '")} - {duedate:date("' + _("l jS F Y") + '")}'
			+			'</td></tr>\n'
			+		'</table>\n'
			+ '</td></tr>\n'
			+	'<tr style="height:40px;">'
				// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for formatting instructions
			+		'<th class="date-header-center">{date1:date("' + _("l jS F") + '")}</th>'
				// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for formatting instructions
			+		'<th class="date-header-center">{date2:date("' + _("l jS F") + '")}</th>'
			+	'</tr>\n'
			+	'<tr style="height:20%;">'
			+		'<td valign="top"><table id="date1">{date1_table_data}</table></td>'
			+		'<td valign="top"><table id="date2">{date2_table_data}</table></td>'
			+	'</tr>\n'
			+	'<tr style="height:40px;">'
				// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for formatting instructions
			+		'<th class="date-header-center">{date3:date("' + _("l jS F") + '")}</th>'
				// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for formatting instructions
			+		'<th class="date-header-center">{date4:date("' + _("l jS F") + '")}</th>'
			+	'</tr>\n'
			+	'<tr style="height:20%;">'
			+		'<td valign="top"><table id="date3">{date3_table_data}</table></td>'
			+		'<td valign="top"><table id="date4">{date4_table_data}</table></td>'
			+	'</tr>\n'
			+	'<tr style="height:40px;">'
				// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for formatting instructions
			+		'<th class="date-header-center">{date5:date("' + _("l jS F") + '")}</th>'
				// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for formatting instructions
			+		'<th class="date-header-center">{date6:date("' + _("l jS F") + '")}</th>'
			+	'</tr>\n'
			+	'<tr style="height:20%;">'
			+		'<td valign="top"><table id="date5">{date5_table_data}</table></td>'
			+		'<td valign="top"><table id="date6">{date6_table_data}</table></td>'
			+	'</tr>\n'
			+	'<tr style="height:40px;">'
				// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for formatting instructions
			+		'<th class="date-header-center">{date7:date("' + _("l jS F") + '")}</th>'
			+	'</tr>\n'
			+	'<tr style="height:20%;">'
			+		'<td valign="top"><table id="date7">{date7_table_data}</table></td>'
			+	'</tr>\n</table>\n'
			// Bottom name and print date
			+ '<table class="bottom">'
			+		'<tr>'
						// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for formatting instructions
			+			'<td>'+_('Printed by') + ' {fullname} ' + _('at') + ' {currenttime:formatDefaultTimeString("' + _("l jS F Y {0}") + '")}</td>'
			+		'</tr>'
			+ '</table>';
	}
});
// -*- coding: utf-8; indent-tabs-mode: nil -*-
Ext.namespace('Zarafa.calendar.printer');

/**
 * @class Zarafa.calendar.printer.WorkWeekViewRenderer
 * @extends Zarafa.calendar.printer.DaysViewRenderer
 *
 * Prints a single workweek calendar overview
 */
Zarafa.calendar.printer.WorkWeekViewRenderer = Ext.extend(Zarafa.calendar.printer.DaysViewRenderer, {

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

		Ext.applyIf(config, {
			timeStyle: 'width:33%;'
		});

		Zarafa.calendar.printer.WorkWeekViewRenderer.superclass.constructor.call(this, config);
	},

	/**
	 * Returns the HTML that will be placed into the <body> part of the print window.
	 * @param {Ext.Component} component The component to render
	 * @return {String} The HTML fragment to place inside the print window's <body> element
	 */
	generateBodyTemplate: function(context) {
		/* +---------------------------------------------------------------+
		 * | Overview of {fullname}     | datepicker_left datepicker_right |
		 * | {foldernames}              | datepicker_left datepicker_right |
		 * | Start day - end day        |                                  |
		 * +---------------------------------------------------------------+
		 * +---------------------------------------------------------------+
		 * |             day 1             |             day 2             |
		 * +---------------------------------------------------------------+
		 * |                               |                               |
		 * |                               |                               |
		 * |                               |                               |
		 * +---------------------------------------------------------------+
		 * |             day 3             |             day 4             |
		 * +---------------------------------------------------------------+
		 * |                               |                               |
		 * |                               |                               |
		 * |                               |                               |
		 * +---------------------------------------------------------------+
		 * |             day 5             |                               |
		 * +---------------------------------------------------------------+
		 * |                               |                               |
		 * |                               |                               |
		 * |                               |                               |
		 * +---------------------------------------------------------------+
		 *
		 * Printed by {fullname} at {date}
		 */

				return '<table class="print-calendar" cellpadding=0 cellspacing=0>\n'
			+	'<tr style="height:10%;"><td colspan=2>'
			+		'<table id="top">\n'
			+			'<tr><td align="center">' +_('An overview of') + ' {fullname} {foldernames}</td>'
			+			'<td align="center" rowspan="2"valign="top" width="10%"><div id="datepicker_left"></div></td>'
			+			'<td align="center" rowspan="2" valign="top" width="10%"><div id="datepicker_right"></div></td></tr>\n'
			+			'<tr><td align="left" valign="top" style="font-size: large;">'
			+				'{startdate:date("' + _("l jS F Y") + '")} - {duedate:date("' + _("l jS F Y") + '")}'
			+			'</td></tr>\n'
			+		'</table>\n'
			+ '</td></tr>\n'
			+	'<tr style="height:40px;">'
				// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for formatting instructions
			+		'<th class="date-header-center">{date1:date("' + _("l jS F") + '")}</th>'
				// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for formatting instructions
			+		'<th class="date-header-center">{date2:date("' + _("l jS F") + '")}</th>'
			+	'</tr>\n'
			+	'<tr style="height:20%;">'
			+		'<td valign="top"><table id="date1">{date1_table_data}</table></td>'
			+		'<td valign="top"><table id="date2">{date2_table_data}</table></td>'
			+	'</tr>\n'
			+	'<tr style="height:40px;">'
				// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for formatting instructions
			+		'<th class="date-header-center">{date3:date("' + _("l jS F") + '")}</th>'
				// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for formatting instructions
			+		'<th class="date-header-center">{date4:date("' + _("l jS F") + '")}</th>'
			+	'</tr>\n'
			+	'<tr style="height:20%;">'
			+		'<td valign="top"><table id="date3">{date3_table_data}</table></td>'
			+		'<td valign="top"><table id="date4">{date4_table_data}</table></td>'
			+	'</tr>\n'
			+	'<tr style="height:40px;">'
				// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for formatting instructions
			+		'<th class="date-header-center">{date5:date("' + _("l jS F") + '")}</th>'
			+	'</tr>\n'
			+	'<tr style="height:20%;">'
			+		'<td valign="top"><table id="date5">{date5_table_data}</table></td>'
			+	'</tr>\n</table>\n'
			// Bottom name and print date
			+ '<table class="bottom">'
			+		'<tr>'
						// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for formatting instructions
			+			'<td>'+_('Printed by') + ' {fullname} ' + _('at') + ' {currenttime:formatDefaultTimeString("' + _("l jS F Y {0}") + '")}</td>'
			+		'</tr>'
			+ '</table>';
	}
});
Ext.namespace('Zarafa.calendar.ui.html');

/**
 * @class Zarafa.calendar.ui.html.AppointmentBoxView
 * @extends Zarafa.calendar.ui.AppointmentView
 *
 * Appointment view used in {@link Zarafa.calendar.ui.AbstractCalendarBoxView CalendarBoxView}. It represents each appointment as one
 * or more horizontal rectangles on one or more week rows.
 * <p>
 * The view tries to minimise the number of required HTML elements by using background images to represent the busy status
 * strips on the left side of appointments. The background of each view is implemented using a scaled IMG tag.
 */
Zarafa.calendar.ui.html.AppointmentBoxView = Ext.extend(Zarafa.calendar.ui.AppointmentView, {
	/**
	 * Array of objects containing the {@link Zarafa.calendar.data.AppointmentBounds bounds} for the
	 * {@link #body} elements which are part of the appointment. This field is initialized in
	 * {@link #layoutInBody} using the function
	 * {@link Zarafa.calendar.ui.AbstractCalendarBoxView#dateRangeToBodyBounds dateRangeToBodyBounds}.
	 * @property
	 * @type Array
	 */
	bounds: undefined,

	/**
	 * The main text which will be rendered into the body of the appointment. This field
	 * is initialized using the {@link #mainTextRenderer}.
	 * @property
	 * @type String
	 */
	mainRenderedText: '',

	/**
	 * The subtext which will be rendered alongside the {@link #mainRenderedText}. This field
	 * is initialized using the {@link #subTextRenderer}.
	 * @property
	 * @type String
	 */
	subRenderedText: '',

	/**
	 * This will mark the appointment as selected or unselected.
	 * @param {Boolean} selected True if the appointment should be marked as selected.
	 * @override
	 */
	setSelected: function(selected)
	{
		Zarafa.calendar.ui.html.AppointmentBoxView.superclass.setSelected.call(this, selected);

		if ( Ext.isEmpty(this.appointmentBoxes) ) {
			// Should not happen, but better safe than sorry
			return;
		}

		if (selected) {
			this.appointmentBoxes.forEach(function(appointmentBox) {
				appointmentBox.addClass('k-selected');
				appointmentBox.set({ 'aria-selected': 'true' });
			});

			// when selecting appointment set focus also so key shortcuts work properly
			this.focus();
		} else {
			this.appointmentBoxes.forEach(function(appointmentBox) {
				appointmentBox.removeClass('k-selected');
				appointmentBox.set({ 'aria-selected': 'false' });
			});
		}
	},

	/**
	 * Tests whether a mouse event is over the body start (left) resize handle.
	 * @param {Ext.EventObject} event event object.
	 * @return {Boolean} true iff the event is over the resize handle.
	 * @override
	 */
	eventOverBodyStartHandle: function(event)
	{
		return Zarafa.calendar.ui.html.AppointmentDaysView.prototype.eventOverStartHandle.call(this, event);
	},

	/**
	 * Tests whether a mouse event is over the body due (right) resize handle.
	 * @param {Ext.EventObject} event event object.
	 * @return {Boolean} true iff the event is over the resize handle.
	 * @override
	 */
	eventOverBodyDueHandle: function(event)
	{
		return Zarafa.calendar.ui.html.AppointmentDaysView.prototype.eventOverDueHandle.call(this, event);
	},

	/**
	 * Tests whether a mouse event is over the appointment when laid out in the calendar body.
	 * @param {Ext.EventObject} event event object.
	 * @return {Boolean} true if the event is over the appointment.
	 * @override
	 */
	eventOverBody: function(event)
	{
		return Zarafa.calendar.ui.html.AppointmentDaysView.prototype.eventOverAppointment.call(this, event);
	},

	/**
	 * Lays out the header elements of the view.
	 * NOTE: this function does nothing as the boxView does not render
	 * any headers for an appointment.
	 * @override
	 * @private
	 */
	layoutInHeader: Ext.emptyFn,

	/**
	 * Creates elements to represent the range when shown in the header.
	 * NOTE: this function does nothing as the boxView does not render
	 * any headers for an appointment.
	 * @private
	 * @override
	 */
	createHeader: Ext.emptyFn,

	/**
	 * Destroys the header elements.
	 * NOTE: this function does nothing as the boxView does not render
	 * any headers for an appointment.
	 * @private
	 * @override
	 */
	destroyHeader: Ext.emptyFn,

	/**
	 * Lays out the body of the view. This will generate the Body bounts using the
	 * function {@link Zarafa.calendar.ui.AbstractCalendarBoxView.dateRangeToBodyBounds dateRangeToBodyBounds},
	 * the bounds will be used for laying out the body elements using {@link #layoutBodyElements}.
	 * @private
	 * @override
	 */
	layoutInBody: function()
	{
		// get an array of bounds (left, right, top, bottom) objects to represent the range on the calendar body
		this.bounds = this.parentView.dateRangeToBodyBounds(this.getDateRange(), this.slot);

		// Draw the body elements to match the bounds
		if (!Ext.isEmpty(this.bounds)) {
			Zarafa.calendar.ui.html.AppointmentDaysView.prototype.layoutAppointment.call(this, this.bounds, this.parentView.bodyAppointmentLayer, true);
		}
	},

	/**
	 * Body start time text renderer. This will return the start time in string which
	 * must be displayed most prominently in the appointment when it has been rendered in the main contents
	 * section of the calendar. This applies to non-all-day appointments and while calendar was
	 * {@link Zarafa.calendar.data.DataModes#MONTH month view} mode.
	 * @return {String} The start time text of appointment.
	 * @private
	 */
	startTimeTextRenderer: function()
	{
		return Ext.util.Format.htmlEncode(this.record.get('startdate').format(this.timeFormat));
	},

	/**
	 * Body end time text renderer. This will return the start time in string which
	 * must be displayed most prominently in the appointment when it has been rendered in the main contents
	 * section of the calendar. This applies to non-all-day appointments and while calendar was
	 * {@link Zarafa.calendar.data.DataModes#MONTH month view} mode.
	 * @return {String} The end time text of appointment.
	 * @private
	 */
	endTimeTextRenderer: function()
	{
		return Ext.util.Format.htmlEncode(this.record.get('duedate').format(this.timeFormat));
	}
});
Ext.namespace('Zarafa.calendar.ui.html');

/**
 * @class Zarafa.calendar.ui.html.AppointmentDaysView
 * @extends Zarafa.calendar.ui.AppointmentView
 *
 * This view represents a {@link Zarafa.core.data.IPMRecord record} within the
 * {@link Zarafa.calendar.ui.AbstractCalendarView view}.
 */
Zarafa.calendar.ui.html.AppointmentDaysView = Ext.extend(Zarafa.calendar.ui.AppointmentView, {

	/**
	 * The opacity used to blur the appointment to the background when it is not part of the active
	 * Calendar. It is set in a value between 0 and 1.
	 * @property
	 * @type Number
	 */
	opacityNonActiveAppointment: 0.4,

	/**
	 * The {@link Ext.Element elements} (boxes) that make up the appointment.
	 */
	appointmentBoxes: [],

	/**
	 * Renders the appointment in its parent view.
	 *
	 * @param {Array} bounds The outer dimensions of the appointment. Can be multiple boxes if spanning multiple days.
	 * @param {Ext.Element} layer The layer in which to render the appointment. Can be the headerAppointmentLayer or
	 * the bodyAppointmentLayer of the parent view.
	 * @param {Boolean} showStartTime True to render the start time of the appointment
	 */
	layoutAppointment: function(bounds, layer, showStartTime) {
		if ( this.appointmentBoxes && this.appointmentBoxes.length ) {
			// We have already laid out the appointment. Let's remove it
			// before we lay out the updated appointment
			this.appointmentBoxes.forEach(function(box) {
				box.remove();
			});
		}

		var color = this.getAppointmentColor();
		// Check if we have a light or dark appointment color
		var isDarkColor = Zarafa.core.ColorSchemes.isDark(color);
		var colorClass = this.isActive() && isDarkColor ? ' k-dark' : ' k-light';
		var activeClass = this.isActive() ? '' : ' k-inactive';
		var stripWidth = this.getStripWidth();
		var titleText = this.mainTextRenderer() + ' '+ this.subTextRenderer();
		var icons = this.iconRenderer();
		var iconsHtml = '';
		icons.forEach(function(icon) {
			var iconClass = this.isActive() && isDarkColor  ? 'icon_'+icon+'_white' : 'icon_'+icon;
			iconsHtml += '<div class="k-icon ' + iconClass + '" aria-hidden="true"></div>';
		}, this);
		if ( iconsHtml ) {
			iconsHtml = '<div class="k-icons">' + iconsHtml + '</div>';
		}

		var busyStatus = this.getBusyStatus();
		switch ( busyStatus ) {
			case Zarafa.core.mapi.BusyStatus.FREE:
				var busyClass = ' k-status-free';
				break;
			case Zarafa.core.mapi.BusyStatus.TENTATIVE:
				busyClass = ' k-status-tentative';
				break;
			case Zarafa.core.mapi.BusyStatus.OUTOFOFFICE:
				busyClass = ' k-status-outofoffice';
				break;
			case Zarafa.core.mapi.BusyStatus.WORKINGELSEWHERE:
				busyClass = ' k-status-workingelsewhere';
				break;
			default:
				busyClass = ' k-status-busy';
				break;
		}

		// Appointments can consist of multiple bounds when spanning multiple days.
		// We'll loop over the bounds to draw all boxes.
		this.appointmentBoxes = [];
		bounds.forEach(function(bound, index) {
			var width = bound.right - bound.left;
			var height = bound.bottom - bound.top;
			var orderClass = '';
			var resizeHandleStart = '';
			var resizeHandleEnd = '';
			if ( index === 0 ) {
				orderClass += ' k-first';
				resizeHandleStart = '<div class="k-resizehandle k-resizehandle-start" aria-hidden="true"></div>';

				if ( showStartTime === true && !this.isAllDay() ) {
					var startTimeText = Ext.util.Format.htmlDecode(this.startTimeTextRenderer());
					//drawTextObject.endTimeText = Ext.util.Format.htmlDecode(this.endTimeTextRenderer());
					titleText = startTimeText + ' ' + titleText;
				}
			}
			if ( index === bounds.length - 1 ) {
				orderClass += ' k-last';
				resizeHandleEnd = '<div class="k-resizehandle k-resizehandle-end" aria-hidden="true"></div>';
			}
			var appointmentBox = layer.createChild({
				cls: 'k-appointment-box' + colorClass + activeClass + orderClass,
				html: resizeHandleStart + resizeHandleEnd + '<div class="k-status'  + busyClass + '" style="width:' + stripWidth + 'px; border-color:' + color + ';" aria-hidden="true"></div>' +
						iconsHtml + '<span class="k-title">' + titleText + '</span>',
				style: 'background-color:' + color + ';' +
						'left:' + (bound.left + 0) + 'px;' +
						'top:' + bound.top + 'px;' +
						'width:' + (width - 2) + 'px;' +
						'height:' + height + 'px;'
			});
			appointmentBox.set({
				'role': 'button',
				'aria-label': Ext.util.Format.stripTags(titleText).trim(),
				'tabindex': '-1'
			});

			// Relay some events to the parent view
			appointmentBox.on('mouseover', function(event) {
				this.parentView.fireEvent('appointmentmouseover', this.parentView, this.getRecord(), event);
			}, this);
			appointmentBox.on('mouseout', function(event) {
				this.parentView.fireEvent('appointmentmouseout', this.parentView, this.getRecord(), event);
			}, this);
			appointmentBox.on('dblclick', function(event) {
				this.parentView.fireEvent('dblclick', this.parentView, event, this.getRecord());
			}, this);
			appointmentBox.on('contextmenu', function(event) {
				var xy = event.getXY();
				var range = this.parentView.screenLocationToDateRange(xy[0], xy[1]);

				// Fire the calendar's context menu event.
				this.parentView.fireEvent('contextmenu', this, event, this.getRecord(), range);
			}, this);
			appointmentBox.on('keydown', function(event) {
				if (event.getKey() === event.ENTER || event.getKey() === event.SPACE) {
					event.stopEvent();
					this.parentView.fireEvent('dblclick', this.parentView, event, this.getRecord());
				}
			}, this);

			this.appointmentBoxes.push(appointmentBox);
		}, this);

		// Make sure we draw the selection outline when the appointment was selected
		if ( this.selected ) {
			this.setSelected(true);
		}
	},

	/**
	 * Lays out the header elements of the view.
	 * @private
	 * @override
	 */
	layoutInHeader: function() {
		// Get the bounds of the header from the parent calendar.
		this.bounds = [this.parentView.dateRangeToHeaderBounds(this.getDateRange(), this.slot, 1, true)];

		this.layoutAppointment(this.bounds, this.parentView.getHeaderAppointmentLayer());
	},

	/**
	 * Renders appointments in the body of the calendar
	 * @private
	 * @override
	 */
	layoutInBody: function() {
		this.bounds = this.parentView.dateRangeToBodyBounds(
			this.getAdjustedDateRange(),
			this.slot,
			this.slotCount,
			true
		);

		var bodyAppointmentLayer = this.parentView.getBodyAppointmentLayer();

		return this.layoutAppointment(this.bounds, bodyAppointmentLayer);
	},

	/**
	 * This will mark the appointment as selected, and will draw the
	 * Selection Outline around the appointment.
	 * @param {Boolean} selected True if the appointment should be marked as selected.
	 * @override
	 */
	setSelected: function(selected)
	{
		Zarafa.calendar.ui.html.AppointmentDaysView.superclass.setSelected.call(this, selected);

		this.appointmentBoxes.forEach(function(appointmentBox) {
			if ( selected ) {
				appointmentBox.addClass('k-selected');
				appointmentBox.set({ 'aria-selected': 'true' });

				// when selecting appointment set focus also so key shortcuts work properly
				this.focus();
			} else {
				appointmentBox.removeClass('k-selected');
				appointmentBox.set({ 'aria-selected': 'false' });
			}
		}, this);
	},

	/**
	 * Tests whether a mouse event is over the appointment.
	 *
	 * @param {Ext.EventObject} event event object.
	 * @return {Boolean} true if the event is over the appointment.
	 */
	eventOverAppointment: function(event) {
		if ( !event.target ) {
			return false;
		}

		var el = Ext.fly(event.target).findParent('.k-appointment-box');
		if ( !el ) {
			return false;
		}

		if ( Ext.isEmpty(this.appointmentBoxes) ) {
			// This appointment is not visible
			// (in month view, appointments that don't fit in the day anymore
			// will not be rendered)
			return false;
		}

		return this.appointmentBoxes.some(function(appointmentBox){
			return appointmentBox.dom === el;
		});
	},

	/**
	 * Tests whether a mouse event is over an appointment in the header of the calendar.
	 *
	 * @param {Ext.EventObject} event event object.
	 * @return {Boolean} true if the event is over the appointment.
	 * @override
	 */
	eventOverHeader: function(event)
	{
		return this.eventOverAppointment(event);
	},

	/**
	 * Tests whether a mouse event is over an appointment in the body of the calendar.
	 *
	 * @param {Ext.EventObject} event event object.
	 * @return {Boolean} true if the event is over the appointment.
	 * @override
	 */
	eventOverBody: function(event) {
		return this.eventOverAppointment(event);
	},

	/**
	 * Tests whether a mouse event is over the "start resize" handle.
	 *
	 * @param {Ext.EventObject} event event object.
	 * @return {Boolean} true if the event is over the resize handle.
	 */
	eventOverStartHandle: function(event)
	{
		return this.appointmentBoxes[0].down('.k-resizehandle-start', true) === event.target;
	},

	/**
	 * Tests whether a mouse event is over the "due resize" handle.
	 *
	 * @param {Ext.EventObject} event event object.
	 * @return {Boolean} true if the event is over the resize handle.
	 */
	eventOverDueHandle: function(event) {
		return this.appointmentBoxes[this.appointmentBoxes.length - 1].down('.k-resizehandle-end', true) === event.target;
	},

	/**
	 * Tests whether a mouse event is over the header start (left) resize handle.
	 *
	 * @param {Ext.EventObject} event event object.
	 * @return {Boolean} true iff the event is over the resize handle.
	 * @override
	 */
	eventOverHeaderStartHandle: function(event)
	{
		return this.eventOverStartHandle(event);
	},

	/**
	 * Tests whether a mouse event is over the header due (right) resize handle.
	 *
	 * @param {Ext.EventObject} event event object.
	 * @return {Boolean} true iff the event is over the resize handle.
	 * @override
	 */
	eventOverHeaderDueHandle: function(event)
	{
		return this.eventOverDueHandle(event);
	},

	/**
	 * Returns true when the event is over the 'resize start handle' of the appointment.
	 * False otherwise
	 *
	 * TODO(ronald): This should be handled by this component itself.
	 *
	 * @param {EveExt.EventObjectnt} event
	 * @return {Boolean}
	 */
	eventOverBodyStartHandle: function(event) {
		return this.eventOverStartHandle(event);
	},

	/**
	 * Returns true when the event is over the 'resize start handle' of the appointment.
	 * False otherwise
	 *
	 * TODO(Ronald): This should be handled by this component itself.
	 *
	 * @param {EveExt.EventObjectnt} event
	 * @return {Boolean}
	 */
	eventOverBodyDueHandle: function(event) {
		return this.eventOverDueHandle(event);
	}
});
Ext.namespace('Zarafa.calendar.ui.html');

/**
 * @class Zarafa.calendar.ui.html.AppointmentProxy
 * @extends Zarafa.calendar.ui.AppointmentView
 *
 * A special {@link Zarafa.calendar.ui.AppointmentView} which acts as proxy for the
 * {@link Zarafa.calendar.ui.CalendarViewDropZone} when an appointment is being resized or
 * dragged over the calendar.
 *
 * This will render the selected area as a grey block with some extra information text.
 */
Zarafa.calendar.ui.html.AppointmentProxy = Ext.extend(Zarafa.calendar.ui.AppointmentView, {

	/**
	 * @cfg {Boolean} showTime initial value of showTime. When true the start and due times are rendered in the view body. Defaults to false.
	 */
	showTime: false,

	/**
	 * The &lt;div&gt; element for the body of the appointment. This element is
	 * created using the {@link #createDiv} function within {@link #createBodyElement}.
	 * @property
	 * @type Array
	 */
	body: undefined,

	/**
	 * The CSS class which must be applied to the {@link #header} element.
	 * This class can be set using {@link #setHeaderClass}.
	 * FIXME: This class doesn't work, as it is overridden by the subclass...
	 * @property
	 * @type String
	 */
	headerClassName: '',

	/**
	 * The CSS class which must be applied to the {@link #body} element.
	 * This class can be set using {@link #setBodyClass}.
	 * FIXME: This class doesn't work, as it is overridden by the subclass...
	 * @property
	 * @type String
	 */
	bodyClassName: '',

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

		Ext.applyIf(config, {
			baseCls: 'zarafa-calendar',
			itemCls: 'selection'
		});

		this.addEvents(
			/**
			 * @event keypress
			 * Fires when a 'keypress' event is detected while this selection range was focussed
			 * @param {Ext.EventObject} event The Ext.EventObject encapsulating the DOM event
			 * @param {HtmlElement} element The target of the event
			 * @param {Object} options The options configuration passed to the addListener call
			 */
			'keypress',
			/**
			 * @event keydown
			 * Fires when a 'keydown' event is detected while this selection range was focussed
			 * @param {Ext.EventObject} event The Ext.EventObject encapsulating the DOM event
			 * @param {HtmlElement} element The target of the event
			 * @param {Object} options The options configuration passed to the addListener call
			 */
			'keydown',
			/**
			 * @event keyup
			 * Fires when a 'keyup' event is detected while this selection range was focussed
			 * @param {Ext.EventObject} event The Ext.EventObject encapsulating the DOM event
			 * @param {HtmlElement} element The target of the event
			 * @param {Object} options The options configuration passed to the addListener call
			 */
			'keyup'
		);

		Zarafa.calendar.ui.html.AppointmentProxy.superclass.constructor.call(this, config);
	},

	/**
	 * Initialises the AppointmentProxy.
	 * @private
	 * @override
	 */
	init: function()
	{
		Zarafa.calendar.ui.html.AppointmentProxy.superclass.init.call(this);

		this.body = [];
	},

	/**
	 * Sets date range. This method does not auto-update.
	 * @param {Zarafa.core.DateRange} dateRange
	 */
	setDateRange: function(dateRange)
	{
		if (this.dateRange) {
			this.mun(this.dateRange, 'update', this.onDateRangeUpdate, this);
		}
		Zarafa.calendar.ui.html.AppointmentProxy.superclass.setDateRange.call(this, dateRange);
		if (this.dateRange) {
			this.mon(this.dateRange, 'update', this.onDateRangeUpdate, this);
		}
	},

	/**
	 * Renders the view.
	 * @param {Ext.Element} container The Ext.Element into which the view must be rendered.
	 */
	render: function(container)
	{
		Zarafa.calendar.ui.html.AppointmentProxy.superclass.render.apply(this, arguments);

		// Relay key events
		this.focusEl.relayEvent('keypress', this);
		this.focusEl.relayEvent('keydown', this);
		this.focusEl.relayEvent('keyup', this);
	},

	/**
	 * Sets the class or classes on the view header element.
	 * @param {String} className class name or class name list
	 */
	setHeaderClass: function(className)
	{
		this.headerClassName = className;
	},

	/**
	 * Sets the class or classes on the view body elements.
	 * @param {String} className class name or class name list
	 */
	setBodyClass: function(className)
	{
		this.bodyClassName = className;
	},

	/**
	 * Toggles whether the view should show the start and due times in the view body.
	 * @param {Boolean} showTime if true the view will show the start and due times in the calendar body.
	 */
	setShowTime: function(showTime)
	{
		this.showTime = showTime;
	},

	/**
	 * Adds text to the view body containing the start and due times of the date range.
	 * @private
	 */
	createTimeText: function ()
	{
		if (this.body.length >= 1) {
			var dateRange = this.getDateRange();
			var startDate = dateRange.getStartDate();
			var dueDate = dateRange.getDueDate();

			var time = String.format('{0} - {1}', startDate.formatDefaultTime(), dueDate.formatDefaultTime());

			if (this.getFirstBodyElement().dom.innerHTML !== time) {
				this.getFirstBodyElement().dom.innerHTML = time;
			}
		}
	},

	/**
	 * Removes text from the view body.
	 * @private
	 */
	clearTimeText: function()
	{
		if (this.body.length > 0) {
			this.getFirstBodyElement().dom.innerHTML = '';
		}
	},

	/**
	 * Sets the visibility of the range
	 * @param {Boolean} visible if true the view will be shown, if false it will be hidden
	 */
	setVisible: function(visible)
	{
		Zarafa.calendar.ui.html.AppointmentProxy.superclass.setVisible.call(this, visible);

		if (visible) {
			this.setBodyClass('zarafa-calendar-selection zarafa-calendar-selection-dragging zarafa-calendar-dragproxy-time');
			this.setHeaderClass('zarafa-calendar-selection zarafa-calendar-selection-dragging');
		} else {
			this.setBodyClass('zarafa-calendar-selection');
			this.setHeaderClass('zarafa-calendar-selection');
		}

		//Always show focus element.
		if(this.focusEl) {
			this.focusEl.show();
		}

		if (this.rendered) {
			this.layout();
		}
	},

	/**
	 * Lays out the elements of the view body.
	 * @param {Object[]} bounds array of bounds (left, right, top, bottom) objects.
	 * @private
	 */
	layoutBodyElements: function(bounds)
	{
		// optionally generate time text
		if (this.showTime) {
			this.createTimeText();
		} else {
			this.clearTimeText();
		}

		// resize the body elements to match the bounds
		for (var i=0; i<bounds.length; i++) {
			this.body[i].dom.className = this.bodyClassName;
			this.body[i].setLeftTop(bounds[i].left, bounds[i].top);
			this.body[i].setSize(bounds[i].right - bounds[i].left, bounds[i].bottom - bounds[i].top);
		}
	},

	/**
	 * Event handler which is fired when the {@link #dateRange} is updated.
	 * When the component is rendered and visible, then this will relayout
	 * the view.
	 * @param {Zarafa.core.DateRange} dateRange the changed daterange
	 */
	onDateRangeUpdate: function(dateRange)
	{
		if (this.rendered && this.isVisible()) {
			this.layout();
		}
	},

	/**
	 * Date ranges may change dynamically, for instance when a selection is dragged or when an appointment is resized.
	 * In such cases the view that represents the range must dynamically add and remove 'boxes' as the range may span
	 * a variable number of days in both the header and body.
	 * <p>
	 * This method adds a single such 'box' by creating a new &lt;div&gt; element and attaching events to it.
	 * The method {@link #destroyBodyElement} should remove any elements created with this method.
	 * @private
	 * @override
	 */
	createBodyElement: function()
	{
		var calendarBody = this.parentView.getCalendarBody();

		var element = this.createDiv(calendarBody, 'body');
		this.mon(element, 'contextmenu', this.onContextMenu, this);
		this.mon(element, 'dblclick', this.onDoubleClick, this);
	},

	/**
	 * This function will cleanup any elements created with {@link #createBodyElement}.
	 * @private
	 * @override
	 */
	destroyBodyElement: function()
	{
		var element = this.body.pop();
		this.mun(element, 'contextmenu', this.onContextMenu, this);
		this.mun(element, 'dblclick', this.onDoubleClick, this);
		this.remove(element);
	},

	/**
	 * Creates elements to represent the range when shown in the header.
	 * @private
	 */
	createHeader: function()
	{
		this.createDiv(this.parentView.getCalendarHeader(), 'header');
		this.mon(this.header, 'contextmenu', this.onContextMenu, this);
		this.mon(this.header, 'dblclick', this.onDoubleClick, this);
	},

	/**
	 * Destroys the header element(s).
	 * @private
	 */
	destroyHeader: function()
	{
		this.mun(this.header, 'contextmenu', this.onContextMenu, this);
		this.mun(this.header, 'dblclick', this.onDoubleClick, this);
		this.remove(this.header);

		this.header = undefined;
	},

	/**
	 * Lays out the header element of the view.
	 * @private
	 */
	layoutInHeader: function()
	{
		// Get the bounds of the header from the parent calendar.
		var bounds = this.parentView.dateRangeToHeaderBounds(this.getDateRange());

		// If header doesn't exist yet, create it
		if (!this.header) {
			this.createHeader();
		}

		// If body exists, destroy it
		while (this.body.length) {
			this.destroyBodyElement();
		}

		this.header.dom.className = this.headerClassName;

		this.header.setLeftTop(bounds.left, bounds.top);
		this.header.setSize(bounds.right - bounds.left, this.parentView.getAppointmentHeaderheight());
	},

	/**
	 * Lays out the body of the view.
	 * @private
	 * @override
	 */
	layoutInBody: function()
	{
		var bounds = this.parentView.dateRangeToBodyBounds(this.dateRange);

		// If the header exists, destroy it
		if (this.header) {
			this.destroyHeader();
		}

		// make sure the number of DIV elements we have allocated matches the size of the
		// bounds array
		while (this.body.length < bounds.length) {
			this.createBodyElement();
		}
		while (this.body.length > bounds.length) {
			this.destroyBodyElement();
		}

		// Lay out the body elements to match the bounds
		this.layoutBodyElements(bounds);
	},

	/**
	 * Layout method. We override this here to change the default behavior where
	 * the range is only shown on the body if the duration is less than 24 hours.
	 * During appointment dragging and resizing it looks weird if the range is
	 * shown on the header only i such a case.
	 * @protected
	 */
	onLayout: function()
	{
		Zarafa.calendar.ui.html.AppointmentProxy.superclass.onLayout.call(this);

		// The selectionRange is updated, make sure we mark
		// this selection as focussed.
		this.focus();
	},

	/**
	 * @return {Ext.Element} first element of the body of the view.
	 */
	getFirstBodyElement: function()
	{
		return this.body[0];
	},

	/**
	 * Handles the onContextMenu event, and refires it on the parent calendar view.
	 * @param {Object} event The event object
	 * @private
	 */
	onContextMenu: function(event)
	{
		this.parentView.fireEvent('contextmenu', this.parentView, event);
	},

	/**
	 * Handles the onDoubleClick event, and refires it on the parent calendar view.
	 * @param {Object} event The event object
	 * @private
	 */
	onDoubleClick: function(event)
	{
		var model = this.parentView.parentView.model;

		model.createRecord(function(record){
			this.parentView.fireEvent('dblclick', this.parentView, event, record);
		}.createDelegate(this, [event], true), this.parentView.getSelectedFolder(), this.getDateRange());
	}
});
Ext.namespace('Zarafa.calendar.ui.html');

/**
 * @class Zarafa.calendar.ui.html.CalendarBoxView
 * @extends Zarafa.calendar.ui.AbstractCalendarBoxView
 *
 * HTML based implementation of {@link Zarafa.calendar.ui.AbstractCalendarBoxView AbstractCalendarBoxView}.
 *
 * Using layering we can optimize the rendering of the calendar. Whenever an appoint changes, we are not
 * interested in redrawing the complete calendar and all appointments in it (This would become a performance
 * issue for calendars with many appointments). So with 2 layers we can create the following setup:
 *
 *  - Layer 1: The calendar
 *    This contains the calendar background. For the header this means the background color, and the
 *    name of the date which is being displayed in the color (optionally additional markup is applied
 *    in case the rendered date is 'today'). For the body this means that all timestrips are rendered
 *    into the calendar (using the correct theme color).
 *  - Layer 2: The appointments
 *    This contains all the appointments which are available in the calendar. None of the appointments
 *    are allowed to overlap with another appointment (The time can obviously overlap, but
 *    the appointments are placed under each other until the maximum number of appointment that can be
 *    shown is reached. After that an "expand button" is rendered. Whenever a single appointment
 *    changes, we only have to rerender that single appointment (since it doesn't overlap with any other
 *    appointment, we can be sure that redrawing the appointment will not affect other appointments).
 */
Zarafa.calendar.ui.html.CalendarBoxView = Ext.extend(Zarafa.calendar.ui.AbstractCalendarBoxView, {
	/**
	 * The element for the top-row in the calendar which contains the
	 * day name for each individual column. Since the header does not contain appointments,
	 * we don't need extra layers for displaying the appointments.
	 * @property
	 * @type Ext.Element
	 */
	headerBackgroundLayer: undefined,

	/**
	 * The element for the body in the calendar which contains the
	 * timestrips and the appointments.
	 * This element is 'layer 1' for the body, containing only the background.
	 * @property
	 * @type Ext.Element
	 */
	bodyBackgroundLayer: undefined,

	/**
	 * The element for the body in the calendar which contains the
	 * timestrips and the appointments.
	 * This element is 'layer 2' for the body, containing only the appointments.
	 * @property
	 * @type Ext.Element
	 */
	bodyAppointmentLayer: undefined,

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

		Ext.applyIf(config, {
			baseCls: 'zarafa-calendar',
			enableDD: true,
			ddGroup: 'dd.mapiitem',
			bodyDropConfig: {
				ddGroup: 'dd.mapiitem',
				headerMode: false,
				selectingSnapMode: Zarafa.calendar.data.SnapModes.DAY,
				draggingSnapMode: Zarafa.calendar.data.SnapModes.NONE
			}
		});

		Zarafa.calendar.ui.html.CalendarBoxView.superclass.constructor.call(this, config);
	},

	/**
	 * Renders the view. Generates the body and header layers.
	 * @param {Ext.Element} container The Ext.Element into which the view must be rendered.
	 * @private
	 * @override
	 */
	render: function(container)
	{
		Zarafa.calendar.ui.html.CalendarBoxView.superclass.render.call(this, container);

		this.create('div', this.header, 'headerBackgroundLayer', 'k-html-header-background');
		this.create('div', this.body, 'bodyBackgroundLayer', 'k-html-body-background');
		this.create('div', this.body, 'bodyAppointmentLayer', 'k-html-body-appointment');

		this.mon(this.bodyAppointmentLayer, {
			'contextmenu': this.onBodyContextMenu,
			scope: this
		});
	},

	/**
	 * Create a new {@link Zarafa.calendar.ui.AppointmentView Appointment} object
	 * for the given {@link Zarafa.core.data.IPMRecord record}.
	 * @param {Zarafa.core.data.IPMRecord} record The record for which the AppointmentView
	 * must be created.
	 * @private
	 */
	createAppointment: function(record)
	{
		return new Zarafa.calendar.ui.html.AppointmentBoxView({
			parentView: this,
			record: record,
			calendarColorScheme: this.calendarColorScheme
		});
	},

	/**
	 * Create an Appointment Proxy object which can represent the selected text
	 * Must be implemented by the subclasses.
	 * @return Zarafa.calendar.ui.AbstractDateRangeView
	 * @protected
	 */
	createAppointmentProxy: function()
	{
		return new Zarafa.calendar.ui.html.AppointmentProxy({ showTime: false });
	},

	/**
	 * Sets the text on the headers for each day column. The header title is generated using the
	 * {@link #getDayHeaderTitle} function.
	 * @private
	 */
	drawDayHeaders: function()
	{
		var width = this.header.getWidth();
		var height = this.header.getHeight();

		// First resize the element of the header and remove the contents
		this.headerBackgroundLayer.setSize(width, height);
		this.headerBackgroundLayer.dom.innerHTML = '';

		// Set the background color
		var headerColor = this.calendarColorScheme.header;
		this.headerBackgroundLayer.setStyle({
			'background': headerColor
		});

		// Check if we have a light or dark color
		var isDarkColor = Zarafa.core.ColorSchemes.isDark(this.calendarColorScheme.base);
		if ( isDarkColor ){
			this.headerBackgroundLayer.addClass('k-dark');
			this.headerBackgroundLayer.removeClass('k-light');
		} else {
			this.headerBackgroundLayer.addClass('k-light');
			this.headerBackgroundLayer.removeClass('k-dark');
		}

		// Mark inactive calendars so CSS can mute them
		if ( this.active ){
			this.headerBackgroundLayer.removeClass('k-inactive-calendar');
		} else {
			this.headerBackgroundLayer.addClass('k-inactive-calendar');
		}

		var startDate = this.getVisibleDateRange().getStartDate().clone();

		// Set the startDate to 12:00 to prevent problems when the DST switch
		// occurs at 00:00 (like in Brasil).
		startDate.setHours(12);

		// Set the correct number of css grid columns for the header, with the correct width
		var gridTemplateColumns = '';
		for (var i = 0; i < this.numDaysInWeek; i++) {
			gridTemplateColumns += (100/this.numDaysInWeek) + '% ';
		}
		this.headerBackgroundLayer.setStyle({
			'grid-template-columns': gridTemplateColumns,
			'msGridColumns': gridTemplateColumns
		});

		// Create a new div (column) for every day
		for (i = 0; i < this.numDaysInWeek; i++) {
			var date = startDate.add(Date.DAY, i).clearTime();
			var text = this.getDayHeaderTitle(date, width/this.numDaysInWeek);
			this.headerBackgroundLayer.createChild({
				cls: 'k-day-' + i,
				style: '-ms-grid-column: ' + (i + 1),
				html: '<div class="k-cal-header-title">' + text + '</div>'
			});
		}
	},

	/**
	 * Draws the boxes that represent the days in the box view.
	 * @private
	 */
	drawDays: function()
	{
		// Clear all the contents of the body
		this.bodyBackgroundLayer.dom.innerHTML = '';
		this.bodyAppointmentLayer.dom.innerHTML = '';

		// Create a css grid to easily create the background of the days
		var visibleRange = this.getVisibleDateRange();
		var visibleWeeks = this.getVisibleWeekCount(visibleRange);
		var templateColumns = '';
		for ( var col=0; col<this.numDaysInWeek; col++) {
			templateColumns += 'calc(100%/' + this.numDaysInWeek + ') ';
		}
		var rowColumns = '';
		for ( var row=0; row<visibleWeeks; row++) {
			rowColumns += 'calc(100%/' + visibleWeeks + ') ';
		}
		this.bodyBackgroundLayer.setStyle({
			'grid-template-rows': rowColumns,
			'grid-template-columns': templateColumns,
			'msGridRows': rowColumns,
			'msGridColumns': templateColumns
		});

		var headerBgColor = Zarafa.core.ColorSchemes.createLightColor(this.calendarColorScheme.base, 1.6);
		var boxWidth = this.bodyAppointmentLayer.getWidth() / this.numDaysInWeek;
		var boxHeight = this.bodyAppointmentLayer.getHeight() / visibleWeeks;
		this.dayBoxConfigurations.forEach(function(dayBox, i) {
			var d = i % this.numDaysInWeek;
			var w = Math.floor(i / this.numDaysInWeek);
			var nextDay = this.dayBoxConfigurations[i+1];
			var className = 'k-day-' + d + ' k-week-' + w;
			if ( dayBox.today ) {
				className += ' k-action-border k-today';
			}
			if ( nextDay && nextDay.today && Math.floor((i+1) / this.numDaysInWeek) === w ) {
				className += ' k-action-border';
			}

			// Determine the background color of the day box body area.
			var bgColor = '';
			if (dayBox.active) {
				switch (dayBox.busyStatus) {
					case Zarafa.core.mapi.BusyStatus.FREE:
						bgColor = 'white';
						break;
					case Zarafa.core.mapi.BusyStatus.BUSY:
						bgColor = '#d7e2f1';
						break;
					case Zarafa.core.mapi.BusyStatus.TENTATIVE:
						bgColor = this.calendarColorScheme.stripnormal;
						className += 'k-status-tentative';
						break;
					case Zarafa.core.mapi.BusyStatus.OUTOFOFFICE:
						bgColor = '#e7d7ef';
						break;
					case Zarafa.core.mapi.BusyStatus.WORKINGELSEWHERE:
						bgColor = '#dbe8f5';
						break;
					default:
						break;
				}
			} else {
				// Set the color for non-active days (in this view these are the days of other months)
				bgColor = Zarafa.core.ColorSchemes.createLightColor(this.calendarColorScheme.base, 1.9);
			}

			var cell = this.bodyBackgroundLayer.createChild({
				cls: className,
				html: '',
				style: 'grid-column:' + (d+1) + ' / ' + (d+1) + ';' +
						'-ms-grid-column:' + (d+1) + ';' +
						'-ms-grid-column-span: 1;' +
						'grid-row:' + (w+1) + ' / ' + (w+1) + ';' +
						'-ms-grid-row:' + (w+1) + ';' +
						'-ms-grid-row-span: 1;' +
						'border-color:' + this.calendarColorScheme.header + ';' +
						'background-color:' + bgColor
			});

			var hdr = cell.createChild({
				cls: 'k-header',
				html: dayBox.date.format(_("jS")),
				style: 'background:' + headerBgColor,
				role: 'button',
				tabindex: '0',
				'aria-label': dayBox.date.format(_("F j"))
			});
			hdr.on('click', this.onClickToOpenDayView.createDelegate(this, [dayBox.date]), this);
			hdr.on('keydown', function(e) {
				if (e.getKey() === e.ENTER || e.getKey() === e.SPACE) {
					e.stopEvent();
					this.onClickToOpenDayView(dayBox.date);
				}
			}, this);

			// TODO: This should be drawn together with the appointment instead of with the background,
			// so we can split it later.
			// Draw expand button. Draw it in the appointment layer so it won't be covered
			// by appointments or selections and we can interact with it
			if (dayBox.overflow) {
				var expandBtn = this.bodyAppointmentLayer.createChild({
					cls: 'k-expand-btn',
					'ext:qtip': _('Switch to day view to see all appointments'),
					'ext:qwidth': 'auto',
					role: 'button',
					tabindex: '0',
					'aria-label': _('Show all appointments'),
					style: 'border-color:' + this.calendarColorScheme.header + ';' +
							'left:' + ((d + 1) * boxWidth - 20) + 'px;' +
							'top:' + ((w + 1) * boxHeight - 20) + 'px;'
				});

				// Add handler
				expandBtn.on('click', this.onClickToOpenDayView.createDelegate(this, [dayBox.date]), this);
				expandBtn.on('keydown', function(e) {
					if (e.getKey() === e.ENTER || e.getKey() === e.SPACE) {
						e.stopEvent();
						this.onClickToOpenDayView(dayBox.date);
					}
				}, this);
			}

		}, this);
	},

	/**
	 * Event handler for the mouse click event on header or the expand button of a day. Will fire the
	 * {#dayclick} event that will be relayed by the parent views until the
	 * {@link Zarafa.calendar.ui.CalendarBlockPanel#onDayCLick onDayCLick} handler will process it.
	 * @param {Date} date The date of the day that needs to be shown
	 */
	onClickToOpenDayView: function(date) {
		this.fireEvent('dayclick', this, date);
	},

	/**
	 * Event handler for the right-button mouse click event on the calendar body.
	 * @param {Ext.EventObject} event ExtJS event object
	 * @private
	 */
	onBodyContextMenu: function(event)
	{
		var xy = event.getXY();
		var range = this.screenLocationToDateRange(xy[0], xy[1]);

		// Fire the calendar's context menu event.
		// Since the user did not click on an appointment (will be handled by the appointment classes),
		// we do not have a record. So we'll pass null
		this.fireEvent('contextmenu', this, event, null, range);
	},

	/**
	 * Event handler which is fired when the {@link #selectionModel} fires the
	 * {@link Zarafa.calendar.ui.AppointmentSelectionModel#appointmentdeselect appointmentdeselect} event.
	 * @param {Zarafa.calendar.ui.AppointmentSelectionModel} selectionModel The selection model which fired the event.
	 * @param {Zarafa.core.data.IPMRecord} record The record which was deselected.
	 * @private
	 * @override
	 */
	onAppointmentDeselect: function(selectionModel, record)
	{
		Zarafa.calendar.ui.html.CalendarBoxView.superclass.onAppointmentDeselect.call(this, selectionModel, record);

		var appointment = this.findAppointment(record);
		if ( appointment ) {
			appointment.setSelected(false);
		}
	},

	/**
	 * Lays out the view. This function is called after {@link #render} and is used
	 * to update the view to the latest situation. When an appointment, or setting
	 * has been changed, the {@link #layout} function must change the look to reflect
	 * the new changes.
	 * @protected
	 */
	onLayout: function()
	{
		// Perform greedy coloring on the appointments. This calculates how overlapping appointments are laid out
		// in rows on the view.
		this.doGreedyColoring(this.appointments, true);

		// Parent layout
		Zarafa.calendar.ui.html.CalendarBoxView.superclass.onLayout.call(this);

		this.container.removeClass('k-calendar-day-view-1-days');
		this.container.removeClass('k-calendar-day-view-5-days');
		this.container.removeClass('k-calendar-day-view-7-days');
		this.container.addClass('k-calendar-month-view');

		// Check if we have a light or dark color scheme
		var isDarkColor = Zarafa.core.ColorSchemes.isDark(this.calendarColorScheme.base);
		if ( isDarkColor ){
			this.headerBackgroundLayer.removeClass('light-background');
		} else {
			this.headerBackgroundLayer.addClass('light-background');
		}

		// Update the height of the body.
		var height = this.parentView.scrollable.getHeight();
		this.body.setHeight(height);

		// Calculate the positions of all dayBoxes, and start layout
		// to position the div elements.
		this.calculateDayBoxConfigurations();
		this.drawDayHeaders();
		this.drawDays();
	}
});
Ext.namespace('Zarafa.calendar.ui.html');

/**
 * @class Zarafa.calendar.ui.html.CalendarDaysView
 * @extends Zarafa.calendar.ui.AbstractCalendarDaysView
 *
 * HTML based implementation of {@link Zarafa.calendar.ui.AbstractCalendarDaysView AbstractCalendarDaysView}.
 *
 * Using layering we can optimize the rendering of the calendar. Whenever an appoint changes, we are not
 * interested in redrawing the complete calendar and all appointments in it (This would become a performance
 * issue for calendars with many appointments). So with 2 layers we can create the following setup:
 *
 *  - Layer 1: The calendar
 *    This contains the calendar background. For the header this means the background color, and the
 *    name of the date which is being displayed in the color (optionally additional markup is applied
 *    in case the rendered date is 'today'). For the body this means that all timestrips are rendered
 *    into the calendar (using the correct theme color).
 *  - Layer 2: The appointments
 *    This contains all the appointments which are available in the calendar. None of the appointments
 *    are allowed to overlap with another appointment (The time can obviously overlap, but in that case
 *    the appointments are placed side-by-side inside the same column). Whenever a single appointment
 *    changes, we only have to redraw that single appointment (since it doesn't overlap with any other
 *    appointment, we can be sure that redrawing the appointment will not affect other appointments).
 */
Zarafa.calendar.ui.html.CalendarDaysView = Ext.extend(Zarafa.calendar.ui.AbstractCalendarDaysView, {
	/**
	 * The element for the top-row in the calendar which contains the
	 * dayname for each individual column, as well as the display for all all-day appointments.
	 * This element is 'layer 1' for the header, containing only the background.
	 * @property
	 * @type Ext.Element
	 */
	headerBackgroundLayer: undefined,

	/**
	 * The element for the top-row in the calendar which contains the
	 * dayname for each individual column, as well as the display for all all-day appointments.
	 * This element contains the appointments and sets the background color of the header.
	 * @property
	 * @type Ext.Element
	 */
	headerAppointmentLayer: undefined,

	/**
	 * The element for the body in the calendar which contains the
	 * timestrips and the appointments.
	 * This element is 'layer 1' for the body, containing only the background.
	 * @property
	 * @type Ext.Element
	 */
	bodyBackground: undefined,

	/**
	 * The element for the body in the calendar which contains the
	 * timestrips and the appointments.
	 * This element is 'layer 2' for the body, containing only the appointments.
	 * @property
	 * @type Ext.Element
	 */
	bodyAppointment: undefined,

	/**
	 * The Appointment over which the cursor is currently hovering.
	 * @property
	 * @type Zarafa.calendar.ui.html.AppointmentDaysView
	 */
	appointmentOver: undefined,

	/**
	 * The &lt;img&gt; element which indicate current time on time strip and
	 * placed at the left of {@link #indicatorLine}.
	 * This element is created using {@link #create} during {@link #render}.
	 * @property
	 * @type Ext.Element
	 */
	indicatorIcon: undefined,

	/**
	 * The &lt;div&gt; element which is used to draw a line on time strip.
	 * This element is created using {@link #createDiv} during {@link #render}.
	 * @property
	 * @type Ext.Element
	 */
	indicatorLine: undefined,

	/**
	 * The task which gets executed every minute in a multi-threaded manner and
	 * draws indicator elements honoring current time.
	 * @property
	 * @type Object
	 */
	indicatorTask: undefined,

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

		Ext.applyIf(config, {
			baseCls: 'zarafa-calendar',
			enableDD: true,
			ddGroup: 'dd.mapiitem'
		});

		Zarafa.calendar.ui.html.CalendarDaysView.superclass.constructor.call(this, config);

		this.on('destroy', this.onDestroy, this);
	},

	/**
	 * Renders the view. Generates the body and header layers.
	 * @param {Ext.Element} container The Ext.Element into which the view must be rendered.
	 * @private
	 * @override
	 */
	render: function(container)
	{
		Zarafa.calendar.ui.html.CalendarDaysView.superclass.render.call(this, container);

		this.dayPositions = this.calculateDayLayoutPositions();

		this.headerBackgroundLayer = this.header.createChild({cls: 'k-html-header-background'});
		this.headerAppointmentLayer = this.header.createChild({cls: 'k-html-header-appointment'});

		this.bodyBackground = this.body.createChild({cls: 'k-html-body-background'});
		this.bodyAppointment = this.body.createChild({cls: 'k-html-body-appointment'});

		// Hook mouse events to the selection layers
		this.mon(this.getHeaderAppointmentLayer(), {
			'contextmenu': this.onBodyContextMenu,
			scope: this
		});
		this.mon(this.getBodyAppointmentLayer(), {
			'contextmenu': this.onBodyContextMenu,
			scope: this
		});

		// Create current-time indicator, line and icon
		this.create({tag: 'img', src: Ext.BLANK_IMAGE_URL}, this.parentView.scrollable, 'indicatorIcon', 'icon_indicator_calendar k-calendar-timestrip-indicator-icon');
		this.createDiv(this.parentView.scrollable, 'indicatorLine', 'k-calendar-timestrip-indicator-line');
	},

	/**
	 * Handler which executes once the {@link Ext.Component} gets destroyed.
	 * This will stop the {@link #indicatorTask}.
	 * @param {Ext.Component} component The component which gets destroyed
	 * must be created.
	 * @private
	 */
	onDestroy: function(component)
	{
		if(this.indicatorTask){
			Ext.TaskMgr.stop(this.indicatorTask);
		}
	},

	/**
	 * Create a new {@link Zarafa.calendar.ui.AppointmentView Appointment} object
	 * for the given {@link Zarafa.core.data.IPMRecord record}.
	 * @param {Zarafa.core.data.IPMRecord} record The record for which the AppointmentView
	 * must be created.
	 * @private
	 */
	createAppointment: function(record)
	{
		return new Zarafa.calendar.ui.html.AppointmentDaysView({
			parentView: this,
			record: record,
			calendarColorScheme: this.calendarColorScheme
		});
	},

	/**
	 * Removes a child view from this view.
	 * @param {Zarafa.core.ui.View} child child view to be removed
	 * @param {Boolean} destroy if true, destroy the child view
	 * @override
	 */
	removeChildView: function(child, destroy)
	{
		// If the childview was the appointment we are hovering over,
		// we need to reset the appointmentOver property.
		if (this.appointmentOver && this.appointmentOver === child) {
			this.fireEvent('appointmentmouseout', this, this.appointmentOver.getRecord());
			this.appointmentOver = false;
		}

		return Zarafa.calendar.ui.html.CalendarDaysView.superclass.removeChildView.apply(this, arguments);
	},

	/**
	 * Create an Appointment Proxy object which can represent the selected time slot
	 * @return Zarafa.calendar.ui.html.AppointmentProxy
	 * @protected
	 */
	createAppointmentProxy: function()
	{
		return new Zarafa.calendar.ui.html.AppointmentProxy({ showTime: true });
	},

	/**
	 * Returns the element which must be used to draw Appointments for the body ({@link #bodyAppointment Layer2}).
	 * @return {Ext.Element} The calendar body element
	 */
	getBodyAppointmentLayer: function()
	{
		return this.bodyAppointment;
	},

	/**
	 * Returns the {@link #headerAppointmentLayer element} which must be used to draw Appointments for the header.
	 * @return {Ext.Element} The calendar header element
	 */
	getHeaderAppointmentLayer: function()
	{
		return this.headerAppointmentLayer;
	},

	/**
	 * Basically draws the rendered header. Creates the CSS grid columns for the calendar header.
	 * Sets the background color. Sets the text on the headers for each day.
	 * The header title is generated using the {@link #getDayHeaderTitle} function.
	 *
	 * @param {Array} dayPositions The array of {@link Zarafa.calendar.data.DayLayoutPosition LayoutPositions} for the various days.
	 * @private
	 */
	drawHeader: function(dayPositions)
	{
		// Resize the header
		var width = this.header.getWidth();
		var height = this.header.getHeight();
		this.headerBackgroundLayer.setSize(width, height);
		this.headerBackgroundLayer.dom.innerHTML = '';
		this.headerAppointmentLayer.setSize(width, height);
		this.headerAppointmentLayer.dom.innerHTML = '';

		// Set the background color
		var headerColor = this.calendarColorScheme.header;
		this.headerBackgroundLayer.setStyle({
			'background': headerColor
		});

		// Check if we have a light or dark color
		var isDarkColor = Zarafa.core.ColorSchemes.isDark(this.calendarColorScheme.base);
		if ( isDarkColor ){
			this.headerBackgroundLayer.addClass('k-dark');
		} else {
			this.headerBackgroundLayer.removeClass('k-dark');
		}

		// Mark inactive calendars so CSS can mute them
		if ( this.active ){
			this.headerBackgroundLayer.removeClass('k-inactive-calendar');
		} else {
			this.headerBackgroundLayer.addClass('k-inactive-calendar');
		}

		// Set the correct number of css grid columns for the header, with the correct width
		var gridTemplateColumns = dayPositions.map(function(pos) {
			return (100/dayPositions.length) + '% ';
		}).join(' ');
		this.headerBackgroundLayer.setStyle({
			'grid-template-columns': gridTemplateColumns,
			'msGridColumns': gridTemplateColumns
		});

		// Create a new div (column) for every day
		dayPositions.forEach(function(pos, i) {
			var className = 'k-day-'+i + (pos.today ? ' k-today k-active-border' : '');
			if ( dayPositions[i+1] && dayPositions[i+1].today ) {
				className += ' k-active-border';
			}
			this.headerBackgroundLayer.createChild({
				cls: className,
				style: '-ms-grid-column: ' + (i+1),
				html: '<div class="k-cal-header-title">' + this.getDayHeaderTitle(pos.date, pos.right - pos.left) + '</div>'
			});
		}, this);
	},

	/**
	 * Draws the calendar body. The body consists of dayStrips which indicate the borders
	 * between the different strips, the workingHours, and the horizontal lines indicating
	 * the time strips.
	 * @param {Array} dayPositions The array of {@link Zarafa.calendar.data.DayLayoutPosition LayoutPositions} for the various days.
	 * @private
	 */
	drawBody: function(dayPositions)
	{
		//var todayPosition;
		var width = this.body.getWidth();
		var height = this.body.getHeight();
		this.bodyBackground.setSize(width, height);
		this.bodyBackground.dom.innerHTML = '';
		this.bodyAppointment.setSize(width, height);
		this.bodyAppointment.dom.innerHTML = '';

		var unitHeight = this.parentView.timeUnitHeight;
		var numLines = Math.round(height / unitHeight);
		var workStart = (this.parentView.firstWorkingHour / 60) * (numLines/24) + 1; // grid numbering starts at 1, so add 1
		var workEnd = (this.parentView.lastWorkingHour / 60) * (numLines/24) + 1; // grid numbering starts at 1, so add 1

		var templateColumns = '';
		for ( var col=0; col<dayPositions.length; col++) {
			templateColumns += 'calc(100%/' + dayPositions.length + ') ';
		}
		var rowColumns = '';
		for ( var row=0; row<numLines; row++) {
			rowColumns += 'calc(100%/' + numLines + ') ';
		}
		this.bodyBackground.setStyle({
			'grid-template-rows': rowColumns,
			'grid-template-columns': templateColumns,
			'msGridRows': rowColumns,
			'msGridColumns': templateColumns
		});
		this.bodyAppointment.setStyle({
			'grid-template-columns': templateColumns,
			'msGridColumns': templateColumns
		});

		// draw the lines of the background
		var html = '';
		for (var i = 0, len = dayPositions.length; i < len; i++) {
			var pos = dayPositions[i];
			var nextDay = dayPositions[i+1];

			for ( var h=0; h<numLines; h++ ) {
				// Add a cell to the grid
				var className = 'k-day-' + i + ' k-line-' + h + (dayPositions[i].today ? ' k-today' : '');
				var style = 'grid-column: ' + (i+1) + ' / ' + (i+1) + '; ' +
							'-ms-grid-column:' + (i+1) + ';' +
							'-ms-grid-column-span: 1;' +
							'grid-row:' + (h+1) + ' / ' + (h+1) + '; ' +
							'-ms-grid-row:' + (h+1) + ';' +
							'-ms-grid-row-span: 1;' +
							'border-bottom-color:' + this.calendarColorScheme.linenormal + ';';

				// Draw a border in active color around today
				// TODO(Ronald): Update JSON themes and core themes and example theme
				if ( pos.today || (nextDay && nextDay.today) ) {
					className += ' k-active-border';
				} else {
					style += ' border-color:' + this.calendarColorScheme.linenormal + ';';
				}

				// draw a lighter shade of background for days that are working days
				if (pos.workingDay && h+1>=workStart && h+1<workEnd) {
					style += ' background-color: ' + this.calendarColorScheme.stripworking + ';';
				} else {
					style += ' background-color: ' + this.calendarColorScheme.stripnormal + ';';
				}

				html += '<div class="' + className + '" style="' + style + '"></div>';
			}
		}
		this.bodyBackground.dom.innerHTML = html;
	},

	/**
	 * Draws thin line over calendar. The line indicates current-time.
	 * @private
	 */
	drawCurrentTimeIndicator: function()
	{
		if (!this.parentView) {
			return;
		}

		// Layout current-time indicator components based on current date and time
		var totalHeight = this.parentView.numHours * this.parentView.getHourHeight();
		var verticalPosition = Math.floor(this.getDateVerticalPosition(new Date()) * totalHeight);

		// Minus 6 pixel because current-time denoted by the middle of icon
		// And the size of icon is 12x12 pixels
		this.indicatorIcon.setTop(verticalPosition - 6);
		this.indicatorLine.setWidth(this.width + this.leftOffset);

		// Minus 0.5 pixel because the 1px-thick-line should positioned exactly in middle of icon
		this.indicatorLine.setTop(verticalPosition - 0.5);
	},

	/**
	 * Tests if a given mouse event is over a header. Used to implement the functionality where the user clicks
	 * on the header of a day strip zoom to that day.
	 * @param {Ext.EventObject} event ExtJS event object
	 * @return {Boolean} True iff the user clicked on the header area
	 * @private
	 */
	eventInHeader: function(event)
	{
		// Calculate the x,y position of the mouse relative the the body layer.
		var element = this.getHeaderActionLayer();
		var x = event.getPageX() - element.getX();
		var y = event.getPageY() - element.getY();

		// If the y-position is out of range, return false.
		if (y > this.parentView.headerTextHeight) {
			return false;
		}

		return (!Ext.isEmpty(this.screenLocationToDate(x, y)));
	},

	/**
	 * This will determine on which {@link Zarafa.core.data.IPMRecord record}
	 * the event was fired.
	 * @param {Ext.EventObject} event The event for which the selection must be updated.
	 * @return {Zarafa.core.data.IPMRecord} The record (if any) on which
	 * the event was fired.
	 * @private
	 */
	getRecordForHeaderEvent: function(event)
	{
		// Check if the mouse is over one of the appointments.
		for (var i = 0, len = this.appointments.length; i < len; i++) {
			var appointment = this.appointments[i];
			if (appointment.eventOverHeader(event)) {
				return appointment.getRecord();
			}
		}
	},

	/**
	 * Event handler for the right-button mouse click event on the calendar header. It checks to see if there's
	 * an appointment under the mouse.
	 * @param {Ext.EventObject} event ExtJS event object
	 * @private
	 */
	onHeaderContextMenu: function(event)
	{
		var record = this.getRecordForHeaderEvent(event);
		var range;

		if (!record) {
			var xy = event.getXY();
			range = this.screenLocationToDateRange(xy[0], xy[1]);
		}

		// Fire the calendar's context menu event.
		this.fireEvent('contextmenu', this, event, record, range);
	},

	/**
	 * Event handler for the right-button mouse click event on the calendar body.
	 * The contextmenu event of appointments will be handled by the AppointmentView.
	 * @param {Ext.EventObject} event ExtJS event object
	 * @private
	 */
	onBodyContextMenu: function(event)
	{
		var xy = event.getXY();
		var range = this.screenLocationToDateRange(xy[0], xy[1]);

		// Fire the calendar's context menu event.
		this.fireEvent('contextmenu', this, event, null, range);
	},

	/**
	 * Event handler which is fired when the {@link #selectionModel} fires the
	 * {@link Zarafa.calendar.ui.AppointmentSelectionModel#appointmentdeselect appointmentdeselect} event.
	 *
	 * @param {Zarafa.calendar.ui.AppointmentSelectionModel} selectionModel The selection model which fired the event.
	 * @param {Zarafa.core.data.IPMRecord} record The record which was deselected.
	 * @private
	 * @override
	 */
	onAppointmentDeselect: function(selectionModel, record)
	{
		Zarafa.calendar.ui.html.CalendarDaysView.superclass.onAppointmentDeselect.call(this, selectionModel, record);

		var appointment = this.findAppointment(record);
		if ( appointment ) {
			appointment.setSelected(false);
		}
	},

	/**
	 * Lays out the view. This function is called after {@link #render} and is used
	 * to update the view to the latest situation. When an appointment, or setting
	 * has been changed, the {@link #layout} function must change the look to reflect
	 * the new changes.
	 * @protected
	 */
	onLayout: function()
	{
		Zarafa.calendar.ui.html.CalendarDaysView.superclass.onLayout.call(this);

		var dayPositions = this.calculateDayLayoutPositions();

		this.container.removeClass('k-calendar-month-view');
		this.container.removeClass('k-calendar-day-view-1-days');
		this.container.removeClass('k-calendar-day-view-5-days');
		this.container.removeClass('k-calendar-day-view-7-days');
		if ( dayPositions.length <= 7 ) {
			this.container.addClass('k-calendar-day-view-' + dayPositions.length + '-days');
		}

		this.drawHeader(dayPositions);
		this.drawBody(dayPositions);

		if ( this.getDateRange().containsDate(new Date()) ) {
			this.setIndicatorDisplayed(true);
			this.drawCurrentTimeIndicator();

			if (!Ext.isDefined(this.indicatorTask)) {
				this.indicatorTask = Ext.TaskMgr.start({
					run: function(){
						this.drawCurrentTimeIndicator();
					},
					scope: this,
					interval: 60000
				});
			}
		} else {
			this.setIndicatorDisplayed(false);

			if (this.indicatorTask) {
				Ext.TaskMgr.stop(this.indicatorTask);
			}
		}
	},

	/**
	 * Hides or shows {@link #indicatorIcon} and {@link #indicatorLine}.
	 * @param {Boolean} value True to display the element, false otherwise.
	 */
	setIndicatorDisplayed: function(value)
	{
		this.indicatorIcon.setDisplayed(value);
		this.indicatorLine.setDisplayed(value);
	}
});
Ext.namespace('Zarafa.common');

/**
 * @class Zarafa.common.CommonContext
 * @extends Zarafa.core.Context
 */
Zarafa.common.CommonContext = Ext.extend(Zarafa.core.Context, {
	/**
	 * @constructor
	 * @param config
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			hasToolbar: false,
			hasContentPanel: false
		});

		Zarafa.common.CommonContext.superclass.constructor.call(this, config);

		// Register categories for the settings
		this.registerInsertionPoint('context.settings.categories', this.createDelegateSettingsCategory, this);
		this.registerInsertionPoint('context.settings.categories', this.createSendAsSettingsCategory, this);
		this.registerInsertionPoint('context.settings.categories', this.createRuleSettingsCategory, this);
		//this.registerInsertionPoint('context.settings.categories', this.createNotificationSettingsCategory, this);

		// Register common specific dialog types
		Zarafa.core.data.SharedComponentType.addProperty('common.dialog.copymoverecords');
		Zarafa.core.data.SharedComponentType.addProperty('common.dialog.recurrence');
		Zarafa.core.data.SharedComponentType.addProperty('common.dialog.categories');
		Zarafa.core.data.SharedComponentType.addProperty('common.dialog.widgets');
		Zarafa.core.data.SharedComponentType.addProperty('common.dialog.checknames');
		Zarafa.core.data.SharedComponentType.addProperty('common.dialog.restoreitems');
		Zarafa.core.data.SharedComponentType.addProperty('common.dialog.reminder');
		Zarafa.core.data.SharedComponentType.addProperty('common.contextmenu.previewpanel.extrainfo');
		Zarafa.core.data.SharedComponentType.addProperty('common.contextmenu.importance');
		Zarafa.core.data.SharedComponentType.addProperty('common.contextmenu.category');
		Zarafa.core.data.SharedComponentType.addProperty('common.contextmenu.categories');
		Zarafa.core.data.SharedComponentType.addProperty('common.contextmenu.freebusy');
		Zarafa.core.data.SharedComponentType.addProperty('common.contextmenu.freebusy.timelinebody');
		Zarafa.core.data.SharedComponentType.addProperty('common.contextmenu.freebusy.timelineheader');
		Zarafa.core.data.SharedComponentType.addProperty('common.contextmenu.flags');
		Zarafa.core.data.SharedComponentType.addProperty('common.printer.renderer');
		Zarafa.core.data.SharedComponentType.addProperty('common.rules.dialog.ruleswordsedit');
		Zarafa.core.data.SharedComponentType.addProperty('common.attachment.dialog.attachitem');
		Zarafa.core.data.SharedComponentType.addProperty('common.attachment.dialog.mixattachitem');
		Zarafa.core.data.SharedComponentType.addProperty('common.attachment.dialog.attachitem.columnmodel');
		Zarafa.core.data.SharedComponentType.addProperty('common.attachment.dialog.attachitem.textrenderer');
		Zarafa.core.data.SharedComponentType.addProperty('common.attachment.dialog.importtofolder');
		Zarafa.core.data.SharedComponentType.addProperty('common.sendas.dialog.sendaseditcontentpanel');
		Zarafa.core.data.SharedComponentType.addProperty('common.managecc.dialog.managecceditcontentpanel');
		Zarafa.core.data.SharedComponentType.addProperty('common.categories.dialogs.newcategory');
		Zarafa.core.data.SharedComponentType.addProperty('common.categories.dialogs.renamecategory');
		Zarafa.core.data.SharedComponentType.addProperty('common.flags.dialogs.customflag');
	},

	/**
	 * Bid for the type of shared component and the given record.
	 * @param {Zarafa.core.data.SharedComponentType} type Type of component a context can bid for.
	 * @param {Ext.data.Record} record Optionally passed record.
	 * @return {Number} The bid for the shared component
	 */
	bidSharedComponent: function(type, record)
	{
		var bid = -1;

		if (Array.isArray(record)) {
			record = record[0];
		}

		switch (type) {
			// Bid for common dialogs
			case Zarafa.core.data.SharedComponentType['common.dialog.copymoverecords']:
			case Zarafa.core.data.SharedComponentType['common.dialog.recurrence']:
			case Zarafa.core.data.SharedComponentType['common.dialog.categories']:
			case Zarafa.core.data.SharedComponentType['common.attachment.dialog.attachitem']:
			case Zarafa.core.data.SharedComponentType['common.attachment.dialog.mixattachitem']:
			case Zarafa.core.data.SharedComponentType['common.attachment.dialog.importtofolder']:
			case Zarafa.core.data.SharedComponentType['common.dialog.widgets']:
			case Zarafa.core.data.SharedComponentType['common.dialog.checknames']:
			case Zarafa.core.data.SharedComponentType['common.dialog.restoreitems']:
			case Zarafa.core.data.SharedComponentType['common.dialog.reminder']:
			case Zarafa.core.data.SharedComponentType['common.contextmenu.previewpanel.extrainfo']:
			case Zarafa.core.data.SharedComponentType['common.contextmenu.freebusy.timelinebody']:
			case Zarafa.core.data.SharedComponentType['common.contextmenu.freebusy.timelineheader']:
			case Zarafa.core.data.SharedComponentType['common.contextmenu.importance']:
			case Zarafa.core.data.SharedComponentType['common.contextmenu.category']:
			case Zarafa.core.data.SharedComponentType['common.contextmenu.flags']:
			case Zarafa.core.data.SharedComponentType['common.contextmenu.categories']:
			case Zarafa.core.data.SharedComponentType['common.rules.dialog.ruleswordsedit']:
			case Zarafa.core.data.SharedComponentType['common.sendas.dialog.sendaseditcontentpanel']:
			case Zarafa.core.data.SharedComponentType['common.managecc.dialog.managecceditcontentpanel']:
			case Zarafa.core.data.SharedComponentType['common.categories.dialogs.newcategory']:
			case Zarafa.core.data.SharedComponentType['common.categories.dialogs.renamecategory']:
			case Zarafa.core.data.SharedComponentType['common.flags.dialogs.customflag']:
				bid = 1;
				break;
			case Zarafa.core.data.SharedComponentType['common.contextmenu.freebusy']:
				if (record instanceof Zarafa.core.data.IPMRecipientRecord) {
					bid = 1;
				}
				break;
			case Zarafa.core.data.SharedComponentType['common.contextmenu']:
				if (record instanceof Zarafa.core.data.IPMRecipientRecord ||
					record instanceof Zarafa.core.data.IPMAttachmentRecord ||
					record instanceof Zarafa.common.manageCc.data.IPMCcRecipientRecord) {
					bid = 1;
				}
				break;
			case Zarafa.core.data.SharedComponentType['common.create']:
				if (record instanceof Zarafa.core.data.IPMRecipientRecord) {
					bid = 1;
				} else if(record instanceof Zarafa.common.delegates.data.DelegateRecord) {
					bid = 1;
				} else if(record instanceof Zarafa.common.rules.data.RulesRecord) {
					bid = 1;
				}
				break;
			case Zarafa.core.data.SharedComponentType['common.view']:
				if (record instanceof Zarafa.core.data.IPMRecipientRecord) {
					bid = 1;
				} else if(record instanceof Zarafa.core.data.IPMAttachmentRecord) {
					bid = 0;
				}
				break;
			case Zarafa.core.data.SharedComponentType['common.hovercard']:
				if (record instanceof Zarafa.core.data.IPMRecipientRecord) {
					bid = 1;
				}
				break;
		}
		return bid;
	},

	/**
	 * Will return the reference to the shared component.
	 * Based on the type of component requested a component is returned.
	 * @param {Zarafa.core.data.SharedComponentType} type Type of component a context can bid for.
	 * @param {Ext.data.Record} record Optionally passed record.
	 * @return {Ext.Component} Component
	 */
	getSharedComponent: function(type, record)
	{
		var component;
		switch (type) {
			case Zarafa.core.data.SharedComponentType['common.dialog.copymoverecords']:
				component = Zarafa.common.dialogs.CopyMoveContentPanel;
				break;
			case Zarafa.core.data.SharedComponentType['common.dialog.recurrence']:
				component = Zarafa.common.recurrence.dialogs.RecurrenceContentPanel;
				break;
			case Zarafa.core.data.SharedComponentType['common.dialog.categories']:
				component = Zarafa.common.categories.dialogs.CategoriesContentPanel;
				break;
			case Zarafa.core.data.SharedComponentType['common.attachment.dialog.attachitem']:
				component = Zarafa.common.attachment.dialogs.AttachItemContentPanel;
				break;
			case Zarafa.core.data.SharedComponentType['common.attachment.dialog.mixattachitem']:
				component = Zarafa.common.attachment.dialogs.MixAttachItemContentPanel;
				break;
			case Zarafa.core.data.SharedComponentType['common.attachment.dialog.importtofolder']:
				component = Zarafa.common.attachment.dialogs.ImportToFolderContentPanel;
				break;
			case Zarafa.core.data.SharedComponentType['common.dialog.widgets']:
				component = Zarafa.core.ui.widget.WidgetContentPanel;
				break;
			case Zarafa.core.data.SharedComponentType['common.dialog.checknames']:
				component = Zarafa.common.checknames.dialogs.CheckNamesContentPanel;
				break;
			case Zarafa.core.data.SharedComponentType['common.dialog.restoreitems']:
				component = Zarafa.common.restoreitem.dialogs.RestoreItemContentPanel;
				break;
			case Zarafa.core.data.SharedComponentType['common.dialog.reminder']:
				component = Zarafa.common.reminder.dialogs.ReminderContentPanel;
				break;
			case Zarafa.core.data.SharedComponentType['common.rules.dialog.ruleswordsedit']:
				component = Zarafa.common.rules.dialogs.RulesWordsEditContentPanel;
				break;
			case Zarafa.core.data.SharedComponentType['common.managecc.dialog.managecceditcontentpanel']:
				component = Zarafa.common.manageCc.dialogs.ManageCcEditContentPanel;
				break;
			case Zarafa.core.data.SharedComponentType['common.sendas.dialog.sendaseditcontentpanel']:
				component = Zarafa.common.sendas.dialogs.SendAsEditContentPanel;
				break;

			case Zarafa.core.data.SharedComponentType['common.create']:
				if (record instanceof Zarafa.core.data.IPMRecipientRecord) {
					component = Zarafa.common.recipientfield.ui.EditRecipientContentPanel;
				} else if(record instanceof Zarafa.common.delegates.data.DelegateRecord) {
					component = Zarafa.common.delegates.dialogs.DelegatePermissionContentPanel;
				} else if(record instanceof Zarafa.common.rules.data.RulesRecord) {
					component = Zarafa.common.rules.dialogs.RulesEditContentPanel;
				}
				break;
			case Zarafa.core.data.SharedComponentType['common.view']:
				if (record instanceof Zarafa.core.data.IPMRecipientRecord) {
					component = Zarafa.common.recipientfield.ui.ViewRecipientContentPanel;
				} else if (record instanceof Zarafa.core.data.IPMAttachmentRecord) {
					if (Zarafa.common.Actions.isSupportedDocument(record.get('name')) && Zarafa.common.Actions.isFilePreviewerEnabled()) {
						component = Zarafa.common.previewer.ui.ViewerContainer;
					} else {
						component = this;
					}
				}
				break;
			case Zarafa.core.data.SharedComponentType['common.contextmenu.previewpanel.extrainfo']:
				component = Zarafa.common.ui.messagepanel.ExtraInfoContextMenu;
				break;
			case Zarafa.core.data.SharedComponentType['common.contextmenu.freebusy']:
				component = Zarafa.common.freebusy.ui.FreebusyContextMenu;
				break;
			case Zarafa.core.data.SharedComponentType['common.contextmenu.freebusy.timelinebody']:
				component = Zarafa.common.freebusy.ui.FreebusyTimelineBodyContextMenu;
				break;
			case Zarafa.core.data.SharedComponentType['common.contextmenu.freebusy.timelineheader']:
				component = Zarafa.common.freebusy.ui.FreebusyTimelineHeaderContextMenu;
				break;
			case Zarafa.core.data.SharedComponentType['common.contextmenu']:
				if(record instanceof Zarafa.common.manageCc.data.IPMCcRecipientRecord){
					component = Zarafa.common.manageCc.ui.ManageCcGridContextMenu;
				} else if (record instanceof Zarafa.core.data.IPMRecipientRecord) {
					component = Zarafa.common.recipientfield.ui.RecipientContextMenu;
				} else if (record instanceof Zarafa.core.data.IPMAttachmentRecord) {
					component = Zarafa.common.attachment.ui.AttachmentContextMenu;
				}
				break;
			case Zarafa.core.data.SharedComponentType['common.contextmenu.importance']:
				component = Zarafa.common.ui.ImportanceMenu;
				break;
			case Zarafa.core.data.SharedComponentType['common.contextmenu.category']:
				component = Zarafa.common.categories.ui.CategoryContextMenu;
				break;
			case Zarafa.core.data.SharedComponentType['common.contextmenu.categories']:
				component = Zarafa.common.categories.ui.CategoriesContextMenu;
				break;
			case Zarafa.core.data.SharedComponentType['common.contextmenu.flags']:
				component = Zarafa.common.flags.ui.FlagsMenu;
				break;
			case Zarafa.core.data.SharedComponentType['common.categories.dialogs.newcategory']:
				component = Zarafa.common.categories.dialogs.NewCategoryPanel;
				break;
			case Zarafa.core.data.SharedComponentType['common.categories.dialogs.renamecategory']:
				component = Zarafa.common.categories.dialogs.RenameCategoryPanel;
				break;
			case Zarafa.core.data.SharedComponentType['common.flags.dialogs.customflag']:
				component = Zarafa.common.flags.dialogs.CustomFlagContentPanel;
				break;
			case Zarafa.core.data.SharedComponentType['common.hovercard']:
				component = Zarafa.common.recipientfield.ui.RecipientHoverCardView;
				break;
		}
		return component;
	},

	/**
	 * Default doOpen that will be called if no other context wins the bid
	 *
	 * @param {Zarafa.core.data.IPMAttachmentRecord} records The record to be opened
	 */
	doOpen: function(record)
	{
		Zarafa.common.Actions.downloadAttachment(record);
	},

	/**
	 * Create the delegate {@link Zarafa.settings.ui.SettingsCategory Settings Category}
	 * to the {@link Zarafa.settings.SettingsContext}. This will create new
	 * {@link Zarafa.settings.ui.SettingsCategoryTab tabs} for the
	 * {@link Zarafa.calendar.ui.SettingsDelegatesCategory Delegate}
	 * in the {@link Zarafa.settings.ui.SettingsCategoryWidgetPanel Widget Panel}.
	 * @param {String} insertionName insertion point name that is currently populated
	 * @param {Zarafa.settings.ui.SettingsMainPanel} settingsMainPanel settings main panel
	 * which is populating this insertion point
	 * @param {Zarafa.settings.SettingsContext} settingsContext settings context
	 * @return {Array} configuration object for the categories to register
	 * @private
	 */
	createDelegateSettingsCategory: function(insertionName, settingsMainPanel, settingsContext)
	{
		return {
			xtype: 'zarafa.settingsdelegatecategory',
			settingsContext: settingsContext
		};
	},

	/**
	 * Create the send as {@link Zarafa.settings.ui.SettingsCategory Settings Category}
	 * to the {@link Zarafa.settings.SettingsContext}. This will create new
	 * {@link Zarafa.settings.ui.SettingsCategoryTab tabs} for the
	 * {@link Zarafa.calendar.ui.SettingsSendAsCategory SendAs}
	 * in the {@link Zarafa.settings.ui.SettingsCategoryWidgetPanel Widget Panel}.
	 * @param {String} insertionName insertion point name that is currently populated
	 * @param {Zarafa.settings.ui.SettingsMainPanel} settingsMainPanel settings main panel
	 * which is populating this insertion point
	 * @param {Zarafa.settings.SettingsContext} settingsContext settings context
	 * @return {Array} configuration object for the categories to register
	 * @private
	 */
	createSendAsSettingsCategory: function(insertionName, settingsMainPanel, settingsContext)
	{
		return {
			xtype: 'zarafa.settingssendascategory',
			settingsContext: settingsContext
		};
	},

	/**
	 * Create the Rule {@link Zarafa.settings.ui.SettingsCategory Settings Category}
	 * to the {@link Zarafa.settings.SettingsContext}. This will create new
	 * {@link Zarafa.settings.ui.SettingsCategoryTab tabs} for the
	 * {@link Zarafa.calendar.ui.SettingsRuleCategory Rules}
	 * in the {@link Zarafa.settings.ui.SettingsCategoryWidgetPanel Widget Panel}.
	 * @param {String} insertionName insertion point name that is currently populated
	 * @param {Zarafa.settings.ui.SettingsMainPanel} settingsMainPanel settings main panel
	 * which is populating this insertion point
	 * @param {Zarafa.settings.SettingsContext} settingsContext settings context
	 * @return {Array} configuration object for the categories to register
	 * @private
	 */
	createRuleSettingsCategory: function(insertionName, settingsMainPanel, settingsContext)
	{
		return {
			xtype: 'zarafa.settingsrulecategory',
			settingsContext: settingsContext
		};
	},

	/**
	 * Create the Notification {@link Zarafa.settings.ui.SettingsCategory Settings Category}
	 * to the {@link Zarafa.settings.SettingsContext}. This will create new
	 * {@link Zarafa.settings.ui.SettingsCategoryTab tabs} for the
	 * {@link Zarafa.common.settings.SettingsNotificationsCategory Notifications}
	 * in the {@link Zarafa.settings.ui.SettingsCategoryWidgetPanel Widget Panel}.
	 * @param {String} insertionName insertion point name that is currently populated
	 * @param {Zarafa.settings.ui.SettingsMainPanel} settingsMainPanel settings main panel
	 * which is populating this insertion point
	 * @param {Zarafa.settings.SettingsContext} settingsContext settings context
	 * @return {Array} configuration object for the categories to register
	 * @private
	 */
	createNotificationSettingsCategory: function(insertionName, settingsMainPanel, settingsContext)
	{
		return {
      xtype: 'zarafa.settingsnotificationscategory',
      settingsContext: settingsContext
    };
	}
});

Zarafa.onReady(function() {
	container.registerContext(new Zarafa.core.ContextMetaData({
		name: 'default',
		allowUserVisible: false,
		pluginConstructor: Zarafa.common.CommonContext
	}));
});
/*
 * #dependsFile client/zarafa/common/rules/data/RulesData.js
 */
Ext.namespace('Zarafa.common.data');


/**
 * @class Zarafa.common.data.ActionRulesFactory
 * The factory class which holds and manages definition of Rules Actions.
 *
 * @singleton
 */
Zarafa.common.data.ActionRulesFactory = Ext.extend(Object,{

	/**
	 * Key-value map of Action Definitions as registered by
	 * {@link Zarafa.common.rules.data.RulesData#getAction}.
	 *
	 * @property
	 * @type Object
	 */
	actionDefinitions: {},

	/**
	 * @constructor
	 * @param {Object} config configuration object.
	 */
	constructor: function(config)
	{
		config = config || {};
		this.actionDefinitions = Zarafa.common.rules.data.RulesData.getAction();

		Zarafa.common.data.ActionRulesFactory.superclass.constructor.call(this, config);
	},

	/**
	 * Register a new Action Definition for the given key value.
	 * This function will be used to dynamically register an action definition on runtime.
	 *
	 * @param {Zarafa.common.rules.data.ActionFlags} key The key value on how the definition is stored
	 * in the definitions table.
	 * @param {Function} actionFunction which should return action object for the given key.
	 * @return {Function} The Action Definition or false if given condition is not registered.
	 * If there already exists a definition then function will return that definition.
	 */
	registerActionDefinition: function(key, actionFunction)
	{
		if (this.actionDefinitions[key]) {
			return this.actionDefinitions[key];
		}

		if (!Ext.isEmpty(key) && Ext.isFunction(actionFunction)) {
			this.actionDefinitions[key] = actionFunction;
			return this.actionDefinitions[key];
		}
		return false;
	},

	/**
	 * Get Action Definition for the given key value.
	 *
	 * @param {Zarafa.common.rules.data.ActionFlags} key The key value on how the definition is stored
	 * in the definitions table.
	 * @return {Function} registered Action Definition from definitions table or false if given condition is not registered.
	 */
	getActionById: function(key)
	{
		if (!Ext.isEmpty(this.actionDefinitions[key])) {
			return this.actionDefinitions[key];
		}
		return false;
	},

	/**
	 * Unregister the Action Definition for the given key value.
	 * This function will be used to dynamically unregister definition on runtime.
	 *
	 * @param {Zarafa.common.rules.data.ActionFlags} key The key value on how the definition is stored
	 * in the definitions table.
	 * @return {Boolean} true if successfully definition removed else false.
	 */
	unregisterActionDefinition: function(key)
	{
		if (!Ext.isEmpty(this.actionDefinitions[key])) {
			delete this.actionDefinitions[key];
			return true;
		}
		return false;
	}
});

Zarafa.common.data.AbstractRulesFactory.registerFactory(Zarafa.common.data.RulesFactoryType.ACTION, new Zarafa.common.data.ActionRulesFactory());/*
 * #dependsFile client/zarafa/common/rules/data/RulesData.js
 * #dependsFile client/zarafa/common/rules/data/ConditionFlags.js
 */
Ext.namespace('Zarafa.common.data');


/**
 * @class Zarafa.common.data.ConditionRuleFactory
 * The factory class which holds and manages definition of Rules Condition.
 * It also has function to get Exception restriction {@link Zarafa.common.data.ConditionRuleFactory#getExceptionById}
 *
 * @singleton
 */
Zarafa.common.data.ConditionRuleFactory = Ext.extend(Object,{

	/**
	 * Key-value map of condition Definition as registered by
	 * {@link Zarafa.common.rules.data.RulesData#getConditionRestriction}.
	 *
	 * @property
	 * @type Object
	 */
	conditionDefinitions: {},

	/**
	 * @constructor
	 * @param {Object} config configuration object.
	 */
	constructor: function(config)
	{
		config = config || {};
		this.conditionDefinitions = Zarafa.common.rules.data.RulesData.getConditionRestriction();

		Zarafa.common.data.ConditionRuleFactory.superclass.constructor.call(this, config);
	},

	/**
	 * Register a new Condition Definition for the given key value.
	 * This function will be used to dynamically register a condition definition on runtime.
	 *
	 * @param {Zarafa.common.rules.data.ConditionFlags} key The key value on how the definition is stored
	 * in the definitions table.
	 * @param {Function} conditionFunction which should return restriction for the given key.
	 * @return {Function} The Condition Definition or false if there's problem in registering.
	 * If there already exists a definition for the given key then function will return that definition.
	 */
	registerConditionDefinition: function(key, conditionFunction)
	{
		if (this.conditionDefinitions[key]) {
			return this.conditionDefinitions[key];
		}

		if (!Ext.isEmpty(key) && Ext.isFunction(conditionFunction)) {
			this.conditionDefinitions[key] = conditionFunction;
			return this.conditionDefinitions[key];
		}
		return false;
	},

	/**
	 * Get Condition Definition for the given key value.
	 *
	 * @param {Zarafa.common.rules.data.ConditionFlags} key The key value on how the definition is stored
	 * in the definitions table.
	 * @return {Function} registered Condition Definition from definitions table or false if given condition is not registered.
	 */
	getConditionById: function(key)
	{
		if (!Ext.isEmpty(this.conditionDefinitions[key])) {
			return this.conditionDefinitions[key];
		}
		return false;
	},

	/**
	 * Get Exception restriction for the given key value.
	 *
	 * @param {Zarafa.common.rules.data.ConditionFlags} key The key value on how the definition is stored
	 * in the definitions table.
	 * @return {Object} restriction for the exception else returns false if exception can not be generated.
	 */
	getExceptionById: function(key, options)
	{
		if (Ext.isEmpty(key)) {
			return false;
		}

		var conditionFunction = this.getConditionById(key);
		var condition = Ext.isFunction(conditionFunction) ? conditionFunction(options) : false;
		if (condition) {
			var RestrictionFactory = Zarafa.core.data.RestrictionFactory;
			return RestrictionFactory.createResNot(condition);
		}

		return false;
	},

	/**
	 * Unregister a Condition Definition for the given key value.
	 * This function will be used to dynamically unregister definition on runtime.
	 *
	 * @param {Zarafa.common.rules.data.ConditionFlags} key The key value on how the definition is stored
	 * in the definitions table.
	 * @return {Boolean} The true if successfully definition removed else false.
	 */
	unregisterConditionDefinition: function(key)
	{
		if (!Ext.isEmpty(this.conditionDefinitions[key])) {
			delete this.conditionDefinitions[key];
			return true;
		}
		return false;
	}
});

Zarafa.common.data.AbstractRulesFactory.registerFactory(Zarafa.common.data.RulesFactoryType.CONDITION, new Zarafa.common.data.ConditionRuleFactory());Ext.namespace('Zarafa.common.favorites.data');

/**
 * @class Zarafa.common.favorites.data.MAPIFavoritesSubStore
 * @extends Zarafa.hierarchy.data.IPFSubStore
 *
 * {@link Zarafa.hierarchy.data.MAPIFavoritesSubStore} holds {@link Zarafa.hierarchy.data.FavoritesFolderRecord} as records,
 * which defines favorites folders of all opened stores(own, shared, public)
 */
Zarafa.common.favorites.data.MAPIFavoritesSubStore = Ext.extend(Zarafa.hierarchy.data.IPFSubStore, {
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor: function(config)
	{
		config = config || {};

		Zarafa.common.favorites.data.MAPIFavoritesSubStore.superclass.constructor.call(this, config);
	}
});/*
 * #dependsFile client/zarafa/core/mapi/ObjectType.js
 * #dependsFile client/zarafa/core/mapi/DisplayType.js
 * #dependsFile client/zarafa/core/data/RecordCustomObjectType.js
 * #dependsFile client/zarafa/core/data/Record.js
 * #dependsFile client/zarafa/core/data/RecordFactory.js
 */
Ext.namespace('Zarafa.common.manageCc.data');

/**
 * @class Zarafa.common.manageCc.data.IPMCcRecipientRecordFields
 *
 * Array of default fields for the {@link Zarafa.common.manageCc.data.IPMCcRecipientRecordFields} object.
 * These fields will always be added, regardless of the exact type of
 * {@link Zarafa.common.manageCc.data.IPMCcRecipientRecordFields record}.
 */
Zarafa.common.manageCc.data.IPMCcRecipientRecordFields = [
  {name: 'entryid'},
  {name: 'search_key'},
  {name: 'rowid', type: 'int'},
  {name: 'object_type', type: 'int', defaultValue: Zarafa.core.mapi.ObjectType.MAPI_MAILUSER},
  {name: 'display_name'},
  {name: 'display_type', type: 'int', defaultValue: Zarafa.core.mapi.DisplayType.DT_MAILUSER},
  {name: 'display_type_ex', type: 'int', defaultValue: Zarafa.core.mapi.DisplayType.DT_MAILUSER},
  {name: 'email_address'},
  {name: 'smtp_address'},
  {name: 'address_type', type: 'string', defaultValue: 'SMTP'},
  {name: 'recipient_type', type: 'int', defaultValue: Zarafa.core.mapi.RecipientType.MAPI_CC},
  {name: 'reply_mail', type:'boolean', defaultValue: true},
  {name: 'new_mail', type:'boolean', defaultValue: true}
];

/**
 * @class Zarafa.common.manageCc.data.IPMCcRecipientRecord
 * @extends Zarafa.core.data.IPMRecipientRecord
 */
Zarafa.common.manageCc.data.IPMCcRecipientRecord = Ext.extend(Zarafa.core.data.IPMRecipientRecord, {
});

// Register a custom type to be used by the Record Factory
Zarafa.core.data.RecordCustomObjectType.addProperty('ZARAFA_CC_RECIPIENT');

Zarafa.core.data.RecordFactory.setBaseClassToCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_CC_RECIPIENT, Zarafa.common.manageCc.data.IPMCcRecipientRecord);
Zarafa.core.data.RecordFactory.addFieldToCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_CC_RECIPIENT, Zarafa.common.manageCc.data.IPMCcRecipientRecordFields);
/*
 * #dependsFile client/zarafa/core/data/ListModuleStore.js
 */
Ext.namespace('Zarafa.common.outofoffice.data');

/**
 * @class Zarafa.common.outofoffice.data.OofStore
 * @extends Zarafa.core.data.ListModuleStore
 *
 * OofStore store that will be used to load out of office information from server.
 */
Zarafa.common.outofoffice.data.OofStore = Ext.extend(Zarafa.core.data.ListModuleStore, {

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

    var recordType = Zarafa.core.data.RecordFactory.getRecordClassByCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_OOF_SETTINGS);

    Ext.applyIf(config, {
      proxy: new Zarafa.core.data.IPMProxy({
        listModuleName: Zarafa.core.ModuleNames.getListName('OUTOFOFFICESETTINGS'),
        itemModuleName: Zarafa.core.ModuleNames.getItemName('OUTOFOFFICESETTINGS')
      }),

      reader: new Zarafa.core.data.JsonReader({
        dynamicRecord: false,
        id: 'store_entryid',
        idProperty: 'store_entryid',
      }, recordType),

      batch: true,
      autoLoad: true
    });

    Zarafa.common.outofoffice.data.OofStore.superclass.constructor.call(this, config);
  }
});
Ext.namespace('Zarafa.common.recipientfield.ui');

/**
 * @class Zarafa.common.recipientfield.ui.RecipientList
 * @extends Zarafa.common.recipientfield.ui.RecipientField
 * @xtype zarafa.recipientlist
 * This extends the {@link Zarafa.common.recipientfield.ui.RecipientField RecipientField}
 * and transforms the boxes into a large list.
 */
Zarafa.common.recipientfield.ui.RecipientList = Ext.extend(Zarafa.common.recipientfield.ui.RecipientField, {

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

		Ext.applyIf(config, {
			boxType: 'zarafa.recipientbox',
			boxConfig: {
				height: config.inputFieldHeight || this.inputFieldHeight
			},
			listMode: true,
			autoHeight: true,
			autoScroll: false
		});

		Zarafa.common.recipientfield.ui.RecipientList.superclass.constructor.call(this, config);
	}
});

Ext.reg('zarafa.recipientlist', Zarafa.common.recipientfield.ui.RecipientList);
Ext.namespace('Zarafa.common.restoreitem.data');

/**
 * @class Zarafa.common.restoreitem.data.RestoreItemStore
 * @extends Zarafa.core.data.ListModuleStore
 * @xtype zarafa.restoreitemstore
 *
 * The RestoreItemStore used to load data related to soft deleted messages/folders, which can be used to restore or permanent delete it
 */
Zarafa.common.restoreitem.data.RestoreItemStore = Ext.extend(Zarafa.core.data.ListModuleStore, {

	/**
	 * @cfg {HexString} entryId The entryid of the {@link Zarafa.hierarchy.data.MAPIFolderRecord folder} for which soft deleted items will be obtained.
	 */
	entryId: undefined,

	/**
	 * @cfg {HexString} storeEntryId entry id of the store.
	 */
	storeEntryId: undefined,

	/**
	 * The current type of soft deleted items list.
	 * Default value is 'message' as By default Grid is loaded with soft deleted message list.
	 * This String is changed by {@link Zarafa.common.restoreitem.dialogs.RestoreItemPanel#onRadioChecked}.
	 *
	 * @property
	 * @type String
	 * @private
	 */
	itemType: 'message',

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

		var recordType = Zarafa.core.data.RecordFactory.getRecordClassByObjectType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_RESTOREITEM);

		Ext.applyIf(config, {
			remoteSort: true,
			sortInfo: {
				field: 'deleted_on',
				direction: 'DESC'
			},
			actionType: Zarafa.core.Actions['list'],
			writer: new Zarafa.core.data.JsonWriter(),
			reader: new Zarafa.core.data.JsonReader({
				dynamicRecord: false
			}, recordType),
			proxy: new Zarafa.core.data.IPMProxy({
				listModuleName: Zarafa.core.ModuleNames.getListName('RESTOREITEMS'),
				itemModuleName: Zarafa.core.ModuleNames.getItemName('RESTOREITEMS')
			})
		});

		Zarafa.common.restoreitem.data.RestoreItemStore.superclass.constructor.call(this, config);
	},

	/**
	 * Setter function used to set the value of {@link #itemType}
	 *
	 * @param itemType The current type of soft deleted items list.
	 */
	setItemType: function (itemType)
	{
		this.itemType = itemType;
	},

	/**
	 * Loads the Record cache from the configured {@link #proxy} using the configured {@link #reader}.
	 * @param {Object} options An object containing properties which control loading.
	 * @return {Boolean} true if super class load method will call successfully, false otherwise.
	 */
	load: function(options)
	{
		if (!Ext.isObject(options)) {
			options = {};
		}

		if (!Ext.isObject(options.params)) {
			options.params = {};
		}

		Ext.apply(options.params, {
			itemType: this.itemType
		});

		// Always use folder which is bind with restore item dialog.
		options.folder = [this.folder];

		return Zarafa.common.restoreitem.data.RestoreItemStore.superclass.load.call(this, options);
	},

	/**
	 * Function which used to reload the store.
	 */
	reload: Ext.emptyFn,
});

Ext.reg('zarafa.restoreitemstore', Zarafa.common.restoreitem.data.RestoreItemStore);
Ext.namespace('Zarafa.common.rules.data');

/**
 * @class Zarafa.common.rules.data.RulesResponseHandler
 * @extends Zarafa.core.data.IPMResponseHandler
 */
Zarafa.common.rules.data.RulesResponseHandler = Ext.extend(Zarafa.core.data.IPMResponseHandler, {
	/**
	 * Reads all {@link Zarafa.core.data.MAPIRecord records} from the response data from the server,
	 * and correlates them to the {@link #sendRecords} list.
	 * @param {Object} response The response data from the server containing the
	 * {@link Zarafa.core.data.MAPIRecord records}
	 * @private
	 */
	correlateRecordFromResponse: function(response)
	{
		var responseObj = this.reader.readResponse(Ext.data.Api.actions.read, response);
		var records = [];

		// We expect as many rules back, as we send to the server,
		// so no need to correlate anything. We will allow everything
		// to be overridden.
		if (!Ext.isEmpty(responseObj.data) && !Ext.isEmpty(this.sendRecords)) {
			records = responseObj.data.clone();
		}
		return records;
	}
});
/*
 * #dependsFile client/zarafa/core/mapi/ObjectType.js
 * #dependsFile client/zarafa/core/mapi/DisplayType.js
 * #dependsFile client/zarafa/core/data/RecordCustomObjectType.js
 * #dependsFile client/zarafa/core/data/Record.js
 * #dependsFile client/zarafa/core/data/RecordFactory.js
 */
Ext.namespace('Zarafa.common.sendas.data');

/**
 * @class Zarafa.common.sendas.data.IPMFromRecipientRecordFields
 *
 * Array of default fields for the {@link Zarafa.common.sendas.data.IPMFromRecipientRecordFields} object.
 * These fields will always be added, regardless of the exact type of
 * {@link Zarafa.common.sendas.data.IPMFromRecipientRecordFields record}.
 */
Zarafa.common.sendas.data.IPMFromRecipientRecordFields = [
  {name: 'entryid'},
  {name: 'search_key'},
  {name: 'rowid', type: 'int'},
  {name: 'object_type', type: 'int', defaultValue: Zarafa.core.mapi.ObjectType.MAPI_MAILUSER},
  {name: 'display_name'},
  {name: 'display_type', type: 'int', defaultValue: Zarafa.core.mapi.DisplayType.DT_MAILUSER},
  {name: 'display_type_ex', type: 'int', defaultValue: Zarafa.core.mapi.DisplayType.DT_MAILUSER},
  {name: 'email_address'},
  {name: 'smtp_address'},
  {name: 'address_type', type: 'string', defaultValue: 'SMTP'},
  {name: 'recipient_type', type: 'int'},
  {name: 'reply_mail', type:'boolean', defaultValue: false},
  {name: 'new_mail', type:'boolean', defaultValue: false},
  {name: 'forward_mail', type: 'boolean', defaultValue: false}
];

/**
 * @class Zarafa.common.sendas.data.IPMFromRecipientRecord
 * @extends Zarafa.core.data.IPMRecipientRecord
 */
Zarafa.common.sendas.data.IPMFromRecipientRecord = Ext.extend(Zarafa.core.data.IPMRecipientRecord, {
});

// Register a custom type to be used by the Record Factory
Zarafa.core.data.RecordCustomObjectType.addProperty('ZARAFA_FROM_RECIPIENT');

Zarafa.core.data.RecordFactory.setBaseClassToCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_FROM_RECIPIENT, Zarafa.common.sendas.data.IPMFromRecipientRecord);
Zarafa.core.data.RecordFactory.addFieldToCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_FROM_RECIPIENT, Zarafa.common.sendas.data.IPMFromRecipientRecordFields);
/*
 * #dependsFile client/zarafa/contact/ContactContextModel.js
 */
Ext.namespace('Zarafa.contact');

/**
 * @class Zarafa.contact.ContactContext
 * @extends Zarafa.core.Context
 * This class will be used as a controller between {@link Zarafa.contact.ContactContextModel ContactContextModel}
 * and {@link Zarafa.contact.ui.ContactMainPanel ContactMainPanel}
 */
Zarafa.contact.ContactContext = Ext.extend(Zarafa.core.Context, {
	// Insertion points for this class
	/**
	 * @insert main.maintoolbar.view.contact
	 * Insertion point for populating the main toolbar with a View button. This item is only visible
	 * when this context is active.
	 * @param {Zarafa.mail.ContactContext} context This context
	 */

	/**
	 * When searching, this property marks the {@link Zarafa.core.Context#getCurrentView view}
	 * which was used before {@link #onSearchStart searching started} the view was switched to
	 * {@link Zarafa.contact.data.Views#SEARCH}.
	 * @property
	 * @type Mixed
	 * @private
	 */
	oldView: undefined,

	/**
	 * When searching, this property marks the {@link Zarafa.core.Context#getCurrentViewMode viewmode}
	 * which was used before {@link #onSearchStart searching started} the viewmode was switched to
	 * {@link Zarafa.contact.data.ViewModes#SEARCH}.
	 * @property
	 * @type Mixed
	 * @private
	 */
	oldViewMode: undefined,

	/*
	 * @constructor
	 * @param {Object} config configuration object
	 */
	constructor: function(config)
	{
		config = config || {};
		Ext.applyIf(config, {
			current_view: Zarafa.contact.data.Views.LIST,
			current_view_mode: Zarafa.contact.data.ViewModes.NORMAL
		});

		// The "New contact" and "New distribution list" buttons which are available in all contexts
		this.registerInsertionPoint('main.maintoolbar.new.item', this.createNewContactButton, this);
		this.registerInsertionPoint('main.maintoolbar.new.item', this.createNewDistributionListButton, this);

		// Context menu button to add a recipient as contact
		this.registerInsertionPoint('context.common.recipientfield.contextmenu.actions', this.createContactFromRecipientButton, this);

		// Recipient hover card button to add a recipient as contact
		this.registerInsertionPoint('context.common.recipientfield.hovercardview.actions', this.createContactFromHoverCardRecipientButton, this);
		// The tab in the top tabbar
		this.registerInsertionPoint('main.maintabbar.left', this.createMainTab, this);

		Zarafa.contact.ContactContext.superclass.constructor.call(this, config);

		// Add a tree control showing a list of contact folders to the navigation panel.
		// The control will be shown when the user selects the contact context from the button panel.
		this.registerInsertionPoint('navigation.center', this.createContactNavigationPanel, this);

		// Register contact specific dialog types
		Zarafa.core.data.SharedComponentType.addProperty('contact.detailsparser');
		Zarafa.core.data.SharedComponentType.addProperty('contact.dialog.contact.namedetails');
		Zarafa.core.data.SharedComponentType.addProperty('contact.dialog.contact.addressdetails');
		Zarafa.core.data.SharedComponentType.addProperty('contact.dialog.contact.phonedetails');
		Zarafa.core.data.SharedComponentType.addProperty('contact.dialog.distlist.externalmember');
		Zarafa.core.data.SharedComponentType.addProperty('contact.dialog.options');

		// If additional prefix added by user in config.php file than append it into existing prefix's list
		var prefix = container.getServerConfig().getContactPrefix();
		if (Ext.isDefined(prefix) && Array.isArray(prefix)) {
			Zarafa.contact.data.config.Prefix = Zarafa.contact.data.config.Prefix.concat(prefix);
		}

		// If additional suffix added by user in config.php file than append it into existing suffix's list
		var suffix = container.getServerConfig().getContactSuffix();
		if (Ext.isDefined(suffix) && Array.isArray(suffix)) {
			Zarafa.contact.data.config.Suffix = Zarafa.contact.data.config.Suffix.concat(suffix);
		}
	},

	/**
	 * @return {Zarafa.contact.ContactContextModel} the contact context model
	 */
	getModel: function()
	{
		if (!Ext.isDefined(this.model)) {
			this.model = new Zarafa.contact.ContactContextModel();
			this.model.on({
				'searchstart': this.onModelSearchStart,
				'searchstop': this.onModelSearchStop,
				scope: this
			});
		}
		return this.model;
	},

	/**
	 * Event handler for the {@link #model}#{@link Zarafa.core.ContextModel#searchstart searchstart} event.
	 * This will {@link #switchView switch the view} to {@link Zarafa.contact.data.Views#SEARCH search mode}.
	 * The previously active {@link #getCurrentView view} will be stored in the {@link #oldView} and will
	 * be recovered when the {@link #onModelSearchStop search is stopped}.
	 * @param {Zarafa.core.ContextModel} model The model which fired the event
	 * @private
	 */
	onModelSearchStart: function(model)
	{
		if(this.getCurrentView() !== Zarafa.contact.data.Views.SEARCH && this.getCurrentViewMode() !== Zarafa.contact.data.ViewModes.SEARCH){
			this.oldView = this.getCurrentView();
			this.oldViewMode = this.getCurrentViewMode();
			this.switchView(Zarafa.contact.data.Views.SEARCH, Zarafa.contact.data.ViewModes.SEARCH);
		}
	},

	/**
	 * Event handler for the {@link #model}#{@link Zarafa.core.ContextModel#searchstop searchstop} event.
	 * This will {@link #switchView switch the view} to the {@link #oldView previous view}.
	 * @param {Zarafa.core.ContextModel} model The model which fired the event
	 * @private
	 */
	onModelSearchStop: function(model)
	{
		this.switchView(this.oldView, this.oldViewMode);
		delete this.oldView;
		delete this.oldViewMode;
	},

	/**
	 * Bid for the type of shared component and the given record.
	 * @param {Zarafa.core.data.SharedComponentType} type Type of component a context can bid for.
	 * @param {Ext.data.Record} record Optionally passed record.
	 * @return {Number} The bid for the shared component
	 */
	bidSharedComponent: function(type, record)
	{
		var bid = -1;

		if (Array.isArray(record)) {
			record = record[0];
		}

		switch (type) {
			// Bid for create/view dialog for opening contact/distlist records
			case Zarafa.core.data.SharedComponentType['common.create']:
			case Zarafa.core.data.SharedComponentType['common.view']:
			case Zarafa.core.data.SharedComponentType['common.preview']:
				if (record instanceof Zarafa.core.data.IPMRecord && record.isMessageClass([ 'IPM.Contact', 'IPM.DistList' ], true)) {
					bid = 1;
				// If the guid of the entryid indicates this record comes from the Contact Provider
				// then we also want this record. This happens when opening an Addressbook record.
				} else if (record instanceof Zarafa.core.data.MAPIRecord) {
					var entryid = record.get('entryid');
					if(entryid && Zarafa.core.EntryId.hasContactProviderGUID(entryid)) {
						bid = 1;
					}
				}
				break;
			// Bid for specific contact dialogs
			case Zarafa.core.data.SharedComponentType['contact.dialog.contact.namedetails']:
			case Zarafa.core.data.SharedComponentType['contact.dialog.contact.addressdetails']:
			case Zarafa.core.data.SharedComponentType['contact.dialog.contact.phonedetails']:
			case Zarafa.core.data.SharedComponentType['contact.dialog.distlist.externalmember']:
				bid = 1;
				break;
			case Zarafa.core.data.SharedComponentType['contact.dialog.options']:
				if (record instanceof Zarafa.core.data.IPMRecord && record.isMessageClass([ 'IPM.Contact', 'IPM.DistList' ], true)) {
					bid = 1;
				}
				break;
			case Zarafa.core.data.SharedComponentType['common.contextmenu']:
				if (record instanceof Zarafa.core.data.IPMRecord && record.isMessageClass([ 'IPM.Contact', 'IPM.DistList' ], true)) {
					bid = 1;
				} else if(record instanceof Zarafa.core.data.IPMAttachmentRecord && record.isContactPhoto()) {
					bid = 2;
				}
				break;
			case Zarafa.core.data.SharedComponentType['common.printer.renderer']:
				if (record instanceof Zarafa.core.data.IPMRecord && record.get('object_type') === Zarafa.core.mapi.ObjectType.MAPI_MESSAGE) {
					if (record.isMessageClass([ 'IPM.Contact', 'IPM.DistList' ], true)) {
						bid = 1;
					}
				} else if (record instanceof Zarafa.contact.ContactContext) {
					// @todo
					bid = -1;
				}
				break;
			// Bid for the details parser
			case Zarafa.core.data.SharedComponentType['contact.detailsparser']:
				bid = 1;
				break;
			case Zarafa.core.data.SharedComponentType['common.attachment.dialog.attachitem.columnmodel']:
				if (record instanceof Zarafa.hierarchy.data.MAPIFolderRecord) {
					if (record.isContainerClass('IPF.Contact', true)) {
						bid = 1;
					}
				}
				break;
			case Zarafa.core.data.SharedComponentType['common.attachment.dialog.attachitem.textrenderer']:
				if (record instanceof Zarafa.core.data.IPMRecord && record.isMessageClass([ 'IPM.Contact', 'IPM.DistList' ], true)) {
					bid = 1;
				}
				break;
		}
		return bid;
	},

	/**
	 * Will return the reference to the shared component.
	 * Based on the type of component requested a component is returned.
	 * @param {Zarafa.core.data.SharedComponentType} type Type of component a context can bid for.
	 * @param {Ext.data.Record} record Optionally passed record.
	 * @return {Ext.Component} Component
	 */
	getSharedComponent: function(type, record)
	{
		var component;
		switch (type) {
			case Zarafa.core.data.SharedComponentType['common.create']:
			case Zarafa.core.data.SharedComponentType['common.view']:
				if (record instanceof Zarafa.core.data.IPMRecord){
					if (record.isMessageClass('IPM.Contact', true)) {
						component = Zarafa.contact.dialogs.ContactContentPanel;
					} else if (record.isMessageClass('IPM.DistList', true)) {
						component = Zarafa.contact.dialogs.DistlistContentPanel;
					}
				} else if (record instanceof Zarafa.core.data.MAPIRecord) {
					var entryid = record.get('entryid');
					if (entryid && Zarafa.core.EntryId.hasContactProviderGUID(entryid)) {
						if (record.get('object_type') === Zarafa.core.mapi.ObjectType.MAPI_MAILUSER) {
							component = Zarafa.contact.dialogs.ContactContentPanel;
						} else if (record.get('object_type') === Zarafa.core.mapi.ObjectType.MAPI_DISTLIST) {
							component = Zarafa.contact.dialogs.DistlistContentPanel;
						}
					}
				}
				break;
			case Zarafa.core.data.SharedComponentType['common.preview']:
				component = Zarafa.contact.ui.ContactPreviewPanel;
				break;
			case Zarafa.core.data.SharedComponentType['contact.dialog.contact.namedetails']:
				component = Zarafa.contact.dialogs.ContactNameContentPanel;
				break;
			case Zarafa.core.data.SharedComponentType['contact.dialog.contact.addressdetails']:
				component = Zarafa.contact.dialogs.ContactAddressContentPanel;
				break;
			case Zarafa.core.data.SharedComponentType['contact.dialog.contact.phonedetails']:
				component = Zarafa.contact.dialogs.ContactPhoneContentPanel;
				break;
			case Zarafa.core.data.SharedComponentType['contact.dialog.distlist.externalmember']:
				component = Zarafa.contact.dialogs.DistlistExternalMemberContentPanel;
				break;
			case Zarafa.core.data.SharedComponentType['contact.dialog.options']:
				component = Zarafa.contact.dialogs.ContactOptionsContentPanel;
				break;
			case Zarafa.core.data.SharedComponentType['common.contextmenu']:
				if (record instanceof Zarafa.core.data.IPMAttachmentRecord && record.isContactPhoto()){
					component = Zarafa.contact.ui.ContactPhotoContextMenu;
				} else {
					component = Zarafa.contact.ui.ContactContextMenu;
				}
				break;
			case Zarafa.core.data.SharedComponentType['common.printer.renderer']:
				if (record instanceof Zarafa.core.data.IPMRecord && record.get('object_type') === Zarafa.core.mapi.ObjectType.MAPI_MESSAGE) {
					if (record.isMessageClass('IPM.Contact', true)) {
						component = Zarafa.contact.printer.ContactRenderer;
					} else {
						component = Zarafa.contact.printer.DistlistRenderer;
					}
				} else {
					component = undefined; // Zarafa.contact.printer.ContactCardViewRenderer;
				}
				break;
			case Zarafa.core.data.SharedComponentType['contact.detailsparser']:
				component = Zarafa.contact.data.ContactDetailsParser;
				break;
			case Zarafa.core.data.SharedComponentType['common.attachment.dialog.attachitem.columnmodel']:
				component = Zarafa.contact.attachitem.AttachContactColumnModel;
				break;
			case Zarafa.core.data.SharedComponentType['common.attachment.dialog.attachitem.textrenderer']:
				if (record.isMessageClass('IPM.DistList', true)) {
					component = Zarafa.contact.attachitem.AttachDistlistRenderer;
				} else {
					component = Zarafa.contact.attachitem.AttachContactRenderer;
				}
				break;
		}
		return component;
	},

	/**
	 * Creates the contact tree that is shown when the user selects the contact context from the
	 * button panel. It shows a tree of available contact folders that can be checked and unchecked.
	 * @private
	 */
	createContactNavigationPanel: function()
	{
		return {
			xtype: 'zarafa.contextnavigation',
			context: this,
			items: [{
				xtype: 'panel',
				id: 'zarafa-navigationpanel-contacts-navigation',
				cls: 'zarafa-context-navigation-block',
				layout: 'fit',
				items: [{
					xtype: 'zarafa.hierarchytreepanel',
					id: 'zarafa-navigationpanel-contacts-navigation-tree',
					model: this.getModel(),
					IPMFilter: 'IPF.Contact',
					hideDeletedFolders: true,
					enableDD: true,
					enableItemDrop: true,
					deferredLoading: true,
					bbarConfig: {
						defaultSelectedSharedFolderType: Zarafa.hierarchy.data.SharedFolderTypes['CONTACT'],
						buttonText: _('Open Shared Contacts')
					}
				}]
			}]
		};
	},

	/**
	 * enable the content panel when a context switch happens
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder folder to bid on.
	 * @return {Number} returns a number which is used for bidding for a folder, highest bidder
	 * will be allowed to show the contents
	 */
	bid: function(folder)
	{
		// the folder contains items of type IPF.Contact, return 1
		if (folder.isContainerClass('IPF.Contact', true)) {
			return 1;
		}

		// return -1, don't handle this content type
		return -1;
	},

	/**
	 * this will create a content panel, which is a container for all the views
	 * @return {Object} configuration object to create {@link Zarafa.contact.ui.ContactMainPanel ContactMainPanel}
	 */
	createContentPanel: function()
	{
		// create object of ContactMainPanel that will create the views
		return {
			xtype: 'zarafa.contactmainpanel',
			id: 'zarafa-mainpanel-contentpanel-contacts',
			context: this
		};
	},

	/**
	 * Returns the buttons for the dropdown list of the VIEW-button in the main toolbar. It will use the
	 * main.maintoolbar.view.contact insertion point to allow other plugins to add their items at the end.
	 *
	 * @return {Ext.Component[]} an array of components
	 */
	getMainToolbarViewButtons: function()
	{
		var items = container.populateInsertionPoint('main.maintoolbar.view.contact', this) || [];

		var defaultItems = [{
			id: 'zarafa-maintoolbar-view-contacts-businesscards',
			text: _('Business Cards'),
			overflowText: _('Business Cards'),
			iconCls: 'icon_contact_card_view',
			valueView: Zarafa.contact.data.Views.ICON,
			valueViewMode: Zarafa.contact.data.ViewModes.BUSINESS,
			valueDataMode: Zarafa.contact.data.DataModes.CHARACTER_RESTRICT,
			handler: this.onContextSelectView,
			scope: this
		},{
			id: 'zarafa-maintoolbar-view-contacts-phonlist',
			text: _('Phone List'),
			overflowText: _('Phone List'),
			iconCls: 'icon_contact_list',
			valueView: Zarafa.contact.data.Views.LIST,
			valueViewMode: Zarafa.contact.data.ViewModes.NORMAL,
			valueDataMode: Zarafa.contact.data.DataModes.ALL,
			handler: this.onContextSelectView,
			scope: this
		}];

		return defaultItems.concat(items);
	},

	/**
	 * Event handler which is fired when one of the View buttons
	 * has been pressed. This will call {@link Zarafa.contact.ContactContext#setView setView}
	 * to update the view.
	 * @param {Ext.Button} button The button which was pressed
	 * @private
	 */
	onContextSelectView: function(button)
	{
		this.getModel().setDataMode(button.valueDataMode);
		this.switchView(button.valueView, button.valueViewMode);
	},

	/**
	 * Create "New Contact" {@link Ext.menu.MenuItem item} for the "New item"
	 * {@link Ext.menu.Menu menu} in the {@link Zarafa.core.ui.MainToolbar MainToolbar}.
	 * This button should be shown in all {@link Zarafa.core.Context contexts} and
	 * is used to create a new contact.
	 *
	 * @return {Object} The menu item for creating a new contact item
	 * @static
	 */
	createNewContactButton: function()
	{
		return {
			xtype: 'menuitem',
			id: 'zarafa-maintoolbar-newitem-contact',
			text: _('Contact'),
			tooltip: _('Contact') + Zarafa.core.KeyMapMgr.formatShortcutHint('Ctrl + Alt + C', false),
			plugins: 'zarafa.menuitemtooltipplugin',
			handler: function()
			{
				Zarafa.contact.Actions.openCreateContactContent(this.getModel());
			},
			scope: this,
			iconCls: 'icon_new_contact',
			newMenuIndex: 3,
			context: 'contact'
		};
	},

	/**
	 * Create "Add to contact list" item for {@link Zarafa.common.recipientfield.ui.RecipientContextMenu contextmenu}
	 * in when opening a context menu on a recipient field
	 *
	 * @param {String} insertionPoint The name of the insertion point
	 * @param {Zarafa.common.recipientfield.ui.RecipientContextMenu} contextMenu The context menu
	 * @return {Object} The menu item for creating a new context menu contact item
	 * @static
	 */
	createContactFromRecipientButton: function(insertionPoint, contextMenu)
	{
		return {
			xtype: 'zarafa.conditionalitem',
			text: _('Add to contacts'),
			iconCls: 'icon_new_contact',
			handler: Zarafa.contact.Actions.openRecipientContactContent,
			scope: this
		};
	},

	/**
	 * Create "Add to contact list" item for {@link Zarafa.common.recipientfield.ui.RecipientHoverCardView recipienthovercardview}
	 * in when opening a recipienthovercard
	 *
	 * @param {String} insertionPoint The name of the insertion point
	 * @param {Zarafa.common.recipientfield.ui.RecipientHoverCardView} recipienthovercardview
	 * @return {Object} The button for creating a new {@link Zarafa.common.recipientfield.ui.RecipientHoverCardButton recipienthovercardbutton}
	 * @static
	 */
	createContactFromHoverCardRecipientButton: function(insertionPoint, recipienthovercardview)
	{
		return {
			xtype: 'zarafa.recipienthovercardbutton',
			iconCls: 'icon_new_contact',
			tooltip: _('Add to contacts'),
			handler: Zarafa.contact.Actions.openRecipientContactContent,
			scope: this
		};
	},

	/**
	 * Populates the View button in the main toolbar
	 * @return {Array} items The menu items available for printing in this context
	 */
	getMainToolbarPrintButtons: function()
	{
		var items = container.populateInsertionPoint('main.toolbar.print.contact', this) || [];

		var defaultItems = [{
			xtype:'zarafa.conditionalitem',
			id: 'zarafa-maintoolbar-print-selectedcontact',
			overflowText: _('Print selected contact'),
			iconCls: 'icon_print_contact',
			tooltip: _('Print selected contact') + Zarafa.core.KeyMapMgr.formatShortcutHint('Ctrl + P', true),
			plugins: 'zarafa.menuitemtooltipplugin',
			text: _('Print selected contact'),
			hideOnDisabled: false,
			singleSelectOnly: true,
			handler: this.onPrintSelected.createDelegate(this, [_('No contact selected')], 2),
			scope: this
		}];

		return defaultItems.concat(items);
	},

	/**
	 * Create "New distribution list" {@link Ext.menu.MenuItem item} for the "New item"
	 * {@link Ext.menu.Menu menu} in the {@link Zarafa.core.ui.MainToolbar MainToolbar}.
	 * This button should be shown in all {@link Zarafa.core.Context contexts} and
	 * is used to create a new distribution list.
	 *
	 * @return {Object} The menu item for creating a new distribution item
	 * @static
	 */
	createNewDistributionListButton: function(context)
	{
		//create new Distribution list button, as we don't want support create Distribution list function in Milestone 6 launch
		return {
			xtype: 'menuitem',
			id: 'zarafa-maintoolbar-newitem-distlist',
			tooltip: _('Distribution list') + Zarafa.core.KeyMapMgr.formatShortcutHint('Ctrl + Alt + D', false),
			plugins: 'zarafa.menuitemtooltipplugin',
			text: _('Distribution list'),
			handler: function()
			{
				Zarafa.contact.Actions.openCreateDistlistContent(this.getModel());
			},
			scope: this,
			iconCls: 'icon_new_distlist',
			newMenuIndex: 3,
			context: 'contact'
		};
	},

	/**
	 * Adds a button to the top tab bar for this context.
	 * @return {Object} The button for the top tabbar
	 * @private
	 */
	createMainTab: function()
	{
		return {
			text: this.getDisplayName(),
			tabOrderIndex: 4,
			context: this.getName(),
			id: 'mainmenu-button-contacts'
		};
	},

	/**
	 * Event handler which is executed right before the {@link #viewmodechange}
	 * event is fired. This will check which {@link Zarafa.contact.data.ViewModes ViewMode}
	 * was applied and thus which kind of {@link Zarafa.core.ContextModel#groupBy grouping}
	 * must be applied to the {@link #model}.
	 *
	 * @param {Zarafa.core.Context} context The context which fired the event.
	 * @param {Mixed} newViewMode The selected View Mode.
	 * @param {Mixed} oldViewMode The previously selected View Mode.
	 * @private
	 */
	onViewModeChange: function(context, newViewMode, oldViewMode)
	{
		var model = this.getModel();

		switch (newViewMode) {
			case Zarafa.contact.data.ViewModes.SEARCH:
			case Zarafa.contact.data.ViewModes.NORMAL:
				model.clearGrouping();
				break;
			case Zarafa.contact.data.ViewModes.GROUP_CATEGORY:
				model.groupBy('categories');
				break;
			case Zarafa.contact.data.ViewModes.GROUP_COMPANY:
				model.groupBy('company_name');
				break;
			case Zarafa.contact.data.ViewModes.GROUP_LOCATION:
				model.groupBy('office_location');
				break;
		}
	}
});

Zarafa.onReady(function() {
	container.registerContext(new Zarafa.core.ContextMetaData({
		name: 'contact',
		displayName: _('Contacts'),
		allowUserVisible: false,
		pluginConstructor: Zarafa.contact.ContactContext
	}));
});
Ext.namespace('Zarafa.contact');

/**
 * @class Zarafa.contact.ContactStore
 * @extends Zarafa.core.data.ListModuleStore
 * @xtype zarafa.contactstore
 *
 * this will contain all records fetched from the server side code
 */
Zarafa.contact.ContactStore = Ext.extend(Zarafa.core.data.ListModuleStore, {
	/**
	 * @constructor
	 * @param {Object} config configuration params that should be used to create instance of this store.
	 */
	constructor: function(config)
	{
		config = config || {};

		// Apply default settings.
		Ext.applyIf(config, {
			preferredMessageClass: 'IPM.Contact',
			defaultSortInfo: {
				field: 'fileas',
				direction: 'desc'
			}
		});

		Zarafa.contact.ContactStore.superclass.constructor.call(this, config);
	}
});

Ext.reg('zarafa.contactstore', Zarafa.contact.ContactStore);
/*
 * We depend on DistlistMemberRecord rather the RecordCustomObjectType
 * as ZARAFA_DISTLIST_MEMBER is defined in DistlistMemberRecord.
 * #dependsFile client/zarafa/contact/DistlistMemberRecord.js
 */
Ext.namespace('Zarafa.contact');

/**
 * @class Zarafa.contact.DistlistMemberStore
 * @extends Zarafa.core.data.MAPISubStore
 */
Zarafa.contact.DistlistMemberStore = Ext.extend(Zarafa.core.data.MAPISubStore, {
	/**
	 * @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_DISTLIST_MEMBER,

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

		Ext.applyIf(config, {
			// provide a default writer
			writer: new Zarafa.contact.data.JsonMemberWriter(),
			// provide a default reader
			reader: new Zarafa.contact.data.JsonMemberReader({
				customObjectType: config.customObjectType || this.customObjectType
			})
		});

		Zarafa.contact.DistlistMemberStore.superclass.constructor.call(this, config);
	},

	/**
	 * Parse a String into a {@link Zarafa.contact.DistlistMemberRecord}.
	 * @param {String} str The string to parse
	 * @return {Zarafa.contact.DistlistMemberRecord} The created member
	 */
	parseMember: function(str)
	{
		var member = 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;
		}

		member.beginEdit();

		if (mailStart != -1) {
			member.set('display_name', str.substring(0, mailStart-1).trim());
			member.set('email_address', str.substring(mailStart + 1, mailEnd).trim());
		} else {
			member.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)) {
				member.set('email_address', '');
			} else {
				member.set('email_address', str);
			}
		}

		member.set('address_type', 'SMTP');
		member.set('distlist_type', Zarafa.core.mapi.DistlistType.DL_EXTERNAL_MEMBER);

		member.endEdit();

		return member;
	}
});
Ext.namespace('Zarafa.contact.ui');

/**
 * @class Zarafa.contact.ui.MemberBoxField
 * @extends Zarafa.common.recipientfield.ui.RecipientField
 * @xtype zarafa.memberboxfield
 *
 * Special {@link Zarafa.common.ui.BoxField boxfield} which is used
 * for displaying Distllist Member records. This works similar to the
 * {@link Zarafa.common.recipientfield.ui.RecipientField} regarding
 * resolving, but it will mark any non-distlist member user as invalid.
 *
 * If the {@link Zarafa.core.plugins.RecordComponentUpdaterPlugin} is installed
 * in the {@link #plugins} array of this component, this component will automatically
 * load the {@link Zarafa.core.data.IPMRecipientStore RecipientStore} into the component.
 * Otherwise the user of this component needs to call {@link #setRecipientStore}.
 */
Zarafa.contact.ui.MemberBoxField = Ext.extend(Zarafa.common.recipientfield.ui.RecipientField, {
	/**
	 * @cfg {Zarafa.core.mapi.RecipientType} defaultRecipientType
	 * @hide
	 */

	/**
	 * @cfg {Zarafa.core.mapi.RecipientType} filterRecipientType
	 * @hide
	 */

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

		Ext.applyIf(config, {
			boxType: 'zarafa.memberbox',
			enableComboBox: false,
			editable: true,
			height: 30
		});

		Ext.apply(config, {
			defaultRecipientType: undefined,
			filterRecipientType: undefined
		});

		Zarafa.contact.ui.MemberBoxField.superclass.constructor.call(this, config);
	},

	/**
	 * Set the store on this field. See {@link #setBoxStore}.
	 * @param {Zarafa.contact.DistlistMemberStore} store The store to set on this field
	 */
	setMemberStore: function(store)
	{
		return this.setBoxStore.apply(this, arguments);
	},

	/**
	 * Get the store attached to this field. See {@link #getBoxStore}.
	 * @return {Zarafa.contact.DistlistMemberStore} store
	 */
	getMemberStore: function()
	{
		return this.getBoxStore();
	},

	/**
	 * Update the components with the given record.
	 *
	 * @param {Zarafa.core.data.MAPIRecord} record The record to update in this component
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 */
	update: function(record, contentReset)
	{
		if (record && record instanceof Zarafa.core.data.MAPIRecord) {
			// In case the recordcomponentupdaterplugin is installed
			// we have a special action to update the component.
			if (contentReset && record.isOpened()) {
				this.setMemberStore(record.getMemberStore());
			}
		} else {
			// The recordcomponentupdaterplugin is not installed and the
			// caller really wants to perform the update() function. Probably
			// a bad move, but lets not disappoint the caller.
			Zarafa.contact.ui.MemberBoxField.superclass.update.apply(this, arguments);
		}
	},

	/**
	 * Called by {@link #filterRecords} to check if the given record
	 * must be visible in the field or not. For the recipientfield, we check
	 * the {@link filterRecipientType} property and prevent that the
	 * {@link Zarafa.core.data.IPMRecipientRecord#isMeetingOrganizer meeting organizer}
	 * are visible in the box.
	 * @param {Ext.data.Record} record The record to filter
	 * @return {Boolean} True if the record should be visible, false otherwise
	 * @protected
	 */
	filterRecord: function(record)
	{
		return true;
	},

	/**
	 * Called to handle the input when the user presses the handleInputKey or another trigger makes
	 * this component need to handle the input. Has to be overwritten to implement the desired
	 * behavior and the creation of the correct type of record.
	 * @param {String} value The value from the input field
	 */
	handleInput: function(value)
	{
		// FIXME: Disallow typing in HTML formatting...
		var splitted = value.split(this.delimiterCharacter);
		var newRecords = [];

		for (var i = 0; i < splitted.length; i++) {
			var str = splitted[i].trim();
			if (!Ext.isEmpty(str)) {
				var record = this.boxStore.parseMember(str);
				newRecords.push(record);
			}
		}

		if(newRecords.length > 0){
			this.boxStore.add(newRecords);
		}
	},

	/**
	 * Event handler when a Box has been double-clicked.
	 * @param {Zarafa.common.recipientfield.ui.RecipientField} field This field to which the box belongs
	 * @param {Zarafa.common.recipientfield.ui.RecipientBox} box The box for which was double-clicked
	 * @param {Zarafa.core.data.IPMRecipientRecord} record The record which is attached to the box
	 * @private
	 */
	onBoxDblClick: function(field, box, record)
	{
		if (box.isValidRecord(record)) {
			Zarafa.contact.Actions.openDistlistMember(record);
		}
	},

	/**
	 * Event handler when the contextmenu is requested for a Box.
	 * @param {Zarafa.common.recipientfield.ui.RecipientField} field This field to which the box belongs
	 * @param {Zarafa.common.recipientfield.ui.RecipientBox} box The box for which the contextmenu is requested
	 * @param {Zarafa.core.data.IPMRecipientRecord} record The record which is attached to the box
	 * @private
	 */
	onBoxContextMenu: Ext.emptyFn
});

Ext.reg('zarafa.memberboxfield', Zarafa.contact.ui.MemberBoxField);
Ext.namespace('Zarafa.hierarchy');

/**
 * @class Zarafa.hierarchy.HierarchyContext
 * @extends Zarafa.core.Context
 */
Zarafa.hierarchy.HierarchyContext = Ext.extend(Zarafa.core.Context, {
	/*
	 * @constructor
	 * @param config
	 */
	constructor: function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			hasToolbar: false,
			hasContentPanel: false
		});

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

		// Register hierarchy specific dialog types
		Zarafa.core.data.SharedComponentType.addProperty('hierarchy.dialog.folderselection');
		Zarafa.core.data.SharedComponentType.addProperty('hierarchy.dialog.folderproperties');
		Zarafa.core.data.SharedComponentType.addProperty('hierarchy.dialog.foldersize');
		Zarafa.core.data.SharedComponentType.addProperty('hierarchy.dialog.opensharedfolder');
		Zarafa.core.data.SharedComponentType.addProperty('hierarchy.dialog.brokenfiles');
	},

	/**
	 * Bid for the type of shared component and the given record.
	 * This will bid on a dialog.crate or dialog.view for a record with a
	 * message class set to IPM or IPM.Note.
	 * @param {Zarafa.core.data.SharedComponentType} type Type of component a context can bid for.
	 * @param {Ext.data.Record} record Optionally passed record.
	 * @return {Number} The bid for the shared component
	 */
	bidSharedComponent: function(type, record)
	{
		var bid = -1;

		switch (type) {
			case Zarafa.core.data.SharedComponentType['common.create']:
			case Zarafa.core.data.SharedComponentType['common.contextmenu']:
				if (record instanceof Zarafa.core.data.MAPIRecord) {
					if (record.get('object_type') == Zarafa.core.mapi.ObjectType.MAPI_FOLDER && !record.isSearchFolder()) {
						bid = 1;
					}
				}
				break;
			case Zarafa.core.data.SharedComponentType['hierarchy.dialog.folderselection']:
			case Zarafa.core.data.SharedComponentType['hierarchy.dialog.folderproperties']:
			case Zarafa.core.data.SharedComponentType['hierarchy.dialog.foldersize']:
			case Zarafa.core.data.SharedComponentType['hierarchy.dialog.opensharedfolder']:
			case Zarafa.core.data.SharedComponentType['hierarchy.dialog.brokenfiles']:
				bid = 1;
				break;
		}
		return bid;
	},

	/**
	 * Will return the reference to the shared component.
	 * Based on the type of component requested a component is returned.
	 * @param {Zarafa.core.data.SharedComponentType} type Type of component a context can bid for.
	 * @param {Ext.data.Record} record Optionally passed record.
	 * @return {Ext.Component} Component
	 */
	getSharedComponent: function(type, record)
	{
		var component;
		switch (type) {
			case Zarafa.core.data.SharedComponentType['common.create']:
				component = Zarafa.hierarchy.dialogs.CreateFolderContentPanel;
				break;
			case Zarafa.core.data.SharedComponentType['hierarchy.dialog.folderselection']:
				component = Zarafa.hierarchy.dialogs.FolderSelectionContentPanel;
				break;
			case Zarafa.core.data.SharedComponentType['hierarchy.dialog.folderproperties']:
				component = Zarafa.hierarchy.dialogs.FolderPropertiesContentPanel;
				break;
			case Zarafa.core.data.SharedComponentType['hierarchy.dialog.foldersize']:
				component = Zarafa.hierarchy.dialogs.FolderSizeContentPanel;
				break;
			case Zarafa.core.data.SharedComponentType['hierarchy.dialog.opensharedfolder']:
				component = Zarafa.hierarchy.dialogs.SharedFolderContentPanel;
				break;
			case Zarafa.core.data.SharedComponentType['common.contextmenu']:
				component = Zarafa.hierarchy.ui.ContextMenu;
				break;
			case Zarafa.core.data.SharedComponentType['hierarchy.dialog.brokenfiles']:
				component = Zarafa.hierarchy.dialogs.BrokenFilesContentPanel;
				break;
		}
		return component;
	}
});

Zarafa.onReady(function() {
	container.registerContext(new Zarafa.core.ContextMetaData({
		name: 'hierarchy',
		allowUserVisible: false,
		pluginConstructor: Zarafa.hierarchy.HierarchyContext
	}));
});
/*
 * #dependsFile client/zarafa/core/mapi/ObjectType.js
 * #dependsFile client/zarafa/core/data/Record.js
 * #dependsFile client/zarafa/core/data/RecordFactory.js
 * #dependsFile client/zarafa/hierarchy/data/CounterTypes.js
 * #dependsFile client/zarafa/hierarchy/data/MAPIFolderPermissionsSubStore.js
 * #dependsFile client/zarafa/hierarchy/data/MAPIFolderSubStore.js
 */
Ext.namespace('Zarafa.hierarchy.data');

/**
 * @class Zarafa.hierarchy.data.MAPIFolderRecordFields
 * Array of {@link Ext.data.Field field} configurations for the
 * {@link Zarafa.hierarchy.data.MAPIFolderRecord MAPIFolderRecord} object.
 */
Zarafa.hierarchy.data.MAPIFolderRecordFields = [
	{name: 'entryid'},
	{name: 'parent_entryid'},
	{name: 'store_entryid'},
	{name: 'object_type', type: 'int', defaultValue: Zarafa.core.mapi.ObjectType.MAPI_FOLDER},
	{name: 'folder_type', type: 'int', defaultValue: Zarafa.core.mapi.MAPIFolderType.FOLDER_GENERIC},
	{name: 'folder_pathname'},
	{name: 'display_name'},
	{name: 'container_class', type: 'string', defaultValue: 'IPF.Note'},
	{name: 'content_count', type: 'int', defaultValue: 0},
	{name: 'content_unread', type: 'int', defaultValue: 0},
	{name: 'has_subfolder', type: 'boolean', defaultValue: false},
	{name: 'comment', type: 'string'},
	{name: 'creation_time', type: 'date', dateFormat: 'timestamp', defaultValue: null},
	{name: 'message_size', type: 'int'},
	{name: 'total_message_size', type: 'int'},
	{name: 'deleted_on', type: 'date', dateFormat: 'timestamp', defaultValue: null},
	{name: 'rights', type: 'int'},
	{name: 'access', type: 'int'},
	{name: 'extended_flags', type: 'int', defaultValue: 0},
	{name: 'assoc_content_count', type: 'int', defaultValue: 0},
	{name: 'is_unavailable', type: 'boolean', defaultValue: false},
	{name: 'isFavorites', type: 'boolean', defaultValue: false},
	{name: 'recursive', type: 'boolean', defaultValue: false}
];

Zarafa.core.data.RecordFactory.addFieldToObjectType(Zarafa.core.mapi.ObjectType.MAPI_FOLDER, Zarafa.hierarchy.data.MAPIFolderRecordFields);
Zarafa.core.data.RecordFactory.setSubStoreToObjectType(Zarafa.core.mapi.ObjectType.MAPI_FOLDER, 'permissions', Zarafa.hierarchy.data.MAPIFolderPermissionsSubStore);
Zarafa.core.data.RecordFactory.setSubStoreToObjectType(Zarafa.core.mapi.ObjectType.MAPI_FOLDER, 'folders', Zarafa.hierarchy.data.MAPIFolderSubStore);
Zarafa.core.data.RecordFactory.addListenerToObjectType(Zarafa.core.mapi.ObjectType.MAPI_FOLDER, 'createphantom', function(record) {
	// Phantom records must always be marked as opened (they contain the full set of data)
	record.afterOpen();
});

/**
 * @class Zarafa.hierarchy.data.MAPIFolderRecord
 * @extends Zarafa.core.data.IPFRecord
 *
 */
Zarafa.hierarchy.data.MAPIFolderRecord = Ext.extend(Zarafa.core.data.IPFRecord, {

	/**
	 * Usually called by the {@link Ext.data.Store} which owns the Record.
	 * Commits all changes made to the Record since either creation, or the last commit operation.
	 * <p>Developers should subscribe to the {@link Ext.data.Store#update} event
	 * to have their code notified of commit operations.</p>
	 * @param {Boolean} silent (optional) True to skip notification of the owning
	 * store of the change (defaults to false)
	 */
	commit: function()
	{
		// Check if the parent folder still is correct.
		if (this.cacheParentFolder) {
			if (!Zarafa.core.EntryId.compareEntryIds(this.get('parent_entryid'), this.cacheParentFolder.get('entryid'))) {
				delete this.cacheParentFolder;
			}
		}
		Zarafa.hierarchy.data.MAPIFolderRecord.superclass.commit.apply(this, arguments);
	},

	/**
	 * Returns a {@link Zarafa.hierarchy.data.MAPIStoreRecord MAPIStoreRecord} for the record
	 * @return {Zarafa.hierarchy.data.MAPIStoreRecord} MAPIStoreRecord or false if {@link Zarafa.core.data.IPFSubStore IPFSubStore}
	 * is not defined.
	 */
	getMAPIStore: function()
	{
		var store = this.getStore();
		if (store && store instanceof Zarafa.hierarchy.data.IPFSubStore) {
			return store.getParentRecord();
		} else {
			store = container.getHierarchyStore();
			return store.getById(this.get('store_entryid'));
		}
	},

	/**
	 * Returns the {@link Zarafa.hierarchy.data.IPFSubStore} which contains all folders
	 * of the store in which the current store is located. This is safer then using {@link #getStore},
	 * as this function will use {@link #getMAPIStore} to obtain the parent {@link Zarafa.hierarchy.data.MAPIStoreRecord}
	 * and is thus safe when the current record is located in the {@link Zarafa.core.data.ShadowStore}.
	 * @return {Zarafa.hierarchy.data.IPFSubStore} The substore containing all folders
	 */
	getMAPIFolderStore: function()
	{
		var store = this.getMAPIStore();
		if (store) {
			return store.getSubStore('folders');
		}

		return false;
	},

	/**
	 * @param {String} key key of the default folder entry (like inbox, contacts etc.)
	 * @return {Boolean} true if the folder has a type same as the passed argument type.
	 */
	isSpecialFolder: function(key)
	{
		return this.getDefaultFolderKey() === key;
	},

	/**
	 * @return {Boolean} true if folder is IPM_Subtree of own store
	 */
	isOwnRoot: function()
	{
		return (this.isIPMSubTree() && this.getMAPIStore().isDefaultStore());
	},

	/**
	 * Return {String} a shared folder key ('inbox', 'contacts', etc.) if the folder is a shared folder, or undefined otherwise.
	 */
	getSharedFolderKey: function()
	{
		var MAPIStore = this.getMAPIStore();
		if(MAPIStore) {
			return MAPIStore.getSharedFolderKey(this.get('entryid'));
		}
	},

	/**
	 * @return {Boolean} True iff the folder is a shared folder.
	 */
	isSharedFolder: function()
	{
		return Ext.isDefined(this.getSharedFolderKey()) ? true : false;
	},

	/**
	 * @return {String} a default folder key ('inbox', 'contacts', etc.) if the folder is a default folder, or undefined if otherwise.
	 */
	getDefaultFolderKey: function()
	{
		var MAPIStore = this.getMAPIStore();
		if(MAPIStore) {
			return MAPIStore.getDefaultFolderKey(this.get('entryid'));
		}
	},

	/**
	 * This will check whether the selected folder is default folder or not.
	 * @return {Boolean} true if the folder is a default folder
	 */
	isDefaultFolder: function()
	{
		return Ext.isDefined(this.getDefaultFolderKey()) ? true : false;
	},

	/**
	 * @return {Boolean} true if the folder is the subtree folder of its store else false.
	 */
	isIPMSubTree: function()
	{
		var MAPIStore = this.getMAPIStore();
		if(MAPIStore) {
			return Zarafa.core.EntryId.compareEntryIds(this.get('entryid'), MAPIStore.get('subtree_entryid'));
		}

		return false;
	},

	/**
	 * @return {Boolean} true if the folder is the to-do list search folder of the store, else false.
	 */
	isTodoListFolder: function()
	{
		var MAPIStore = container.getHierarchyStore().getDefaultStore();
		if (MAPIStore) {
			return Zarafa.core.EntryId.compareEntryIds(this.get('entryid'), MAPIStore.get('default_folder_todolist'));
		}
		return false;
	},

	/**
	 * @return {Boolean} true if the folder is the subtree folder of its store else false.
	 */
	isFavoritesRootFolder: function()
	{
		var MAPIStore = container.getHierarchyStore().getDefaultStore();
		if (MAPIStore) {
			return Zarafa.core.EntryId.compareEntryIds(this.get('entryid'), MAPIStore.get('common_view_entryid'));
		}
		return false;
	},

	/**
	 * Helper function which identify that folder is allow to drag and drop any item/folder.
	 * Special folders like search folder, favorites root folder and todo-list folder are not
	 * supported drag and drop items.
	 *
	 * @returns {boolean} return true if destination folder in normal or default folders else false.
	 */
	isDropTargetForItems: function()
	{
		return !(this.isSearchFolder() || this.isFavoritesRootFolder() || this.isTodoListFolder());
	},

	/**
	 * Helper function which identify that targeted folder is allow to drag-drop folder items or not.
	 *
	 * @returns {Boolean} return true if targeted folder allow to drag-drop any folder items else false.
	 */
	isDropTargetForFolders: function()
	{
		return this.isDropTargetForItems() && !this.isFavoritesFolder();
	},

	/**
	 * @returns {Boolean} true if the folder is the favorites folder else false.
	 */
	isFavoritesFolder: function()
	{
		return this.get('isFavorites');
	},

	/**
	 * @return {Boolean} true if the folder is the RSS feeds folder else false.
	 */
	isRSSFolder: function()
	{
		return Zarafa.core.ContainerClass.isClass(this.get('container_class'), 'IPF.Note.OutlookHomepage', true) && this.getParentFolder().isIPMSubTree();
	},

	/**
	 * @return {Boolean} true if the folder is the calendar folder else false.
	 */
	isCalendarFolder: function()
	{
		return Zarafa.core.ContainerClass.isClass(this.get('container_class'), 'IPF.Appointment', true);
	},

	/**
	 * @return {Boolean} true if the folder is the contact folder else false.
	 */
	isContactFolder: function()
	{
		return Zarafa.core.ContainerClass.isClass(this.get('container_class'), 'IPF.Contact', true);
	},

	/**
	 * @return {Boolean} true if the folder is search folder else false.
	 */
	isSearchFolder: function()
	{
		return this.get('folder_type') === Zarafa.core.mapi.MAPIFolderType.FOLDER_SEARCH;
	},

	/**
	 * Helper function to get display name of {@link Zarafa.hierarchy.data.MAPIFolderRecord MAPIFolderRecord},
	 * For a subtree folder it will get name of {@link Zarafa.hierarchy.data.MAPIStoreRecord MAPIStoreRecord},
	 * and for normal folders it will get it from display_name property.
	 * @return {String} name of the folder.
	 */
	getDisplayName: function()
	{
		if (this.isIPMSubTree()) {
			return this.getMAPIStore().get('display_name');
		} else if (this.isFavoritesRootFolder()){
			return _('Favorites');
		} else {
			return this.get('display_name');
		}
	},

	/**
	 * Helper function to obtain the fully qualified display name. For normal folders, this will return
	 * the same value as {@link #getDisplayName}, but for folders inside a shared store, this will return
	 * a string which includes the owner of the folder store. e.g.
	 * 'Calender of John Doe' or 'Contacts in Public Folders'
	 * @return {String} name of the folder
	 */
	getFullyQualifiedDisplayName: function()
	{
		var store = this.getMAPIStore();

		if (this.isIPMSubTree()) {
			return this.getDisplayName();
		} else if (store.isPublicStore()) {
			/* # TRANSLATORS: Display name of a folder: "FOLDERNAME - STORENAME". */
			return String.format(_('{0} - {1}'), this.getDisplayName(), store.get('display_name'));
		} else if (store.isSharedStore()) {
			/* # TRANSLATORS: Displayed on top of a tree with folders: "FOLDERNAME - USERNAME". */
			return String.format(_('{0} - {1}'), this.getDisplayName(), store.get('mailbox_owner_name'));
		} else {
			return this.getDisplayName();
		}
	},

	/**
	 * Function will return parent {@link Zarafa.hierarchy.data.MAPIFolderRecord MAPIFolderRecord}
	 * of this {@link Zarafa.hierarchy.data.MAPIFolderRecord MAPIFolderRecord}.
	 * @return {Zarafa.hierarchy.data.MAPIFolderRecord} parent {@link Zarafa.hierarchy.data.MAPIFolderRecord MAPIFolderRecord} or
	 * false if parent folder doesn't exist.
	 */
	getParentFolder: function()
	{
		if (!this.cacheParentFolder) {
			// don't get parent folders above IPM_SUBTREE as we are not interested in it:)
			// and also in public store ipm_subtree['entryid'] === ipm_subtree['parent_entryid']
			var parentEntryid = this.get('parent_entryid');
			if (!this.isIPMSubTree() && !Ext.isEmpty(parentEntryid)) {
				this.cacheParentFolder = this.getMAPIFolderStore().getById(parentEntryid);
			}

			// Guarantee that the parent folder knows it has children...
			// Don't use record::set() as we don't want to trigger updates.
			if (this.cacheParentFolder) {
				this.cacheParentFolder.data.has_subfolder = true;
			}
		}

		return this.cacheParentFolder;
	},

	/**
	 * Function is used to check the {@link Zarafa.hierarchy.data.MAPIFolderRecord MAPIFolder}
	 * is a <b>descendent</b> folder of the default folders 'Deleted Items' or 'Junk Mails'.
	 * @return {Boolean} returns true if given folder belongs to 'Deleted Items' or 'Junk Mails' folder.
	 */
	isInDeletedItems: function()
	{
		var mapiStore = this.getMAPIStore();
		if(mapiStore){
			if(mapiStore.isDefaultStore()) {
				var parentFolder = this.getParentFolder();
				if (!parentFolder || parentFolder.isIPMSubTree()) {
					// If there is no parent folder, or the parent is the subtree,
					// then the item is definitely not in the deleted items.
					return false;
				} else if (parentFolder.isSpecialFolder('wastebasket') || parentFolder.isSpecialFolder('junk')) {
					// The item is in the wastebasker or junk folder.
					// It is considered to be deleted.
					return true;
				} else {
					// Perhaps the parent is inside the deleted items?
					return parentFolder.isInDeletedItems();
				}
			}
		}

		return false;
	},

	/**
	 * Function will return path of the {@link Zarafa.hierarchy.data.MAPIFolderRecord MAPIFolderRecord}
	 * in the form of \\Store name\parent\parent.
	 * @return {String} path of the {@link Zarafa.hierarchy.data.MAPIFolderRecord MAPIFolderRecord}
	 * related to ipm_subtree.
	 */
	getPath: function()
	{
		var path = '';
		var parentFolder = this.getParentFolder();

		while (parentFolder) {
			path = '\\' + parentFolder.getDisplayName() + path;

			if(parentFolder.isIPMSubTree()) {
				// Add extra slash to indicate the start of the path.
				path = '\\' + path;
			}

			parentFolder = parentFolder.getParentFolder();
		}

		return path;
	},

	/**
	 * @return {Boolean} True if the folder is a Favorite folder.
	 */
	isFavoriteFolder: function()
	{
		return this.isSpecialFolder('favorites');
	},

	/**
	 * Function is use to identify selected folder marks favorites.
	 *
	 * @return {Boolean} returns true if given record exists in {@link Zarafa.common.favorites.data.MAPIFavoritesSubStore favorites} store
	 * else return false;
	 */
	existsInFavorites: function()
	{
		var favoritesStore = this.getMAPIStore().getFavoritesStore();
		if(Ext.isDefined(favoritesStore)) {
			var recordIndex = favoritesStore.find('entryid', this.get('entryid'));
			return recordIndex !== -1;
		}
		return false;
	},

	/**
	 * Function is used to retrieve {@link Zarafa.common.favorites.data.FavoritesFolderRecord favorites} record
	 * from {@link Zarafa.common.favorites.data.MAPIFavoritesSubStore favorites} store.
	 * @return {Zarafa.common.favorites.data.FavoritesFolderRecord} return favorites folder record
	 */
	getFavoritesFolder: function()
	{
		return this.getMAPIStore().getFavoritesStore().getById(this.get('entryid'));
	},

	/**
	 * @return {Zarafa.hierarchy.data.MAPIFolderRecord} IPM_COMMON_VIEWS folder which is used as favorites root folder.
	 */
	getFavoritesRootFolder: function()
	{
		return this.getMAPIStore().getFavoritesRootFolder();
	},

	/**
	 * Add current {@link Zarafa.hierarchy.data.MAPIFolderRecord folder} to Favorites list
	 * it can be favorites/search folder.
	 *
	 * @param {String} searchStoreEntryId store entryId in which this search folder is belongs.
	 */
	addToFavorites: function(searchStoreEntryId)
	{
		this.addMessageAction('action_type', 'addtofavorites');
		var isSearchFolder = this.isSearchFolder();
		if (isSearchFolder) {
			this.addMessageAction('isSearchFolder', isSearchFolder);
			this.addMessageAction('search_store_entryid', searchStoreEntryId);
		}
	},

	/**
	 * Remove current {@link Zarafa.hierarchy.data.FavoritesFolderRecord folder} to Favorites list.
	 */
	removeFromFavorites: function()
	{
		this.addMessageAction('action_type', 'removefavorites');
		var isSearchFolder = this.isSearchFolder();
		if (isSearchFolder) {
			this.addMessageAction('isSearchFolder', isSearchFolder);
		}
	},

	/**
	 * Empty the {@link Zarafa.hierarchy.data.MAPIFolderRecord DeletedItems} folder
	 */
	emptyFolder: function()
	{
		//@TODO: fire event beforeemptyfolder

		this.addMessageAction('action_type', 'emptyfolder');
	},

	/**
	 * Mark all messages contained by this {@link Zarafa.hierarchy.data.MAPIFolderRecord folder}
	 */
	seadReadFlags: function()
	{
		this.addMessageAction('action_type', 'readflags');
	},

	/**
	 * Returns all child folders of given folder.
	 *
	 * @return {Array} array of child {@link Zarafa.hierarchy.data.MAPIFolderRecord folders}
	 */
	getChildren: function()
	{
		var rs = [];

		if (this.get('has_subfolder')) {
			this.getMAPIFolderStore().each(function(record) {
				if (this === record.getParentFolder()) {
					rs.push(record);
				}
			}, this);
		}

		return rs;
	},

	/**
	 * Get {@link Zarafa.hierarchy.data.CounterTypes CounterType} to be used to display unread/total items in a
	 * {@link Zarafa.hierarchy.data.MAPIFolderRecord MAPIFolderRecord}. Which will be added to {@link Zarafa.hierarchy.ui.FolderNode FolderNode}.
	 * Function will also check which counter should be shown based on extended_flags property, and it will also check
	 * if the counter value is greater then zero or not if it is not then not logical to return counter type as it will
	 * not be useful.
	 * @return {Zarafa.hierarchy.data.CounterTypes}	type of counter.
	 */
	getCounterType: function()
	{
		// Non-openable hierarchy elements should never show counters
		if (this.isIPMSubTree() || this.isFavoritesRootFolder()) {
			return Zarafa.hierarchy.data.CounterTypes.NONE;
		}

		var extendedFlags = this.get('extended_flags');

		if ((extendedFlags & Zarafa.core.mapi.FolderExtendedFlags.DEFAULT) === Zarafa.core.mapi.FolderExtendedFlags.DEFAULT) {
			// ExtendedFlags are either not set on folder or it should use default implementation
			// Drafts, Outbox, and Junk intentionally display total item count.
			var isSpecial = this.isSpecialFolder('drafts') || this.isSpecialFolder('outbox') || this.isSpecialFolder('junk');

			if (isSpecial && this.get('content_count') > 0) {
				return Zarafa.hierarchy.data.CounterTypes.TOTAL;
			} else if (!isSpecial && this.get('content_unread') > 0 && !this.isSearchFolder()) {
				return Zarafa.hierarchy.data.CounterTypes.UNREAD;
			}
		} else if ((extendedFlags & Zarafa.core.mapi.FolderExtendedFlags.USE_UNREAD_COUNT) === Zarafa.core.mapi.FolderExtendedFlags.USE_UNREAD_COUNT &&
				this.get('content_unread') > 0) {
			// ExtendedFlags says use unread count
			return Zarafa.hierarchy.data.CounterTypes.UNREAD;
		} else if ((extendedFlags & Zarafa.core.mapi.FolderExtendedFlags.USE_TOTAL_COUNT) === Zarafa.core.mapi.FolderExtendedFlags.USE_TOTAL_COUNT &&
				this.get('content_count') > 0) {
			// ExtendedFlags says use total count
			return Zarafa.hierarchy.data.CounterTypes.TOTAL;
		}

		return Zarafa.hierarchy.data.CounterTypes.NONE;
	},

	/**
	 * Function will return counter value that should be added to {@link Zarafa.hierarchy.ui.FolderNode FolderNode}.
	 * @param {Zarafa.hierarchy.data.CounterTypes} counterType (optional) counter type for which counter value will be returned.
	 * @return {Number} total/unread counter value or undefined.
	 */
	getCounterValue: function(counterType)
	{
		if(!counterType) {
			counterType = this.getCounterType();
		}

		switch(counterType) {
			case Zarafa.hierarchy.data.CounterTypes.TOTAL:
				return this.get('content_count');
			case Zarafa.hierarchy.data.CounterTypes.UNREAD:
				return this.get('content_unread');
			case Zarafa.hierarchy.data.CounterTypes.NONE:
			/* falls through */
			default:
				return undefined;
		}
	},

	/**
	 * Helper function which used to check folder has create rights.
	 *
	 * @returns {Boolean} return true if folder have create rights else false.
	 */
	hasCreateRights: function ()
	{
		return (this.get('rights') & Zarafa.core.mapi.Rights.RIGHTS_CREATE) > 0;
	},

	/**
	 * Helper function which used to check folder has delete rights.
	 *
	 * @returns {Boolean} return true if folder have delete rights else false.
	 */
	hasDeleteOwnRights: function ()
	{
		return (this.get('rights') & Zarafa.core.mapi.Rights.RIGHTS_DELETE_OWNED) > 0;
	}
});

Zarafa.core.data.RecordFactory.setBaseClassToObjectType(Zarafa.core.mapi.ObjectType.MAPI_FOLDER, Zarafa.hierarchy.data.MAPIFolderRecord);
/*
 * #dependsFile client/zarafa/core/mapi/ObjectType.js
 * #dependsFile client/zarafa/core/data/Record.js
 * #dependsFile client/zarafa/core/data/RecordFactory.js
 * #dependsFile client/zarafa/hierarchy/data/IPFSubStore.js
 */
Ext.namespace('Zarafa.hierarchy.data');

/**
 * @class Zarafa.hierarchy.data.MAPIStoreRecordFields
 * Array of {@link Ext.data.Field field} configurations for the
 * {@link Zarafa.core.data.IPFRecord IPFRecord} object.
 */
Zarafa.hierarchy.data.MAPIStoreRecordFields = [
	{name: 'store_entryid'},
	{name: 'common_view_entryid'},
	{name: 'object_type', type: 'int', defaultValue: Zarafa.core.mapi.ObjectType.MAPI_STORE},
	{name: 'display_name', type: 'string'},
	{name: 'mdb_provider'},
	{name: 'subtree_entryid'},
	{name: 'store_support_mask', type: 'int'},
	{name: 'store_size', type: 'int', defaultValue: null},
	{name: 'quota_warning', type: 'int', defaultValue: null},
	{name: 'quota_soft', type: 'int', defaultValue: null},
	{name: 'quota_hard', type: 'int', defaultValue: null},
	{name: 'user_name', type: 'string'},
	{name: 'mailbox_owner_entryid'},
	{name: 'mailbox_owner_name'},
	{name: 'default_folder_inbox'},
	{name: 'default_folder_outbox'},
	{name: 'default_folder_sent'},
	{name: 'default_folder_wastebasket'},
	{name: 'default_folder_favorites'},
	{name: 'default_folder_publicfolders'},
	{name: 'default_folder_calendar'},
	{name: 'default_folder_contact'},
	{name: 'default_folder_drafts'},
	{name: 'default_folder_journal'},
	{name: 'default_folder_note'},
	{name: 'default_folder_task'},
	{name: 'default_folder_todolist'},
	{name: 'default_folder_junk'},
	{name: 'default_folder_syncissues'},
	{name: 'default_folder_conflicts'},
	{name: 'default_folder_localfailures'},
	{name: 'default_folder_serverfailures'},
	{name: 'shared_folder_all'},
	{name: 'shared_folder_inbox'},
	{name: 'shared_folder_calendar'},
	{name: 'shared_folder_contact'},
	{name: 'shared_folder_note'},
	{name: 'shared_folder_task'}
];

Zarafa.core.data.RecordFactory.addFieldToObjectType(Zarafa.core.mapi.ObjectType.MAPI_STORE, Zarafa.hierarchy.data.MAPIStoreRecordFields);
Zarafa.core.data.RecordFactory.setSubStoreToObjectType(Zarafa.core.mapi.ObjectType.MAPI_STORE, 'folders', Zarafa.hierarchy.data.IPFSubStore);
Zarafa.core.data.RecordFactory.setSubStoreToObjectType(Zarafa.core.mapi.ObjectType.MAPI_STORE, 'favorites', Zarafa.common.favorites.data.MAPIFavoritesSubStore);

Zarafa.core.data.RecordFactory.addListenerToObjectType(Zarafa.core.mapi.ObjectType.MAPI_STORE, 'createphantom', function(record) {
	// Phantom records must always be marked as opened (they contain the full set of data)
	record.afterOpen();
});

/**
 * @class Zarafa.hierarchy.data.MAPIStoreRecord
 * @extends Zarafa.core.data.IPFRecord
 *
 */
Zarafa.hierarchy.data.MAPIStoreRecord = Ext.extend(Zarafa.core.data.IPFRecord, {
	/**
	 * The base array of ID properties which is copied to the {@link #idProperties}
	 * when the record is being created.
	 * @property
	 * @type Array
	 * @private
	 */
	baseIdProperties: [ 'store_entryid', 'user_name' ],

	/**
	 * Retrieves a folder by MAPI id.
	 * @param {String} entryid the MAPI entry id of the folder.
	 * @return {Zarafa.hierarchy.data.MAPIFolderRecord} folder object or undefined if not found.
	 */
	getFolder: function(entryid)
	{
		var store = this.getFolderStore();

		if (store) {
			return store.getById(entryid);
		}
	},

	/**
	 * @return {Boolean} True if this store supports search folders else false.
	 */
	hasSearchSupport: function()
	{
		if (this.get('store_support_mask')) {
			if(Zarafa.core.mapi.StoreSupportMask.hasSearchSupport(this.get('store_support_mask'))) {
				return true;
			}
		}

		return false;
	},

	/**
	 * @return {Boolean} True if this is the public {@link Zarafa.core.MAPIStore store}.
	 */
	isPublicStore: function()
	{
		return (this.get('mdb_provider') === Zarafa.core.mapi.MDBProvider.ZARAFA_STORE_PUBLIC_GUID);
	},

	/**
	 * @return {Boolean} True if this is the default {@link Zarafa.core.MAPIStore store}.
	 */
	isDefaultStore: function()
	{
		return (this.get('mdb_provider') === Zarafa.core.mapi.MDBProvider.ZARAFA_SERVICE_GUID);
	},

	/**
	 * @return {Boolean} returns true this is a shared store else false
	 */
	isSharedStore: function()
	{
		return (this.get('mdb_provider') === Zarafa.core.mapi.MDBProvider.ZARAFA_STORE_DELEGATE_GUID);
	},

	/**
	 * @return {Boolean} returns true if this is a archive store else false
	 */
	isArchiveStore: function()
	{
		return (this.get('mdb_provider') === Zarafa.core.mapi.MDBProvider.ZARAFA_STORE_ARCHIVER_GUID);
	},

	/**
	 * @param {String} entryid the MAPI entry id of the folder.
	 * @return {String} a default folder key ('inbox', 'contacts', etc.) if the folder is a default folder, or undefined if otherwise.
	 */
	getDefaultFolderKey: function(entryid)
	{
		for(var key in this.data) {
			if (key.indexOf('default_folder_') === 0 && this.get(key) == entryid) {
				return key.substring('default_folder_'.length);
			}
		}
	},

	/**
	 * Function will return default {@link Zarafa.hierarchy.data.MAPIFolderRecord MAPIFolderRecord}
	 * of this {@link Zarafa.hierarchy.data.MAPIStoreRecord MAPIStoreRecord}.
	 * @param {String} name name of the default folder (i.e. 'inbox' or 'contacts')
	 * @return {Zarafa.hierarchy.data.MAPIFolderRecord} folder if a default folder
	 * with the given name was found, or undefined otherwise.
	 */
	getDefaultFolder: function(name)
	{
		var defaultFolderID = this.get('default_folder_' + name);

		if(defaultFolderID) {
			return this.getFolder(defaultFolderID);
		}
	},

	/**
	 * @param {String} entryid the MAPI entry id of the folder.
	 * @return {String} a shared folder key ('inbox', 'contacts', etc.) if the folder is a shared folder, or undefined if otherwise.
	 */
	getSharedFolderKey: function(entryid)
	{
		for(var key in this.data) {
			if (key.indexOf('shared_folder_') === 0 && this.get(key) == entryid) {
				return key.substring('shared_folder_'.length);
			}
		}
	},

	/**
	 * Function will return shared {@link Zarafa.hierarchy.data.MAPIFolderRecord MAPIFolderRecord}
	 * of this {@link Zarafa.hierarchy.data.MAPIStoreRecord MAPIStoreRecord}.
	 * @param {String} name name of the shared folder (i.e. 'inbox' or 'contacts')
	 * @return {Zarafa.hierarchy.data.MAPIFolderRecord} folder if a shared folder
	 * with the given name was found, or undefined otherwise.
	 */
	getSharedFolder: function(name)
	{
		var sharedFolderID = this.get('shared_folder_' + name);

		if(sharedFolderID) {
			return this.getFolder(sharedFolderID);
		}
	},

	/**
	 * @return {Zarafa.hierarchy.data.MAPIFolderRecord} subtree folder.
	 */
	getSubtreeFolder: function()
	{
		return this.getFolder(this.get('subtree_entryid'));
	},

	/**
	 * Function is used to get the favorites root folder.
	 * @return {Zarafa.hierarchy.data.MAPIFolderRecord} favorites root folder.
	 */
	getFavoritesRootFolder: function ()
	{
		var defaultStore = container.getHierarchyStore().getDefaultStore();
		if(defaultStore) {
			return defaultStore.getFolder(this.get('common_view_entryid'));
		}
		return false;
	},

	/**
	 * Not to be implemented by {@link Zarafa.hierarchy.data.MAPIStoreRecord MAPIStoreRecord}
	 * Get the Message Action list for the {@link Zarafa.core.data.MAPIRecord record}.
	 * @return {Mixed} The Message Action list.
	 * @hide
	 */
	getMessageActions: Ext.emptyFn,

	/**
	 * Not to be implemented by {@link Zarafa.hierarchy.data.MAPIStoreRecord MAPIStoreRecord}
	 * Add action to Message Action list
	 * @param {String} name The action name to add to the list.
	 * @hide
	 */
	addMessageAction: Ext.emptyFn,

	/**
	 * Not to be implemented by {@link Zarafa.hierarchy.data.MAPIStoreRecord MAPIStoreRecord}
	 * Delete action from the Message Action list
	 * @param {String} name The action name to delete from the list.
	 * @hide
	 */
	deleteMessageAction: Ext.emptyFn,

	/**
	 * Not to be implemented bby {@link Zarafa.hierarchy.data.MAPIStoreRecord MAPIStoreRecord}
	 * Clear all Message Actions.
	 * @hide
	 */
	clearMessageActions: Ext.emptyFn,

	/**
	 * Not to be implemented by {@link Zarafa.hierarchy.data.MAPIStoreRecord MAPIStoreRecord}
	 * Copy the {@link Zarafa.hierarchy.data.MAPIFolderRecord MAPIFolderRecord} to a different {@link Zarafa.hierarchy.data.MAPIFolderRecord MAPIFolderRecord}.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder The folder to copy the record to
	 * @hide
	 */
	copyTo: Ext.emptyFn,

	/**
	 * Not to be implemented by {@link Zarafa.hierarchy.data.MAPIStoreRecord MAPIStoreRecord}
	 * Move the {@link Zarafa.hierarchy.data.MAPIFolderRecord MAPIFolderRecord} to a different {@link Zarafa.hierarchy.data.MAPIFolderRecord MAPIFolderRecord}.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder The folder to copy the record to
	 * @hide
	 */
	moveTo: Ext.emptyFn,

	/**
	 * Returns whether the MAPIStoreRecord supports the use of folders or not (See {@link #supportsSubStore}).
	 * @return {Boolean} True if folders are supported.
	 */
	supportsFolders: function()
	{
		return this.supportsSubStore('folders');
	},

	/**
	 * Creates a Folder store for the {@link Zarafa.hierarchy.data.IPFRecord IPFRecord} (See {@link #createSubStore}).
	 * @return {Zarafa.hierarchy.data.IPFSubStore} The new Folder store.
	 */
	createFolderStore: function()
	{
		return this.createSubStore('folders');
	},

	/**
	 * Set the Folder store for the {@link Zarafa.hierarchy.data.IPFRecord IPFRecord} (See {@link #setSubStore}).
	 * @param {Zarafa.hierarchy.data.IPFSubStore} folderStore The Folder store.
	 * @return {Zarafa.hierarchy.data.IPFSubStore} The Folder store.
	 */
	setFolderStore: function(folderStore)
	{
		return this.setSubStore('folders', folderStore);
	},

	/**
	 * Get the Folder store for the {@link Zarafa.core.data.IPFRecord IPFRecord} (See {@link #getSubStore}).
	 * @return {Zarafa.hierarchy.data.IPFSubStore} The Folder store.
	 */
	getFolderStore: function()
	{
		return this.getSubStore('folders');
	},

	/**
	 * Get the favorites store for the {@link Zarafa.core.data.IPFRecord IPFRecord}.
	 * @returns {Zarafa.hierarchy.data.IPFSubStore} The Favorites store.
	 */
	getFavoritesStore: function ()
	{
		var defaultStore = container.getHierarchyStore().getDefaultStore();
		if(defaultStore) {
			return defaultStore.getSubStore('favorites');
		}
		return false;
	}
});

Zarafa.core.data.RecordFactory.setBaseClassToObjectType(Zarafa.core.mapi.ObjectType.MAPI_STORE, Zarafa.hierarchy.data.MAPIStoreRecord);
/*
 * #dependsFile client/zarafa/hierarchy/data/SharedFolderTypes.js
 * #dependsFile client/zarafa/core/mapi/ObjectType.js
 */
Ext.namespace('Zarafa.hierarchy.dialogs');

/**
 * @class Zarafa.hierarchy.dialogs.SharedFolderContentPanel
 * @extends Zarafa.core.ui.ContentPanel
 * @xtype zarafa.sharedfoldercontentpanel
 */
Zarafa.hierarchy.dialogs.SharedFolderContentPanel = Ext.extend(Zarafa.core.ui.ContentPanel, {
	/**
	 * @cfg {Zarafa.hierarchy.data.SharedFolderTypes} defaultSelectedFolderType Default selected folder in combolist.
	 */
	defaultSelectedFolderType: Zarafa.hierarchy.data.SharedFolderTypes['ALL'],

	/**
	 * @cfg {Zarafa.core.data.IPMRecipientStore} store The store in which the user is stored which
	 * will contain the user whose store we wish to open.
	 */
	store: undefined,

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

		if (!config.store) {
			config.store = new Zarafa.core.data.IPMRecipientStore({
				allowResolvingToLocalContacts: false,
				allowResolvingToGABGroups: false,
				preferDisplayNameForResolve: true
			});
		}

		config = Ext.applyIf(config, {
			// Override from Ext.Component
			xtype: 'zarafa.sharedfoldercontentpanel',
			layout: 'fit',
			title: _('Open Shared Folders'),
			width: 330,
			height: 250,
			items: [{
				xtype: 'zarafa.sharedfolderpanel',
				ref: 'sharedFolderPanel',
				store: config.store,
				defaultSelectedFolderType: config.defaultSelectedFolderType || this.defaultSelectedFolderType,
				buttons: [{
					text: _('Open'),
					handler: this.onOpen,
					scope: this
				},{
					text: _('Cancel'),
					handler: this.onCancel,
					scope: this
				}]
			}]
		});

		Zarafa.hierarchy.dialogs.SharedFolderContentPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Event handler which is raised when the user clicks the "Ok" {@link Ext.Button button}
	 *
	 * This will apply the current categories to all {@link Zarafa.core.data.IPMRecord records}
	 * and will close the panel.
	 * @private
	 */
	onOpen: function()
	{
		if (this.store && this.store.getCount() > 0) {
			var recipient = this.store.getAt(0);

			// If the recipient has not yet been resolved, then we should
			// wait until the store has correctly resolved the user.
			if (!recipient.isResolved() && !recipient.attemptedToResolve()) {
				this.mon(this.store, 'resolved', this.onOpen, this, { single: true });
				return;
			}

			// Check if we have a valid user which has a store. When this is not the case
			// just show an error message and keep the panel open to allow the user
			// a second chance of entering a valid user.
			if (!recipient.isResolved()) {
				Ext.MessageBox.show({
					title: _('Unresolved user'),
					msg: String.format(_('Username \'{0}\' could not be resolved.'), recipient.get('display_name')),
					buttons: Ext.MessageBox.OK
				});
				return;
			} else if (recipient.get('object_type') !== Zarafa.core.mapi.ObjectType.MAPI_MAILUSER) {
				Ext.MessageBox.show({
					title: _('Invalid user'),
					msg: String.format(_('Username \'{0}\' is not a valid user.'), recipient.get('display_name')),
					buttons: Ext.MessageBox.OK
				});
				return;
			} else if ((recipient.get('smtp_address') || recipient.get('email_address') || '').toLowerCase() ===
				(container.getUser().getEmailAddress() || '').toLowerCase()) {
				Ext.MessageBox.show({
					title: _('Own store'),
					msg: _('It is not possible to open your own store twice.'),
					buttons: Ext.MessageBox.OK
				});
				return;
			}

			var name = recipient.get('smtp_address') || recipient.get('email_address');
			var options = this.sharedFolderPanel.getFolderOptions();

			// Check if we are able to open the Shared Store, if not
			// it has already been opened.
			var opened = container.getHierarchyStore().open(name, options['type'], options['subfolders']);
			if (!opened) {
				Ext.MessageBox.show({
					title: _('Folder already open'),
					msg: _('This shared folder is already open.'),
					buttons: Ext.MessageBox.OK
				});
			} else {
				this.close();
			}
		} else {
			Ext.MessageBox.show({
				title: _('No user'),
				msg: _('You must specify a user'),
				buttons: Ext.MessageBox.OK
			});
		}
	},

	/**
	 * Event handler which is raised when the user clicks the "Cancel" {@link Ext.Button button}
	 *
	 * This will close the panel without saving
	 * @private
	 */
	onCancel: function()
	{
		this.close();
	}
});

Ext.reg('zarafa.sharedfoldercontentpanel', Zarafa.hierarchy.dialogs.SharedFolderContentPanel);
/*
 * #dependsFile client/zarafa/hierarchy/data/SharedFolderTypes.js
 */
Ext.namespace('Zarafa.hierarchy.dialogs');

/**
 * @class Zarafa.hierarchy.dialogs.SharedFolderPanel
 * @extends Ext.Panel
 * @xtype zarafa.sharedfolderpanel
 */
Zarafa.hierarchy.dialogs.SharedFolderPanel = Ext.extend(Ext.Panel, {
	/**
	 * @cfg {Zarafa.hierarchy.data.SharedFolderTypes} defaultSelectedFolderType Default selected folder in combolist.
	 */
	defaultSelectedFolderType: Zarafa.hierarchy.data.SharedFolderTypes['ALL'],

	/**
	 * @cfg {Zarafa.core.data.IPMRecipientStore} store The store in which the user is stored which
	 * will contain the user whose store we wish to open.
	 */
	store: undefined,

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

		Ext.applyIf(config, {
			layout: 'form',
			border: false,
			labelWidth: 87,
			flex: 1,
			items: [
				this.createRecipientField(config.store),
				this.createFolderTypeCombo(config.defaultSelectedFolderType),
				this.createSubfolderSelection()
			]
		});

		Zarafa.hierarchy.dialogs.SharedFolderPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Creates recipient selection field.
	 * @param {Zarafa.core.data.IPMRecipientStore} store The store to hook into the RecipientField
	 * @return {Object} config object for creating recipient selection field.
	 * @private
	 */
	createRecipientField: function(store)
	{
		return {
			xtype: 'zarafa.compositefield',
			hideLabel: true,
			style: 'margin-top: 10px; margin-bottom: 10px;',
			anchor:'100%',
			items: [{
				xtype: 'button',
				anchor: '100%',
				text: _('Name') + ':',
				handler: this.onSelectUser,
				scope: this
			},{
				xtype: 'zarafa.addressbookboxfield',
				fieldLabel: _('Name'),
				ref: '../userField',
				boxStore: store,
				flex: 1,
				boxLimit: 1
			}]
		};
	},

	/**
	 * Creates folder type selection combo,
	 * To chose which type of shared folder user want to open.
	 * @param {Zarafa.hierarchy.data.SharedFolderTypes} defaultSelectedFolderType The folder type which will be
	 * selected by default
	 * @return {Object} config object for creating folder type selection combo.
	 * @private
	 */
	createFolderTypeCombo: function(defaultSelectedFolderType)
	{
		return {
			xtype: 'combo',
			fieldLabel: _('Folder type'),
			typeAhead: true,
			triggerAction: 'all',
			lazyRender: true,
			mode: 'local',
			style: 'margin-bottom: 10px;',
			anchor:'100%',
			value: defaultSelectedFolderType,
			store: new Ext.data.JsonStore({
				fields: ['value', 'name'],
				data: Zarafa.hierarchy.data.SharedFolderTypes.folders
			}),
			lazyInit: false,
			editable: false,
			displayField: 'name',
			valueField: 'value',
			ref: 'folderTypeCombo',
			listeners: {
				select: this.onFolderTypeSelect,
				scope: this
			}
		};
	},

	/**
	 * Creates subfolder selection checkbox,
	 * To chose whether user want to open subfolders of the shared folders.
	 * @return {Object} config object for subfolder selection checkbox.
	 * @private
	 */
	createSubfolderSelection: function()
	{
		return {
			xtype: 'checkbox',
			ref: 'checkSubFolders',
			boxLabel: _('Show subfolders'),
			anchor:'100%'
		};
	},

	/**
	 * Called by ExtJs during rendering of the component. This will call
	 * {@link #updateSubFolderCheckBox} to initialize the dialog.
	 * @private
	 */
	onRender: function()
	{
		Zarafa.hierarchy.dialogs.SharedFolderPanel.superclass.onRender.apply(this, arguments);
		this.updateSubFolderCheckBox();
	},

	/**
	 * Event handler which is fired when the user presses the 'Name' button.
	 * This will open the Address Book User Selection Dialog to select a user.
	 * @private
	 */
	onSelectUser: function()
	{
		Zarafa.common.Actions.openABUserSelectionContent({
			callback: this.abCallBack,
			scope: this,
			hideContactsFolders: true,
			listRestriction: {
				hide_users: [ 'contact', 'system' ],
				hide_groups: true,
				hide_companies: true
			}
		});
	},

	/**
	 * Callback function for {@link Zarafa.addressbook.dialogs.ABUserSelectionContentPanel AddressBook}
	 * @param {Ext.data.Record} record user selected from AddressBook
	 * @private
	 */
	abCallBack: function(record)
	{
		this.store.removeAll();
		this.store.add(record.convertToRecipient());
	},

	/**
	 * Called when the user selects a different Folder Type from the ComboBox.
	 * This will call {@link #updateSubFolderCheckBox}.
	 * @param {Ext.form.ComboBox} field The combobox which was changed
	 * @param {Mixed} newValue The new value which was selected
	 * @private
	 */
	onFolderTypeSelect: function(field, newValue)
	{
		this.updateSubFolderCheckBox();
	},

	/**
	 * This will enabled/disable the "Show Subfolders" checkbox depending if the
	 * Folder Type selection is for the {@link Zarafa.common.data.FolderContentTypes#ipmsubtree} or not.
	 * @private
	 */
	updateSubFolderCheckBox: function()
	{
		var enabled = this.folderTypeCombo.getValue() !== Zarafa.hierarchy.data.SharedFolderTypes['ALL'];
		this.checkSubFolders.setDisabled(!enabled);
	},

	/**
	 * Obtain the options which are currently selected in the form.
	 * @return {Object} The Shared Folder options which were selected
	 */
	getFolderOptions: function()
	{
		return {
			type: this.folderTypeCombo.getValue(),
			subfolders: this.checkSubFolders.getValue()
		};
	}
});

Ext.reg('zarafa.sharedfolderpanel', Zarafa.hierarchy.dialogs.SharedFolderPanel);
Ext.namespace('Zarafa.mail');

/**
 * @class Zarafa.mail.MailContext
 * @extends Zarafa.core.Context
 */
Zarafa.mail.MailContext = Ext.extend(Zarafa.core.Context, {
	// Insertion points for this class
	/**
	 * @insert main.maintoolbar.view.mail
	 * Insertion point for populating the main toolbar with a View button. This item is only visible
	 * when this context is active.
	 * @param {Zarafa.mail.MailContext} context This context
	 */

	/**
	 * When searching, this property marks the {@link Zarafa.core.Context#getCurrentView view}
	 * which was used before {@link #onSearchStart searching started} the view was switched to
	 * {@link Zarafa.mail.data.Views#SEARCH}.
	 * @property
	 * @type Mixed
	 * @private
	 */
	oldView: undefined,

	/**
	 * When searching, this property marks the {@link Zarafa.core.Context#getCurrentViewMode viewmode}
	 * which was used before {@link #onSearchStart searching started} the viewmode was switched to
	 * {@link Zarafa.mail.data.ViewModes#SEARCH}.
	 * @property
	 * @type Mixed
	 * @private
	 */
	oldViewMode: undefined,

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

		Ext.applyIf(config, {
			current_view: Zarafa.mail.data.Views.LIST,
			current_view_mode: Zarafa.mail.data.ViewModes.RIGHT_PREVIEW
		});

		Zarafa.mail.MailContext.superclass.constructor.call(this, config);

		// The "New email" button which is available in all contexts
		this.registerInsertionPoint('main.maintoolbar.new.item', this.createNewMailButton, this);
		this.registerInsertionPoint('context.mainpaneltoolbar.item', this.createFilterButton, this);

		// The tab in the top tabbar
		this.registerInsertionPoint('main.maintabbar.left', this.createMainTab, this);

		// Add a tree control showing a list of note folders to the navigation panel.
		// The control will be shown when the user selects the note context from the button panel.
		this.registerInsertionPoint('navigation.center', this.createMailNavigationPanel, this);

		// Register the Mail category for the settings
		this.registerInsertionPoint('context.settings.categories', this.createSettingCategories, this);

		// Register insertion point to insert toolbar buttons on the right side of menu.
		var toolbarButtons = new Zarafa.common.ui.PreviewPanelToolbarButtons({model: this.getModel()});
		this.registerInsertionPoint('previewpanel.toolbar.right', toolbarButtons.getToolbarButtons, toolbarButtons);

		// Extend the Contact and AddressBook context with email buttons
		this.registerInsertionPoint('context.addressbook.contextmenu.actions', this.createSendEmailContextItem, this);
		this.registerInsertionPoint('context.contact.contextmenu.actions', this.createSendEmailContextItem, this);
		this.registerInsertionPoint('context.contact.contactcontentpanel.toolbar.actions', this.createSendEmailButton, this);
		this.registerInsertionPoint('context.contact.distlistcontentpanel.toolbar.actions', this.createSendEmailButton, this);

		// Register mail specific dialog types
		Zarafa.core.data.SharedComponentType.addProperty('mail.dialog.options');
    Zarafa.core.data.SharedComponentType.addProperty('mail.dialog.delayeddelivery');
	},

	/**
	 * @return {Zarafa.mail.MailContextModel} the mail context model
	 */
	getModel: function()
	{
		if (!Ext.isDefined(this.model)) {
			this.model = new Zarafa.mail.MailContextModel();
			this.model.on({
				'searchstart': this.onModelSearchStart,
				'searchstop': this.onModelSearchStop,
				'livescrollstart': this.onModelLiveScrollStart,
				'livescrollstop': this.onModelLiveScrollStop,
				scope: this
			});
		}
		return this.model;
	},

	/**
	 * Event handler for the {@link #model}#{@link Zarafa.core.ContextModel#livescrollstart live scroll start} event.
	 * This will {@link #switchView switch the view} to {@link Zarafa.mail.data.Views#LIVESCROLL live scroll mode}.
	 * The previously active {@link #getCurrentView view} will be stored in the {@link #oldView} and will
	 * be recovered when the {@link #onModelLiveScrollStop live scroll is stopped}.
	 * @param {Zarafa.core.ContextModel} model The model which fired the event
	 * @private
	 */
	onModelLiveScrollStart: function(model)
	{
		if(this.getCurrentView() !== Zarafa.mail.data.Views.LIVESCROLL && this.getCurrentViewMode() !== Zarafa.mail.data.ViewModes.LIVESCROLL){
			/*
			 * Check that current view model is NO_PREVIEW, RIGHT_PREVIEW,
			 * or BOTTOM_PREVIEW then set the view mode to oldViewMode and view to oldView.
			 */
			if(Zarafa.mail.data.ViewModes.isMainViewMode(this.getCurrentViewMode())) {
				this.oldView = this.getCurrentView();
				this.oldViewMode = this.getCurrentViewMode();
			}
			this.switchView(Zarafa.mail.data.Views.LIVESCROLL, Zarafa.mail.data.ViewModes.LIVESCROLL);
		}
	},

	/**
	 * Event handler for the {@link #model}#{@link Zarafa.core.ContextModel#livescrollstop live scroll stop} event.
	 * This will {@link #switchView switch the view} to the {@link #oldView previous view}.
	 * @param {Zarafa.core.ContextModel} model The model which fired the event
	 * @private
	 */
	onModelLiveScrollStop: function(model)
	{
		/*
		 * Check requires to prevent loading old view again when user
		 * - Loads all mails in grid using live scroll
		 * - Switch the view
		 * - Click on refresh button
		 * To overcome the above mentioned problem,
		 * we have to check that currently selected mode is one of the main view mode
		 * (NO_PREVIEW, RIGHT_PREVIEW and BOTTOM_PREVIEW) then switch the view with it
		 * else switch with old view and view mode.
		 */
		if(Zarafa.mail.data.ViewModes.isMainViewMode(this.getCurrentViewMode())) {
			this.switchView(this.getCurrentView(), this.getCurrentViewMode());
		} else {
			this.switchView(this.oldView, this.oldViewMode);
		}
		delete this.oldView;
		delete this.oldViewMode;
	},

	/**
	 * Event handler for the {@link #model}#{@link Zarafa.core.ContextModel#searchstart searchstart} event.
	 * This will {@link #switchView switch the view} to {@link Zarafa.mail.data.Views#SEARCH search mode}.
	 * The previously active {@link #getCurrentView view} will be stored in the {@link #oldView} and will
	 * be recovered when the {@link #onModelSearchStop search is stopped}.
	 * @param {Zarafa.core.ContextModel} model The model which fired the event
	 * @private
	 */
	onModelSearchStart: function(model)
	{
		if(this.getCurrentView() !== Zarafa.mail.data.Views.SEARCH && this.getCurrentViewMode() !== Zarafa.mail.data.ViewModes.SEARCH){
			/*
			 * Check that current view model is one of the view mode from
			 * NO_PREVIEW, RIGHT_PREVIEW and BOTTOM_PREVIEW then set the view mode to
			 * oldViewMode and view to oldView.
			 */
			if(Zarafa.mail.data.ViewModes.isMainViewMode(this.getCurrentViewMode())) {
				this.oldView = this.getCurrentView();
				this.oldViewMode = this.getCurrentViewMode();
			}
			this.switchView(Zarafa.mail.data.Views.SEARCH, Zarafa.mail.data.ViewModes.SEARCH);
		}
	},

	/**
	 * Event handler for the {@link #model}#{@link Zarafa.core.ContextModel#searchstop searchstop} event.
	 * This will {@link #switchView switch the view} to the {@link #oldView previous view}.
	 * @param {Zarafa.core.ContextModel} model The model which fired the event
	 * @private
	 */
	onModelSearchStop: function(model)
	{
		this.switchView(this.oldView, this.oldViewMode);
		delete this.oldView;
		delete this.oldViewMode;
	},

	/**
	 * Bid for the given {@link Zarafa.hierarchy.data.MAPIFolderRecord folder}
	 * This will bid on any folder of container class 'IPF.Note'.
	 *
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder The folder for which the context is bidding
	 * @return {Number} 1 when the contexts supports the folder, -1 otherwise
	 */
	bid: function(folder)
	{
		// The folder contains items of type IPF.Note
		if (folder.isContainerClass('IPF.Note', true)) {
			return 1;
		} else if (folder.isContainerClass('IPF', true)) {
			// Fallback for unknown folders...
			return 0;
		}

		// return -1, don't know this content type
		return -1;
	},

	/**
	 * Bid for the type of shared component and the given record.
	 * This will bid on a common.create or common.view for a
	 * record with a message class set to IPM or IPM.Note.
	 * @param {Zarafa.core.data.SharedComponentType} type Type of component a context can bid for.
	 * @param {Ext.data.Record} record Optionally passed record.
	 * @return {Number} The bid for the shared component
	 */
	bidSharedComponent: function(type, record)
	{
		var bid = -1;

		if (Array.isArray(record)) {
			record = record[0];
		}

		switch (type) {
			case Zarafa.core.data.SharedComponentType['common.create']:
			case Zarafa.core.data.SharedComponentType['common.view']:
			case Zarafa.core.data.SharedComponentType['common.preview']:
				if (record instanceof Zarafa.core.data.IPMRecord) {
					if (record.get('object_type') === Zarafa.core.mapi.ObjectType.MAPI_MESSAGE) {
						if (record.isMessageClass([ 'IPM.Note', 'IPM.Schedule.Meeting', 'REPORT.IPM','REPORT.IPM.Note' ], true)) {
							bid = 1;
						} else {
							bid = 0;
						}
					}
				}
				break;
			// Bid for mail specific dialog
			case Zarafa.core.data.SharedComponentType['mail.dialog.options']:
      case Zarafa.core.data.SharedComponentType['mail.dialog.delayeddelivery']:
				bid = 1;
				break;
			case Zarafa.core.data.SharedComponentType['common.contextmenu']:
				if (record instanceof Zarafa.core.data.IPMRecord) {
					if (record.get('object_type') === Zarafa.core.mapi.ObjectType.MAPI_MESSAGE) {
						if (record.isMessageClass([ 'IPM.Note', 'IPM.TaskRequest', 'IPM.Schedule.Meeting', 'REPORT.IPM','REPORT.IPM.Note' ], true)) {
							bid = 1;
						} else {
							bid = 0;
						}
					}
				}
				break;
			case Zarafa.core.data.SharedComponentType['common.printer.renderer']:
				if (record instanceof Zarafa.core.data.IPMRecord) {
					if (record.get('object_type') === Zarafa.core.mapi.ObjectType.MAPI_MESSAGE) {
						// @todo test if report mails print well in default mail renderer
						if (record.isMessageClass([ 'IPM.Note', 'REPORT.IPM','REPORT.IPM.Note' ], true)) {
							bid = 1;
						}
					}
				}
				break;
			case Zarafa.core.data.SharedComponentType['common.attachment.dialog.attachitem.columnmodel']:
				if (record instanceof Zarafa.hierarchy.data.MAPIFolderRecord) {
					if (record.isContainerClass('IPF.Note', true)) {
						bid = 1;
					} else if (record.isContainerClass('IPF', true)) {
						// Lowest bid, so if any context has not bidded for any component then by default this context will bid
						bid = 0;
					}
				}
				break;
			case Zarafa.core.data.SharedComponentType['common.attachment.dialog.attachitem.textrenderer']:
				if (record instanceof Zarafa.core.data.IPMRecord) {
					if (record.isMessageClass([ 'IPM.Note', 'REPORT.IPM', 'REPORT.IPM.Note' ], true)) {
						bid = 1;
					} else if (record.isMessageClass('IPM', true)) {
						// Lowest bid, so if any context has not bidded for any component then by default this context will bid
						bid = 0;
					}
				}
				break;
		}
		return bid;
	},

	/**
	 * Will return the reference to the shared component.
	 * Based on the type of component requested a component is returned.
	 * @param {Zarafa.core.data.SharedComponentType} type Type of component a context can bid for.
	 * @param {Ext.data.Record} record Optionally passed record.
	 * @return {Ext.Component} Component
	 */
	getSharedComponent: function(type, record)
	{
		var component;
		switch (type) {
			case Zarafa.core.data.SharedComponentType['common.create']:
				component = Zarafa.mail.dialogs.MailCreateContentPanel;
				break;
			case Zarafa.core.data.SharedComponentType['common.view']:
				component = Zarafa.mail.dialogs.ShowMailContentPanel;
				break;
			case Zarafa.core.data.SharedComponentType['mail.dialog.options']:
				if (!record || record.phantom || record.isUnsent()) {
					component = Zarafa.mail.dialogs.MailCreateOptionsContentPanel;
				} else {
					component = Zarafa.mail.dialogs.MailViewOptionsContentPanel;
				}
				break;
			case Zarafa.core.data.SharedComponentType['common.preview']:
				component = Zarafa.mail.ui.MailViewPanel;
				break;
			case Zarafa.core.data.SharedComponentType['common.contextmenu']:
				component = Zarafa.mail.ui.MailGridContextMenu;
				break;
			case Zarafa.core.data.SharedComponentType['common.printer.renderer']:
				if (record instanceof Zarafa.core.data.IPMRecord) {
					component = Zarafa.mail.printer.MailRenderer;
				}
				break;
			case Zarafa.core.data.SharedComponentType['common.attachment.dialog.attachitem.columnmodel']:
				component = Zarafa.mail.attachitem.AttachMailColumnModel;
				break;
			case Zarafa.core.data.SharedComponentType['common.attachment.dialog.attachitem.textrenderer']:
				component = Zarafa.mail.attachitem.AttachMailRenderer;
				break;
      case Zarafa.core.data.SharedComponentType['mail.dialog.delayeddelivery']:
        component = Zarafa.mail.dialogs.DelayedDeliveryContentPanel;
		}
		return component;
	},

	/**
	 * Obtain the {@link Zarafa.mail.ui.MailPanel Mailpanel} object
	 *
	 * @return {Zarafa.mail.ui.MailPanel} The main panel which should
	 * be used within the {@link Zarafa.core.Context context}
	 */
	createContentPanel: function()
	{
		return {
			xtype: 'zarafa.mailpanel',
			id: 'zarafa-mainpanel-contentpanel-mail',
			context: this
		};
	},

	/**
	 * Creates the mail tree that is shown when the user selects the mail context from the
	 * button panel. It shows a tree of available mail folders
	 * @private
	 */
	createMailNavigationPanel: function()
	{
		return {
			xtype: 'zarafa.contextnavigation',
			context: this,
			items: [{
				xtype: 'panel',
				id: 'zarafa-navigationpanel-mail-navigation',
				cls: 'zarafa-context-navigation-block',
				ref: 'mailnavigation',
				layout: 'fit',
				items: [{
					xtype: 'zarafa.hierarchytreepanel',
					id: 'zarafa-navigationpanel-mail-navigation-tree',
					model: this.getModel(),
					IPMFilter: 'IPF.Note',
					hideDeletedFolders: false,
					enableDD: true,
					enableItemDrop: true,
					deferredLoading: true,
					bbarConfig: {
						defaultSelectedSharedFolderType: Zarafa.hierarchy.data.SharedFolderTypes['MAIL'],
						buttonText: _('Open Shared Mails')
					}
				}]
			}]
		};
	},

	/**
	 * Create "New Email" {@link Ext.menu.MenuItem item} for the "New item"
	 * {@link Ext.menu.Menu menu} in the {@link Zarafa.core.ui.MainToolbar MainToolbar}.
	 * This button should be shown in all {@link Zarafa.core.Context contexts} and
	 * is used to create a new Email.
	 *
	 * @return {Object} The menu item for creating a new Email item
	 */
	createNewMailButton: function()
	{
		return {
			xtype: 'menuitem',
			id: 'zarafa-maintoolbar-newitem-mail',
			tooltip: _('Email') + Zarafa.core.KeyMapMgr.formatShortcutHint('Ctrl + Alt + X', false),
			plugins: 'zarafa.menuitemtooltipplugin',
			text: _('Email'),
			iconCls: 'icon_new_email',
			newMenuIndex: 1,
			context: 'mail',
			handler: function()
			{
				Zarafa.mail.Actions.openCreateMailContent(this.getModel());
			},
			scope: this
		};
	},

	/**
	 * Handler for the insertion points for extending the Contacts and AddressBook context menus
	 * with buttons to send a mail to the given Contact and Address Book.
	 * @private
	 */
	createSendEmailContextItem: function()
	{
		return {
			text: _('Send email'),
			iconCls: 'icon_new_email',
			scope: this,
			handler: function(item) {
				Zarafa.mail.Actions.openCreateMailContentForContacts(
					this.getModel(),
					item.parentMenu.records, {
					ABDialog: item.parentMenu.dialog
				});
			},
			beforeShow: function(item, records) {
				var visible = false;
				var hasMixRecord = false;
				for (var i = 0, len = records.length; i < len; i++) {
					var record = records[i];
					if (hasMixRecord == false && !record.isMessageClass(['IPM.Contact', 'IPM.DistList'])) {
						hasMixRecord = true;
					}
					if (visible == false && this.isSendEmailButtonVisible(record)) {
						visible = true;
					}
				}
				item.setVisible(visible && !hasMixRecord);
			}
		};
	},

	/**
	 * Handler for the insertion points for extending the Contacts and Distribution Dialogs
	 * with buttons to send a mail to the given Contact or Distribution list.
	 * @private
	 */
	createSendEmailButton: function()
	{
		return {
			xtype: 'button',
			plugins: [ 'zarafa.recordcomponentupdaterplugin' ],
			iconCls: 'icon_new_email',
			overflowText: _('Send email'),
			tooltip: _('Send email'),
			handler: function(btn) {
				Zarafa.mail.Actions.openCreateMailContentForContacts(this.getModel(), btn.record);
			},
			scope: this,
			update: function(record, resetContent) {
				this.record = record;
				if (resetContent) {
					// Small workaround, update is called from the btn scope,
					// but the handler from the Context scope. So access
					// isSendEmailButtonVisible from the scope.
					if (!this.scope.isSendEmailButtonVisible(record)) {
						this.hide();
					}
				}
			}
		};
	},

	/**
	 * Check if the given record (which represents a Contact or Distribution list
	 * can be mailed (this requires the record not to be a {@link Ext.data.Record#phantom}
	 * and the Contact should {@link Zarafa.contact.ContactRecord#hasEmailAddress have an email address}.
	 * @param {Zarafa.core.data.MAPIRecord} record The record to check
	 * @return {Boolean} True if we can send an email to this contact/distlist
	 * @private
	 */
	isSendEmailButtonVisible: function(record)
	{
		if (record.phantom) {
			return false;
		} else if (record.isMessageClass('IPM.Contact')) {
			if (!record.hasEmailAddress()) {
				return false;
			}
		}

		return true;
	},

	/**
	 * Create the mail {@link Zarafa.settings.ui.SettingsCategory Settings Category}
	 * to the {@link Zarafa.settings.SettingsContext}. This will create new
	 * {@link Zarafa.settings.ui.SettingsCategoryTab tabs} for the
	 * {@link Zarafa.mail.ui.SettingsMailCategory Mail} and the
	 * {@link Zarafa.mail.ui.SettingsOutOfOfficeCategory Out of Office}
	 * in the {@link Zarafa.settings.ui.SettingsCategoryWidgetPanel Widget Panel}.
	 * @param {String} insertionName insertion point name that is currently populated
	 * @param {Zarafa.settings.ui.SettingsMainPanel} settingsMainPanel settings main panel
	 * which is populating this insertion point
	 * @param {Zarafa.settings.SettingsContext} settingsContext settings context
	 * @return {Array} configuration object for the categories to register
	 * @private
	 */
	createSettingCategories: function(insertionName, settingsMainPanel, settingsContext)
	{
		return [{
			xtype: 'zarafa.settingsmailcategory',
			settingsContext: settingsContext
		},{
			xtype: 'zarafa.settingsoutofofficecategory',
			settingsContext: settingsContext
		},{
			xtype: 'zarafa.settingssafesendercategory',
			settingsContext: settingsContext
		}];
	},

	/**
	 * Returns the buttons for the dropdown list of the VIEW-button in the main toolbar. It will use the
	 * main.maintoolbar.view.mail insertion point to allow other plugins to add their items at the end.
	 *
	 * @return {Ext.Component[]} an array of components
	 */
	getMainToolbarViewButtons: function()
	{
		var items = container.populateInsertionPoint('main.maintoolbar.view.mail', this) || [];

		var defaultItems = [{
			id: 'zarafa-maintoolbar-view-mail-nopreview',
			overflowText: _('No preview'),
			iconCls: 'icon_previewpanel_off',
			text: _('No preview'),
			valueView: Zarafa.mail.data.Views.LIST,
			valueViewMode: Zarafa.mail.data.ViewModes.NO_PREVIEW,
			handler: this.onContextSelectView,
			scope: this
		},{
			id: 'zarafa-maintoolbar-view-mail-previewright',
			overflowText: _('Right preview'),
			iconCls: 'icon_previewpanel_right',
			text: _('Right preview'),
			valueView: Zarafa.mail.data.Views.LIST,
			valueViewMode: Zarafa.mail.data.ViewModes.RIGHT_PREVIEW,
			handler: this.onContextSelectView,
			scope: this
		},{
			id: 'zarafa-maintoolbar-view-mail-previewbottom',
			overflowText: _('Bottom preview'),
			iconCls: 'icon_previewpanel_bottom',
			text: _('Bottom preview'),
			valueView: Zarafa.mail.data.Views.LIST,
			valueViewMode: Zarafa.mail.data.ViewModes.BOTTOM_PREVIEW,
			handler: this.onContextSelectView,
			scope: this
		}];

		return defaultItems.concat(items);
	},

	/**
	 * Event handler which is fired when one of the View buttons
	 * has been pressed. This will call {@link #setView setView}
	 * to update the view.
	 * @param {Ext.Button} button The button which was pressed
	 * @private
	 */
	onContextSelectView: function(button)
	{
		var model = this.getModel();
		this.getModel().setDataMode(model.getCurrentDataMode());
		this.switchView(button.valueView, button.valueViewMode);
	},

	/**
	 * Returns the buttons for the dropdown list of the Print button in the main toolbar. It will use the
	 * main.maintoolbar.print.mail insertion point to allow other plugins to add their items at the end.
	 *
	 * @return {Ext.Component[]} an array of components
	 */
	getMainToolbarPrintButtons: function()
	{
		var items = container.populateInsertionPoint('main.toolbar.print.mail', this) || [];

		var defaultItems = [{
			xtype: 'zarafa.conditionalitem',
			id: 'zarafa-maintoolbar-print-singlemail',
			overflowText: _('Print single email'),
			iconCls: 'icon_print',
			tooltip: _('Print selected email') + Zarafa.core.KeyMapMgr.formatShortcutHint('Ctrl + P', true),
			plugins: 'zarafa.menuitemtooltipplugin',
			text: _('Print single email'),
			hideOnDisabled: false,
			singleSelectOnly: true,
			handler: this.onPrintSelected.createDelegate(this, [_('No message selected')], 2),
			scope: this
		}];

		return defaultItems.concat(items);
	},

	/**
	 * Event handler which is fired when the 'print list' item in the dropdown has been pressed
	 * This calls {@link Zarafa.common.Actions.openPrintDialog} with the current context.
	 * @private
	 */
	onPrintList: function()
	{
		Zarafa.common.Actions.openPrintDialog(this);
	},

	/**
	 * Adds a button to the top tab bar for this context.
	 * @return {Object} The button for the top tabbar
	 * @private
	 */
	createMainTab: function()
	{
		return {
			text: this.getDisplayName(),
			tabOrderIndex: 2,
			context: this.getName(),
			id: 'mainmenu-button-mail'
		};
	},

	/**
	 * Create filter button in {@link Zarafa.common.ui.ContextMainPanelToolbar ContextMainPanelToolbar}
	 *
	 * @param {String} insertionPoint The insertionPoint text.
	 * @param {Zarafa.core.Context} currentContext The current context in which this
	 * insertion point triggered.
	 * @return {Object} configuration object to create filter button.
	 */
	createFilterButton: function(insertionPoint, currentContext)
	{
		var hidden = true;
		if (Ext.isDefined(currentContext)) {
			hidden = currentContext.getName() !== 'mail';
		}
		var isCurrDataModeUnread = this.model.getCurrentDataMode() === Zarafa.mail.data.DataModes.UNREAD;
		return {
			xtype: 'splitbutton',
			cls: 'k-filter-options-btn',
			text: '<span>' + _('Filter') + '</span>',
			overflowText: _('Filter'),
			iconCls: 'icon_filter',
			ref: 'filterBtn',
			hidden: hidden,
			nonEmptySelectOnly: true,
			model: this.model,
			menu: {
				items: [{
					xtype: 'menucheckitem',
					text: _('Unread'),
					model: this.model,
					cls: 'k-unread-filter-btn',
					ref: '../unreadBtn',
					iconCls: isCurrDataModeUnread ? 'x-menu-item-icon' : 'k-hide-img',
					valueDataMode: Zarafa.mail.data.DataModes.UNREAD,
					checked: isCurrDataModeUnread,
					checkHandler: this.onUnreadToggle,
					scope: this
				}]
			},
			handler: function() {
				if(this.isXType("splitbutton")) {
					this.showMenu();
				}
			},
			listeners: {
				afterrender: function(button) {
					// Add selection class on main filter button
					// and avoid this when filter button is included in more menu.
					if (Ext.isDefined(button.unreadBtn) && isCurrDataModeUnread) {
						button.btnEl.addClass('k-selection');
						button.pressed = true;
						this.model.getStore().hasFilterApplied = true;
					}
				}
			}
		};
	},

	/**
	 * Handler called when check 'Unread' button has been clicked.
	 *
	 * @param {Ext.menu.CheckItem} menuItem The menu item which is clicked.
	 * @param {Boolean} checked The checked is true if {@link Ext.menu.CheckItem CheckItem} was clicked.
	 */
	onUnreadToggle: function(menuItem, checked)
	{
		var mainPanelToolbar = container.getContentPanel().getTopToolbar();
		var filterBtn = mainPanelToolbar.filterBtn;
		var imgTag = menuItem.el.child('img');
		var model = menuItem.model;
		var store = model.getStore();
		if(checked) {
			imgTag.addClass('x-menu-item-icon');
			imgTag.removeClass('k-hide-img');
			filterBtn.btnEl.addClass('k-selection');
			store.hasFilterApplied = true;
			model.setDataMode(menuItem.valueDataMode);
		} else {
			imgTag.removeClass('x-menu-item-icon');
			filterBtn.btnEl.removeClass('k-selection');
			store.stopFilter();
			model.setDataMode(Zarafa.mail.data.DataModes.ALL);
		}
	}
});

Zarafa.onReady(function() {
	container.registerContext(new Zarafa.core.ContextMetaData({
		name: 'mail',
		displayName: _('Mail'),
		allowUserVisible: false,
		pluginConstructor: Zarafa.mail.MailContext
	}));
});
/*
 * #dependsFile client/zarafa/mail/data/ConversationManagers.js
 * #dependsFile client/zarafa/mail/data/ConversationItemsResponseHandler.js
 */
Ext.namespace('Zarafa.mail');

/**
 * @class Zarafa.mail.MailStore
 * @extends Zarafa.core.data.ListModuleStore
 * @xtype zarafa.mailstore
 *
 * The MailStore class provides a way to connect the 'maillistmodule' in the server back-end to an
 * Ext.grid.GridPanel object. It provides a means to retrieve mail listings asynchronously.
 * The MailStore object, once instantiated, will be able to retrieve and list mails from a
 * single specific folder only.
 */
Zarafa.mail.MailStore = Ext.extend(Zarafa.core.data.ListModuleStore, {

	/**
	 * @constructor
	 * @param {Object} config configuration params that should be used to create instance of this store.
	 */
	constructor: function(config)
	{
		config = config || {};

		// Apply default settings.
		Ext.applyIf(config, {
			preferredMessageClass: 'IPM.Note',
			defaultSortInfo: {
				field: 'message_delivery_time',
				direction: 'desc'
			},
			conversationManager: new Zarafa.mail.data.ConversationManagers()
		});

		Zarafa.mail.MailStore.superclass.constructor.call(this, config);

		// Client-side conversation grouping: the server delivers a plain,
		// date-sorted message list with a conversation_id on every item and the
		// store folds the items into conversations. Rebuild that structure
		// whenever the record set changes.
		this.conversationHeaderRecords = {};
		this.fetchedSentConversations = {};
		this.sentConversationCounts = {};
		this.queriedSentConversations = {};
		this.regroupTask = new Ext.util.DelayedTask(this.regroupConversations, this);
		this.on('load', this.onConversationViewLoad, this);
		this.on('add', this.scheduleRegroup, this);
		this.on('remove', this.scheduleRegroup, this);

		// Don't register standalone stores (used in modal dialogs) with the PresenceManager
		if ( !this.standalone ){
			Zarafa.core.PresenceManager.registerStore(this, ['sender', 'received_by', 'sent_representing']);
		}
	},

	/**
	 * Filter a list of {@link Zarafa.core.data.IPMRecord records} by checking if the record
	 * belongs to this Store. This comparison is based on checking if the entryid of the given
	 * records match the entryid of the records inside the store.
	 *
	 * What will be returned is an object containing the records as present inside the store,
	 * and the data objects which should be applied to them (the data from the records of the
	 * store that triggered the event).
	 *
	 * Function will also mark all updates that are already opened of this meeting to be
	 * out dated and force it to be opened again when its loaded in preview pane.
	 *
	 * @param {Zarafa.core.data.IPMRecord|Array} records The record or records to filter
	 * @param {Ext.data.Api.actions} action The API action for which the updates are looked for.
	 * @return {Object} Object containing the key 'records' containing the array of records inside
	 * this store, and the key 'updatedRecords' containing the array of records which should be
	 * applied to those records.
	 * @private
	 */
	getRecordsForUpdateData: function(records, action)
	{
		if (!Ext.isDefined(records) || action !== Ext.data.Api.actions.open) {
			return Zarafa.mail.MailStore.superclass.getRecordsForUpdateData.apply(this, arguments);
		}

		var results = { records: [], updatedRecords: [] };

		if (!Array.isArray(records)) {
			records = [ records ];
		}

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

			if(record instanceof Zarafa.calendar.MeetingRequestRecord) {
				// use CleanGlobalObjectId, so we can force reload of all exceptions also for recurring series
				var globalObjectId = record.get('goid2');

				if (Ext.isEmpty(globalObjectId)) {
					// no global object id found, we can't do anything
					return Zarafa.mail.MailStore.superclass.getRecordsForUpdateData.apply(this, arguments);
				}

				// find all meeting request records which is earlier/latest update of this meeting request record
				var index = -1;
				do {
					index = this.findExact('goid2', globalObjectId, index + 1);

					// check if we got any matching record or not
					if(index < 0) {
						continue;
					}

					var rec = this.getAt(index);

					// check if we found the same record which we updated, for that we need to pass updated data
					// instead of just cloning the same record
					if (!record.equals(rec)) {
						if(rec.isOpened()) {
							// indicate that record data is outdated, record should be reopened again when needed
							rec.opened = false;

							results.records.push(rec);
							results.updatedRecords.push({});
						}
					} else {
						// add original updated record with updated data
						results.records.push(this.getAt(index));
						results.updatedRecords.push(record);
					}
				} while(index != -1);
			} else if(record.isMessageClass('IPM.TaskRequest', true)) {
				// Don't update MailStore-record when it is an IPM.TaskRequest/Accept/Decline/Update
				// because when user tried to open that particular task request, we actually open
				// the associated task.
				continue;
			} else {
				// for other type of mails, just invoke parent class
				return Zarafa.mail.MailStore.superclass.getRecordsForUpdateData.apply(this, arguments);
			}
		}

		return results;
	},

	/**
	 * Function is used to set the filter restriction if {#hasFilterApplied filter is enabled}
	 * while reloading mail store.
	 * @param {Object} options An options which set the filter restriction in params
	 * if filter was already applied on store.
	 */
	reload: function(options)
	{
		if (this.hasFilterApplied) {
			options = Ext.apply(options||{}, {
				params:{
					restriction:{
						filter: this.getFilterRestriction(Zarafa.common.data.Filters.UNREAD)
					}
				}
			});
		}
		Zarafa.mail.MailStore.superclass.reload.call(this, options);
	},

	/**
	 * Used to check if this store contains conversations. (records grouped by conversation and
	 * prefixed with conversation header records)
	 *
	 * @return {Boolean} True if the store contains conversations, or false otherwise.
	 */
	containsConversations: function()
	{
		// Check if the user enabled conversation view in his settings
		if (container.isEnabledConversation() === false) {
			return false;
		}

		// Check if the user is using infinite scroll
		var infiniteScrollEnabled = container.getSettingsModel().get('zarafa/v1/contexts/mail/enable_live_scroll');
		if (!infiniteScrollEnabled) {
			return false;
		}

		// Search results and filtered (e.g. unread only) lists are shown flat.
		if (this.hasSearchResults || this.hasFilterApplied) {
			return false;
		}

		// Conversations are only shown for the default (newest first) sorting.
		// Any other sorting shows the sorted, flat list.
		var sort = this.sortInfo || this.defaultSortInfo;
		if (sort && (sort.field !== 'message_delivery_time' || String(sort.direction).toUpperCase() !== 'DESC')) {
			return false;
		}

		// Check if this store contains the inbox. It is the only folder that can be rendered
		// with conversation view.
		var inbox = container.getHierarchyStore().getDefaultFolder('inbox');
		if (!inbox) {
			return false;
		}
		return (Zarafa.core.EntryId.compareEntryIds(this.entryId, inbox.get('entryid')));
	},

	/**
	 * Schedules a (buffered) {@link #regroupConversations} run. Used for events
	 * that can fire in quick succession, like the removal of multiple records.
	 * @private
	 */
	scheduleRegroup: function()
	{
		this.regroupTask.delay(10);
	},

	/**
	 * Called when the store has loaded. A fresh (non-appending) load replaced
	 * all records: the sent items that were fetched for expanded conversations
	 * are gone from the store, and the cached sent counts may be stale (e.g. a
	 * reply was sent in the meantime), so they are re-queried. The old counts
	 * are kept for display until the fresh ones arrive to avoid regrouping
	 * twice.
	 *
	 * @param {Zarafa.mail.MailStore} store This store.
	 * @param {Zarafa.mail.MailRecord[]} records The loaded records.
	 * @param {Object} options The load options.
	 * @private
	 */
	onConversationViewLoad: function(store, records, options)
	{
		if (!options || options.add !== true) {
			this.fetchedSentConversations = {};
			this.queriedSentConversations = {};
		}
		this.regroupConversations();
	},

	/**
	 * Rebuilds the conversation structure of the store: messages that share a
	 * conversation_id are folded into a conversation, prefixed with a locally
	 * created header record whose entryid ('conversation-<id>') is stable across
	 * reloads. When the conversation view is not active, previously created
	 * structure is removed again.
	 *
	 * The grid shows the records through the conversation filter (see
	 * {@link #filterByConversations}): items of collapsed conversations are
	 * hidden, the header records are always visible.
	 * @private
	 */
	regroupConversations: function()
	{
		var active = this.containsConversations();
		var collection = this.snapshot || this.data;
		var items = collection.getRange();
		var changed = false;

		// Collect the real records; previously generated headers are dropped
		// (they are regenerated below when conversations are active).
		var inboxRecords = [];
		var sentRecords = [];
		items.forEach(function(record) {
			if (record.isSyntheticConversationHeader === true) {
				changed = true;
				return;
			}
			if (record.fetchedByConversationView === true) {
				sentRecords.push(record);
			} else {
				inboxRecords.push(record);
			}
		});

		if (!active) {
			// Flatten: drop the sent items that were fetched for expanded
			// conversations and reset the conversation fields.
			var flat = [];
			changed = changed || sentRecords.length > 0;
			inboxRecords.forEach(function(record) {
				if (record.get('depth') !== 0 || record.get('conversation_count') !== 0) {
					record.data.depth = 0;
					record.data.conversation_count = 0;
					changed = true;
				}
				record.conversationHeaderId = undefined;
				flat.push(record);
			});

			if (changed) {
				collection.clear();
				collection.addAll(flat);
			}
			// Undo a previously applied conversation filter.
			if (this.snapshot) {
				this.data = this.snapshot;
				delete this.snapshot;
				changed = true;
			}
			if (changed) {
				this.fireEvent('datachanged', this);
			}
			return;
		}

		var recordTime = function(record) {
			var date = record.get('message_delivery_time') || record.get('client_submit_time');
			return Ext.isDate(date) ? date.getTime() : 0;
		};

		// The store order may have been rearranged by a previous regroup, so
		// restore the canonical newest-first order of the inbox flow.
		inboxRecords.sort(function(a, b) {
			return recordTime(b) - recordTime(a);
		});

		// Group the records by their conversation id.
		var groups = {};
		inboxRecords.forEach(function(record) {
			var conversationId = record.get('conversation_id');
			if (Ext.isEmpty(conversationId)) {
				return;
			}
			(groups[conversationId] = groups[conversationId] || []).push(record);
		});
		sentRecords.forEach(function(record) {
			var conversationId = record.get('conversation_id');
			if (!Ext.isEmpty(conversationId) && groups[conversationId]) {
				groups[conversationId].push(record);
			}
			// Sent items whose inbox counterparts are gone are dropped.
		});

		// Emit the new record sequence: conversations (of more than one
		// message, counting known Sent Items) become a header followed by
		// their items, at the position of their newest inbox message.
		var result = [];
		var emittedConversations = {};
		var unqueriedConversations = [];
		var me = this;
		inboxRecords.forEach(function(record) {
			var conversationId = record.get('conversation_id');
			var members = Ext.isEmpty(conversationId) ? false : groups[conversationId];

			record.data.folder_name = 'inbox';

			// Number of messages of this conversation in the Sent Items folder
			// that have not been fetched (and thus are not part of members) yet.
			var pendingSent = 0;
			if (members) {
				if (me.queriedSentConversations[conversationId] !== true) {
					unqueriedConversations.push(conversationId);
				}
				if (me.fetchedSentConversations[conversationId] !== true) {
					pendingSent = me.sentConversationCounts[conversationId] || 0;
				}
			}

			if (!members || members.length + pendingSent <= 1) {
				// Not part of a conversation.
				if (record.get('depth') !== 0 || record.get('conversation_count') !== 0) {
					record.data.depth = 0;
					record.data.conversation_count = 0;
				}
				record.conversationHeaderId = undefined;
				result.push(record);
				return;
			}

			if (emittedConversations[conversationId] === true) {
				return;
			}
			emittedConversations[conversationId] = true;

			members.sort(function(a, b) {
				return recordTime(b) - recordTime(a);
			});

			var header = me.getConversationHeaderRecord(conversationId, members, members.length + pendingSent);
			result.push(header);
			members.forEach(function(member) {
				member.data.depth = 1;
				member.data.conversation_count = 0;
				member.conversationHeaderId = header.get('entryid');
				result.push(member);
			});

			// An expanded conversation whose sent items are not in the store
			// (e.g. a reload replaced all records) fetches them again.
			if (pendingSent > 0 && me.isConversationOpened(header)) {
				me.fetchConversationSentItems(header);
			}
		});

		collection.clear();
		collection.addAll(result);
		this.filterByConversations();

		// Ask the server which of the newly loaded conversations have messages
		// in the Sent Items folder; those must be presented as conversations
		// even when only one of their messages is in the Inbox.
		this.requestSentConversationCounts(unqueriedConversations);
	},

	/**
	 * Requests, for the given conversation ids, the number of messages each
	 * conversation has in the Sent Items folder. Ids that were requested
	 * before are skipped. The response is handled by
	 * {@link #onConversationCountsResponse}.
	 *
	 * @param {String[]} conversationIds The conversation ids to query.
	 * @private
	 */
	requestSentConversationCounts: function(conversationIds)
	{
		var query = [];
		Ext.each(conversationIds, function(conversationId) {
			if (this.queriedSentConversations[conversationId] !== true) {
				this.queriedSentConversations[conversationId] = true;
				query.push(conversationId);
			}
		}, this);

		if (Ext.isEmpty(query)) {
			return;
		}

		container.getRequest().singleRequest(
			'maillistmodule',
			'conversationcounts',
			{
				store_entryid: this.storeEntryId,
				conversation_ids: query
			},
			new Zarafa.mail.data.ConversationItemsResponseHandler({
				store: this,
				queriedIds: query
			})
		);
	},

	/**
	 * Handles the response of the 'conversationcounts' request. When the sent
	 * count of any of the queried conversations changed (it can also decrease,
	 * e.g. after a reload with stale cached counts), the conversation structure
	 * is rebuilt.
	 *
	 * @param {Object} response The 'conversationcounts' response data.
	 * @param {String[]} queriedIds The conversation ids that were queried.
	 */
	onConversationCountsResponse: function(response, queriedIds)
	{
		var counts = (response && Ext.isObject(response.counts)) ? response.counts : {};

		var changed = false;
		Ext.each(queriedIds || [], function(conversationId) {
			var count = counts[conversationId] || 0;
			if ((this.sentConversationCounts[conversationId] || 0) !== count) {
				if (count > 0) {
					this.sentConversationCounts[conversationId] = count;
				} else {
					delete this.sentConversationCounts[conversationId];
				}
				changed = true;
			}
		}, this);

		if (changed) {
			this.regroupConversations();
		}
	},

	/**
	 * Returns the header record for the given conversation, creating it when it
	 * does not exist yet. The header carries the aggregated state of the
	 * conversation: the number of items, the date of the newest item and the
	 * unread state.
	 *
	 * @param {String} conversationId The conversation id (hex) of the conversation.
	 * @param {Zarafa.mail.MailRecord[]} members The items of the conversation, newest first.
	 * @param {Number} totalCount The number of items of the conversation, including
	 * Sent Items messages that have not been fetched yet.
	 * @return {Zarafa.mail.MailRecord} The header record.
	 * @private
	 */
	getConversationHeaderRecord: function(conversationId, members, totalCount)
	{
		var newest = members[0];
		var read = members.every(function(member) {
			return !Ext.isFunction(member.isRead) || member.isRead();
		});

		var values = {
			entryid: 'conversation-' + conversationId,
			conversation_id: conversationId,
			depth: 0,
			conversation_count: totalCount,
			message_class: 'IPM.Note',
			subject: newest.get('normalized_subject') || newest.get('subject'),
			normalized_subject: newest.get('normalized_subject'),
			message_delivery_time: newest.get('message_delivery_time') || newest.get('client_submit_time'),
			message_flags: read ? Zarafa.core.mapi.MessageFlags.MSGFLAG_READ : 0,
			parent_entryid: newest.get('parent_entryid'),
			store_entryid: newest.get('store_entryid'),
			// Fallback for the sender column when no participant names can be
			// determined from the conversation items.
			sent_representing_name: newest.get('sent_representing_name'),
			sender_name: newest.get('sender_name')
		};

		var header = this.conversationHeaderRecords[conversationId];
		if (header) {
			Ext.apply(header.data, values);
			return header;
		}

		header = Zarafa.core.data.RecordFactory.createRecordObjectByMessageClass('IPM.Note', values);
		header.phantom = false;
		header.isSyntheticConversationHeader = true;
		// The renderers reach the store through the record to inspect the
		// conversation (e.g. record.getStore().isConversationOpened(record)).
		header.join(this);
		this.conversationHeaderRecords[conversationId] = header;

		return header;
	},

	/**
	 * Checks if the given record is part of an expanded conversation
	 *
	 * @param {Zarafa.mail.MailRecord} record The record that should be checked
	 * @return {Boolean} True is the record is part of an expanded conversation,
	 * false otherwise
	 */
	isConversationOpened: function(record)
	{
		var openedRecordManager = this.conversationManager.getOpenedRecordManager();
		if (record.isConversationHeaderRecord()) {
			return openedRecordManager.containsKey(record.get('entryid'));
		}

		return openedRecordManager.containsKey(record.conversationHeaderId);
	},

	/**
	 * Filters the store to not show items in conversations that have not been expanded
	 */
	filterByConversations: function()
	{
		if (!this.containsConversations()) {
			return;
		}

		var openedRecordManager = this.conversationManager.getOpenedRecordManager();

		this.filterBy(function(record) {
			return record.get('depth') === 0 || openedRecordManager.containsKey(record.conversationHeaderId) === true;
		}, this);
	},

	/**
	 * Function toggles the conversation.
	 *
	 * @param {Zarafa.core.IPMRecord} headerRecord The header record of conversation.
	 * @param {Boolean} expand The expand is true if conversation needs to expand else false.
	 */
	toggleConversation: function(headerRecord, expand)
	{
		if (!headerRecord.isConversationHeaderRecord()) {
			return;
		}
		var headerRecordEntryId = headerRecord.get('entryid');
		var openedRecordManager = this.conversationManager.getOpenedRecordManager();
		var hide = Ext.isDefined(expand) ? !expand : openedRecordManager.containsKey(headerRecordEntryId);

		if (hide) {
			openedRecordManager.removeKey(headerRecordEntryId);
		} else {
			openedRecordManager.add(headerRecordEntryId, true);
			this.fetchConversationSentItems(headerRecord);
		}

		this.filterByConversations();
	},

	/**
	 * Fetches the Sent Items messages that belong to the given conversation from
	 * the server (once per conversation) and merges them into the conversation.
	 *
	 * @param {Zarafa.mail.MailRecord} headerRecord The header record of the conversation.
	 * @private
	 */
	fetchConversationSentItems: function(headerRecord)
	{
		var conversationId = headerRecord.get('conversation_id');
		if (Ext.isEmpty(conversationId) || this.fetchedSentConversations[conversationId] === true) {
			return;
		}
		this.fetchedSentConversations[conversationId] = true;

		container.getRequest().singleRequest(
			'maillistmodule',
			'conversationitems',
			{
				store_entryid: this.storeEntryId,
				conversation_id: conversationId
			},
			new Zarafa.mail.data.ConversationItemsResponseHandler({
				store: this
			})
		);
	},

	/**
	 * Handles the response of the 'conversationitems' request: the received
	 * Sent Items messages are added to the store and the conversation structure
	 * is rebuilt so they appear inside their conversation.
	 *
	 * @param {Object} response The 'conversationitems' response data.
	 */
	onConversationItemsResponse: function(response)
	{
		var items = response ? response.item : undefined;
		if (Ext.isEmpty(items)) {
			return;
		}
		if (!Array.isArray(items)) {
			items = [items];
		}

		var readerData = this.reader.readRecords({
			count: items.length,
			item: items
		});

		var collection = this.snapshot || this.data;
		var existing = {};
		collection.each(function(record) {
			existing[record.get('entryid')] = true;
		});

		var added = false;
		Ext.each(readerData.records, function(record) {
			if (existing[record.get('entryid')] === true) {
				return;
			}
			record.fetchedByConversationView = true;
			record.data.folder_name = 'sent_items';
			record.join(this);
			collection.add(record);
			added = true;
		}, this);

		if (added) {
			this.regroupConversations();
		}
	},

	/**
	 * Function will collapse all the conversation except the given header record in parameter
	 * and if no header record is provided then it will collapse all the conversation.
	 *
	 * @param {Zarafa.core.IPMRecord} headerRecord The header record of conversation.
	 */
	collapseAllConversation: function(headerRecord)
	{
		if (!this.containsConversations()) {
			return;
		}
		this.conversationManager.closeAll(headerRecord);
		this.filterByConversations();
	},

	/**
	 * Helper function to open (expand) a conversation
	 *
	 * @param {Zarafa.mail.MailRecord} headerRecord
	 */
	expandConversation: function(headerRecord)
	{
		this.toggleConversation(headerRecord, true);
	},

	/**
	 * Returns all the records in the store that are part of the conversation identified
	 * by the given header record
	 *
	 * @param {Zarafa.core.data.MapiRecord} headerRecord The header record of the conversation
	 * @return {Zarafa.core.data.MapiRecord[]} The records that belong to the requested conversation
	 */
	getConversationItemsFromHeaderRecord: function(headerRecord)
	{
		var items = this.snapshot || this.data;
		var index = items.findIndex('id', headerRecord.id) + 1;
		var retVal = [];
		var item;
		while ((item = items.get(index)) && item.get('depth') > 0) {
			retVal.push(item);
			index++;
		}

		return retVal;
	},

	/**
	 * Returns header record identified by the given record item which is part of that conversation.
	 * Or it returns false if given record is not part of any conversation.
	 *
	 * @param {Zarafa.core.data.MapiRecord} record The conversation item record whose header record needs to be found.
	 * @return {Zarafa.core.data.MapiRecord} returns header record for the given conversation item
	 * or false if given record is not part of any conversation.
	 */
	getHeaderRecordFromItem: function(record)
	{
		if (!Ext.isDefined(record) || record.isNormalRecord()) {
			return false;
		} else if (record.isConversationHeaderRecord()) {
			return record;
		}

		var items = this.snapshot || this.data;
		var index = items.findIndex('id', record.id) - 1;
		var headerRecord = false;
		var item;
		while ((item = items.get(index))) {
			if (item.get('depth') === 0 && item.get('conversation_count') > 0) {
				headerRecord = item;
				break;
			}
			index--;
		}

		return headerRecord;
	},

	/**
	 * Returns the number of conversations that are contained by the store
	 *
	 * @return {Number} Number of conversation in the store
	 */
	getConversationCount: function()
	{
		if (!this.containsConversations()) {
			return 0;
		}

		var count = 0;
		var items = this.snapshot ? this.snapshot.items : this.getRange();
		items.forEach(function(item) {
			if (item.get('depth') === 0) {
				count++;
			}
		});

		return count;
	},

	/**
	 * Returns the number of items that have the store has
	 * corresponds as a parent folder. (i.e. for conversations it will only
	 * count the items from the Inbox folder and not the items from the Sent Items
	 * folder)
	 *
	 * @return {Number} The number of items
	 */
	getStoreLength: function()
	{
		// For conversations only count the items from the Inbox folder: not the
		// conversation header records and not the fetched Sent Items.
		if (this.containsConversations()) {
			var count = 0;
			var items = this.snapshot ? this.snapshot.items : this.getRange();
			items.forEach(function(item) {
				if (item.get('folder_name') === 'inbox') {
					count++;
				}
			});

			return count;
		}

		return Zarafa.mail.MailStore.superclass.getStoreLength.apply(this, arguments);
	},

	/**
	 * Function which provide the restriction based on the given {@link Zarafa.common.data.Filters.UNREAD Filter}
	 *
	 * @param {Zarafa.common.data.Filters} filterType The filterType which needs to perform on store.
	 * @return {Array|false} RES_BITMASK restriction else false.
	 */
	getFilterRestriction: function(filterType)
	{
		if (filterType === Zarafa.common.data.Filters.UNREAD) {
			var unreadFilterRestriction = Zarafa.core.data.RestrictionFactory.dataResBitmask(
				'PR_MESSAGE_FLAGS',
				Zarafa.core.mapi.Restrictions.BMR_EQZ,
				Zarafa.core.mapi.MessageFlags.MSGFLAG_READ);

			var model = container.getCurrentContext().getModel();
			if (model) {
				var previewedRecord = model.getPreviewRecord();

				// Add preview record in filter restriction so we can
				// make it remains preview in preview panel.
				if(!Ext.isEmpty(previewedRecord) && this.hasFilterApplied) {
					return Zarafa.core.data.RestrictionFactory.createResOr([
						Zarafa.core.data.RestrictionFactory.dataResProperty(
							'entryid',
							Zarafa.core.mapi.Restrictions.RELOP_EQ,
							previewedRecord.get('entryid')
						),
						unreadFilterRestriction
					]);
				}
			}

			return unreadFilterRestriction;
		}
		return false;
	}
});

Ext.reg('zarafa.mailstore', Zarafa.mail.MailStore);
Ext.namespace('Zarafa.mail.dialogs');

/**
 * @class Zarafa.mail.dialogs.MailCreateContentPanel
 * @extends Zarafa.core.ui.MessageContentPanel
 * @xtype zarafa.mailcreatecontentpanel
 */
Zarafa.mail.dialogs.MailCreateContentPanel = Ext.extend(Zarafa.core.ui.MessageContentPanel, {
	/**
	 * The {@link Zarafa.mail.MailContextModel} which is obtained using {@link #getContextModel}.
	 * @property
	 * @type Zarafa.mail.MailContextModel
	 */
	model: undefined,

	/**
	 * True if the BCC field should be shown.
	 * @property
	 * @type Boolean
	 */
	showbcc: false,

	/**
	 * @cfg {Boolean} useHtml True to enable the HTML editor in this panel
	 * If not provided, the value will be obtained from the {@link Zarafa.settings.SettingsModel}.
	 */
	useHtml: false,

	/**
	 * True if the From field should be shown.
	 * @property
	 * @type Boolean
	 */
	showfrom: false,

	/**
	 * The queue containing callback functions which can be used to validate the
	 * {@link Zarafa.core.data.IPMRecord} before opening send later dialog. If the queue
	 * completes successfully, the dialog will open, otherwise it will be cancelled.
	 * @property
	 * @type Zarafa.core.data.CallbackQueue
	 * @protected
	 */
	sendLaterValidationQueue: undefined,

	/**
	 * @cfg {Zarafa.addressbook.dialogs.AddressBookContentPanel} ABDialog only available when
	 * user select "Send email" address book context menu item.
	 */
	ABDialog: undefined,

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

		if (!Ext.isDefined(config.useHtml)) {
			config.useHtml = container.getSettingsModel().get('zarafa/v1/contexts/mail/dialogs/mailcreate/use_html_editor');
		}

		config.plugins = Ext.value(config.plugins, []);
		if (container.getSettingsModel().get('zarafa/v1/contexts/mail/autosave_enable') === true) {
			config.plugins.push({
				ptype: 'zarafa.autosavemessageplugin'
			});
		}

		// Add in some standard configuration data.
		Ext.applyIf(config, {
			// Override from Ext.Component
			xtype: 'zarafa.mailcreatecontentpanel',
			// Override from Ext.Component
			layout: 'fit',
			title: _('Email'),
			recordComponentPluginConfig: Ext.applyIf(config.recordComponentPluginConfig || {}, {
				allowWrite: true
			}),
			closeOnSend: true,
			confirmClose: true,
			items: [{
				xtype: 'zarafa.mailcreatepanel',
				ref: 'mainPanel',
				useHtml: config.useHtml,
				tbar:{
					xtype: 'zarafa.mailcreatetoolbar'
				}
			}]
		});

		this.addEvents(
			/**
			 * Fires when the user toggle a button in toolbar.
			 * This event will link the toolbar with mainpanel in panel
			 * @param {Zarafa.core.ui.ContentPanel} contentpanel
			 * @param {Boolean} true if toggle state ie. pressed or else false
			 */
			'bcctoggle',
			/**
			 * Fires when the user toggle a button in toolbar.
			 * This event will link the toolbar with mainpanel in contentpanel
			 * @param {Zarafa.core.ui.ContentPanel} contentpanel
			 * @param {Boolean} true if toggle state ie. pressed or else false
			 */
			'fromtoggle'
		);

		// Call parent constructor
		Zarafa.mail.dialogs.MailCreateContentPanel.superclass.constructor.call(this, config);

		// Initialize the Send Later Validation queue
		// which is used for validating the message before opening send later dialog
		this.createSendLaterValidationQueue();
	},

	/**
	 * If {@link showInfoMask} is enabled, this will display the {@link #savingText} to the user.
	 * @protected
	 * @overridden
	 */
	displayInfoMask: function()
	{
		if (this.showInfoMask === false) {
			return;
		}

		if (this.isSaving && !this.isSending) {
			this.savingEl = container.getNotifier().notify('info.mailsaving', _('Saving…'), {
				toolbar: this.mainPanel.getTopToolbar()
			});
		} else {
			Zarafa.mail.dialogs.MailCreateContentPanel.superclass.displayInfoMask.apply(this, arguments);
		}
	},

	/**
	 * If {@link #showInfoMask} is enabled, and {@link #displayInfoMask} has been called, this
	 * will remove the saving notification. When saving has been successful, a new notification
	 * will be shown to display the {@link #savingDoneText} with timestamp.
	 * @param {Boolean} success false to disable the display of {@link #savingDoneText}.
	 * @protected
	 * @overridden
	 */
	hideInfoMask: function(success)
	{
		if (this.showInfoMask === false) {
			return;
		}

		// If there isn't any message action set on the record, then it's just the save mail action.
		if (this.isSaving && !this.isSending) {
			var message = (success) ? String.format(_('Saved at {0}'), this.record.get('last_modification_time').formatDefaultTime()) : _('Saving failed');
			container.getNotifier().notify('info.mailsaved', message, {
				toolbar: this.mainPanel.getTopToolbar()
			});
		} else {
			Zarafa.mail.dialogs.MailCreateContentPanel.superclass.hideInfoMask.apply(this, arguments);
		}
	},

	/**
	 * Event which is fired when the {@link #record} has been completely loaded.
	 * This will close the {@link Zarafa.addressbook.dialogs.AddressBookContentPanel AddressBookContentPanel} if
	 * it is open.
	 *
	 * @param {Ext.Container} panel The panel to which the record was set
	 * @param {Zarafa.core.data.MAPIRecord} record The record which was updated
	 * @private
	 */
	onLoadRecord: function(panel, record)
	{
		Zarafa.mail.dialogs.MailCreateContentPanel.superclass.onLoadRecord.apply(this, arguments);

		if (Ext.isDefined(this.ABDialog)) {
			this.ABDialog.close();
		}
	},

	/**
	 * Load record into content panel
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The record to load
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 */
	update: function(record, contentReset)
	{
		var hasFrom = false;
		var hasBcc = false;
		/**
		 * Bcc field is shown when either of condition if true:
		 * 1) When user has selected Bcc Field to be shown always OR
		 * 2) When record itself has bcc recipients
		 *
		 * Same way From field is shown when either of condition if true:
		 * 1) When user has selected From Field to be shown always OR
		 * 2) When record has set delegator in sent_representing_* info.
		 */
		if (contentReset) {
			var delegatorFieldStore = this.mainPanel.fromRecipientField.getBoxStore();
			if(!record.userIsStoreOwner()) {
				var delegator = container.getHierarchyStore().getById(record.get('store_entryid'));
				record.setDelegatorInfo(delegator);
			}
			
			// Get the recipeint for the from field.
			var delegatorRecord = record.getDefaultFromRecipeint();

			if(Ext.isDefined(delegatorRecord) && delegatorRecord) {
				hasFrom = true;
				// We need to maintain that in from field there should be only one user,
				// So remove earlier added user.
				delegatorFieldStore.removeAll();
				delegatorFieldStore.add(delegatorRecord);
			}
			hasBcc = record.getSubStore('recipients').hasBccRecipients();
			this.fireEvent('bcctoggle', this, this.showbcc || hasBcc, false);
			this.fireEvent('fromtoggle', this, this.showfrom || hasFrom, false);

			if (this.inputAutoFocusPlugin) {
				switch (record.getMessageAction('action_type')) {
					case Zarafa.mail.data.ActionTypes.REPLY:
					case Zarafa.mail.data.ActionTypes.REPLYALL:
					case Zarafa.mail.data.ActionTypes.EDIT_AS_NEW:
						// Automatically place the focus on the body
						this.inputAutoFocusPlugin.setAutoFocus('zarafa.editorfield');
						break;
					case Zarafa.mail.data.ActionTypes.FORWARD:
					case Zarafa.mail.data.ActionTypes.FORWARD_ATTACH:
					/* falls through */
					default:
						this.inputAutoFocusPlugin.setAutoFocus(this.mainPanel.toRecipientField);
						break;
				}
			}
		} else {
			if (record.isSubStoreModifiedSincelastUpdate('recipients')) {
				hasBcc = record.getSubStore('recipients').hasBccRecipients();
				this.fireEvent('bcctoggle', this, this.showbcc || hasBcc, false);
			}

			if (record.isModifiedSinceLastUpdate('sent_representing_email_address')) {
				if(!Ext.isEmpty(record.get('sent_representing_email_address'))) {
					hasFrom = true;
				}

				this.fireEvent('fromtoggle', this, (this.showfrom || hasFrom), false);
			}
		}

		this.updateTitleFromRecord(record);
		this.updateIconFromRecord(record);
	},

	/**
	 * Update this panel's icon class from the record that it contains
	 * First obtains the icon class from a mapping, then calls {@link #setIcon}
	 *
	 * @param {Zarafa.core.data.MAPIRecord} record The record bound to this component
	 * @private
	 */
	updateIconFromRecord: function(record)
	{
		//TODO: create a new icon mapping for tabs
		var iconCls = Zarafa.common.ui.IconClass.getIconClass(record);
		this.setIcon(iconCls);
	},

	/**
	 * When record has been updated, title also has to be - for instance if we have the subject
	 * in the title and the subject changes
	 * Calls {@link #setTitle} this.setTitle in order to update
	 * @param {Zarafa.core.data.MAPIRecord} record The record that has been updated
	 */
	updateTitleFromRecord: function(record)
	{
		var subject = record.get('subject');
		if(!Ext.isEmpty(subject)){
			this.setTitle(subject);
		} else {
			this.setTitle(this.initialConfig.title);
		}
	},

	/**
	 * Register the {@link #stateEvents state events} to the {@link #saveState} callback function.
	 * @private
	 */
	initStateEvents: function()
	{
		Zarafa.mail.dialogs.MailCreateContentPanel.superclass.initStateEvents.call(this);

		this.on('bcctoggle', this.saveState, this);
		this.on('fromtoggle', this.saveState, this);
	},

	/**
	 * Called when bcctoggle or fromtoggle event is fired, if the argument
	 * saveState is true it will save the state of the mailcreatecontentpanel
	 * to grommunio Web user settings.
	 *
	 * @param {Zarafa.mail.dialogs.MailCreateContentPanel} panel the mail create content panel
	 * @param {Boolean} visible visibility of button
	 * @param {Boolean} saveState save the state of the button.
	 */
	saveState: function(panel, visible, saveState)
	{
		if(saveState){
			Zarafa.mail.dialogs.MailCreateContentPanel.superclass.saveState.call(this);
		}
	},

	/**
	 * When {@link #stateful} the State object which should be saved into the
	 * {@link Ext.state.Manager}.
	 * @return {Object} The state object
	 * @protected
	 */
	getState: function()
	{
		var state = Zarafa.mail.dialogs.MailCreateContentPanel.superclass.getState.apply(this, arguments);
		return Ext.apply(state || {}, { showbcc: this.showbcc, showfrom: this.showfrom });
	},

	/**
	 * Saves bcc field's state into settings and fires 'bcctoggle' event to update UI
	 * @param {Boolean} true, if Bcc field is visible else false
	 */
	toggleBccState: function(showBcc)
	{
		this.showbcc = showBcc;
		this.fireEvent('bcctoggle', this, showBcc, true);
	},

	/**
	 * Saves from field's state into settings and fires 'fromtoggle' event to update UI
	 * @param {Boolean} true, if From field is visible else false
	 */
	toggleFromState: function(showFrom)
	{
		this.showfrom = showFrom;
		this.fireEvent('fromtoggle', this, showFrom, true);
	},

	/**
	 * Function gets the {@link Zarafa.mail.MailContextModel MailContextModel} attached to
	 * {@link Zarafa.mail.MailContext MailContext}.
	 * @return {Zarafa.mail.MailContextModel} Mail context model
	 */
	getContextModel: function()
	{
		if(!this.model) {
			var parentFolder = this.get('parent_entryid');

			if(!Ext.isEmpty(parentFolder)) {
				parentFolder = container.getHierarchyStore().getById(parentFolder);
			}

			if(Ext.isEmpty(parentFolder)) {
				parentFolder = container.getHierarchyStore().getDefaultFolder('drafts');
			}

			// now do round of bidding to find context attached to the folder we got
			var context = container.getContextByFolder(parentFolder);
			this.model = context.getModel();
		}

		return this.model;
	},

	/**
	 * Helper function which is used to validate {@link #record} and open the send later dialog
	 */
	sendLaterRecord: function ()
	{
		this.sendLaterValidationQueue.run(this.onCompleteValidateSendLaterRecord, this);
	},

	/**
	 * Create and initialize the {@link #sendLaterValidationQueue}. This will add two validations steps
	 * which must be executed to determine if the message has valid recipients.
	 * @protected
	 */
	createSendLaterValidationQueue: function ()
	{
		this.sendLaterValidationQueue = new Zarafa.core.data.CallbackQueue();

		// Add a validation step to determine if there are recipients
		this.sendLaterValidationQueue.add(this.validateEmptyRecipients, this);
		// Add a validation step to warn about missing attachments when keywords are found
		this.sendLaterValidationQueue.add(this.validateMissingAttachment, this);
		// Add a validation step to determine if all recipients are resolved
		this.sendLaterValidationQueue.add(this.validateResolvedRecipients, this);
	},

	/**
	 * Callback function for the {@link #sendLaterValidationQueue} which is called when the queue has been
	 * completed. If the queue ended successfully then it will call the {@link Zarafa.mail.Actions#openDelayedDeliveryContent}.
	 * @param {Boolean} success True if the queue ended successfully
	 * @private
	 */
	onCompleteValidateSendLaterRecord: function (success)
	{
		if (success) {
			Zarafa.mail.Actions.openDelayedDeliveryContent(this.record, this);
		}
	}
});

Ext.reg('zarafa.mailcreatecontentpanel', Zarafa.mail.dialogs.MailCreateContentPanel);
/*
 * #dependsFile client/zarafa/common/data/ImportanceFlags.js
 * #dependsFile client/zarafa/mail/data/SensitivityFlags.js
 */
Ext.namespace('Zarafa.mail.dialogs');

/**
 * @class Zarafa.mail.dialogs.MailOptionsSettingsPanel
 * @extends Ext.Panel
 * @xtype zarafa.mailoptionssettingspanel
 *
 * Panel for users to set message settings on a given {@link Zarafa.mail.MailRecord record},
 * like the {@link Zarafa.core.mapi.Importance} or {@link Zarafa.core.mapi.Sensitivity}.
 */
Zarafa.mail.dialogs.MailOptionsSettingsPanel = Ext.extend(Ext.form.FormPanel, {
	/**
	 * The record on which this panel is operating on. This record is provided through the
	 * {@link #update} function.
	 * @property
	 * @type Zarafa.core.data.IPMRecord
	 */
	record: undefined,

	/**
	 * @cfg {Ext.data.Store/Object} importanceStore The store (or store configuration object),
	 * which contains the {@link Zarafa.core.mapi.Importance importance} data.
	 */
	importanceStore: undefined,

	/**
	 * @cfg {Ext.data.Store/Object} sensitivityStore The store (or store configuration object),
	 * which contains the {@link Zarafa.core.mapi.Sensitivity sensitivity} data.
	 */
	sensitivityStore: undefined,

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

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

		if (!config.importanceStore) {
			config.importanceStore = {
				xtype: 'jsonstore',
				fields: ['value', 'name'],
				data: Zarafa.common.data.ImportanceFlags.flags
			};
		}

		if (!config.sensitivityStore) {
			config.sensitivityStore = {
				xtype: 'jsonstore',
				fields: ['value', 'name'],
				data: Zarafa.mail.data.SensitivityFlags.flags
			};
		}

		Ext.applyIf(config, {
			xtype: 'zarafa.mailoptionssettingspanel',
			title: _('Message Settings'),
			layout: 'form',
			items: [{
				xtype: 'combo',
				fieldLabel: _('Importance'),
				ref: 'importanceCombo',
				anchor: '100%',
				name: 'importance',
				store: config.importanceStore,
				mode: 'local',
				triggerAction: 'all',
				displayField: 'name',
				valueField: 'value',
				lazyInit: false,
				forceSelection: true,
				editable: false,
				listeners: {
					select: this.onFieldSelect,
					scope: this
				}
			},{
				xtype: 'combo',
				fieldLabel: _('Sensitivity'),
				ref: 'sensitivityCombo',
				anchor: '100%',
				name: 'sensitivity',
				store: config.sensitivityStore,
				mode: 'local',
				triggerAction: 'all',
				displayField: 'name',
				valueField: 'value',
				lazyInit: false,
				forceSelection: true,
				editable: false,
				listeners: {
					select: this.onFieldSelect,
					scope: this
				}
			}]
		});

		Zarafa.mail.dialogs.MailOptionsSettingsPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Event handler which is fired when a combobox selection has changed.
	 * This will update the corresponding field inside the {@link Zarafa.core.data.IPMRecord record}
	 * @param {Ext.form.ComboBox} combo The combobox which was selected
	 * @param {Ext.data.Record} record The selected record
	 * @param {Number} index The index of the selected record
	 * @private
	 */
	onFieldSelect: function(combo, record, index)
	{
		this.record.set(combo.getName(), record.get(combo.valueField));
	},

	/**
	 * Update the {@link Ext.Panel Panel} with the given {@link Zarafa.core.data.IPMRecord IPMRecord}
	 * @param {Zarafa.core.data.IPMRecord} record The record to 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 (record) {
			this.getForm().loadRecord(record);

			// Disable the sensitivity comboBox when reading received mail.
			if (!record.phantom && !record.isUnsent()) {
				this.sensitivityCombo.disable();
			} else {
				this.sensitivityCombo.enable();
			}
		} else {
			this.importanceCombo.setValue(1);
			this.sensitivityCombo.setValue(0);
		}
	},

	/**
	 * Update the {@link Zarafa.core.data.IPMRecord IPMRecord} with the data from the {@link Ext.Panel Panel}.
	 * @param {Zarafa.core.data.IPMRecord} record The record which has to be updated
	 */
	updateRecord: function(record)
	{
		this.getForm().updateRecord(record);

		if(record.get('sensitivity') === Zarafa.core.mapi.Sensitivity['PRIVATE']) {
			record.set('private', true);
		} else {
			record.set('private', false);
		}
	}
});

Ext.reg('zarafa.mailoptionssettingspanel', Zarafa.mail.dialogs.MailOptionsSettingsPanel);
Ext.namespace('Zarafa.mail.dialogs');

/**
 * @class Zarafa.mail.dialogs.ShowMailContentPanel
 * @extends Zarafa.core.ui.MessageContentPanel
 * @xtype zarafa.showmailcontentpanel
 */
Zarafa.mail.dialogs.ShowMailContentPanel = Ext.extend(Zarafa.core.ui.MessageContentPanel, {

	/**
	 * The {@link Zarafa.mail.MailContextModel} which is obtained using {@link #getContextModel}.
	 * @property
	 * @type Zarafa.mail.MailContextModel
	 */
	model: undefined,

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

		// Add in some standard configuration data.
		Ext.applyIf(config, {
			// Override from Ext.Component
			xtype: 'zarafa.showmailcontentpanel',
			// Override from Ext.Panel
			layout: 'fit',
			title: _('no subject'),
			recordComponentPluginConfig: Ext.applyIf(config.recordComponentPluginConfig || {}, {
				allowWrite: true
			}),
			closeOnSend: true,
			items: [ this.createPanel() ]
		});

		// Call parent constructor
		Zarafa.mail.dialogs.ShowMailContentPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Add the main Window Panel to the content panel. This will contain
	 * a {@link Zarafa.core.ui.ContentPanelToolbar} and a {@link Zarafa.mail.ui.MailViewPanel}.
	 * @return {Object} The configuration object for the panel.
	 * @private
	 */
	createPanel: function()
	{
		// Create a new panel and add it.
		return {
			xtype: 'zarafa.mailviewpanel',
			tbar: {
				xtype: 'zarafa.showmailtoolbar'
			}
		};
	},

	/**
	 * Function gets the {@link Zarafa.mail.MailContextModel MailContextModel} attached to
	 * {@link Zarafa.mail.MailContext MailContext}.
	 * @return {Zarafa.mail.MailContextModel} Mail context model
	 */
	getContextModel: function()
	{
		if(!this.model) {
			var hierarchy = container.getHierarchyStore();
			var parentFolder = this.get('parent_entryid');

			if(!Ext.isEmpty(parentFolder)) {
				parentFolder = hierarchy.getById(parentFolder);
			}

			if(Ext.isEmpty(parentFolder)) {
				parentFolder = hierarchy.getDefaultFolder('drafts');
			}

			// now do round of bidding to find context attached to the folder we got
			var context = container.getContextByFolder(parentFolder);
			this.model = context.getModel();
		}

		return this.model;
	},

	/**
	 * Update the components with the given record.
	 *
	 * @param {Zarafa.core.data.MAPIRecord} record The record to update in this component
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 */
	update: function(record, contentReset)
	{
		this.updateIconFromRecord(record);
		this.updateTitleFromRecord(record);
	},

	/**
	 * When record has been updated, title also has to be - for instance if we have the subject
	 * in the title and the subject changes
	 * Calls {@link #setTitle} this.setTitle in order to update
	 * @param {Zarafa.core.data.MAPIRecord} record The record that has been updated
	 */
	updateTitleFromRecord: function(record)
	{
		var subject = record.get('subject');
		if(!Ext.isEmpty(subject)){
			this.setTitle(subject);
		} else {
			this.setTitle(this.initialConfig.title);
		}
	},

	/**
	 * Update this panel's icon class from the record that it contains
	 * First obtains the icon class from a mapping, then calls {@link #setIcon}
	 *
	 * @param {Zarafa.core.data.MAPIRecord} record The record bound to this component
	 * @private
	 */
	updateIconFromRecord: function(record)
	{
		//TODO: create a new icon mapping for tabs
		var iconCls = Zarafa.common.ui.IconClass.getIconClass(record);
		this.setIcon(iconCls);
	}
});

Ext.reg('zarafa.showmailcontentpanel', Zarafa.mail.dialogs.ShowMailContentPanel);
/*
 * #dependsFile client/zarafa/note/NoteContextModel.js
 */
Ext.namespace('Zarafa.note');

/**
 * @class Zarafa.note.NoteContext
 * @extends Zarafa.core.Context
 *
 * This class will be used as a controller between {@link Zarafa.note.NoteContextModel NoteContextModel}
 * and {@link Zarafa.note.ui.NoteMainPanel NoteMainPanel}
 * Context that handles displaying note type messages.
 */
Zarafa.note.NoteContext = Ext.extend(Zarafa.core.Context, {
	// Insertion points for this class
	/**
	 * @insert main.maintoolbar.view.note
	 * Insertion point for populating the main toolbar with a View button. This item is only visible
	 * when this context is active.
	 * @param {Zarafa.mail.NoteContext} context This context
	 */

	/**
	 * When searching, this property marks the {@link Zarafa.core.Context#getCurrentView view}
	 * which was used before {@link #onSearchStart searching started} the view was switched to
	 * {@link Zarafa.note.data.Views#SEARCH}.
	 * @property
	 * @type Mixed
	 * @private
	 */
	oldView: undefined,

	/**
	 * When searching, this property marks the {@link Zarafa.core.Context#getCurrentViewMode viewmode}
	 * which was used before {@link #onSearchStart searching started} the viewmode was switched to
	 * {@link Zarafa.note.data.ViewModes#SEARCH}.
	 * @property
	 * @type Mixed
	 * @private
	 */
	oldViewMode: undefined,

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

		Ext.applyIf(config, {
			current_view: Zarafa.note.data.Views.ICON,
			current_view_mode: Zarafa.note.data.ViewModes.NORMAL
		});

		// The tab in the top tabbar
		this.registerInsertionPoint('main.maintabbar.left', this.createMainTab, this);

		// The "New Note" button which is available in all contexts
		this.registerInsertionPoint('main.maintoolbar.new.item', this.createNewNoteButton, this);

		Zarafa.note.NoteContext.superclass.constructor.call(this, config);

		// Add a tree control showing a list of note folders to the navigation panel.
		// The control will be shown when the user selects the note context from the button panel.
		this.registerInsertionPoint('navigation.center', this.createNoteNavigationPanel, this);

		Zarafa.core.data.SharedComponentType.addProperty('note.dialog.options');
	},

	/**
	 * @return {Zarafa.note.NoteContextModel} the notet context model
	 */
	getModel: function()
	{
		if (!Ext.isDefined(this.model)) {
			this.model = new Zarafa.note.NoteContextModel();
			this.model.on({
				'searchstart': this.onModelSearchStart,
				'searchstop': this.onModelSearchStop,
				scope: this
			});
		}
		return this.model;
	},

	/**
	 * Event handler for the {@link #model}#{@link Zarafa.core.ContextModel#searchstart searchstart} event.
	 * This will {@link #switchView switch the view} to {@link Zarafa.note.data.Views#SEARCH search mode}.
	 * The previously active {@link #getCurrentView view} will be stored in the {@link #oldView} and will
	 * be recovered when the {@link #onModelSearchStop search is stopped}.
	 * @param {Zarafa.core.ContextModel} model The model which fired the event
	 * @private
	 */
	onModelSearchStart: function(model)
	{
		if(this.getCurrentView() !== Zarafa.note.data.Views.SEARCH && this.getCurrentViewMode() !== Zarafa.note.data.ViewModes.SEARCH){
			this.oldView = this.getCurrentView();
			this.oldViewMode = this.getCurrentViewMode();
			this.switchView(Zarafa.note.data.Views.SEARCH, Zarafa.note.data.ViewModes.SEARCH);
		}
	},

	/**
	 * Event handler for the {@link #model}#{@link Zarafa.core.ContextModel#searchstop searchstop} event.
	 * This will {@link #switchView switch the view} to the {@link #oldView previous view}.
	 * @param {Zarafa.core.ContextModel} model The model which fired the event
	 * @private
	 */
	onModelSearchStop: function(model)
	{
		this.switchView(this.oldView, this.oldViewMode);
		delete this.oldView;
		delete this.oldViewMode;
	},

	/**
	 * bid to get selected on Mapi folder selection
	 */
	bid: function(folder)
	{
		// Bid 1 when the folder is of the IPF.StickyNote type.
		if (folder.isContainerClass('IPF.StickyNote', true)) {
			return 1;
		}

		return -1;
	},

	/**
	 * Bid for the type of shared component and the given record.
	 * @param {Zarafa.core.data.SharedComponentType} type Type of component a context can bid for.
	 * @param {Ext.data.Record} record Optionally passed record.
	 * @return {Number} The bid for the shared component
	 */
	bidSharedComponent: function(type, record)
	{
		var bid = -1;

		if (Array.isArray(record)) {
			record = record[0];
		}

		switch (type) {
			case Zarafa.core.data.SharedComponentType['common.create']:
			case Zarafa.core.data.SharedComponentType['common.view']:
				if (record instanceof Zarafa.core.data.IPMRecord && record.isMessageClass('IPM.StickyNote', true)) {
					bid = 1;
				}
				break;
			case Zarafa.core.data.SharedComponentType['common.contextmenu']:
				if (record instanceof Zarafa.core.data.IPMRecord && record.isMessageClass('IPM.StickyNote', true)) {
					bid = 1;
				}
				break;
			case Zarafa.core.data.SharedComponentType['common.printer.renderer']:
				if (record instanceof Zarafa.core.data.IPMRecord && record.isMessageClass('IPM.StickyNote', true)) {
					bid = 1;
				}
				break;
			case Zarafa.core.data.SharedComponentType['common.attachment.dialog.attachitem.columnmodel']:
				if (record instanceof Zarafa.hierarchy.data.MAPIFolderRecord) {
					if (record.isContainerClass('IPF.StickyNote', true)) {
						bid = 1;
					}
				}
				break;
			case Zarafa.core.data.SharedComponentType['common.attachment.dialog.attachitem.textrenderer']:
				if (record instanceof Zarafa.core.data.IPMRecord && record.get('object_type') === Zarafa.core.mapi.ObjectType.MAPI_MESSAGE) {
					if (record.isMessageClass('IPM.StickyNote', true)) {
						bid = 1;
					}
				}
				break;
			case Zarafa.core.data.SharedComponentType['note.dialog.options']:
				if (record instanceof Zarafa.core.data.IPMRecord && record.isMessageClass('IPM.StickyNote', true)) {
					bid = 1;
				}
				break;
		}
		return bid;
	},

	/**
	 * Will return the reference to the shared component.
	 * Based on the type of component requested a component is returned.
	 * @param {Zarafa.core.data.SharedComponentType} type Type of component a context can bid for.
	 * @param {Ext.data.Record} record Optionally passed record.
	 * @return {Ext.Component} Component
	 */
	getSharedComponent: function(type, record)
	{
		var component;
		switch (type) {
			case Zarafa.core.data.SharedComponentType['common.create']:
			case Zarafa.core.data.SharedComponentType['common.view']:
				component = Zarafa.note.dialogs.NoteEditContentPanel;
				break;
			case Zarafa.core.data.SharedComponentType['common.contextmenu']:
				component = Zarafa.note.ui.NoteContextMenu;
				break;
			case Zarafa.core.data.SharedComponentType['note.dialog.options']:
				component = Zarafa.note.dialogs.NoteOptionsContentPanel;
				break;
			case Zarafa.core.data.SharedComponentType['common.printer.renderer']:
				if (record instanceof Zarafa.core.data.IPMRecord && record.get('object_type') === Zarafa.core.mapi.ObjectType.MAPI_MESSAGE) {
					component = Zarafa.note.printer.NoteRenderer;
				}
				break;
			case Zarafa.core.data.SharedComponentType['common.attachment.dialog.attachitem.columnmodel']:
				component = Zarafa.note.attachitem.AttachNoteColumnModel;
				break;
			case Zarafa.core.data.SharedComponentType['common.attachment.dialog.attachitem.textrenderer']:
				component = Zarafa.note.attachitem.AttachNoteRenderer;
				break;
		}
		return component;
	},

	/**
	 * Creates the note tree that is shown when the user selects the note context from the
	 * button panel. It shows a tree of available note folders that can be checked and unchecked.
	 * @private
	 */
	createNoteNavigationPanel: function()
	{
		return {
			xtype: 'zarafa.contextnavigation',
			context: this,
			items: [{
				xtype: 'panel',
				id: 'zarafa-navigationpanel-notes-navigation',
				cls: 'zarafa-context-navigation-block',
				layout: 'fit',
				items: [{
					xtype: 'zarafa.hierarchytreepanel',
					id: 'zarafa-navigationpanel-notes-navigation-tree',
					model: this.getModel(),
					IPMFilter: 'IPF.StickyNote',
					hideDeletedFolders: true,
					enableDD: true,
					enableItemDrop: true,
					deferredLoading: true,
					bbarConfig: {
						defaultSelectedSharedFolderType: Zarafa.hierarchy.data.SharedFolderTypes['NOTE'],
						buttonText: _('Open Shared Notes')
					}
				}]
			}]
		};
	},

	/**
	 * creates a context panel
	 * @return configuration for note context
	 * @private
	 */
	createContentPanel: function()
	{
		return {
			xtype: 'zarafa.notemainpanel',
			id: 'zarafa-mainpanel-contentpanel-notes',
			context: this
		};
	},

	/**
	 * Create "New Sticky Note" {@link Ext.menu.MenuItem item} for the "New item"
	 * {@link Ext.menu.Menu menu} in the {@link Zarafa.core.ui.MainToolbar toolbar}.
	 * This button should be shown in all {@link Zarafa.core.Context contexts} and
	 * is used to create a new Sticky Note.
	 *
	 * @return {Object} The menu item for creating a new Sticky Note item
	 */
	createNewNoteButton: function()
	{
		return {
			xtype: 'menuitem',
			id: 'zarafa-maintoolbar-newitem-note',
			tooltip: _('Sticky note') + Zarafa.core.KeyMapMgr.formatShortcutHint('Ctrl + Alt + S', false),
			plugins: 'zarafa.menuitemtooltipplugin',
			text: _('Sticky note'),
			iconCls: 'icon_new_note',
			newMenuIndex: 5,
			context: 'note',
			handler: function()
			{
				Zarafa.note.Actions.openCreateNoteContent(this.getModel());
			},
			scope: this
		};
	},

	/**
	 * Returns the buttons for the dropdown list of the Print button in the main toolbar. It will use the
	 * main.maintoolbar.print.note insertion point to allow other plugins to add their items at the end.
	 *
	 * @return {Ext.Component[]} an array of components
	 */
	getMainToolbarPrintButtons: function()
	{
		var items = container.populateInsertionPoint('main.toolbar.print.note', this) || [];

		var defaultItems = [{
			xtype: 'zarafa.conditionalitem',
			id: 'zarafa-maintoolbar-print-selectednote',
			overflowText: _('Print selected note'),
			iconCls: 'icon_print_note',
			tooltip: _('Print selected note') + Zarafa.core.KeyMapMgr.formatShortcutHint('Ctrl + P', true),
			plugins: 'zarafa.menuitemtooltipplugin',
			text: _('Print selected note'),
			hideOnDisabled: false,
			singleSelectOnly: true,
			handler: this.onPrintSelected.createDelegate(this, [_('No note selected')], 2),
			scope: this
		}];

		return defaultItems.concat(items);
	},

	/**
	 * Returns the buttons for the dropdown list of the VIEW-button in the main toolbar. It will use the
	 * main.maintoolbar.view.note insertion point to allow other plugins to add their items at the end.
	 *
	 * @return {Ext.Component[]} an array of components
	 */
	getMainToolbarViewButtons: function()
	{
		var items = container.populateInsertionPoint('main.maintoolbar.view.note', this) || [];

		var defaultItems = [{
			id: 'zarafa-maintoolbar-view-notes-icons',
			text: _('Icons'),
			overflowText: _('Icons'),
			iconCls: 'icon_note_icon_view',
			valueView: Zarafa.note.data.Views.ICON,
			valueViewMode: Zarafa.note.data.ViewModes.NORMAL,
			valueDataMode: Zarafa.note.data.DataModes.ALL,
			handler: this.onContextSelectView,
			scope: this
		},{
			id: 'zarafa-maintoolbar-view-notes-list',
			text: _('List view'),
			overflowText: _('List view'),
			iconCls: 'icon_contact_list',
			valueView: Zarafa.note.data.Views.LIST,
			valueViewMode: Zarafa.note.data.ViewModes.NORMAL,
			valueDataMode: Zarafa.note.data.DataModes.ALL,
			handler: this.onContextSelectView,
			scope: this
		},{
			id: 'zarafa-maintoolbar-view-notes-listlastsevendays',
			text: _('List Last Seven Days'),
			overflowText: _('List Last Seven Days'),
			iconCls: 'icon_task_seven_days',
			valueView: Zarafa.note.data.Views.LIST,
			valueViewMode: Zarafa.note.data.ViewModes.NORMAL,
			valueDataMode: Zarafa.note.data.DataModes.LAST_7_DAYS,
			handler: this.onContextSelectView,
			scope: this
		}];

		return defaultItems.concat(items);
	},

	/**
	 * Event handler which is fired when one of the View buttons
	 * has been pressed. This will call {@link Zarafa.note.NoteContext#setView setView}
	 * to update the view.
	 * @param {Ext.Button} button The button which was pressed
	 * @private
	 */
	onContextSelectView: function(button)
	{
		this.getModel().setDataMode(button.valueDataMode);
		this.switchView(button.valueView, button.valueViewMode);
	},

	/**
	 * Adds a button to the top tab bar for this context.
	 * @return {Object} The button for the top tabbar
	 * @private
	 */
	createMainTab: function()
	{
		return {
			text: this.getDisplayName(),
			tabOrderIndex: 6,
			context: this.getName(),
			id: 'mainmenu-button-notes'
		};
	},

	/**
	 * Event handler which is executed right before the {@link #viewmodechange}
	 * event is fired. This will check which {@link Zarafa.note.data.ViewModes ViewMode}
	 * was applied and thus which kind of {@link Zarafa.core.ContextModel#groupBy grouping}
	 * must be applied to the {@link #model}.
	 *
	 * @param {Zarafa.core.Context} context The context which fired the event.
	 * @param {Mixed} newViewMode The selected View Mode.
	 * @param {Mixed} oldViewMode The previously selected View Mode.
	 * @private
	 */
	onViewModeChange: function(context, newViewMode, oldViewMode)
	{
		var model = this.getModel();

		switch (newViewMode) {
			case Zarafa.note.data.ViewModes.NORMAL:
			case Zarafa.note.data.ViewModes.SEARCH:
				model.clearGrouping();
				break;
			case Zarafa.note.data.ViewModes.GROUP_CATEGORY:
				model.groupBy('categories');
				break;
			case Zarafa.note.data.ViewModes.GROUP_COLOR:
				model.groupBy('color');
				break;
		}
	}
});

Zarafa.onReady(function() {
	container.registerContext(new Zarafa.core.ContextMetaData({
		name: 'note',
		displayName: _('Notes'),
		allowUserVisible: false,
		pluginConstructor: Zarafa.note.NoteContext
	}));
});
Ext.namespace('Zarafa.note');

/**
 * @class Zarafa.note.NoteStore
 * @extends Zarafa.core.data.ListModuleStore
 * @xtype zarafa.notestore
 *
 * The NoteStore class provides a way to connect the 'notelistmodule' in the server back-end to an
 * Ext.grid.GridPanel object. It provides a means to retrieve note listings asynchronously.
 * The store has to be initialised with a store Id, which corresponds (somewhat confusingly) to
 * a MAPI store id. The NoteStore object, once instantiated, will be able to retrieve and list
 * notes from a single specific store only.
 *
 * @constructor
 * @param {String} storeId a MAPI id that corresponds with a MAPI store on the server.
 */
Zarafa.note.NoteStore = Ext.extend(Zarafa.core.data.ListModuleStore, {

	/**
	 * @constructor
	 * @param {Object} config configuration params that should be used to create instance of this store.
	 */
	constructor: function(config)
	{
		config = config || {};

		// Apply default settings.
		Ext.applyIf(config, {
			preferredMessageClass: 'IPM.StickyNote',
			defaultSortInfo: {
				field		: 'subject',
				direction	: 'asc'
			}
		});

		Zarafa.note.NoteStore.superclass.constructor.call(this, config);
	}
});

Ext.reg('zarafa.notestore', Zarafa.note.NoteStore);
Ext.namespace('Zarafa.settings');

/**
 * @class Zarafa.settings.SettingsContext
 * @extends Zarafa.core.Context
 */
Zarafa.settings.SettingsContext = Ext.extend(Zarafa.core.Context, {

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

		Ext.applyIf(config, {
			stateful: false
		});

		Zarafa.settings.SettingsContext.superclass.constructor.call(this, config);

		// Register the settings tab in the MainTabBar
		this.registerInsertionPoint('main.maintabbar.right', this.createSettingsMainTab, this);
		// Register some default categories for the settings
		this.registerInsertionPoint('context.settings.categories', this.createSettingCategories, this);

		this.addEvents(
			/**
			 * Fires when the user press a button in required reload message box.
			 * @param {String} button The button which user pressed from
			 * required reload message box
			 */
			'afterrequiredreload'
		);

		// Add a listener to the contextswitch event to collapse the navigation panel
		// when we switch to the settings context
		container.on('contextswitch', this.onContextSwitch);
	},

	/**
	 * @return {Zarafa.settings.SettingsContextModel} the settings context model
	 */
	getModel: function()
	{
		if (!Ext.isDefined(this.model)) {
			this.model = new Zarafa.settings.SettingsContextModel();
		}
		return this.model;
	},

	/**
	 * Switch the currently active view inside the {@link Zarafa.core.Context context}.
	 * This will fire the {@link #viewchange} event.
	 *
	 * Before switching the view, this function will first check if there are
	 * any pending changes.
	 *
	 * @param {Mixed} viewId The view identification
	 */
	setView: function(viewId)
	{
		if (this.current_view != viewId) {
			var model = this.getModel();

			if (model.hasChanges()) {
				Ext.MessageBox.show({
					title: _('Apply changes'),
					msg: _('Do you wish to apply the changes?'),
					fn: this.applyChanges.createDelegate(this, [ viewId ], 1),
					buttons: Ext.MessageBox.YESNOCANCEL
				});
			} else {
				Zarafa.settings.SettingsContext.superclass.setView.call(this, viewId);
			}
		}
	},

	/**
	 * Sets the current view mode from the available view modes.
	 *
	 * Compared to the {@link Zarafa.core.Context superclass} this function
	 * will always fire the viewmode change event regardless if the same value
	 * has been provided or not.
	 *
	 * Fires the {@link #viewmodechange} event.
	 * @param {Number} mode view mode (context should define modes and its numeric values).
	 */
	setViewMode: function(mode)
	{
		var oldMode = this.current_view_mode;
		this.current_view_mode = mode;
		this.fireEvent('viewmodechange', this, this.current_view_mode, oldMode);
	},

	/**
	 * Event handler for {@link #setView}. This will check if the user
	 * wishes to cancel the {@link #setView} action, or wishes to either
	 * {@link Zarafa.settings.SettingsContextModel#applyChanges apply} or
	 * {@link Zarafa.settings.SettingsContextModel#discardChanges discard}
	 * all changes.
	 * @param {String} btn The button which the user pressed
	 * @param {Mixed} viewId the viewId argument from {@link #setView}.
	 * @private
	 */
	applyChanges: function(btn, viewId)
	{
		// The user cancels the switch to a different category
		if (btn === 'cancel') {
			return;
		}

		// Check if the user wishes to save or discard all changes
		var model = this.getModel();
		if (btn === 'yes') {
			model.applyChanges();
		} else {
			model.discardChanges();
		}

		Zarafa.settings.SettingsContext.superclass.setView.call(this, viewId);
	},

	/**
	 * Called before the context is switched in.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder MAPI folder to show.
	 * @param {Boolean} suspended True to enable the ContextModel {@link Zarafa.core.ContextModel#suspendLoading suspended}
	 */
	enable: function(folder, suspended)
	{
		Zarafa.settings.SettingsContext.superclass.enable.apply(this, arguments);

		container.on('beforecontextswitch', this.onBeforeContextSwitch, this);
		this.getModel().getRealSettingsModel().on('save', this.onSaveSettings, this);
	},

	/**
	 * Called before the context is switched out.
	 */
	disable: function()
	{
		// Get the state of the navigation panel so we know if we must expand it again.
		// By default the navigation bar is expanded, but the state setting is not set (undefined).
		// Expand the navigation bar when the state is 1) undefined, 2) true and collapsed.
		// Ref KW-2961.
		var navState = Ext.state.Manager.get(container.getNavigationBar().stateId);
		if ((!Ext.isDefined(navState)) || (navState && !navState.collapsed)) {
			container.getNavigationBar().expand();
		}

		container.un('beforecontextswitch', this.onBeforeContextSwitch, this);

		Zarafa.settings.SettingsContext.superclass.disable.apply(this, arguments);
	},

	/**
	 * Event handler which shows the warning {@link Zarafa.common.dialogs.MessageBox.addCustomButtons messageBox}
	 * if updated settings required the webapp to reload.
	 * @param {Zarafa.settings.SettingsModel} model The model which fired the event.
	 * @param {Object} param The key-value object containing the action and the corresponding
	 * settings which were saved to the server.
	 */
	onSaveSettings: function(model, param)
	{
		if(param.requiresReload) {
			var message = _('You must reload grommunio Web for the changes to take effect. Unsaved changes will be lost.');

			Zarafa.common.dialogs.MessageBox.addCustomButtons({
				title: _('Reload grommunio Web'),
				msg: message,
				fn: this.reloadWebapp,
				customButton: [{
					text: _('Reload'),
					name: 'reload'
				}, {
					text: _('Cancel'),
					name: 'cancel'
				}],
				scope: this
			});
		}
	},

	/**
	 * Event handler for {@link #onSaveSettings}. This will check if the user
	 * wishes to reload the webapp or not.
	 * @param {String} button The button which the user pressed
	 * @private
	 */
	reloadWebapp: function(button)
	{
		if(button === 'reload') {
			this.getModel().getRealSettingsModel().un('save', this.onSaveSettings, this);
			Zarafa.core.Util.reloadWebapp();
		}
		this.fireEvent('afterrequiredreload', this, button);
	},

	/**
	 * Bid for the type of shared component and the given record.
	 * @param {Zarafa.core.data.SharedComponentType} type Type of component a context can bid for.
	 * @param {Ext.data.Record} record Optionally passed record.
	 * @return {Number} The bid for the shared component
	 */
	bidSharedComponent: function(type, record)
	{
		var bid = -1;

		if (Array.isArray(record)) {
			record = record[0];
		}

		switch (type) {
			case Zarafa.core.data.SharedComponentType['common.contextmenu']:
				if (record instanceof Zarafa.settings.ui.SettingsTreeNode) {
					bid = 1;
				}
				break;
		}

		return bid;
	},

	/**
	 * Will return the reference to the shared component.
	 * Based on the type of component requested a component is returned.
	 * @param {Zarafa.core.data.SharedComponentType} type Type of component a context can bid for.
	 * @param {Ext.data.Record} record Optionally passed record.
	 * @return {Ext.Component} Component
	 */
	getSharedComponent: function(type, record)
	{
		var component;

		switch (type) {
			case Zarafa.core.data.SharedComponentType['common.contextmenu']:
				if (record instanceof Zarafa.settings.ui.SettingsTreeNode) {
					component = Zarafa.settings.ui.SettingsContextMenu;
				}
				break;
		}

		return component;
	},

	/**
	 * Obtain the {@link Zarafa.settings.ui.SettingsMainPanel SettingsMainPanel} object
	 *
	 * @return {Zarafa.settings.ui.SetingsMainPanel} The main panel which should
	 * be used within the {@link Zarafa.core.Context context}
	 */
	createContentPanel: function()
	{
		return {
			xtype: 'zarafa.settingsmainpanel',
			id: 'zarafa-mainpanel-contentpanel-settings',
			title: this.getDisplayName(),
			context: this
		};
	},

	/**
	 * Adds a button to the top tab bar for the settings.
	 * @return {Object} The button for the top tabbar
	 * @private
	 */
	createSettingsMainTab: function()
	{
		return {
			text: this.getDisplayName(),
			tabOrderIndex: 1,
			context: this.getName(),
			id: 'mainmenu-button-settings'
		};
	},

	/**
	 * Create the 3 default {@link Zarafa.settings.ui.SettingsCategory Settings Categories}
	 * to the {@link Zarafa.settings.SettingsContext}. This will create new
	 * {@link Zarafa.settings.ui.SettingsCategoryTab tabs} for the
	 * {@link Zarafa.settings.ui.SettingsGeneralCategory General},
	 * {@link Zarafa.settings.ui.SettingsPluginsCategory Plugins},
	 * {@link Zarafa.settings.ui.SettingsAdvancedCategory Advanced settings} and
	 * {@link Zarafa.settings.ui.SettingsCopyrightCategory Copyright notice}
	 * in the {@link Zarafa.settings.ui.SettingsCategoryWidgetPanel Widget Panel}.
	 * @return {Array} configuration object for the categories to register
	 * @private
	 */
	createSettingCategories: function()
	{
		var categories = [{
			xtype: 'zarafa.settingsgeneralcategory',
			settingsContext: this
		}];

		// disable plugin settings if sysadmin wants it
		if (container.getServerConfig().isPluginsEnabled()) {
			categories.push({
				xtype: 'zarafa.settingspluginscategory',
				settingsContext: this
			});
		}

		// disable advanced settings if sysadmin wants it
		if (container.getServerConfig().isAdvancedSettingsEnabled()) {
			categories.push({
				xtype: 'zarafa.settingsadvancedcategory',
				settingsContext: this
			});
		}

		categories = categories.concat([
		{
			xtype: 'zarafa.settingskeyshortcutcategory',
			settingsContext: this
		},{
			xtype: 'zarafa.settingscopyrightcategory',
			settingsContext: this
		}]);

		return categories;
	},

	/**
	 * Event handler which is fired just before the {@link Zarafa.core.Container container}
	 * {@link Zarafa.core.Container#beforecontextswitch switches the context}. This will
	 * check if there are any pending changes which the user might wish to apply or discard.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder folder that is loaded for the new context
	 * @param {Zarafa.core.Context} oldContext context being switched out
	 * @param {Zarafa.core.Context} newContext new context being switched in
	 * @return {Boolean} False to prevent the context from being switched
	 * @private
	 */
	onBeforeContextSwitch: function(folder, oldContext, newContext)
	{
		if (this.getModel().hasChanges()) {
			Ext.MessageBox.show({
				title: _('Apply changes'),
				msg: _('Do you wish to apply the changes?'),
				fn: this.applyChangesContext.createDelegate(this, [ folder, newContext ], 1),
				buttons: Ext.MessageBox.YESNOCANCEL
			});

			// Always return false, we will manually switch context again
			// in the event handler for the message box.
			return false;
		}
	},

	/**
	 * Event handler for {@link #onBeforeContextSwitch}. This will check if the user
	 * wishes to cancel the {@link #setView} action, or wishes to either
	 * {@link Zarafa.settings.SettingsContextModel#applyChanges apply} or
	 * {@link Zarafa.settings.SettingsContextModel#discardChanges discard}
	 * all changes.
	 * @param {String} btn The button which the user pressed
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder The folder to which to
	 * switch when the user is wishes to switch context.
	 * @param {Zarafa.core.Context} newContext The context to which to switch
	 * @private
	 */
	applyChangesContext: function(btn, folder, newContext)
	{
		// The user cancels the switch to a different category
		if (btn === 'cancel') {
			return;
		}

		// Check if the user wishes to save or discard all changes
		var model = this.getModel();
		if (btn === 'yes') {
			model.applyChanges();
		} else {
			model.discardChanges();
		}

		/*
		 * if requiresReload config was true then don't directly switch the context
		 * register the afterrequiredreload event and then switch the context based on the
		 * button pressed by the user from required reload message box.
		 */
		if(model.getRealSettingsModel().requiresReload) {
			this.on('afterrequiredreload', this.onAfterRequiredReload.createDelegate(this, [ folder, newContext ],1), this, {single: true});
		} else {
			container.switchContext(newContext, folder);
		}
	},

	/**
	 * Event handler for the {@link Zarafa.core.Container.contextswitch contextswitch}
	 * event of the {@link Zarafa.core.Container Container} Will collapse the
	 * {@link Zarafa.core.ui.NavigationPanel NavigationPanel} when we switch
	 * to the SettingContext.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder folder that is loaded for the new context
	 * @param {Zarafa.core.Context} oldContext context that was switched out
	 * @param {Zarafa.core.Context} newContext new context that was switched
	 */
	onContextSwitch: function(folder, oldContext, newContext)
	{
		if ( newContext.getName() === 'settings' ) {
			container.getNavigationBar().collapse();
		}
	},

	/**
	 * Function is used to switch the context if cancel button was pressed from
	 * required reload message box.
	 *
	 * @param {Zarafa.core.Context} currentContext The current context.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder The folder to which to
	 * switch when the user is wishes to switch context.
	 * @param {Zarafa.core.Context} newContext The context to which to switch
	 * @param {String} button The button which the user pressed from required reload message box
	 * @private
	 */
	onAfterRequiredReload: function(currentContext, newContextFolder, newContext, button)
	{
		if(button === 'cancel') {
			container.switchContext(newContext, newContextFolder);
		}
	}
});

Zarafa.onReady(function() {
	container.registerContext(new Zarafa.core.ContextMetaData({
		name: 'settings',
		displayName: _('Settings'),
		allowUserVisible: false,
		pluginConstructor: Zarafa.settings.SettingsContext
	}));
});
/*
 * #dependsFile client/zarafa/task/TaskContextModel.js
 */
Ext.namespace('Zarafa.task');

/**
 * @class Zarafa.task.TaskContext
 * @extends Zarafa.core.Context
 *
 * Context that displays tasks. This context has only one view ie. grid view
 */
Zarafa.task.TaskContext = Ext.extend(Zarafa.core.Context, {
	// Insertion points for this class
	/**
	 * @insert main.maintoolbar.view.task
	 * Insertion point for populating the main toolbar with a View button. This item is only visible
	 * when this context is active.
	 * @param {Zarafa.mail.TaskContext} context This context
	 */

	/**
	 * When searching, this property marks the {@link Zarafa.core.Context#getCurrentView view}
	 * which was used before {@link #onSearchStart searching started} the view was switched to
	 * {@link Zarafa.task.data.Views#SEARCH}.
	 * @property
	 * @type Mixed
	 * @private
	 */
	oldView: undefined,

	/**
	 * When searching, this property marks the {@link Zarafa.core.Context#getCurrentViewMode viewmode}
	 * which was used before {@link #onSearchStart searching started} the viewmode was switched to
	 * {@link Zarafa.task.data.ViewModes#SEARCH}.
	 * @property
	 * @type Mixed
	 * @private
	 */
	oldViewMode: undefined,

	/**
	 * @constructor
	 * @param config
	 */
	constructor: function(config)
	{
		config = config || {};
		Ext.applyIf(config, {
			current_view: Zarafa.task.data.Views.LIST,
			current_view_mode: Zarafa.task.data.ViewModes.SIMPLE,
			current_data_mode: Zarafa.task.data.DataModes.ALL
		});

		// The tab in the top tabbar
		this.registerInsertionPoint('main.maintabbar.left', this.createMainTab, this);

		//The task filter buttons
		this.registerInsertionPoint('context.mainpaneltoolbar.item', this.createFilterButtons, this);

		// The "New task" button which is available in all contexts
		this.registerInsertionPoint('main.maintoolbar.new.item', this.createToolbarNewTaskButton, this);
		// The "New task request" button which is available in all contexts
		this.registerInsertionPoint('main.maintoolbar.new.item', this.createToolbarNewTaskRequestButton, this);

		this.registerInsertionPoint('previewpanel.toolbar.left', this.createTaskRequestToolbarButtons, this);

		Zarafa.task.TaskContext.superclass.constructor.call(this, config);

		// Add a tree control showing a list of task folders to the navigation panel.
		// The control will be shown when the user selects the task context from the button panel.
		this.registerInsertionPoint('navigation.center', this.createTaskNavigationPanel, this);

		// Adds convert mail to task contextmenu item in the mail contextmenu.
		this.registerInsertionPoint('context.mail.contextmenu.topoptions', this.convertToTask, this);

		// Register task specific dialog types
		Zarafa.core.data.SharedComponentType.addProperty('task.dialogs.sendtaskrequestconfirmation');
		Zarafa.core.data.SharedComponentType.addProperty('task.dialogs.sendtaskrequestcancellation');
		Zarafa.core.data.SharedComponentType.addProperty('task.contextmenu.flags');
		Zarafa.core.data.SharedComponentType.addProperty('task.dialog.options');
	},

	/**
	 * @return Zarafa.task.TaskContextModel the task context model
	 */
	getModel: function()
	{	if (!Ext.isDefined(this.model)) {
			this.model = new Zarafa.task.TaskContextModel();
			this.model.on({
				'searchstart': this.onModelSearchStart,
				'searchstop': this.onModelSearchStop,
				scope: this
			});
		}
		return this.model;
	},

	/**
	 * Event handler for the {@link #model}#{@link Zarafa.core.ContextModel#searchstart searchstart} event.
	 * This will {@link #switchView switch the view} to {@link Zarafa.task.data.Views#SEARCH search mode}.
	 * The previously active {@link #getCurrentView view} will be stored in the {@link #oldView} and will
	 * be recovered when the {@link #onModelSearchStop search is stopped}.
	 * @param {Zarafa.core.ContextModel} model The model which fired the event
	 * @private
	 */
	onModelSearchStart: function(model)
	{
		if(this.getCurrentView() !== Zarafa.task.data.Views.SEARCH && this.getCurrentViewMode() !== Zarafa.task.data.ViewModes.SEARCH){
			this.oldView = this.getCurrentView();
			this.oldViewMode = this.getCurrentViewMode();
			this.switchView(Zarafa.task.data.Views.SEARCH, Zarafa.task.data.ViewModes.SEARCH);
		}
	},

	/**
	 * Event handler for the {@link #model}#{@link Zarafa.core.ContextModel#searchstop searchstop} event.
	 * This will {@link #switchView switch the view} to the {@link #oldView previous view}.
	 * @param {Zarafa.core.ContextModel} model The model which fired the event
	 * @private
	 */
	onModelSearchStop: function(model)
	{
		this.switchView(this.oldView, this.oldViewMode);
		delete this.oldView;
		delete this.oldViewMode;
	},

	/**
	 * Bid on task folders.
	 * @param {Zarafa.Folder} folder which contains task items.
	 * @return {Number} return 1 if specified folder contains items of type IPF.Task else -1
	 * @method
	 */
	bid: function(folder)
	{

		// the folder contains items of type IPF.Task, return 1
		if (folder.isContainerClass('IPF.Task', true)) {
			return 1;
		}

		// return -1, don't handle this content type
		return -1;

	},

	/**
	 * Bid for the type of shared component and the given record.
	 * @param {Zarafa.core.data.SharedComponentType} type Type of component a context can bid for.
	 * @param {Ext.data.Record} record Optionally passed record.
	 * @return {Number} The bid for the shared component
	 */
	bidSharedComponent: function(type, record)
	{
		var bid = -1;

		if (Array.isArray(record)) {
			record = record[0];
		}

		switch (type) {
			case Zarafa.core.data.SharedComponentType['common.create']:
			case Zarafa.core.data.SharedComponentType['common.view']:
			case Zarafa.core.data.SharedComponentType['common.preview']:
				if (record instanceof Zarafa.core.data.IPMRecord && record.isMessageClass('IPM.Task', true)) {
					bid = 1;
				}
				break;
			case Zarafa.core.data.SharedComponentType['task.dialogs.sendtaskrequestconfirmation']:
			case Zarafa.core.data.SharedComponentType['task.dialogs.sendtaskrequestcancellation']:
				if (record instanceof Zarafa.core.data.IPMRecord && record.get('object_type') == Zarafa.core.mapi.ObjectType.MAPI_MESSAGE) {
					if (record.isMessageClass('IPM.Task', true)) {
						bid = 1;
					}
				}
				break;
			case Zarafa.core.data.SharedComponentType['common.contextmenu']:
				if (record instanceof Zarafa.core.data.IPMRecord) {
					if(this.model.getDefaultFolder().isTodoListFolder()) {
						bid = 2;
					} else if(record.isMessageClass('IPM.Task', true)) {
						bid = 1;
					}
				}
				break;
			case Zarafa.core.data.SharedComponentType['common.printer.renderer']:
				if (record instanceof Zarafa.core.data.IPMRecord && record.get('object_type') === Zarafa.core.mapi.ObjectType.MAPI_MESSAGE) {
					if (record.isMessageClass([ 'IPM.Task' ], true)) {
						bid = 1;
					}
				} else if (record instanceof Zarafa.task.TaskContext) {
					bid = 1;
				}
				break;
			case Zarafa.core.data.SharedComponentType['common.attachment.dialog.attachitem.columnmodel']:
				if (record instanceof Zarafa.hierarchy.data.MAPIFolderRecord) {
					if (record.isContainerClass('IPF.Task', true)) {
						bid = 1;
					}
				}
				break;
			case Zarafa.core.data.SharedComponentType['common.attachment.dialog.attachitem.textrenderer']:
				if (record instanceof Zarafa.core.data.IPMRecord && record.get('object_type') === Zarafa.core.mapi.ObjectType.MAPI_MESSAGE) {
					if (record.isMessageClass('IPM.Task', true)) {
						bid = 1;
					}
				}
				break;
			case Zarafa.core.data.SharedComponentType['task.contextmenu.flags']:
				bid = 1;
				break;
			case Zarafa.core.data.SharedComponentType['task.dialog.options']:
				if (record instanceof Zarafa.core.data.IPMRecord && record.isMessageClass('IPM.Task', true)) {
					bid = 1;
				}
				break;
		}
		return bid;
	},

	/**
	 * Will return the reference to the shared component.
	 * Based on the type of component requested a component is returned.
	 * @param {Zarafa.core.data.SharedComponentType} type Type of component a context can bid for.
	 * @param {Ext.data.Record} record Optionally passed record.
	 * @return {Ext.Component} Component
	 */
	getSharedComponent: function(type, record)
	{
		var component;
		switch (type) {
			case Zarafa.core.data.SharedComponentType['common.create']:
			case Zarafa.core.data.SharedComponentType['common.view']:
				component = Zarafa.task.dialogs.TaskEditContentPanel;
				break;
			case Zarafa.core.data.SharedComponentType['common.preview']:
				component = Zarafa.task.ui.TaskPreviewPanel;
				break;
			case Zarafa.core.data.SharedComponentType['task.dialogs.sendtaskrequestconfirmation']:
			case Zarafa.core.data.SharedComponentType['task.dialogs.sendtaskrequestcancellation']:
				component = Zarafa.task.dialogs.SendTaskRequestConfirmationContentPanel;
				break;
			case Zarafa.core.data.SharedComponentType['common.contextmenu']:
		 		component = Zarafa.task.ui.TaskContextMenu;
				break;
			case Zarafa.core.data.SharedComponentType['task.contextmenu.flags']:
		 		component = Zarafa.task.ui.TaskFlagsMenu;
				break;
			case Zarafa.core.data.SharedComponentType['task.dialog.options']:
				component = Zarafa.task.dialogs.TaskOptionsContentPanel;
				break;
			case Zarafa.core.data.SharedComponentType['common.printer.renderer']:
				if (record instanceof Zarafa.core.data.IPMRecord && record.get('object_type') === Zarafa.core.mapi.ObjectType.MAPI_MESSAGE) {
					component = Zarafa.task.printer.TaskRenderer;
				} else {
					component = Zarafa.task.printer.TaskListViewRenderer;
				}
				break;
			case Zarafa.core.data.SharedComponentType['common.attachment.dialog.attachitem.columnmodel']:
				component = Zarafa.task.attachitem.AttachTaskColumnModel;
				break;
			case Zarafa.core.data.SharedComponentType['common.attachment.dialog.attachitem.textrenderer']:
				component = Zarafa.task.attachitem.AttachTaskRenderer;
				break;
		}
		return component;
	},

	/**
	 * Creates the task tree that is shown when the user selects the task context from the
	 * button panel. It shows a tree of available task folders that can be checked and unchecked.
	 * @private
	 */
	createTaskNavigationPanel: function()
	{
		return {
			xtype: 'zarafa.contextnavigation',
			context: this,
			items: [{
				xtype: 'panel',
				id: 'zarafa-navigationpanel-tasks-navigation',
				cls: 'zarafa-context-navigation-block',
				layout: 'fit',
				items: [{
					xtype: 'zarafa.hierarchytreepanel',
					id: 'zarafa-navigationpanel-tasks-navigation-tree',
					model: this.getModel(),
					IPMFilter: 'IPF.Task',
					hideDeletedFolders: true,
					enableDD: true,
					enableItemDrop: true,
					deferredLoading: true,
					bbarConfig: {
						defaultSelectedSharedFolderType: Zarafa.hierarchy.data.SharedFolderTypes['TASK'],
						buttonText: _('Open Shared Tasks')
					}
				}]
			}]
		};
	},

	/**
	 * Obtain the {@link Zarafa.task.ui.taskPanel taskpanel} object
	 *
	 * @return {Zarafa.task.ui.taskPanel} The main panel which should
	 * be used within the {@link Zarafa.core.Context context}
	 */
	createContentPanel: function()
	{
		return {
			xtype: 'zarafa.taskmainpanel',
			id: 'zarafa-mainpanel-contentpanel-tasks',
			context: this
		};
	},

	/**
	 * Returns the buttons for the dropdown list of the VIEW-button in the main toolbar. It will use the
	 * main.maintoolbar.view.task insertion point to allow other plugins to add their items at the end.
	 *
	 * @return {Ext.Component[]} an array of components
	 */
	getMainToolbarViewButtons: function()
	{
		var items = container.populateInsertionPoint('main.maintoolbar.view.task', this) || [];

		var defaultItems = [{
			id: 'zarafa-maintoolbar-view-tasks-simple',
			text: _('Simple view'),
			overflowText: _('Simple view'),
			iconCls: 'icon_task_simple',
			valueView: Zarafa.task.data.Views.LIST,
			valueViewMode: Zarafa.task.data.ViewModes.SIMPLE,
			handler: this.onContextSelectView,
			scope: this
		},{
			id: 'zarafa-maintoolbar-view-tasks-detailed',
			text: _('Detailed view'),
			overflowText: _('Detailed view'),
			iconCls: 'icon_task_detailed',
			valueView: Zarafa.task.data.Views.LIST,
			valueViewMode: Zarafa.task.data.ViewModes.DETAILED,
			handler: this.onContextSelectView,
			scope: this
		}];

		return defaultItems.concat(items);
	},

	/**
	 * Event handler which is fired when one of the View buttons
	 * has been pressed. This will call {@link Zarafa.task.TaskContext#setView setView}
	 * to update the view.
	 * @param {Ext.Button} button The button which was pressed
	 * @private
	 */
	onContextSelectView: function(button)
	{
		var model = this.getModel();
		model.setDataMode(model.getCurrentDataMode());
		this.switchView(button.valueView, button.valueViewMode);
	},

	/**
	 * Create "New Task" {@link Ext.menu.MenuItem item} for the "New item"
	 * {@link Ext.menu.Menu menu} in the {@link Zarafa.core.ui.MainToolbar MainToolbar}.
	 * This button should be shown in all {@link Zarafa.core.Context contexts} and
	 * is used to create a new task.
	 *
	 * @return {Object} The menu item for creating a new task item
	 * @static
	 */
	createToolbarNewTaskButton: function()
	{
		//create new task button.
		return {

			xtype	: 'menuitem',
			id: 'zarafa-maintoolbar-newitem-task',
			tooltip: _('Task') + Zarafa.core.KeyMapMgr.formatShortcutHint('Ctrl + Alt + K', false),
			plugins: 'zarafa.menuitemtooltipplugin',
			text	: _('Task'),
			handler	: function(){
				Zarafa.task.Actions.openCreateTaskContent(this.getModel());
			},
			iconCls		: 'icon_new_task',
			newMenuIndex: 4,
			context: 'task',
			scope: this
		};
	},

	/**
	 * Create "New Task Request" {@link Ext.menu.MenuItem item} for the "New item"
	 * {@link Ext.menu.Menu menu} in the {@link Zarafa.core.ui.MainToolbar MainToolbar}.
	 * This button should be shown in all {@link Zarafa.core.Context contexts} and
	 * is used to create a new task request.
	 *
	 * @return {Object} The menu item for creating a new task request item
	 * @static
	 */
	createToolbarNewTaskRequestButton: function()
	{
		return {
			xtype: 'menuitem',
			id: 'zarafa-maintoolbar-newitem-task-request',
			tooltip: _('Task request'),
			plugins: 'zarafa.menuitemtooltipplugin',
			text: _('Task request'),
			handler: function(){
				Zarafa.task.Actions.openCreateTaskRequestContent(this.getModel());
			},
			iconCls: 'icon_new-task_request',
			newMenuIndex: 4,
			context: 'task',
			scope: this
		};
	},

	/**
	 * Populates the Print button in the main toolbar
	 * @return {Array} items The menu items available for printing in this context
	 */
	getMainToolbarPrintButtons: function()
	{
		var items = container.populateInsertionPoint('main.toolbar.print.task', this) || [];

		var defaultItems = [{
			xtype: 'zarafa.conditionalitem',
			id: 'zarafa-maintoolbar-print-selectedtask',
			overflowText: _('Print selected task'),
			iconCls: 'icon_print_task',
			tooltip: _('Print selected task') + Zarafa.core.KeyMapMgr.formatShortcutHint('Ctrl + P', true),
			plugins: 'zarafa.menuitemtooltipplugin',
			text: _('Print selected task'),
			hideOnDisabled: false,
			singleSelectOnly: true,
			handler: this.onPrintSelected.createDelegate(this, [_('No task selected')], 2),
			scope: this
		},{
			overflowText: _('Print task list'),
			id: 'zarafa-maintoolbar-print-tasklist',
			iconCls: 'icon_print',
			tooltip: _('Print task list') + Zarafa.core.KeyMapMgr.formatShortcutHint('Ctrl + Alt + P', false),
			plugins: 'zarafa.menuitemtooltipplugin',
			text: _('Print task list'),
			handler: this.onPrintList,
			scope: this
		}];

		return defaultItems.concat(items);
	},

	/**
	 * Handler for printing all {@link Zarafa.core.data.MAPIRecord} records in the current view.
	 * Calls {@link Zarafa.common.Actions.openPrintDialog} openPrintDialog with the current context.
	 * @private
	 */
	onPrintList: function(item)
	{
		Zarafa.common.Actions.openPrintDialog(this);
	},

	/**
	 * Adds a button to the top tab bar for this context.
	 * @return {Object} The button for the top tabbar
	 * @private
	 */
	createMainTab: function()
	{
		return {
			text: this.getDisplayName(),
			tabOrderIndex: 5,
			context: this.getName(),
			id: 'mainmenu-button-tasks'
		};
	},

	/**
	 * Adds a new contextmenu item in the mail context, which converts an email to a task
	 * @return {Zarafa.core.ui.menu.ConditionalItem[]} The Action context menu item
	 * @private
	 */
	convertToTask: function()
	{
		return {
			xtype: 'zarafa.conditionalitem',
			text: _('Create task'),
			iconCls: 'icon_new_task',
			singleSelectOnly: true,
			hidden: true,
			handler: this.onContextItemCreateTask,
			scope: this
		};
	},

	/**
	 * @param {Ext.Component} component The component to which the buttons will be added
	 * @return {Array} Array of configuration objects containing a Buttons which should be
	 * added in the {@link Ext.Toolbar Toolbar}.
	 * @private
	 */
	createTaskRequestToolbarButtons: function(component)
	{
		return [{
			xtype: 'zarafa.taskrequestbutton',
			name: Zarafa.task.data.TaskRequestButtonNames.ACCEPT,
			text: _('Accept'),
			iconCls: 'icon_calendar_appt_accept',
			responseStatus: Zarafa.core.mapi.TaskMode.ACCEPT

		},{
			xtype: 'zarafa.taskrequestbutton',
			name: Zarafa.task.data.TaskRequestButtonNames.DECLINE,
			text: _('Decline'),
			iconCls: 'icon_calendar_appt_cancelled',
			responseStatus: Zarafa.core.mapi.TaskMode.DECLINE
		}];
	},

	/**
	 * Event Handler triggered when {@link #convertToTask convert to task} context menu item is click.
	 * function is used to convert email to task record
	 *
	 * @param {Zarafa.core.ui.menu.ConditionalItem} item context menu item
	 * @param {Ext.EventObject} event The event information
	 * @private
	 */
	onContextItemCreateTask: function(menuItem, event)
	{
		var records = menuItem.getRecords();
		Zarafa.task.Actions.createTaskFromMail(records, this.getModel());
	},

	/**
	 * Create task filter buttons in {@link Zarafa.common.ui.ContextMainPanelToolbar ContextMainPanelToolbar}
	 *
	 * @param {String} insertionPoint The insertionPoint text.
	 * @param {Zarafa.core.Context} currentContext The current context in which this
	 * insertion point triggered.
	 * @return {Object} configuration object to create task filter buttons.
	 */
	createFilterButtons: function(insertionPoint, currentContext) {
		var hidden = currentContext.getName() !== 'task';

		return [{
				xtype: 'button',
				cls: 'k-filter-options-btn',
				text: '<span>' + _('Active') + '</span>',
				overflowText: _('Active'),
				iconCls: 'icon_task_active',
				model: this.getModel(),
				hidden: hidden,
				valueDataMode: Zarafa.task.data.DataModes.ACTIVE,
				enableToggle: true,
				toggleGroup: 'taskFilters',
				toggleHandler: this.onClickToggleHandler,
				listeners: {
					afterrender: this.onAfterRenderFilterButtons,
					scope: this
				},
				scope: this
			}, {
				xtype: 'button',
				cls: 'k-filter-options-btn',
				text: '<span>' + _('Upcoming') + '</span>',
				overflowText: _('Upcoming'),
				iconCls: 'icon_calendar',
				model: this.getModel(),
				hidden: hidden,
				valueDataMode: Zarafa.task.data.DataModes.NEXT_7_DAYS,
				enableToggle: true,
				toggleGroup: 'taskFilters',
				toggleHandler: this.onClickToggleHandler,
				listeners: {
					afterrender: this.onAfterRenderFilterButtons,
					scope: this
				},
				scope: this
			}, {
				xtype: 'button',
				cls: 'k-filter-options-btn',
				text: '<span>' + _('Complete') + '</span>',
				overflowText: _('Complete'),
				iconCls: 'icon_task_complete',
				hidden: hidden,
				model: this.getModel(),
				valueDataMode: Zarafa.task.data.DataModes.COMPLETED,
				enableToggle: true,
				toggleGroup: 'taskFilters',
				toggleHandler: this.onClickToggleHandler,
				listeners: {
					afterrender: this.onAfterRenderFilterButtons,
					scope: this
				},
				scope: this
			}, {
				xtype: 'button',
				cls: 'k-filter-options-btn',
				text: '<span>' + _('Overdue') + '</span>',
				overflowText: _('Overdue'),
				iconCls: 'icon_calendar_appt_newtime',
				model: this.getModel(),
				hidden: hidden,
				valueDataMode: Zarafa.task.data.DataModes.OVERDUE,
				enableToggle: true,
				toggleGroup: 'taskFilters',
				toggleHandler: this.onClickToggleHandler,
				listeners: {
					afterrender: this.onAfterRenderFilterButtons,
					scope: this
				},
				scope: this
			}];
	},

	/**
	 * Function sets selection on the button which has the current data mode.
	 *
	 * @param {Object} button The filter button to be selected.
	 */
	onAfterRenderFilterButtons: function (button)
	{
		if (this.getModel().getCurrentDataMode() === button.valueDataMode) {
			button.btnEl.addClass('k-selection');
			button.pressed = true;
		}
	},

	/**
	 * The function handles the toggling of the filter button. If already pressed,
	 * it resets the {@link Zarafa.task.data.DataModes datamode} and clears the restriction.
	 *
	 * @param {Object} button The filter button pressed by user.
	 * @param {Boolean} state The state of the button, true if pressed.
	 */
	onClickToggleHandler: function (button, state)
	{
		var model = this.getModel();
		button.btnEl.toggleClass('k-selection');
		model.getStore().hasFilterApplied = state;
		model.setDataMode(state ? button.valueDataMode : Zarafa.task.data.DataModes.ALL);
	}
});

Zarafa.onReady(function() {
	container.registerContext(new Zarafa.core.ContextMetaData({
		name: 'task',
		displayName: _('Tasks'),
		allowUserVisible: false,
		pluginConstructor: Zarafa.task.TaskContext
	}));
});
Ext.namespace('Zarafa.task');

/**
 * @class Zarafa.task.TaskStore
 * @extends Zarafa.core.data.ListModuleStore
 * @xtype zarafa.taskstore
 *
 * this will contain all records fetched from the server side code
 */
Zarafa.task.TaskStore = Ext.extend(Zarafa.core.data.ListModuleStore, {
	/**
	 * @constructor
	 * @param {Object} config configuration params that should be used to create instance of this store.
	 */
	constructor: function(config)
	{
		config = config || {};

		// Apply default settings.
		Ext.applyIf(config, {
			preferredMessageClass: 'IPM.Task',
			defaultSortInfo: {
				field: 'duedate',
				direction: 'desc'
			}
		});

		Zarafa.task.TaskStore.superclass.constructor.call(this, config);

		/**
		 * @property {Number} pendingReloadTask The delayed task id for the
		 * debounced To-Do list reload, or null if no reload is pending.
		 * @private
		 */
		this.pendingReloadTask = null;
	},

	/**
	 * Notification handler called by {@link #onNotify} when
	 * a {@link Zarafa.core.data.Notifications#objectCreated objectCreated}
	 * notification has been received.
	 *
	 * For To-Do list folders, reloads are debounced to avoid triggering
	 * expensive search folder re-evaluations on every single notification.
	 *
	 * @param {Zarafa.core.data.Notifications} action The notification action
	 * @param {Array} records The {@link Zarafa.core.data.IPFStore folder}record(s)
	 * which have been affected by the notification.
	 * @param {Object} data The data which has been received from the PHP-side
	 * @param {Number} timestamp The {@link Date#getTime timestamp} on which the notification was received
	 * @param {Boolean} success The success status
	 * @private
	 */
	onNotifyObjectcreated: function(action, records, data, timestamp, success)
	{
		var model = container.getCurrentContext().getModel();
		// Reload task grid only when selected folder is
		// To-Do list folder.
		if (model.getDefaultFolder().isTodoListFolder()) {
			// Skip if we already loaded after this notification's timestamp
			if (timestamp && this.lastExecutionTime(Zarafa.core.Actions['list']) >= timestamp) {
				return;
			}
			// Debounce: coalesce rapid notifications into a single reload
			if (this.pendingReloadTask) {
				clearTimeout(this.pendingReloadTask);
			}
			this.pendingReloadTask = (function() {
				this.pendingReloadTask = null;
				this.reload();
			}).defer(500, this);
		} else {
			Zarafa.task.TaskStore.superclass.onNotifyObjectcreated.apply(this, arguments);
		}
	},

	/**
	 * Function is use to prepare data restriction which is used to check value of properties using logical operators
	 *
	 * @param {String} property whose value should be checked.
	 * @param {Number} operator relational operator that will be used for comparison.
	 * @param {Mixed} value that should be used for checking.
	 * @returns {Array} restriction.
   */
	prepareRestriction: function (property,operator,value)
	{
		return Zarafa.core.data.RestrictionFactory.dataResProperty(property, Zarafa.core.mapi.Restrictions[operator], value);
	},

	/**
	 * Function which provides the restriction based on the given {@link Zarafa.task.data.DataModes datemode}
	 *
	 * @param {Zarafa.task.data.DataModes} datamode The datamode based on which restriction is prepared.
	 * @return {Array|false} returns restriction according to filter else false.
	 */

	getFilterRestriction: function(datamode)
	{
		var restriction = [];
		switch (datamode) {
			case Zarafa.task.data.DataModes.SEARCH:
				this.clearFilter();
				break;
			case Zarafa.task.data.DataModes.ALL:
				this.clearFilter();
				break;
			case Zarafa.task.data.DataModes.ACTIVE:
				restriction = this.prepareRestriction('complete','RELOP_EQ',false);
				break;
			case Zarafa.task.data.DataModes.NEXT_7_DAYS:
				var currentDay = new Date().clearTime();
				var nextSevenDay = currentDay.clone().add(Date.DAY, 7);
				restriction = Zarafa.core.data.RestrictionFactory.createResAnd([
					this.prepareRestriction('duedate','RELOP_GT',currentDay.getTime() / 1000),
					this.prepareRestriction('duedate','RELOP_LT',nextSevenDay.getTime() / 1000)
				]);
				break;
			case Zarafa.task.data.DataModes.OVERDUE:
				var currentDay = new Date().clearTime();
				restriction = Zarafa.core.data.RestrictionFactory.createResAnd([
					this.prepareRestriction('duedate','RELOP_LT',currentDay.getTime() / 1000),
					this.prepareRestriction('complete','RELOP_EQ',false)
				]);
				break;
			case Zarafa.task.data.DataModes.COMPLETED:
				restriction = this.prepareRestriction('complete','RELOP_EQ',true);
				break;
		}
		return restriction;
	}
});

Ext.reg('zarafa.taskstore', Zarafa.task.TaskStore);
Ext.namespace('Zarafa.task.dialogs');

/**
 * @class Zarafa.task.dialogs.TaskEditContentPanel
 * @extends Zarafa.core.ui.MessageContentPanel
 * @xtype zarafa.taskeditcontentpanel
 *
 * this class will be used to create a task edit content panel
 */
Zarafa.task.dialogs.TaskEditContentPanel = Ext.extend(Zarafa.core.ui.MessageContentPanel, {
	/**
	 * @constructor
	 * @param {Object} config Configuration structure
	 */
	constructor: function(config)
	{
		config = config || {};

		config = Ext.applyIf(config, {
			layout: 'fit',
			xtype: 'zarafa.taskeditcontentpanel',
			closeOnSave: true,
			closeOnSend: true,
			title: _('Task'),
			recordComponentPluginConfig: Ext.applyIf(config.recordComponentPluginConfig || {}, {
				allowWrite: true
			}),
			confirmClose: true,
			items:[{
				xtype: 'zarafa.taskpanel',
				tbar: {
					xtype: 'zarafa.tasktoolbar'
				}
			}]
		});

		Zarafa.task.dialogs.TaskEditContentPanel.superclass.constructor.call(this, config);
	},

	/**
	 * Create and initialize the {@link #sendValidationQueue}. This will add various
	 * validation steps which must be executed to determine if the message can be send.
	 * @protected
	 */
	createSendValidationQueue: function()
	{
		Zarafa.task.dialogs.TaskEditContentPanel.superclass.createSendValidationQueue.apply(this, arguments);
		// Add a validation step to determine sender is not assigned task
		// to him self or assigned single task to multiple assignee.
		this.sendValidationQueue.add(this.validateSenderIsNotRecipient, this);
	},

	/**
	 * Validation function for the {@link #sendValidationQueue} to check if the Message
	 * can be send to the recipients.
	 *
	 * This validates if the user is in recipients list, if yes then the warning message
	 * throw to user as user can to assigned task to yourself. Also shows the warning message if
	 * user is trying to assigned single task to multiple user.
	 *
	 * @param {Function} callback The callback to call to continue in the queue
	 * @private
	 */
	validateSenderIsNotRecipient: function (callback)
	{
		if(!this.record.isMessageClass('IPM.Task')) {
			callback(true);
			return;
		}

		var recipientStore = this.record.getRecipientStore();
		var recipients = recipientStore.getRange();
		var isSenderIsRecipient = false;
		var isDistlist = false;
		var recipientCount = 0;
		Ext.each(recipients, function (recipient) {
			if (Zarafa.core.EntryId.compareEntryIds(container.getUser().getEntryId(), recipient.get('entryid')) && !isSenderIsRecipient) {
				isSenderIsRecipient = true;
			}

			if (recipient.get('recipient_type') === Zarafa.core.mapi.RecipientType.MAPI_TO) {
				recipientCount++;
			}

			if (recipient.get('object_type') === Zarafa.core.mapi.ObjectType.MAPI_DISTLIST) {
				isDistlist = true;
				return false;
			}
		},this);

		if (isSenderIsRecipient) {
			Ext.MessageBox.show({
				title: _('Unable to send task request'),
				msg: _('You cannot send a task request to yourself.'),
				buttons: Ext.MessageBox.OK
			});
			callback(false);
		} else if (recipientCount > 1 || isDistlist) {
			Ext.MessageBox.show({
				title: _('Multiple recipients'),
				msg: _('A task request can only have one recipient.'),
				buttons: Ext.MessageBox.OK
			});
			callback(false);
		} else {
			callback(true);
		}
	},

	/**
	 * Update the components with the given record.
	 *
	 * @param {Zarafa.core.data.MAPIRecord} record The record to update in this component
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 */
	update: function(record, contentReset)
	{
		if (record.isOpened()) {
			if (record.isTaskRequest() ||
				(!contentReset && record.isModifiedSinceLastUpdate('taskhistory')
				&& record.get('taskhistory') === Zarafa.core.mapi.TaskHistory.NONE)) {
				this.closeOnSave = false;
			} else {
				this.closeOnSave = true;
			}
		}
		this.updateIconFromRecord(record);
		this.updateTitleFromRecord(record);
	},

	/**
	 * Update this panel's icon class from the record that it contains
	 * First obtains the icon class from a mapping, then calls {@link #setIcon}
	 *
	 * @param {Zarafa.core.data.MAPIRecord} record The record bound to this component
	 * @private
	 */
	updateIconFromRecord: function(record)
	{
		//TODO: create a new icon mapping for tabs
		var iconCls = Zarafa.common.ui.IconClass.getIconClass(record);
		this.setIcon(iconCls);
	},

	/**
	 * When record has been updated, title also has to be - for instance if we have the subject
	 * in the title and the subject changes
	 * Calls {@link #setTitle} this.setTitle in order to update
	 * @param {Zarafa.core.data.MAPIRecord} record The record that has been updated
	 */
	updateTitleFromRecord: function(record)
	{
		var subject = record.get('subject');
		if(!Ext.isEmpty(subject)){
			this.setTitle(subject);
		} else {
			this.setTitle(this.initialConfig.title);
		}
	},

	/**
	 * Save all changes made to the {@link #record} and send
	 * the message to the specified recipients.
	 */
	sendRecord: function()
	{
		if(!this.record.isTaskRequest()) {
			// can not send a non task request record
			return;
		}

		return Zarafa.task.dialogs.TaskEditContentPanel.superclass.sendRecord.apply(this, arguments);
	}
});

// register dialog
Ext.reg('zarafa.taskeditcontentpanel', Zarafa.task.dialogs.TaskEditContentPanel);
Ext.namespace('Zarafa.today');

/**
 * @class Zarafa.today.TodayContext
 * @extends Zarafa.core.Context
 *
 * The today context presents an overview of tasks, notes, and appointments for today. The user can customise
 * the today view by adding and removing widgets. Widgets display information such as the aforementioned
 * daily overviews, but can also be made to display other things such as the time in various places in the world,
 * the weather, or a game.
 */
Zarafa.today.TodayContext = Ext.extend(Zarafa.core.Context, {
	// Insertion points for this class
	/**
	 * @insert main.maintoolbar.view.today
	 * Insertion point for populating the main toolbar with a View button. This item is only visible
	 * when this context is active.
	 * @param {Zarafa.mail.TodayContext} context This context
	 */

	/**
 	 * @constructor
	 * @param config
	 */
	constructor: function(config)
	{
		if (container.getServerConfig().isWidgetEnabled()){
			// The tab in the top tabbar
			this.registerInsertionPoint('main.maintabbar.left', this.createMainTab, this);
		}
		Zarafa.today.TodayContext.superclass.constructor.call(this, config);
	},

	/**
	 * @return {Zarafa.taday.TodayContextModel} the today context model
	 */
	getModel: function()
	{
		if (!Ext.isDefined(this.model)) {
			this.model = new Zarafa.today.TodayContextModel();
		}
		return this.model;
	},

	/**
	 * Bid for the given {@link Zarafa.hierarchy.data.MAPIFolderRecord folder}
	 * This will bid on the {@link Zarafa.hierarchy.data.MAPIFolderRecord#isIPMSubTree SubTree} folder.
	 *
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder The folder for which the context is bidding
	 * @return {Number} 1 when the contexts supports the folder, -1 otherwise
	 */
	bid: function(folder)
	{
		if (folder.isOwnRoot() && container.getServerConfig().isWidgetEnabled()) {
			return 2;
		}

		return 0;
	},

	/**
	 * Obtain the {@link Zarafa.core.ui.widget.WidgetPanel WidgetPanel} object
	 *
	 * @return {Zarafa.core.ui.widget.WidgetPanel} The main panel which should
	 * be used within the {@link Zarafa.core.Context context}
	 */
	createContentPanel: function()
	{
		return {
			xtype: 'zarafa.widgetpanel',
			id: 'zarafa-mainpanel-contentpanel-today',
			settingsPath: 'zarafa/v1/contexts/today',
			showDefaultWidgets: ['mail', 'appointments', 'tasks'],
			context: this,
			collapsible: false,
			numColumns: container.getSettingsModel().get('zarafa/v1/contexts/today/num_columns')
		};
	},

	/**
	 * Returns the buttons for the dropdown list of the VIEW-button in the main toolbar. It will use the
	 * main.maintoolbar.view.today insertion point to allow other plugins to add their items at the end.
	 *
	 * @return {Ext.Component[]} an array of components
	 */
	getMainToolbarViewButtons: function()
	{
		var items = container.populateInsertionPoint('main.maintoolbar.view.today', this) || [];

		return items;
	},

	/**
	 * Adds a button to the top tab bar for this context.
	 * @return {Object} The button for the top tabbar
	 * @private
	 */
	createMainTab: function()
	{
		return {
			text: this.getDisplayName(),
			tabOrderIndex: 1,
			context: this.getName(),
			id: 'mainmenu-button-today'
		};
	}
});

Zarafa.onReady(function() {
	container.registerContext(new Zarafa.core.ContextMetaData({
		name: 'today',
		displayName: _('Today'),
		allowUserVisible: false,
		pluginConstructor: Zarafa.today.TodayContext
	}));
});
/*
 * #dependsFile client/zarafa/core/mapi/ObjectType.js
 * #dependsFile client/zarafa/core/mapi/MessageFlags.js
 * #dependsFile client/zarafa/core/data/Record.js
 * #dependsFile client/zarafa/core/data/RecordFactory.js
 * #dependsFile client/zarafa/core/data/IPMRecipientStore.js
 * #dependsFile client/zarafa/core/data/IPMAttachmentStore.js
 */
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.IPMRecordFields
 * Array of {@link Ext.data.Field field} configurations for the
 * {@link Zarafa.core.data.IPMRecord IPMRecord} object.
 * These fields will be available in all 'IPM' and 'REPORT.IPM' type messages.
 * @private
 */
Zarafa.core.data.IPMRecordFields = [
	{name: 'body', type: 'string'},
	{name: 'html_body', type: 'string'},
	{name: 'isHTML', type:'boolean', defaultValue: false},
	{name: 'entryid'},
	{name: 'creation_time', type:'date', dateFormat:'timestamp', defaultValue: null, sortDir: 'DESC'},
	{name: 'icon_index', type: 'int', defaultValue: -1},
	{name: 'access', type: 'number', defaultValue: Zarafa.core.mapi.Access.ACCESS_READ},
	{name: 'message_class', type: 'string'},
	{name: 'message_flags', type: 'int', defaultValue: Zarafa.core.mapi.MessageFlags.MSGFLAG_UNSENT | Zarafa.core.mapi.MessageFlags.MSGFLAG_READ},
	{name: 'read_receipt_requested', type: 'boolean', defaultValue: false},
	{name: 'parent_entryid'},
	{name: 'store_entryid'},
	{name: 'subject'},
	{name: 'object_type', type: 'int', defaultValue: Zarafa.core.mapi.ObjectType.MAPI_MESSAGE},
	{name: 'normalized_subject'},
	{name: 'last_modification_time', type:'date', dateFormat:'timestamp', defaultValue: null, sortDir: 'DESC'},
	{name: 'last_verb_execution_time', type:'date', dateFormat:'timestamp', defaultValue: null},
	{name: 'last_verb_executed', type: 'int'},
	{name: 'hasattach', type: 'boolean', defaultValue: false},
	{name: 'display_to'},
	{name: 'display_cc'},
	{name: 'display_bcc'},
	{name: 'thumbnail_photo'},
	{name: 'sent_representing_name'},
	{name: 'sent_representing_email_address'},
	{name: 'sent_representing_smtp_address'},
	{name: 'sent_representing_username'},
	{name: 'sent_representing_address_type'},
	{name: 'sent_representing_entryid'},
	{name: 'sent_representing_search_key'},
	{name: 'sent_representing_presence_status'}, // Note: this field will not be filled by the back-end
	{name: 'sender_name'},
	{name: 'sender_email_address'},
	{name: 'sender_username'},
	{name: 'sender_address_type'},
	{name: 'sender_entryid'},
	{name: 'sender_search_key'},
	{name: 'sender_presence_status'}, // Note: this field will not be filled by the back-end
	{name: 'message_size', type: 'int'},
	{name: 'categories'},
	{name: 'deleted_on', type:'date', dateFormat:'timestamp', defaultValue: null},
  {name: 'deferred_send_time', type: 'date', dateFormat: 'timestamp', defaultValue: null},
	// required when converting attachment record to ipm record for opening embedded messages
	{name: 'attach_num', defaultValue: null}
];

// Register these properties as base for the IPM message class, this will ensure that every IPM.* class
// will have these properties. We also install the MAPI_MESSAGE object type in case we receive a broken
// message which only has an object_type but doesn't have a message class.
Zarafa.core.data.RecordFactory.addFieldToMessageClass('IPM', Zarafa.core.data.IPMRecordFields);
Zarafa.core.data.RecordFactory.addFieldToMessageClass('REPORT.IPM', Zarafa.core.data.IPMRecordFields);
Zarafa.core.data.RecordFactory.addFieldToObjectType(Zarafa.core.mapi.ObjectType.MAPI_MESSAGE, Zarafa.core.data.IPMRecordFields);

Zarafa.core.data.RecordFactory.addListenerToMessageClass('IPM', 'createphantom', function(record) {
	// Phantom records must always be marked as opened (they contain the full set of data)
	record.afterOpen();
});

Zarafa.core.data.RecordFactory.addListenerToObjectType(Zarafa.core.mapi.ObjectType.MAPI_MESSAGE, 'createphantom', function(record) {
	// Phantom records must always be marked as opened (they contain the full set of data)
	record.afterOpen();
});

/**
 * @class Zarafa.core.data.IPMRecord
 * @extends Zarafa.core.data.MAPIRecord
 *
 * An extension to the {@link Zarafa.core.data.MAPIRecord Record} specific to IPM Messages.
 *
 * By default, using {@link Zarafa.core.data.IPMRecord.set set} will set a property for the given
 * {@link Zarafa.core.data.IPMRecord record}. However, additional functions have been provided
 * for the manipulation of recipients, attachments and message actions.
 */
Zarafa.core.data.IPMRecord = Ext.extend(Zarafa.core.data.MAPIRecord, {
	/**
	 * When event propagation is enabled, events about {@link Zarafa.core.data.IPMRecord record}
	 * from the {@link Zarafa.core.data.IPMStore store} will be propagated through a new event
	 * to other {@link Zarafa.core.data.IPMStore stores} by the {@link Zarafa.core.data.IPMStoreMgr IPMStoreMgr}
	 * This should be temporarily be disabled when the {@link Zarafa.core.data.IPMStore store}
	 * is handling an event from {@link Zarafa.core.data.IPMStoreMgr IPMStoreMgr} and will raise
	 * a similar event which will be used by the {@link Zarafa.core.data.IPMStoreMgr IPMStoreMgr} again
	 * (which results in circular events).
	 *
	 * This property can be changed by the function {@link #setEventPropagation} and the status
	 * can be requested using {@link #hasEventPropagation}.
	 *
	 * @property
	 * @type Boolean
	 */
	eventPropagation: true,

	/**
	 * The base array of ID properties which is copied to the {@link #idProperties}
	 * when the record is being created.
	 * @property
	 * @type Array
	 * @private
	 */
	baseIdProperties: [ 'entryid', 'store_entryid', 'parent_entryid', 'attach_num' ],

	/**
	 * Compare this {@link Zarafa.core.data.IPMRecord record} instance with another one to see
	 * if they are the same IPM Message from the server (i.e. The entryid matches).
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The IPMRecord to compare with
	 * @return {Boolean} True if the records are the same.
	 */
	equals: function(record)
	{
		// Simplest case, do we have the same object...
		if (this === record) {
			return true;
		}

		// Check if record definition is changed, then consider it as different record
		if(this.fields !== record.fields) {
			return false;
		}

		// When only one of the 2 records is a phantom, then the
		// records can impossibly be equal...
		if (this.phantom !== record.phantom) {
			return false;
		}

		// For phantom records, no entryid exists. Since both objects thus only
		// contain on the client-side and cannot be represented by 2 different
		// objects, comparison on the id-field only is sufficient.
		if (this.phantom) {
			return this.id == record.id;
		} else {
			var equal = Zarafa.core.EntryId.compareEntryIds(this.get('entryid'), record.get('entryid'));
			if(equal) {
				// compare attach_num as well if embedded message
				if(this.isSubMessage() && record.isSubMessage()) {
					equal = this.get('attach_num').equals(record.get('attach_num'));
				} else if (this.isSubMessage() || record.isSubMessage()) {
					equal = false;
				}
			}

			return equal;
		}
	},

	/**
	 * This will determine if the provided className matches the message_class
	 * on this record. This comparison is done case-insensitive. See {@link Zarafa.core.MessageClass#isClass}
	 * for further details.
	 *
	 * @param {String/Array} className The class name which must be compared to the message_class
	 * @param {Boolean} baseOnly (optional) True when only the start of the message_class needs
	 * to match with the className (So isMessageClass('IPM', true) will return true when the
	 * actual message_class is 'IPM.Note'). Defaults to false.
	 * @return True when the given className matches the message_class.
	 */
	isMessageClass: function(className, baseOnly)
	{
		return Zarafa.core.MessageClass.isClass(this.get('message_class'), className, baseOnly);
	},

	/**
	 * Utility function to get sender string from {@link Zarafa.core.data.IPMRecord IPMRecord},
	 * Function will also check if delegate is present then it will generate a different string.
	 * @return {String} string which can be shown in ui as from address.
	 */
	getSenderString: function()
	{
		var sender = '';
		var senderEntryId = this.get('sender_entryid');
		var sentRepresentingEntryId = this.get('sent_representing_entryid');

		if(!Ext.isEmpty(senderEntryId) && !Ext.isEmpty(sentRepresentingEntryId) && !Zarafa.core.EntryId.compareABEntryIds(senderEntryId, sentRepresentingEntryId)) {
			var delegate = !Ext.isEmpty(this.get('sender_name')) ? this.get('sender_name') : this.get('sender_email_address');
			var delegator = !Ext.isEmpty(this.get('sent_representing_name')) ? this.get('sent_representing_name') : this.get('sent_representing_email_address');

			// # TRANSLATORS: {0} indicates name/email address of secretary and {1} indicates boss' name/email address
			sender = String.format(_('{0} on behalf of {1}'), delegate, delegator);
		} else {
			sender = !Ext.isEmpty(this.get('sender_name')) ? this.get('sender_name') : this.get('sender_email_address');
		}

		return sender;
	},

	/**
	 * Helper function to get contents of body property of {@link Zarafa.core.data.IPMRecord IPMRecord}
	 * @param {Boolean} preferHTML True if the HTML body should be returned or not, false if the plain-text
	 * body should be returned.
	 * @return {String} The body property of {@link Zarafa.core.data.IPMRecord IPMRecord}.
	 */
	getBody: function(preferHTML)
	{
		var isHTML = this.get('isHTML');
		var body;

		if (preferHTML === false) {
			if (isHTML) {
				// plain-text requested, convert the body
				body = Zarafa.core.HTMLParser.convertHTMLToPlain(this.get('html_body'));
			} else {
				// Plain-text requested, simply return the basic body
				body = this.get('body');
			}
		} else if (preferHTML === true) {
			if (isHTML) {
				// html requested, simply return the basic body
				// add urls in inline images
				body = this.inlineImgOutlookToZarafa(this.get('html_body'));
			} else {
				// html requested, convert the body
				body = Zarafa.core.HTMLParser.convertPlainToHTML(this.get('body'));
			}
		} else {
			if (isHTML) {
				// add urls in inline images
				body = this.inlineImgOutlookToZarafa(this.get('html_body'));
			} else {
				body = this.get('body');
			}
		}

		return body;
	},

	/**
	 * Helper function to set contents of body property of {@link Zarafa.core.data.IPMRecord IPMRecord}.
	 * @param {String} body body that should be set in {@link Zarafa.core.data.IPMRecord IPMRecord}.
	 * @param {Boolean} isHTMLEditor flag to indicate that we should update html body or plain body
	 * based on type of editor used for modifications.
	 */
	setBody: function(body, isHTMLEditor)
	{
		// Only set the isHTML property when the
		// body has been changed. This prevents the
		// isHTML flag to be sent to client when the
		// body won't be sent.
		if(isHTMLEditor) {
			// convert inline image urls
			body = this.inlineImgZarafaToOutlook(body);

			if (this.get('html_body') !== body) {
				this.set('isHTML', true, true);
				this.set('html_body', body);
			}
		} else {
			// Sometimes record body contains \r\n and editor value always
			// contains \n bue to different line breaking earlier, condition
			// is satisfied and we again set the body in record which mark
			// the record dirty and because of that we get "Unsaved changes..." message box.
			var recordBody = Zarafa.core.HTMLParser.rlnl2nl(this.get('body'));

			if (recordBody !== body) {
				this.set('isHTML', false, true);
				this.set('body', body);
			}
		}
	},

	/**
	 * Function is used to convert inline images source string to proper format which can request the inline
	 * image through server side code. It searches for attach content id property and replaces it with full fledged
	 * url to request image data using download_attachment.php file. This function is called by {@link #getBody}
	 * when requesting html data from record.
	 * @param {String} body html body of the record which should be converted to properly retrieve inline image data.
	 * @return {String} modified data that can retrieve inline images when loaded in editor.
	 */
	inlineImgOutlookToZarafa: function(body)
	{
		var entryid;
		var store;

		var action_type = this.getMessageAction('action_type');
		var attachNum = this.get('attach_num');

		//FIXME: this would only work if all images are either added or from the original record, but not both
		if (this.phantom && (Zarafa.mail.data.ActionTypes.isSendOrForward(action_type) || action_type===Zarafa.mail.data.ActionTypes.EDIT_AS_NEW) ) {
			entryid = this.getMessageAction('source_entryid');
			store = this.getMessageAction('source_store_entryid');
		} else {
			entryid = this.get('entryid');
			store = this.get('store_entryid');
		}

		if (store && entryid) {
			body = Zarafa.core.HTMLParser.inlineImgOutlookToZarafa(body, store, entryid, attachNum);
		}

		return body;
	},

	/**
	 * Function is used to convert inline images source string to proper format which is used by outlook.
	 * It will search for image source containing attach content id property and will replace the whole source string
	 * with just the content id. This function is called by {@link #setBody} we want to save user changed data from
	 * editor the html body of record. This will actually reverse changes done by {@link inlineImgOutlookToZarafa}.
	 * @param {String} body html body from the editor which will be saved in the {@link Zarafa.core.data.IPMRecord IPMRecord}.
	 * @return {String} modified data that can be safely saved in html body of record which is understandable by outlook.
	 */
	inlineImgZarafaToOutlook: function(body)
	{
		return Zarafa.core.HTMLParser.inlineImgZarafaToOutlook(body);
	},

	/**
	 * Convenience method for determining if the message has been read or not.
	 * @return {Boolean} True if this item has been read.
	 */
	isRead: function()
	{
		// embedded message should never be considered as unread messages
		return this.isSubMessage() || (this.get('message_flags') & Zarafa.core.mapi.MessageFlags.MSGFLAG_READ) > 0;
	},

	/**
	 * Convenience method for determining if the message has been sent or not.
	 * @return {Boolean} True if this item has NOT been sent.
	 */
	isUnsent: function()
	{
		return (this.get('message_flags') & Zarafa.core.mapi.MessageFlags.MSGFLAG_UNSENT) > 0;
	},

	/**
	 * Convenience method for determining if the message is a sub message of another message.
	 * @return {Boolean} True if this message is a sub message.
	 */
	isSubMessage: function()
	{
		return !Ext.isEmpty(this.get('attach_num'));
	},

	/**
	 * Convenience method for determining if the message needs a Read Receipt to be send when marking
	 * the message as read.
	 * @return {Boolean} True if this item needs a read receipt to be send.
	 */
	needsReadReceipt: function()
	{
		return (this.get('message_flags') & Zarafa.core.mapi.MessageFlags.MSGFLAG_RN_PENDING) === Zarafa.core.mapi.MessageFlags.MSGFLAG_RN_PENDING;
	},

	/**
	 * Convenience method for determining if the message needs a Non Read Receipt to be send when
	 * deleting the message.
	 * @return {Boolean} True if this item needs a non read receipt to be send.
	 */
	needsNonReadReceipt: function()
	{
		return (this.get('message_flags') & Zarafa.core.mapi.MessageFlags.MSGFLAG_NRN_PENDING) === Zarafa.core.mapi.MessageFlags.MSGFLAG_NRN_PENDING;
	},

	/**
	 * Convenience method for setting the read flag
	 *
	 * @param {Boolean} read True to mark the record as read.
	 */
	setReadFlags: function(read)
	{
		var flags = this.get('message_flags');

		if (read === false) {
			this.set('message_flags', flags & ~Zarafa.core.mapi.MessageFlags.MSGFLAG_READ);
		} else {
			this.set('message_flags', flags | Zarafa.core.mapi.MessageFlags.MSGFLAG_READ);
		}
	},

	/**
	 * Returns whether the IPMRecord supports the use of recipients or not (See {@link #supportsSubStore}).
	 * @return {Boolean} True if recipients are supported.
	 */
	supportsRecipients: function()
	{
		return this.supportsSubStore('recipients');
	},

	/**
	 * Creates a Recipients store for the {@link Zarafa.core.data.IPMRecord IPMRecord} (See {@link #createSubStore}).
	 * @return {Zarafa.core.data.IPMRecipientStore} The new Recipient store.
	 */
	createRecipientStore: function()
	{
		return this.createSubStore('recipients');
	},

	/**
	 * Set the Recipient store for the {@link Zarafa.core.data.IPMRecord record} (See {@link #setSubStore}).
	 * @param {Zarafa.core.data.IPMRecipientStore} recipientStore The Recipient store.
	 * @return {Zarafa.core.data.IPMRecipientStore} The Recipient store.
	 */
	setRecipientStore: function(recipientStore)
	{
		return this.setSubStore('recipients', recipientStore);
	},

	/**
	 * Get the Recipients store for the {@link Zarafa.core.data.IPMRecord IPMRecord} (See {@link #getSubStore}).
	 * @return {Zarafa.core.data.IPMRecipientStore} The Recipient store.
	 */
	getRecipientStore: function()
	{
		return this.getSubStore('recipients');
	},

	/**
	 * Returns whether the IPMRecord supports the use of recipients or not (See {@link #supportsSubStore}).
	 * @return {Boolean} True if recipients are supported.
	 */
	supportsAttachments: function()
	{
		return this.supportsSubStore('attachments');
	},

	/**
	 * Creates a Folder store for the {@link Zarafa.core.data.IPMRecord IPMRecord} (See {@link #createSubStore}).
	 * @return {Zarafa.core.data.IPMAttachmentStore} The new Attachment store.
	 */
	createAttachmentStore: function()
	{
		return this.createSubStore('attachments');
	},

	/**
	 * Set the Attachment store for the {@link Zarafa.core.data.IPMRecord record} (See {@link #setSubStore}).
	 * @param {Zarafa.core.data.IPMAttachmentStore} attachmentStore The Attachmentstore.
	 * @return {Zarafa.core.data.IPMAttachmentStore} The Attachment store.
	 */
	setAttachmentStore: function(attachmentStore)
	{
		return this.setSubStore('attachments', attachmentStore);
	},

	/**
	 * Get the Attachments store for the {@link Zarafa.core.data.IPMRecord IPMRecord} (See {@link #getSubStore}).
	 * @return {Zarafa.core.data.IPMAttachmentStore} The Attachment store.
	 */
	getAttachmentStore: function()
	{
		return this.getSubStore('attachments');
	},

	/**
	 * Helper function to return names of all attachments of message.
	 * @return {String} comma separated attachment names
	 */
	getAttachmentNames: function()
	{
		if (!this.get('hasattach') || this.get('hide_attachments')) {
			return '';
		}

		var store = this.getSubStore('attachments');
		var names = [];

		store.each(function(attach) {
			if(!attach.get('hidden')) {
				names.push(attach.get('name'));
			}
		}, this);

		return names.join('; ');
	},

	/**
	 * Function can be used to check if the current record is faulty or not,
	 * this will check if message_class property in the {@link Zarafa.core.data.IPMRecord IPMRecord}
	 * is empty/'IPM'/'MEMO'/'REPORT' or message_class which not contains 'IPM'
	 * as prefix or suffix followed by dot(.), so we will not be able to display the message
	 * properly.
	 * @return {Boolean} true if message is faulty else false.
	 */
	isFaultyMessage: function()
	{
		var messageClass = this.get('message_class');

		if(Ext.isEmpty(messageClass)) {
			return true;
		}

		// @TODO handle other non supported types
		var faultyMessages = ['IPM', 'MEMO', 'REPORT','IPM.MAIL'];
		return this.isMessageClass(faultyMessages, false);
	},

	/**
	 * Function can be used to repair a faulty message which can not be displayed properly
	 * in webapp, it will change the message class to IPM.Note so all the fields related to
	 * mail record will be populated properly.
	 */
	fixFaultyMessage: function()
	{
		if(!this.isFaultyMessage()) {
			// don't do anything if this is a normal message
			return;
		}

		this.set('message_class', 'IPM.Note');

		// send a request to server to save changes
		this.save();
	},

	/**
	 * Builds URL to download different messages as its respective file format.
	 * Email message will be downloaded as RFC822-formatted stream with eml extension.
	 * It uses {@link Zarafa.core.data.IPMRecord IPMRecord} to get store and message entryids.
	 * But, in the case of downloading all those eml messages in a ZIP, we just need to
	 * pass 'AllAsZip' argument as true in url.
	 * @param {Boolean} allAsZip (optional) True to downloading all the attachments as ZIP
	 * @return {String} URL for downloading message as file.
	 */
	getDownloadMessageUrl: function(allAsZip)
	{
		var url = container.getBaseURL();
		url = Ext.urlAppend(url, 'load=download_message');
		url = Ext.urlAppend(url, 'storeid=' + this.get('store_entryid'));

		if(!allAsZip){
			url = Ext.urlAppend(url, 'entryid=' + this.get('entryid'));
		} else {
			url = Ext.urlAppend(url, 'AllAsZip=true');
		}
		return url;
	},

	/**
	 * Function checks whether record has any visible attachments or not,
	 * Attachment like inline images and smime are invisible attachments
	 * @return {Boolean} True Function will return true if any attachment
	 * is visible; false otherwise
	 */
	hasVisibleAttachments: function()
	{
		var visible = false;
		var attachments = this.getAttachmentStore().getRange();
		Ext.each(attachments, function(attachment){
			if(attachment.get('hidden') === false){
				visible = true;
			}
		}, this);
		return visible;
	},

	/**
	 * Checks if the passed record is a header record, i.e. a fake record that was sent to
	 * show as header of a conversation.
	 *
	 * @return {Boolean} True if the passed record is a conversation header record, false otherwise
	 */
	isConversationHeaderRecord: function()
	{
		if (!Ext.isDefined(this.get('conversation_count')) || !Ext.isDefined(this.get('depth'))) {
			return false;
		}

		return this.get('conversation_count') > 1 && this.get('depth') === 0;
	},

	/**
	 * Checks if the passed record is part of a conversation.
	 *
	 * @return {Boolean} True if the passed record is part of a conversation, false otherwise
	 */
	isConversationRecord: function() {
		if (!Ext.isDefined(this.get('conversation_count')) || !Ext.isDefined(this.get('depth'))) {
			return false;
		}

		return this.get('depth') > 0;
	},

	/**
	 * Checks if the passed record is not part of a conversation and not header record.
	 *
	 * @return {Boolean} True if the passed record is normal record, false otherwise
	 */
	isNormalRecord: function() {
		if (!Ext.isDefined(this.get('conversation_count')) || !Ext.isDefined(this.get('depth'))) {
			return false;
		}

		return this.get('conversation_count') == 0 && this.get('depth') === 0;
	},

	/**
	 * Checks if record is private record or not.
	 * @returns {Boolean} True if the record is private item else false.
	 */
	isPrivate: function()
	{
		return Ext.isDefined(this.get('private')) ? this.get('private') : false;
	},

	/**
	 * Checks if the record is in deleted items or the folder containing the record
	 * is in deleted items
	 * @returns {Boolean} True if the record is in deleted items else false.
	 */
	isInDeletedItems: function() {
		var deletedItems = container.getHierarchyStore().getDefaultFolder('wastebasket');
		var parent_entryid = this.get('parent_entryid');
		if (deletedItems && Zarafa.core.EntryId.compareEntryIds(deletedItems.get('entryid'), parent_entryid)) {
			return true;
		}
		var folder = container.getHierarchyStore().getFolder(parent_entryid);
		if (folder.isInDeletedItems()) {
			return true;
		}
		return false;
	},

	/**
	 * Checks if the record is in the outbox and scheduled to send.
	 *
	 * @returns {Boolean}
	 */
	isScheduled: function() {
		var outbox = container.getHierarchyStore().getDefaultFolder('outbox');
		return !Ext.isEmpty(this.get('deferred_send_time')) && outbox && Zarafa.core.EntryId.compareEntryIds(outbox.get('entryid'), this.get('parent_entryid'));
	}
});

Zarafa.core.data.RecordFactory.setBaseClassToMessageClass('REPORT.IPM', Zarafa.core.data.IPMRecord);
Zarafa.core.data.RecordFactory.setSubStoreToMessageClass('REPORT.IPM', 'recipients', Zarafa.core.data.IPMRecipientStore);
Zarafa.core.data.RecordFactory.setSubStoreToMessageClass('REPORT.IPM', 'attachments', Zarafa.core.data.IPMAttachmentStore);

Zarafa.core.data.RecordFactory.setBaseClassToMessageClass('IPM', Zarafa.core.data.IPMRecord);
Zarafa.core.data.RecordFactory.setSubStoreToMessageClass('IPM', 'recipients', Zarafa.core.data.IPMRecipientStore);
Zarafa.core.data.RecordFactory.setSubStoreToMessageClass('IPM', 'attachments', Zarafa.core.data.IPMAttachmentStore);

Zarafa.core.data.RecordFactory.setBaseClassToObjectType(Zarafa.core.mapi.ObjectType.MAPI_MESSAGE, Zarafa.core.data.IPMRecord);
Ext.namespace('Zarafa.addressbook');

/**
 * @class Zarafa.addressbook.AddressBookSubStore
 * @extends Zarafa.addressbook.AddressBookStore
 *
 * Special {@link Zarafa.addressbook.AddressBookStore} which can act as a substore
 * of other {@link Zarafa.addressbook.AddressBookRecord}.
 */
Zarafa.addressbook.AddressBookSubStore = Ext.extend(Zarafa.addressbook.AddressBookStore, {
	/**
	 * The {@link Zarafa.core.data.MAPIRecord MAPIRecord} that is the parent of this store.
	 * @property
	 * @type Zarafa.core.data.MAPIRecord
	 */
	parentRecord: null,

	constructor: function(config)
	{
		config = config || {};

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

		Ext.applyIf(config, {
			// provide a default reader
			reader: new Zarafa.core.data.JsonReader({
				root: 'item'
			}, recordType)
		});

		Zarafa.addressbook.AddressBookSubStore.superclass.constructor.call(this, config);
	},

	/**
	 * 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;
	}
});
/*
 * #dependsFile client/zarafa/core/mapi/ObjectType.js
 * #dependsFile client/zarafa/core/data/RecordFactory.js
 */
Ext.namespace('Zarafa.common.favorites.data');

/**
 * @class Zarafa.common.favorites.data.FavoritesFolderRecord
 * @extends Zarafa.hierarchy.data.MAPIFolderRecord
 */
Zarafa.common.favorites.data.FavoritesFolderRecord = Ext.extend(Zarafa.hierarchy.data.MAPIFolderRecord, {

	/**
	 * Returns all child folders of given folder.
	 *
	 * @return {Array} array of child {@link Zarafa.hierarchy.data.MAPIFolderRecord folders}
	 */
	getChildren: function()
	{
		var rs = Zarafa.common.favorites.data.FavoritesFolderRecord.superclass.getChildren.apply(this, arguments);

		if (this.isFavoritesRootFolder()) {
			var favoritesStore = this.getMAPIStore().getFavoritesStore();
			favoritesStore.each(function(record) {
				if (record.isFavoritesFolder()) {
					rs.push(record);
				}
			}, this);
		}

		return rs;
	},

	/**
	 * Function is used to get original folder from favorites folder is created.
	 * @return {Zarafa.hierarchy.data.MAPIFolderRecord} return {Zarafa.hierarchy.data.MAPIFolderRecord folder}.
	 */
	getOriginalRecordFromFavoritesRecord: function()
	{
		var mapiStore = container.getHierarchyStore().getById(this.get('store_entryid'));
		return mapiStore.getFolderStore().getById(this.get("entryid"));
	}
});

Zarafa.core.data.RecordFactory.setBaseClassToObjectType(Zarafa.core.mapi.ObjectType.MAPI_FOLDER, Zarafa.common.favorites.data.FavoritesFolderRecord);Ext.namespace('Zarafa.common.freebusy.ui');

/**
 * @class Zarafa.common.freebusy.ui.UserListView
 * @extends Zarafa.common.recipientfield.ui.RecipientList
 * @xtype zarafa.freebusyuserlistview
 */
Zarafa.common.freebusy.ui.UserListView = Ext.extend(Zarafa.common.recipientfield.ui.RecipientList, {

	/**
	 * @cfg {Zarafa.common.freebusy.data.FreebusyModel} model
	 * The model that keeps track of the userStore, dates, etc.
	 */
	model: null,

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

		Ext.applyIf(config, {
			boxType: 'zarafa.userlistbox',
			wrapCls: 'x-form-text x-zarafa-boxfield x-freebusy-userlist-container',
			border: true
		});

		Zarafa.common.freebusy.ui.UserListView.superclass.constructor.call(this, config);

		if (this.model) {
			this.setRecipientStore(this.model.getUserStore());
			this.mon(this.model, 'userstorechange', this.onUserStoreChange, this);
		}
	},

	/**
	 * Event handler which is fired when the {@link #model} fires the
	 * {@link Zarafa.common.freebusy.data.FreebusyModel#userstorechange userstorechange} event. This
	 * will update the {@link #boxStore} with the new userstore.
	 * @param {Zarafa.core.data.RecipientStore} newStore The new userstore
	 * @private
	 */
	onUserStoreChange: function(newStore)
	{
		this.setRecipientStore(newStore);
	},

	/**
	 * Called to filter out records before they are added to this field. Can be overwritten to
	 * implement such a filter. By default it will allow all records.
	 * @param {Zarafa.core.data.IPMRecipientStore} store RecipientStore
	 * @param {Zarafa.core.data.IPMRecipientRecord[]} records The records to be filtered
	 * @return {Zarafa.core.data.IPMRecipientRecord[]} Filtered records
	 */
	filterRecords: function(store, records)
	{
		var ret = [];

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

			if (!Ext.isDefined(this.filterRecipientType) || record.get('recipient_type') === this.filterRecipientType) {
				ret.push(record);
			}
		}

		return ret;
	},

	/**
	 * Callback function from {@link Zarafa.common.ui.Box} which indicates that
	 * the box is being removed by the user. This will fire the {@link #boxremove}
	 * event.
	 * @param {Zarafa.common.ui.Box} box The box which called this function
	 */
	doBoxRemove: function(box)
	{
		if (!box.record.isMeetingOrganizer()) {
			Zarafa.common.freebusy.ui.UserListView.superclass.doBoxRemove.apply(this, arguments);
		}
	},

	/**
	 * Event handler when the contextmenu is requested for a Box.
	 * @param {Zarafa.common.recipientfield.ui.RecipientField} field This field to which the box belongs
	 * @param {Zarafa.common.recipientfield.ui.RecipientBox} box The box for which the contextmenu is requested
	 * @param {Zarafa.core.data.IPMRecipientRecord} record The record which is attached to the box
	 * @private
	 */
	onBoxContextMenu: function(field, box, record)
	{
		Zarafa.core.data.UIFactory.openContextMenu(Zarafa.core.data.SharedComponentType['common.contextmenu.freebusy'], record, { position: box.getEl().getXY(), editable: box.editable});
	}
});

Ext.reg('zarafa.freebusyuserlistview', Zarafa.common.freebusy.ui.UserListView);
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.MessageRecordFields
 * Array of {@link Ext.data.Field field} configurations for the
 * {@link Zarafa.core.data.MAPIRecord MAPIRecord} object which is
 * used as Message (which is sendable/receivable)
 * @private
 */
Zarafa.core.data.MessageRecordFields = [
	{name: 'received_by_name'},
	{name: 'received_by_email_address'},
	{name: 'received_by_username'},
	{name: 'received_by_address_type'},
	{name: 'received_by_entryid'},
	{name: 'received_by_search_key'},
	{name: 'received_by_presence_status'}, // Note: this field will not be filled by the back-end
	{name: 'received_representing_name'},
	{name: 'received_representing_email_address'},
	{name: 'received_representing_address_type'},
	{name: 'received_representing_entryid'},
	{name: 'received_representing_search_key'},
	{name: 'delegated_by_rule', type: 'boolean', defaultValue: false},
	{name: 'message_delivery_time', type:'date', dateFormat:'timestamp', defaultValue: null, sortDir: 'DESC'},
	{name: 'client_submit_time', type:'date', dateFormat:'timestamp', defaultValue: null, sortDir: 'DESC'},
	{name: 'transport_message_headers'},
	{name: 'hide_attachments', type: 'boolean', defaultValue: false},
];

/**
 * @class Zarafa.core.data.MessageRecord
 * @extends Zarafa.core.data.IPMRecord
 *
 * An extension to the {@link Zarafa.core.data.IPMRecord IPMRecord} specific to records which are
 * sendable / receivable.
 */
Zarafa.core.data.MessageRecord = Ext.extend(Zarafa.core.data.IPMRecord, {
	/**
	 * Flag will be used to indicate {@link Zarafa.core.data.MessageRecord MessageRecord} contains external content
	 * in the body property or not. Flag is used here because every time we load the same mail then we don't have to
	 * run through {@link Zarafa.core.HTMLParser HTMLParser} to find out if it contains external content or not
	 * as checking whole body consumes lots of resources so we check only once and store the value for further uses.
	 * @property
	 * @type Boolean
	 */
	externalContent: null,

	/**
	 * Cached sanitized HTML body. This avoids running DOMPurify on the
	 * same message body multiple times when a record is viewed repeatedly.
	 * @property
	 * @type String
	 */
	sanitizedHTMLBody: null,

	/**
	 * Function will check if {@link Zarafa.core.data.IPMRecord IPMRecord} contains external content
	 * in the body property.
	 * @param {String} body (optional) contents of body property of {@link Zarafa.core.data.IPMRecord IPMRecord}.
	 * @return {Boolean} true if {@link Zarafa.core.data.IPMRecord IPMRecord} contains external content else false.
	 */
	hasExternalContent: function(body)
	{
		var hasBodyArgument = Ext.isDefined(body);
		body = hasBodyArgument ? body : this.getBody();

		if (Ext.isEmpty(body)) {
			this.externalContent = false;
			return this.externalContent;
		}

		// Re-evaluate whenever a specific body was provided or message body fields changed.
		// This avoids races where isHTML is temporarily stale while html_body is already available.
		if(hasBodyArgument || !Ext.isBoolean(this.externalContent) || this.isModifiedSinceLastUpdate('html_body') || this.isModifiedSinceLastUpdate('body')) {
			this.externalContent = Zarafa.core.HTMLParser.hasExternalContent(body);
		}

		return this.externalContent;
	},

	/**
	 * Helper function to get contents of body property of {@link Zarafa.core.data.IPMRecord IPMRecord}
	 * it will also check {@link Zarafa.core.Settings Settings} if it needs to remove external content and return
	 * filtered content.
	 * @param {Boolean} preferHTML True if the HTML body should be returned or not, false if the plain-text
	 * body should be returned.
	 * @return {String} filtered contents of body property of {@link Zarafa.core.data.IPMRecord IPMRecord}.
	 */
	getBody: function(preferHTML)
	{
		var isHTML = this.get('isHTML');
		var actualBody = Zarafa.core.data.MessageRecord.superclass.getBody.call(this, preferHTML);

		// If plain-text is requested, or this message is in plain-text, then we don't
		// need to block the external content.
		if (isHTML === true && preferHTML === true && !Ext.isEmpty(actualBody)) {
			// if record is not sent yet then it is a new mail or a draft,
			// so we don't need to block the external content while composing mail.
			if(this.isUnsent() || !this.isExternalContentBlocked(actualBody)) {
				return actualBody;
			}
			return Zarafa.core.HTMLParser.blockExternalContent(actualBody);
		} else {
			return actualBody;
		}
	},

	/**
	 * Return the sanitized HTML body. The result is cached to prevent
	 * expensive sanitization on subsequent requests for the same record.
	 *
	 * @return {String} sanitized HTML body without the DOCTYPE prefix.
	 */
	getSanitizedHtmlBody: function()
	{
		if (!Ext.isEmpty(this.sanitizedHTMLBody) && !this.isModifiedSinceLastUpdate('html_body')) {
			return this.sanitizedHTMLBody;
		}

		var body = this.getBody(true);
		if (container.getServerConfig().getDOMPurifyEnabled()) {
			body = DOMPurify.sanitize(body);
		}
		this.sanitizedHTMLBody = this.cleanupOutlookStyles(body);

		return this.sanitizedHTMLBody;
	},

	/**
	 * Clear the cached sanitized HTML body so memory can be reclaimed when the record leaves the prefetch window.
	 */
	clearSanitizedHtmlBody: function()
	{
		this.sanitizedHTMLBody = null;
	},

	/**
	 * Removes some weird styling that Outlook adds to html mails.
	 *
	 * @param {String} body The text that should be cleaned up
	 * @return {String} The cleaned up text
	 */
	cleanupOutlookStyles: function(body) {
		// clean up some weird Outlook styling
		const el = document.createElement('div');
		el.innerHTML = body;
		const paragraphs = el.querySelectorAll('p');
		for (let i=0; i<paragraphs.length; i++) {
			const p = Ext.fly(paragraphs[i]);

			// First check for MsoListParagraph and MsoListNumber elements created by Outlook
			if (p.hasClass('MsoListParagraph') || p.hasClass('MsoListNumber')) {

				// Outlook uses negative indents for lists.
				// grommunio Web displays the CSS correctly, but results in cut of text for users.
				// If the paragraph has a negative text-indent, we replace it by a positive text-indent.
				// If there is no negative text-indent, we set it to 0.
				// We exclude items that have 'margin-left' and negative text-indent,
				// as those items are identified with multi-indent items
				// and we can still use that CSS.
				// Ref KW-3437
				if (Ext.isEmpty(p.getStyle('margin-left'))) {
					var textIndent = p.getStyle('text-indent').replace('-', '');
					var newTextIndent = !Ext.isEmpty(textIndent) ? textIndent : 0;

					p.setStyle({
						'text-indent': newTextIndent
					});
				}

				// Remove generic browser margins from paragraphs,
				// but leave 'margin-left' as is.
				p.setStyle({
					'margin-right': 0,
					'margin-top': 0,
					'margin-bottom': 0
				});
			}

			// Remove generic browser margins from paragraphs, exclude 'MsoListParagraph' and 'MsoListNumber'
			if (!p.getStyle('margin') && !p.hasClass('MsoListParagraph') && !p.hasClass('MsoListNumber')) {
				p.setStyle({
					margin: 0
				});
			}

			// Try to find and remove the 'non-breaking spaces' that Outlook
			// adds after the numbers and bullets of lists
			var all = paragraphs[i].querySelectorAll('*');
			for (let j=0; j<all.length; j++) {
				// Prevent unnecessary innerHtml over writing when node has child nodes.
				if (all[j].innerText && all[j].innerText.trim() === '' && !all[j].hasChildNodes()) {
					all[j].innerHTML = '&nbsp;';
				}

				if ((/^\d+\.(&nbsp;)+$/).test(all[j].innerHTML)) {
					all[j].innerHTML = all[j].innerHTML.replace(/(&nbsp;)+/, '&nbsp;');
				}
			}

			var font = paragraphs[i].querySelector('font[face="Symbol"]');
			if (font) {
				font = font.querySelectorAll('font');
				for (let j=0; j<font.length; j++) {
					if (font[j].innerText.trim() === '') {
						font[j].innerHTML = '&nbsp;';
					}
				}
			}
		}

		// Remove strange margin on tables
		const tables = el.querySelectorAll('table.MsoNormalTable');
		for (let i=0; i<tables.length; i++) {
			const t = Ext.fly(tables[i]);

			if (parseFloat(t.getStyle('margin-left')) < 0) {
				t.setStyle({
					'margin-left': null
				});
			}
		}

		return el.innerHTML;
	},

	/**
	 * Function is used to convert a mail record to task record.
	 * @param {Zarafa.core.IPMFolder} folder The target folder in which the new record must be
	 * created.
	 * @return {Zarafa.core.data.IPMRecord} record The newly created task.
	 */
	convertToTask: function(folder)
	{
		return this.convertRecord(folder, 'IPM.Task');
	},

	/**
	* Convert a mail record to an appointment record by using the mail's
	* @param {Zarafa.core.IPMFolder} folder The target folder in which the new record must be
	* created.
	* @return {Zarafa.core.data.IPMRecord} record The newly created appointment.
	*/
	convertToAppointment: function(folder)
	{
		return this.convertRecord(folder, 'IPM.Appointment');
	},

	/**
	 * Convert a mail record to a record with the provided messageClass
	 * @param {Zarafa.core.IPMFolder} folder The target folder in which the new record must be
	 * created.
	 * @param {String} messageClass the messageClass of the new item.
	 * @return {Zarafa.core.data.IPMRecord} record The newly created appointment.
	 * @private
	 */
	convertRecord: function(folder, messageClass)
	{
		var defaultStore = folder.getMAPIStore();
		var isHTML = this.get('isHTML');

		var data = {
			store_entryid: folder.get('store_entryid'),
			parent_entryid: folder.get('entryid'),
			subject: this.get('subject'),
			importance: this.get('importance'),
			categories: this.get('categories'),
			isHTML: isHTML,
			owner: defaultStore.isPublicStore() ? container.getUser().getFullName() : defaultStore.get('mailbox_owner_name')
		};

		if (isHTML) {
			data.html_body = this.getBody(true);
		} else {
			data.body = this.getBody(false);
		}

		var newRecord = Zarafa.core.data.RecordFactory.createRecordObjectByMessageClass(messageClass, data);

		// Set icon based on messageClass
		newRecord.set('icon_index', Zarafa.core.mapi.IconIndex[Zarafa.common.ui.IconClass.getIconClassFromMessageClass(newRecord)]);

		/**
		 * By copying the reference to the original mail,
		 * the server is able to add attachments in to the appointment.
		 */
		newRecord.addMessageAction('source_entryid', this.get('entryid'));
		newRecord.addMessageAction('source_store_entryid', this.get('store_entryid'));

		// Initialize the appointmentRecord with attachments
		var store = newRecord.getAttachmentStore();
		var origStore = this.getAttachmentStore();
		origStore.each(function (attach) {
			store.add(attach.copy());
		}, this);

		return newRecord;
	},

	/**
	 * Function will check if the {@link Zarafa.core.data.IPMRecord IPMRecord} contains any external content
	 * in body part and if we should show it or hide it based on {@link Zarafa.core.Settings Settings}.
	 * @param {String} body (optional) contents of body property of {@link Zarafa.core.data.IPMRecord IPMRecord}.
	 * @return {String} filtered contents of body property of {@link Zarafa.core.data.IPMRecord IPMRecord}.
	 */
	isExternalContentBlocked: function(body)
	{
		body = Ext.isDefined(body) ? body : this.getBody();

		if(Ext.isEmpty(body)) {
			// no point of continuing with empty body
			return false;
		}

		if(!this.shouldBlockExternalContent()) {
			return false;
		}

		return this.hasExternalContent(body);
	},

	/**
	 * Determine if external resources should be blocked for this message,
	 * regardless of whether external URLs were already detected in the body.
	 * @return {Boolean} true when policy requires blocking external content.
	 */
	shouldBlockExternalContent: function()
	{
		// check settings
		if(!container.getSettingsModel().get('zarafa/v1/contexts/mail/block_external_content')) {
			return false;
		}

		var blockExternalContent = true;
		var ignoreChecks = false;
		var senderSMTPAddress = (this.get('sent_representing_email_address') || this.get('sender_email_address') || '').toLowerCase();
		var safeSenders = container.getSettingsModel().get('zarafa/v1/contexts/mail/safe_senders_list', true);
		safeSenders = Ext.isArray(safeSenders) ? safeSenders.map(function(s){ return String(s).toLowerCase(); }) : [];

		// if block_status property is set correctly then ignore all settings and show external content
		if(this.checkBlockStatus()) {
			blockExternalContent = false;
			ignoreChecks = true;
		}

		// first check for perfect match
		if(!ignoreChecks) {
			// safe sender list will have higher priority then blocked sender list
			if(safeSenders.indexOf(senderSMTPAddress) != -1) {
				blockExternalContent = false;
				ignoreChecks = true;
			}
		}

		// now check for partial matches
		if(!ignoreChecks) {
			// safe sender list will have higher priority then blocked sender list
			if(Zarafa.core.Util.inArray(safeSenders, senderSMTPAddress, true, true)) {
				blockExternalContent = false;
			}
		}

		return blockExternalContent;
	},

	/**
	 * Function will check block_status property value and compare it with generated value from
	 * message_delivery_time property value and if both matches then we can say that external content
	 * should be shown.
	 * @return {Boolean} returns true if external content should be blocked else false
	 */
	checkBlockStatus: function()
	{
		if (this.senderIsUser()) {
			return true;
		}

		if (!this.get('block_status') || !Ext.isDate(this.get('message_delivery_time'))) {
			return false;
		}

		return this.get('block_status') == this.calculateBlockStatus();
	},

	/**
	 * Function will calculate value of block_status property based on message_delivery_time property value.
	 * Formula for calculation of block status value can be checked at
	 * http://msdn.microsoft.com/en-us/library/ee219242(v=EXCHG.80).aspx.
	 * @return {Number} calculated value of block status property.
	 */
	calculateBlockStatus: function()
	{
		if(!Ext.isDate(this.get('message_delivery_time'))) {
			return 0;
		}

		// generate block status value from message_delivery_time property
		// no of days between 30th december 1899 and 1st jan 1970 = 2209161600 / 86400 = 25569
		var days = 25569;

		// convert message_delivery_time property to number of days from 1st jan 1970
		// 86400 = no of seconds in a day, 1000 is used to convert timestamp from milliseconds to seconds
		days += (this.get('message_delivery_time').getTime() / (86400 * 1000));

		var result = ((days - Math.floor(days)) * 100000000) + 3;
		result = Math.floor(result);

		return result;
	},

	/**
	 * Function is used to check if the sender and receiver in the message is same or different
	 * first it checks for entryids of sender and receiver and if no entryids are present then it checks
	 * on smtp/email address of sender and receiver.
	 * @FIXME when sentItems folder is selected, properties 'received_by_entryid' and 'received_by_email_address' are not set.
	 * @return {Boolean} true if sender and receiver is same user else false.
	 */
	senderIsReceiver: function()
	{
		var senderEntryId = this.get('sent_representing_entryid') || this.get('sender_entryid');
		var receiverEntryId = this.get('received_by_entryid');

		if(!Ext.isEmpty(senderEntryId) && !Ext.isEmpty(receiverEntryId)) {
			// @FIXME tweak EntryId object to handle addressbook entryids also
			return Zarafa.core.EntryId.compareABEntryIds(senderEntryId, receiverEntryId);
		}

		// if no entryids are present then check for smtp address
		var senderAddress = this.get('sent_representing_email_address') || this.get('sender_email_address');
		var receiverAddress = this.get('received_by_email_address');

		if(!Ext.isEmpty(senderAddress) && !Ext.isEmpty(receiverAddress)) {
			return senderAddress === receiverAddress;
		}

		return false;
	},

	/**
	 * Function is used to check if the sender in the message and user logged-in is same or different.
	 * @return {Boolean} true if sender and user logged-in is same user else false.
	 */
	senderIsUser: function()
	{
		var senderEntryId = this.get('sent_representing_entryid') || this.get('sender_entryid');
		var userEntryId = container.getUser().getEntryId();

		if(!Ext.isEmpty(senderEntryId) && !Ext.isEmpty(userEntryId)) {
			return Zarafa.core.EntryId.compareABEntryIds(senderEntryId, userEntryId);
		}

		return false;
	},

	/**
	 * Function is used to check if the sender in the message and user message sender is same or different.
	 * @return {Boolean} true if sender and user logged-in is same user else false.
	 */
	senderIsStoreOwner: function()
	{
		var senderEntryId = this.get('sent_representing_entryid') || this.get('sender_entryid');

		var storeOwner = container.getHierarchyStore().getById(this.get('store_entryid'));
		if(storeOwner) {
			var storeOwnerEntryId = storeOwner.get('mailbox_owner_entryid');

			if(!Ext.isEmpty(senderEntryId) && !Ext.isEmpty(storeOwnerEntryId)) {
				return Zarafa.core.EntryId.compareABEntryIds(senderEntryId, storeOwnerEntryId);
			}
		}

		return false;
	},

	/**
	 * Function is used to check if the sender in the message and user logged-in is same or different.
	 * @return {Boolean} true if store owner and user logged-in is same user else false.
	 */
	userIsStoreOwner: function()
	{
		var userEntryId = container.getUser().getEntryId();
		var storeRecord = container.getHierarchyStore().getById(this.get('store_entryid'));

		if(storeRecord) {
			var storeOwnerEntryId = storeRecord.get('mailbox_owner_entryid');

			if(!Ext.isEmpty(userEntryId) && !Ext.isEmpty(storeOwnerEntryId)) {
				return Zarafa.core.EntryId.compareABEntryIds(userEntryId, storeOwnerEntryId);
			}
		}

		return false;
	},

	/**
	 * Function sets delegator information on the record.
	 * Function checks whether message record is in logged-in user's store or other store,
	 * if it is in other's store then it sent sent_representing_* properties.
	 * @param {Ext.data.Record} delegatorStore The delegator user store's record which we are looking for
	 * @param {Boolean} force forcefully save the changes to server even if its not changed
	 */
	setDelegatorInfo: function(delegatorStore, force)
	{
		if(delegatorStore) {
			force = force || false;

			// user_name is the store owner's account name (their primary email
			// address); mailbox_owner_name is only a display name.
			var ownerAddress = delegatorStore.get('user_name') || '';

			this.set('sent_representing_name', delegatorStore.get('mailbox_owner_name'), force);
			this.set('sent_representing_email_address', ownerAddress, force);
			this.set('sent_representing_address_type', 'SMTP', force);
			this.set('sent_representing_entryid', delegatorStore.get('mailbox_owner_entryid'), force);
			if (Zarafa.core.Util.validateEmailAddress(ownerAddress)) {
				this.set('sent_representing_smtp_address', ownerAddress, force);
			}
		}
	},

	/**
	 * Convert data from the record to an {@link Zarafa.core.data.IPMRecipientRecord}
	 * Invoke {@link Zarafa.core.data.RecordFactory#createRecordObjectByCustomType} with arguments {@link Zarafa.core.data.RecordCustomObjectType.ZARAFA_RECIPIENT} and an {@link Object} containing the mapping of properties.
	 * If sender_entryid is not present, return false
	 * @return {Zarafa.core.data.IPMRecipientRecord}
	 */
	getSender: function()
	{
		if(!this.get('sender_entryid')){
			return false;
		}

		var sender = Zarafa.core.data.RecordFactory.createRecordObjectByCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_RECIPIENT, {
			smtp_address : this.get('sender_email_address'),
			display_name : this.get('sender_name'),
			address_type : this.get('sender_address_type'),
			entryid : this.get('sender_entryid'),
			search_key : this.get('sender_search_key'),
			user_image : this.get('user_image')
		});

		return sender;
	},

	/**
	 * Convert data from the record to an {@link Zarafa.core.data.IPMRecipientRecord}
	 * Invoke {@link Zarafa.core.data.RecordFactory#createRecordObjectByCustomType} with arguments {@link Zarafa.core.data.RecordCustomObjectType.ZARAFA_RECIPIENT} and an {@link Object} containing the mapping of properties.
	 * If sent_representing_entryid is not present, return false
	 * @return {Zarafa.core.data.IPMRecipientRecord}
	 */
	getSentRepresenting: function()
	{
		if(!this.get('sent_representing_entryid')){
			return false;
		}

		var sender = Zarafa.core.data.RecordFactory.createRecordObjectByCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_RECIPIENT, {
			smtp_address : this.get('sent_representing_smtp_address'),
			display_name : this.get('sent_representing_name'),
			address_type : this.get('sent_representing_address_type'),
			entryid : this.get('sent_representing_entryid'),
			search_key : this.get('sent_representing_search_key'),
			user_image : this.get('user_image')
		});

		return sender;
	},

	/**
	 * Function will use 'sendas' data and set the default recipient in the from field, with respective action type value from
	 * {@link Zarafa.mail.data.ActionTypes ActionTypes} of {@link Zarafa.core.data.IPMRecord record}.
	 * It will not set anything if the 'sendas' data is empty or no default recipient has been set for that action type
	 * in 'sendas' settings widget.
	 * @private
	 */
	 setDefaultFromRecipeint: function()
	 {
		 var actionType = this.getMessageAction('action_type');
		 var isCreateAction = !this.hasMessageAction('action_type') && this.phantom;
		 var isReplyAction = actionType === Zarafa.mail.data.ActionTypes.REPLY || actionType === Zarafa.mail.data.ActionTypes.REPLYALL;
		 var isForwarsAction = actionType === Zarafa.mail.data.ActionTypes.FORWARD;
 
		 // return if mail is not reply, new or forward mail.
		 if (isCreateAction === false && isReplyAction === false && isForwarsAction === false) {
			 return;
		 }
 
		 var settingsModel = container.getSettingsModel();
		 var defaultFromRecipients = settingsModel.get('zarafa/v1/contexts/mail/sendas', []);
		 if (!Ext.isEmpty(defaultFromRecipients)) {
			 for (var i = 0; i < defaultFromRecipients.length; i++) {
				 var recipient = defaultFromRecipients[i];
				 if (isCreateAction && recipient['new_mail'] || isReplyAction && recipient['reply_mail'] || isForwarsAction && recipient['forward_mail']) {
					 this.set('sent_representing_name', recipient['display_name']);
					 this.set('sent_representing_email_address', recipient['email_address'] || recipient['smtp_address']);
					 this.set('sent_representing_smtp_address', recipient['smtp_address']);
					 this.set('sent_representing_address_type', recipient['address_type']);
					 this.set('sent_representing_entryid', recipient['entryid']);
					 this.set('sent_representing_search_key', recipient['search_key']);
					 break;
				 }
			 }
		 }
	 },

	/**
	 * Function will use 'sendas' data and return the default {@link Zarafa.core.data.IPMRecipientRecord recipient} for the from field.
	 * If not found then it will create {@link Zarafa.core.data.IPMRecipientRecord recipient} using sent_representing_* properties if available.
	 * It will return false if no default from recipient has been set.
	 * 
	 * @return {Zarafa.core.data.IPMRecipientRecord}
	 * @private
	 */
	getDefaultFromRecipeint: function ()
	{
		var settingsModel = container.getSettingsModel();
		var defaultFromRecipients = settingsModel.get('zarafa/v1/contexts/mail/sendas') || [];

		if (Ext.isEmpty(this.get('sent_representing_email_address'))) {
			return false;
		}

		// Default config to create recipient record.
		var recipeintConfig = {
			display_name: this.get('sent_representing_name'),
			email_address: this.get('sent_representing_email_address'),
			smtp_address: this.get('sent_representing_smtp_address'),
			address_type: this.get('sent_representing_address_type'),
			entryid: this.get('sent_representing_entryid'),
			search_key: this.get('sent_representing_search_key')
		};
		
		// Check whether the sent_representing_* properties had been set by 'from addresses' functionality.
		// If so then get that recipient details from the 'sendas' settings and create config object accordingly.
		var recipient = defaultFromRecipients.find(function (recipient) {
			var sentRepresentingEmail = this.get('sent_representing_email_address');
			return sentRepresentingEmail === recipient['email_address'] || sentRepresentingEmail === recipient['smtp_address'];
		}, this);

		if (Ext.isDefined(recipient)) {
			Ext.apply(recipeintConfig, {
				display_name: recipient['display_name'],
				email_address: recipient['email_address'] || recipient['smtp_address'],
				address_type: recipient['address_type'],
				entryid: recipient['entryid'],
				object_type: recipient['object_type'],
				display_type: recipient['display_type'],
				display_type_ex: recipient['display_type_ex']
			});
		}

		return Zarafa.core.data.RecordFactory.createRecordObjectByCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_RECIPIENT, recipeintConfig);
	},

	/**
	 * Function is used to set the default Cc recipient in New or Reply mail as per the user
	 * configured in settings.
	 */
	setDefaultCcRecipients: function ()
	{
		var actionType = this.getMessageAction('action_type');
		var isCreateAction = !this.hasMessageAction('action_type') && this.phantom;
		var isReplyAction = actionType === Zarafa.mail.data.ActionTypes.REPLY || actionType === Zarafa.mail.data.ActionTypes.REPLYALL;

		// return if mail is not reply or new mail.
		if (isCreateAction === false && isReplyAction === false) {
			return;
		}

		var settingsModel = container.getSettingsModel();
		var defaultCcRecipients = settingsModel.get('zarafa/v1/contexts/mail/cc_recipients');
		var recipientStore = this.getRecipientStore();

		for (var i = 0; i < defaultCcRecipients.length; i++) {
			var recipient = defaultCcRecipients[i];
			if (recipientStore.isRecipientExists(recipient) || isCreateAction && !recipient['new_mail'] || isReplyAction && !recipient['reply_mail']) {
				continue;
			}

			var recipientData = Ext.apply({}, recipient);

			// Create a new recipient containing all data from the original.
			var record = Zarafa.core.data.RecordFactory.createRecordObjectByCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_RECIPIENT, recipientData);

			// We have copied the 'rowid' as well, but new recipients
			// shouldn't have this property as it will be filled in by PHP.
			record.set('rowid', undefined);

			recipientStore.add(record);
		}
	},
});

/**
 * This will initialize the properties for a phantom {@link Zarafa.core.data.MAPIRecord record},
 * which are needed to correctly send out the message.
 * @param {Zarafa.core.data.MAPIRecord} record The phantom record to initialize
 * @method
 */
Zarafa.core.data.MessageRecordPhantomHandler = function(record) {
	var userInfo = container.getUser();

	record.beginEdit();
	record.set('sender_name', userInfo.getFullName());
	record.set('sender_address_type', 'SMTP');
	record.set('sender_email_address', userInfo.getUserName());
	record.set('sender_entryid', userInfo.getEntryId());
	record.set('sender_search_key', userInfo.getSearchKey());

	// set delegate properties if needed
	if(!record.userIsStoreOwner()) {
		var storeRecord = container.getHierarchyStore().getById(record.get('store_entryid'));
		if(storeRecord) {
			record.setDelegatorInfo(storeRecord);
		}
	}

	record.endEdit();
};
/*
 * #dependsFile client/zarafa/core/mapi/ObjectType.js
 * #dependsFile client/zarafa/core/data/RecordFactory.js
 * #dependsFile client/zarafa/addressbook/AddressBookSubStore.js
 * #dependsFile client/zarafa/addressbook/AddressBookTelephoneNumberSubStore.js
 * #dependsFile client/zarafa/addressbook/AddressBookEmailAddressesSubStore.js
 */
Ext.namespace('Zarafa.addressbook');

/**
 * @class Zarafa.addressbook.AddressBookRecordFields
 * Array of {@link Ext.data.Field field} configurations for the
 * {@link Zarafa.core.data.IPMRecord IPMRecord} object.
 * These fields will be available in all
 * {@link Zarafa.core.mapi.ObjectType.MAPI_MAILUSER MAPI_MAILUSER} and
 * {@link Zarafa.core.mapi.ObjectType.MAPI_DISTLIST MAPI_DISTLIST} type messages.
 */
Zarafa.addressbook.AddressBookRecordFields = [
	{name: 'entryid'},
	{name: 'search_key'},
	{name: 'store_entryid'},
	{name: 'full_name'},
	{name: 'fileas'},
	{name: 'object_type', type: 'int', defaultValue: Zarafa.core.mapi.ObjectType.MAPI_MAILUSER },
	{name: 'display_type', type: 'int', defaultValue: Zarafa.core.mapi.DisplayType.DT_MAILUSER },
	{name: 'display_type_ex', type: 'int', defaultValue: Zarafa.core.mapi.DisplayType.DT_MAILUSER },
	{name: 'address_type'},
	{name: 'email_address'},
	{name: 'smtp_address'},
	{name: 'given_name'},
	{name: 'initials'},
	{name: 'surname'},
	{name: 'display_name'},
	{name: 'account'},
	{name: 'ems_ab_thumbnail_photo', defaultValue: ""},
	{name: 'street_address'},
	{name: 'locality'},
	{name: 'state_or_province'},
	{name: 'postal_code'},
	{name: 'country'},
	{name: 'title'},
	{name: 'company_name'},
	{name: 'department_name'},
	{name: 'office_location'},
	{name: 'assistant'},
	{name: 'office_telephone_number'}, // FIXME: Duplicate of business_telephone_number
	{name: 'business_telephone_number'},
	{name: 'business2_telephone_number'},
	{name: 'business2_telephone_number_mv'},
	{name: 'primary_fax_number'},
	{name: 'home_telephone_number'},
	{name: 'home2_telephone_number'},
	{name: 'home2_telephone_number_mv'},
	{name: 'mobile_telephone_number'},
	{name: 'pager_telephone_number'},
	{name: 'comment'},
	{name: 'icon_index'},
	{name: 'is_shared'},
	{name: 'is_contact_item'},
	{name: 'email_index', type: 'int', defaultValue: -1},
];

Zarafa.core.data.RecordFactory.addFieldToObjectType(Zarafa.core.mapi.ObjectType.MAPI_MAILUSER, Zarafa.addressbook.AddressBookRecordFields);
Zarafa.core.data.RecordFactory.addFieldToObjectType(Zarafa.core.mapi.ObjectType.MAPI_DISTLIST, Zarafa.addressbook.AddressBookRecordFields);

// support substores for further AB user details
Zarafa.core.data.RecordFactory.setSubStoreToObjectType(Zarafa.core.mapi.ObjectType.MAPI_MAILUSER, 'ems_ab_manager', Zarafa.addressbook.AddressBookSubStore);
Zarafa.core.data.RecordFactory.setSubStoreToObjectType(Zarafa.core.mapi.ObjectType.MAPI_MAILUSER, 'ems_ab_proxy_addresses', Zarafa.addressbook.AddressBookEmailAddressesSubStore);
Zarafa.core.data.RecordFactory.setSubStoreToObjectType(Zarafa.core.mapi.ObjectType.MAPI_MAILUSER, 'ems_ab_is_member_of_dl', Zarafa.addressbook.AddressBookSubStore);
Zarafa.core.data.RecordFactory.setSubStoreToObjectType(Zarafa.core.mapi.ObjectType.MAPI_MAILUSER, 'ems_ab_reports', Zarafa.addressbook.AddressBookSubStore);
Zarafa.core.data.RecordFactory.setSubStoreToObjectType(Zarafa.core.mapi.ObjectType.MAPI_MAILUSER, 'home2_telephone_numbers', Zarafa.addressbook.AddressBookTelephoneNumberSubStore);
Zarafa.core.data.RecordFactory.setSubStoreToObjectType(Zarafa.core.mapi.ObjectType.MAPI_MAILUSER, 'business2_telephone_numbers', Zarafa.addressbook.AddressBookTelephoneNumberSubStore);

Zarafa.core.data.RecordFactory.setSubStoreToObjectType(Zarafa.core.mapi.ObjectType.MAPI_DISTLIST, 'ems_ab_owner', Zarafa.addressbook.AddressBookSubStore);
Zarafa.core.data.RecordFactory.setSubStoreToObjectType(Zarafa.core.mapi.ObjectType.MAPI_DISTLIST, 'ems_ab_proxy_addresses', Zarafa.addressbook.AddressBookEmailAddressesSubStore);
Zarafa.core.data.RecordFactory.setSubStoreToObjectType(Zarafa.core.mapi.ObjectType.MAPI_DISTLIST, 'ems_ab_is_member_of_dl', Zarafa.addressbook.AddressBookSubStore);
Zarafa.core.data.RecordFactory.setSubStoreToObjectType(Zarafa.core.mapi.ObjectType.MAPI_DISTLIST, 'members', Zarafa.addressbook.AddressBookSubStore);

Zarafa.core.data.RecordFactory.addListenerToObjectType(Zarafa.core.mapi.ObjectType.MAPI_MAILUSER, 'createphantom', function(record) {
	// Phantom records must lways be marked as opened (they contain the full set of data)
	record.afterOpen();
});

Zarafa.core.data.RecordFactory.addListenerToObjectType(Zarafa.core.mapi.ObjectType.MAPI_DISTLIST, 'createphantom', function(record) {
	// Phantom records must lways be marked as opened (they contain the full set of data)
	record.afterOpen();
});

/**
 * @class Zarafa.addressbook.AddressBookRecord
 * @extends Zarafa.core.data.MAPIRecord
 *
 * An extension to the {@link Zarafa.core.data.MAPIRecord Record} specific for Addressbook items
 */
Zarafa.addressbook.AddressBookRecord = Ext.extend(Zarafa.core.data.MAPIRecord, {
	/**
	 * Convert this addressbook record into a {@link Zarafa.core.data.IPMRecipientRecord recipient}
	 * which can be used for composing news mails.
	 *
	 * @param {Zarafa.core.mapi.RecipientType} recipientType (optional) The recipient type which should
	 * be applied to this recipient. Defaults to {@link Zarafa.core.mapi.RecipientType#MAPI_TO}.
	 * @param {Zarafa.core.data.RecordCustomObjectType} recordType The custom record type. It can be either
	 * {@link Zarafa.core.data.RecordCustomObjectType#ZARAFA_RECIPIENT} or {@link Zarafa.core.data.RecordCustomObjectType#ZARAFA_CC_RECIPIENT}
	 * in most of the case.
	 *
	 * @return {Zarafa.core.data.IPMRecipientRecord} The recipientRecord for this addressbook item
	 */
	convertToRecipient: function(recipientType, recordType)
	{
		var recordCustomObjectType = Zarafa.core.data.RecordCustomObjectType;
		recordType = recordCustomObjectType.get(recordType) ? recordType : recordCustomObjectType.ZARAFA_RECIPIENT;
		var recipientRecord = Zarafa.core.data.RecordFactory.createRecordObjectByCustomType(recordType, {
			entryid: this.get('entryid'),
			search_key: this.get('search_key'),
			object_type: this.get('object_type'),
			display_name: this.get('display_name'),
			display_type: this.get('display_type'),
			display_type_ex: this.get('display_type_ex'),
			email_address: this.get('email_address'),
			smtp_address: this.get('smtp_address'),
			address_type: this.get('address_type'),
			recipient_type: recipientType || Zarafa.core.mapi.RecipientType.MAPI_TO
		});

		return recipientRecord;
	},

	/**
	 * Resolve the openable message entryid for a personal contact or distlist. Contact
	 * Provider wrapped entryids are unwrapped; contact folder items carry a trailing
	 * email-index byte (signalled by {@link #email_index}) which is stripped. We rely on
	 * email_index rather than inspecting the trailing bytes, as a valid message entryid can
	 * legitimately end in those byte values.
	 *
	 * @return {String} The message entryid, or the original entryid for other items.
	 */
	getContactMessageEntryId: function()
	{
		var entryid = this.get('entryid');

		var uscoreIndex = entryid.indexOf('_');
		if (uscoreIndex > 0) {
			entryid = entryid.substr(0, uscoreIndex);
		}

		if (Zarafa.core.EntryId.hasContactProviderGUID(entryid)) {
			return Zarafa.core.EntryId.unwrapContactProviderEntryId(entryid);
		}

		if (this.get('email_index') > 0) {
			entryid = entryid.substr(0, entryid.length - 2);
		}

		return entryid;
	},

	/**
	 * Convert this {@link Zarafa.addressbook.AddressBookRecord AddressBookRecord} into a {@link Zarafa.contact.DistlistMemberRecord DistlistMemberRecord}
	 * which can be used as record inside {@link Zarafa.contact.DistlistMemberStore}.
	 *
	 * @return {Zarafa.contact.DistlistMemberRecord} The distribution list member for this address book item
	 */
	convertToDistlistMember: function()
	{
		// by default set it to addressbook user
		var distlistType = Zarafa.core.mapi.DistlistType.DL_USER_AB;
		if(this.get('object_type') === Zarafa.core.mapi.ObjectType.MAPI_DISTLIST) {
			// addressbook group
			distlistType = Zarafa.core.mapi.DistlistType.DL_DIST_AB;
		}

		// Local contacts must be stored as a local item with an openable message entryid,
		// so opening the member resolves against the store instead of the address book.
		var entryid = this.get('entryid');
		if (this.isPersonalContact()) {
			distlistType = Zarafa.core.mapi.DistlistType.DL_USER;
			entryid = this.getContactMessageEntryId();
		} else if (this.isPersonalDistList()) {
			distlistType = Zarafa.core.mapi.DistlistType.DL_DIST;
			entryid = this.getContactMessageEntryId();
		}

		var props = {
			entryid: entryid,
			address_type: this.get('address_type'),
			distlist_type: distlistType,
			display_name: this.get('display_name'),
			email_address: this.get('email_address') || this.get('smtp_address'),
			smtp_address: this.get('smtp_address') || this.get('email_address')
		};

		return Zarafa.core.data.RecordFactory.createRecordObjectByCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_DISTLIST_MEMBER, props);
	},

	/**
	 * Convert this addressbook record into a {@link Zarafa.hierarchy.data.UserPermissionRecord user permission record}
	 * which can be used to assign permissions to the addressbook item for a particular {@link Zarafa.hierarchy.data.MAPIFolder}.
	 * @return {Zarafa.hierarchy.data.UserPermissionRecord} The user permission record for this addressbook item
	 */
	convertToUserPermission: function()
	{
		return Zarafa.core.data.RecordFactory.createRecordObjectByCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_USER_PERMISSION, {
			entryid: this.get('entryid'),
			display_name: this.get('display_name'),
			object_type: this.get('object_type'),
			rights: Zarafa.core.mapi.Rights.RIGHTS_NO_RIGHTS
		});
	},

	/**
	 * Convert this addressbook record into a {@link Zarafa.common.delegates.data.DelegateRecord DelegateRecord}
	 * which can be used for adding new permissions for delegates.
	 *
	 * @return {Zarafa.common.delegates.data.DelegateRecord} The DelegateRecord for this addressbook item.
	 */
	convertToDelegate: function()
	{
		var data = {
			entryid: this.get('entryid'),
			display_name: this.get('display_name')
		};

		return Zarafa.core.data.RecordFactory.createRecordObjectByCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_DELEGATE, data);
	},

	/**
	 * This will determine if the provided className ie. IPM.Contact matches the message_class
	 * on this record. This comparison is done case-insensitive. See {@link Zarafa.core.MessageClass#isClass}
	 * for further details.
	 *
	 * @return {Boolean} True when the given className matches the message_class.
	 */
	isPersonalContact: function()
	{
		if(Zarafa.core.MessageClass.isClass(this.get('message_class'), 'IPM.CONTACT', true)){
			return true;
		}
		if (this.get('object_type') == Zarafa.core.mapi.ObjectType.MAPI_MAILUSER) {
			if (Zarafa.core.EntryId.hasContactProviderGUID(this.get('entryid'))) {
				return true;
			}
			// Contact folder items have the is_contact_item flag set
			// by the server (both personal and shared contact folders).
			if (this.get('is_contact_item')) {
				return true;
			}
		}
		return false;
	},

	/**
	 * This will determine if the provided className ie. IPM.DistList matches the message_class
	 * on this record. This comparison is done case-insensitive. See {@link Zarafa.core.MessageClass#isClass}
	 * for further details.
	 *
	 * @return {Boolean} True when the given className matches the message_class.
	 */
	isPersonalDistList: function()
	{
		if(Zarafa.core.MessageClass.isClass(this.get('message_class'), 'IPM.DISTLIST', true)){
			return true;
		}
		if (this.get('object_type') == Zarafa.core.mapi.ObjectType.MAPI_DISTLIST) {
			if (Zarafa.core.EntryId.hasContactProviderGUID(this.get('entryid'))) {
				return true;
			}
			// Contact folder items have the is_contact_item flag set
			// by the server (both personal and shared contact folders).
			if (this.get('is_contact_item')) {
				return true;
			}
		}
		return false;
	},


	/**
	 * Convert this recipient into a {@link Zarafa.core.data.IPMRecord record}.
	 * This can only work if this recipient is {@link #isResolved resolved}.
	 *
	 * @return {Zarafa.core.data.IPMRecord} The addressbook record which
	 * is represented by this recipient.
	 */
	convertToContactRecord: function()
	{
		var entryid = this.getContactMessageEntryId();

		return Zarafa.core.data.RecordFactory.createRecordObjectByMessageClass('IPM.Contact', {
			entryid: entryid,
			message_class: 'IPM.Contact',
			object_type: Zarafa.core.mapi.ObjectType.MAPI_MESSAGE
		}, entryid);
	},

	/**
	 * Convert this recipient into a {@link Zarafa.core.data.IPMRecord record}.
	 * This can only work if this recipient is {@link #isResolved resolved}.
	 *
	 * @return {Zarafa.core.data.IPMRecord} The addressbook record which
	 * is represented by this recipient.
	 */
	convertToDistListRecord: function()
	{
		var entryid = this.getContactMessageEntryId();

		return Zarafa.core.data.RecordFactory.createRecordObjectByMessageClass('IPM.DistList', {
			entryid: entryid,
			message_class: 'IPM.DistList',
			object_type: Zarafa.core.mapi.ObjectType.MAPI_MESSAGE
		}, entryid);
	},

	/**
	 * Compare this {@link Zarafa.core.data.MAPIRecord record} instance with another one to see
	 * if they are same
	 *
	 * @param {Zarafa.core.data.MAPIRecord} record The Record to compare with
	 * @return {Boolean} True if the records are the same.
	 */
	equals: function(record)
	{
		// Simplest case, do we have the same object...
		if (this === record) {
			return true;
		}

		return Zarafa.core.EntryId.compareABEntryIds(this.get('entryid'),record.get('entryid'));
	},

	isSharedContact: function()
	{
		return !!this.get('is_shared');
	}
});

Zarafa.core.data.RecordFactory.setBaseClassToObjectType(Zarafa.core.mapi.ObjectType.MAPI_MAILUSER, Zarafa.addressbook.AddressBookRecord);
Zarafa.core.data.RecordFactory.setBaseClassToObjectType(Zarafa.core.mapi.ObjectType.MAPI_DISTLIST, Zarafa.addressbook.AddressBookRecord);
/*
 * #dependsFile client/zarafa/core/data/RecordFactory.js
 */
Ext.namespace('Zarafa.contact');

/**
 * @class Zarafa.contact.ContactRecordFields
 * Array of {@link Ext.data.Field field} configurations for the
 * {@link Zarafa.core.data.IPMRecord IPMRecord} object.
 * These fields will be available in all 'IPM.Contact' type messages.
 */
Zarafa.contact.ContactRecordFields = [
	// name
	{name: 'fileas'},
	{name: 'fileas_selection', type: 'int', defaultValue: -1},
	{name: 'display_name'},
	{name: 'given_name'},
	{name: 'middle_name'},
	{name: 'surname'},
	{name: 'nickname'},
	{name: 'display_name_prefix'},
	{name: 'generation'},

	// email
	{name: 'email_address'},
	{name: 'email_index', type: 'number', defaultValue: -1},

	{name: 'email_address_1'},
	{name: 'email_address_display_name_1'},
	{name: 'email_address_display_name_email_1'},
	{name: 'email_address_type_1'},
	{name: 'email_address_entryid_1'},

	{name: 'email_address_2'},
	{name: 'email_address_display_name_2'},
	{name: 'email_address_display_name_email_2'},
	{name: 'email_address_type_2'},
	{name: 'email_address_entryid_2'},

	{name: 'email_address_3'},
	{name: 'email_address_display_name_3'},
	{name: 'email_address_display_name_email_3'},
	{name: 'email_address_type_3'},
	{name: 'email_address_entryid_3'},

	// fax
	{name: 'fax_1_email_address'},							// expansion of primary_fax_number
	{name: 'fax_1_original_display_name'},
	{name: 'fax_1_address_type'},
	{name: 'fax_1_original_entryid'},
	{name: 'fax_2_email_address'},							// expansion of business_fax_number
	{name: 'fax_2_original_display_name'},
	{name: 'fax_2_address_type'},
	{name: 'fax_2_original_entryid'},
	{name: 'fax_3_email_address'},							// expansion of home_fax_number
	{name: 'fax_3_original_display_name'},
	{name: 'fax_3_address_type'},
	{name: 'fax_3_original_entryid'},

	// address
	{name: 'business_address'},
	{name: 'business_address_street'},
	{name: 'business_address_city'},
	{name: 'business_address_state'},
	{name: 'business_address_postal_code'},
	{name: 'business_address_country'},

	{name: 'home_address'},
	{name: 'home_address_street'},
	{name: 'home_address_city'},
	{name: 'home_address_state'},
	{name: 'home_address_postal_code'},
	{name: 'home_address_country'},

	{name: 'other_address'},
	{name: 'other_address_street'},
	{name: 'other_address_city'},
	{name: 'other_address_state'},
	{name: 'other_address_postal_code'},
	{name: 'other_address_country'},

	{name: 'mailing_address', type: 'int'},

	// telephone
	{name: 'home_telephone_number'},
	{name: 'cellular_telephone_number'},
	{name: 'assistant_telephone_number'},
	{name: 'business_telephone_number'},
	{name: 'business2_telephone_number'},
	{name: 'callback_telephone_number'},
	{name: 'car_telephone_number'},
	{name: 'company_telephone_number'},
	{name: 'home2_telephone_number'},
	{name: 'isdn_number'},
	{name: 'other_telephone_number'},
	{name: 'pager_telephone_number'},
	{name: 'primary_telephone_number'},
	{name: 'radio_telephone_number'},
	{name: 'telex_telephone_number'},
	{name: 'ttytdd_telephone_number'},

	// fax
	{name: 'business_fax_number'},
	{name: 'home_fax_number'},
	{name: 'primary_fax_number'},

	// general
	{name: 'title'},
	{name: 'company_name'},
	{name: 'department_name'},
	{name: 'office_location'},
	{name: 'profession'},
	{name: 'manager_name'},
	{name: 'assistant'},
	{name: 'nickname'},
	{name: 'has_picture'},
	{name: 'hide_attachments'},
	{name: 'contact_photo_attach_num', defaultValue: -1},
	{name: 'spouse_name'},
	{name: 'birthday', type: 'date', dateFormat: 'timestamp', defaultValue: null},
	{name: 'wedding_anniversary', type: 'date', dateFormat: 'timestamp', defaultValue: null},
	{name: 'birthday_eventid'},
	{name: 'anniversary_eventid'},
	{name: 'im'},
	{name: 'business_home_page'},
	{name: 'webpage'},
	{name: 'private', type: 'boolean', defaultValue: false},
	{name: 'sensitivity', type: 'int', defaultValue: Zarafa.core.mapi.Sensitivity['NONE']},
	{name: 'contacts_string'},
	{name: 'contacts'},
	{name: 'message_flags', type: 'int', defaultValue: Zarafa.core.mapi.MessageFlags.MSGFLAG_READ},
	{name: 'address_type', type: 'string', defaultValue: 'SMTP'},
	{name: 'address_book_long', type: 'int', defaultValue: 0},
	{name: 'address_book_mv', defaultValue: null},

	/**
	 * Below properties are not actual properties of contact (IPM.Contact), so these properties
	 * are not saved in IMessage object, but instead these properties are used to create recurring
	 * yearly appointment for birthday and wedding_anniversary because when creating recurring
	 * appointment we need timezone struct of client.
	 */
	// TODO: Move all Timezone properties into a single object
	{name: 'timezone', type: 'int'},
	{name: 'timezone_unk', mapping: 'unk', type: 'int'},
	{name: 'timezone_timezonedst', mapping: 'timezonedst', type: 'int'},
	{name: 'timezone_dstendmonth', mapping: 'dstendmonth', type: 'int'},
	{name: 'timezone_dstendweek', mapping: 'dstendweek', type: 'int'},
	{name: 'timezone_dstendday', mapping: 'dstendday', type: 'int'},
	{name: 'timezone_dstendhour', mapping: 'dstendhour', type: 'int'},
	{name: 'timezone_dststartmonth', mapping: 'dststartmonth', type: 'int'},
	{name: 'timezone_dststartweek', mapping: 'dststartweek', type: 'int'},
	{name: 'timezone_dststartday', mapping: 'dststartday', type: 'int'},
	{name: 'timezone_dststarthour', mapping: 'dststarthour', type: 'int'}
];

/**
 * @class Zarafa.contact.ContactRecord
 * @extends Zarafa.core.data.IPMRecord
 *
 * An extension to the {@link Zarafa.core.data.IPMRecord IPMRecord} specific to records which are
 * used as Contacts
 */
Zarafa.contact.ContactRecord = Ext.extend(Zarafa.core.data.IPMRecord, {

	/**
	 * @return {Boolean} True if this recipient has an email address
	 */
	hasEmailAddress: function()
	{
		return !Ext.isEmpty(this.get('email_address_1')) || !Ext.isEmpty(this.get('email_address_2')) || !Ext.isEmpty(this.get('email_address_3'));
	},

	/**
	 * Convert this contact record into a {@link Zarafa.core.data.IPMRecipientRecord recipient}
	 * which can be used for composing news mails. If multiple email addresses are present in contact
	 * then it will create multiple {@link Zarafa.core.data.IPMRecipientRecord IPMRecipientRecord}.
	 *
	 * @param {Zarafa.core.mapi.RecipientType} recipientType (optional) The recipient type which should
	 * be applied to this recipient. Defaults to {@link Zarafa.core.mapi.RecipientType#MAPI_TO}.
	 * @param {Boolean} multiple (Optional) Flag will indicate that caller expects multiple recipient record if contact
	 * contains multiple email addresses or a single recipient with default email address.
	 * @return {Zarafa.core.data.IPMRecipientRecord} The recipientRecord for this addressbook item
	 */
	convertToRecipient: function(recipientType, multiple)
	{
		// every email address will create a new recipient record
		var recipientRecords = [];

		if(multiple === true) {
			for(var index = 1; index < 4; index++) {
				if(!Ext.isEmpty(this.get('email_address_' + index))) {
					var props = {
						entryid: Zarafa.core.EntryId.wrapContactProviderEntryId(this.get('entryid'), Zarafa.core.mapi.ObjectType.MAPI_MAILUSER),
						object_type: Zarafa.core.mapi.ObjectType.MAPI_MAILUSER,
						display_type: Zarafa.core.mapi.DisplayType.DT_MAILUSER,
						display_type_ex: Zarafa.core.mapi.DisplayType.DT_MAILUSER,
						display_name: this.get('email_address_display_name_' + index) || this.get('display_name'),
						smtp_address: this.get('email_address_' + index),
						email_address: this.get('email_address_' + index),
						address_type: this.get('email_address_type_' + index) || 'SMTP',
						recipient_type: recipientType || Zarafa.core.mapi.RecipientType.MAPI_TO
					};

					recipientRecords.push(Zarafa.core.data.RecordFactory.createRecordObjectByCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_RECIPIENT, props));
				}
			}

			return recipientRecords;
		}

		// send only single recipient
		if(!Ext.isEmpty(this.get('email_address'))) {
			var props = {
				entryid: Zarafa.core.EntryId.wrapContactProviderEntryId(this.get('entryid'), Zarafa.core.mapi.ObjectType.MAPI_MAILUSER),
				object_type: Zarafa.core.mapi.ObjectType.MAPI_MAILUSER,
				display_type: Zarafa.core.mapi.DisplayType.DT_MAILUSER,
				display_type_ex: Zarafa.core.mapi.DisplayType.DT_MAILUSER,
				display_name: this.get('email_address_display_name') || this.get('display_name'),
				smtp_address: this.get('email_address'),
				email_address: this.get('email_address'),
				address_type: 'SMTP',
				recipient_type: recipientType || Zarafa.core.mapi.RecipientType.MAPI_TO
			};

			return Zarafa.core.data.RecordFactory.createRecordObjectByCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_RECIPIENT, props);
		}
	},

	/**
	 * Convert this {@link Zarafa.contact.ContactRecord ContactRecord} into a {@link Zarafa.contact.DistlistMemberRecord DistlistMemberRecord}
	 * which can be used as record inside {@link Zarafa.contact.DistlistMemberStore}.
	 * @return {Zarafa.contact.DistlistMemberRecord} The distribution list member for distlist item.
	 */
	convertToDistlistMember: function()
	{
		/*
		 * Contacts may have three different email-ids.
		 * We show all of them in addressbook now if we send original contact's entryid
		 * then there will be duplicate entryids for different email-address of contacts
		 * so we differentiate those by using entryid_1, entryid_2 and entryid_3
		 * so remove '_x' from the entryid_x.
		 */
		var entryid = this.get('entryid');
		var uscoreIndex = entryid.indexOf('_');
		if(uscoreIndex > 0) {
			entryid = entryid.substr(0, uscoreIndex);
		}

		return Zarafa.core.data.RecordFactory.createRecordObjectByCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_DISTLIST_MEMBER, {
			entryid: entryid,
			address_type: this.get('address_type'),
			distlist_type: Zarafa.core.mapi.DistlistType.DL_USER,
			display_name: this.get('display_name'),
			email_address: this.get('email_address')
		});
	},

	/**
	 * This will update all timezone properties inside this record, the properties
	 * will be initialized according to the timezone information which is currently
	 * valid for this browser (See {@link Date#getTimezoneStruct}).
	 */
	updateTimezoneInformation: function()
	{
		var tz = Date.getTimezoneStruct();

		this.beginEdit();
		this.set('timezone', tz.timezone, true);
		this.set('timezone_timezonedst', tz.timezonedst, true);
		this.set('timezone_dstendmonth', tz.dstendmonth, true);
		this.set('timezone_dstendweek', tz.dstendweek, true);
		this.set('timezone_dstendday', tz.dstendday, true);
		this.set('timezone_dstendhour', tz.dstendhour, true);
		this.set('timezone_dststartmonth', tz.dststartmonth, true);
		this.set('timezone_dststartweek', tz.dststartweek, true);
		this.set('timezone_dststartday', tz.dststartday, true);
		this.set('timezone_dststarthour', tz.dststarthour, true);
		this.endEdit();
	},

	/**
	 * Function is used to update subject property on {@link Zarafa.core.data.IPMRecord IPMRecord}
	 * according to changes in display_name property.
	 */
	updateSubject: function()
	{
		// prefixes/suffixes should be removed when saving subject
		var prefix = this.get('display_name_prefix');
		var generation = this.get('generation');
		var subject = this.get('display_name');
		var fileas = this.get('fileas');

		if(!Ext.isEmpty(prefix)) {
			subject = subject.replace(prefix, '');
		}

		if(!Ext.isEmpty(generation)) {
			subject = subject.replace(generation, '');
		}

		subject = subject.trim();

		if (Ext.isEmpty(fileas) && !Ext.isEmpty(subject)) {
			var display = subject.replace(prefix, '').replace(generation, '').replace(',', '');
			var fileasArray = display.trim().split(new RegExp(Zarafa.contact.data.config.SP + '|' + Zarafa.contact.data.config.NBSP, 'g'));

			if (fileasArray.length === 1) {
				fileas = fileasArray[0];
			} else {
				fileas = fileasArray.join(Zarafa.contact.data.config.NBSP);
			}
		}

		this.beginEdit();

		this.set('fileas', fileas);
		this.set('subject', subject);

		this.endEdit();
	},

	/**
	 * Function is used to update address_book_long and address_book_mv properties on {@link Zarafa.core.data.IPMRecord IPMRecord}.
	 * These properties are used by {@link Zarafa.addressbook.dialogs.AddressBookContentPanel AddressBookContentPanel} to show contact items
	 * in the addressbook.
	 *
	 * type of email address    address_book_mv     address_book_long
	 *	email1           0            1 (0x00000001)
	 *	email2           1            2 (0x00000002)
	 *	email3           2            4 (0x00000004)
	 *	fax2(business fax)     3            8 (0x00000008)
	 *	fax3(home fax)       4            16 (0x00000010)
	 *	fax1(primary fax)      5            32 (0x00000020)
	 *
	 *	address_book_mv is a multivalued property so all the values are passed in array
	 *	address_book_long stores sum of the flags these both properties should be in sync always.
	 */
	updateAddressbookProps: function()
	{
		var addressbook_long = 0;
		var addressbook_mv = [];

		this.beginEdit();

		// email address properties
		for(var index = 1; index < 4; index++) {
			if (!Ext.isEmpty(this.get('email_address_' + index))) {
				var emailDisplayName = this.get('fileas') || '';
				if (!Ext.isEmpty(emailDisplayName)) {
					emailDisplayName += ' ';
				}
				emailDisplayName += '(' + this.get('email_address_' + index) + ')';
				this.set('email_address_display_name_' + index, emailDisplayName);

				if (!Ext.isEmpty(this.get('email_address_display_name_' + index))) {
					addressbook_long += Math.pow(2, index - 1);
					addressbook_mv.push(index - 1);


					this.set('email_address_display_name_email_' + index, this.get('email_address_' + index));
				} else {
					// clear previous values
					this.set('email_address_display_name_email_' + index, '');
					this.set('email_address_type_' + index, '');
				}
			} else {
				// clear previous values
				this.set('email_address_display_name_' + index, '');
				this.set('email_address_display_name_email_' + index, '');
				this.set('email_address_type_' + index, '');
			}
		}

		if(!Ext.isEmpty(this.get('primary_fax_number'))) {
			this.set('fax_1_original_display_name', this.get('subject'), true); // same as subject
			this.set('fax_1_address_type', 'FAX', true);
			this.set('fax_1_email_address', this.get('fax_1_original_display_name') + '@' + this.get('primary_fax_number'));

			addressbook_long += 32;
			addressbook_mv.push(5);
		} else {
			// clear previous values
			this.set('fax_1_original_display_name', '');
			this.set('fax_1_address_type', '');
			this.set('fax_1_email_address', '');
		}

		if(!Ext.isEmpty(this.get('business_fax_number'))) {
			this.set('fax_2_original_display_name', this.get('subject'), true); // same as subject
			this.set('fax_2_address_type', 'FAX', true);
			this.set('fax_2_email_address', this.get('fax_2_original_display_name') + '@' + this.get('business_fax_number'));

			addressbook_long += 8;
			addressbook_mv.push(3);
		} else {
			// clear previous values
			this.set('fax_2_original_display_name', '');
			this.set('fax_2_address_type', '');
			this.set('fax_2_email_address', '');
		}

		if(!Ext.isEmpty(this.get('home_fax_number'))) {
			this.set('fax_3_original_display_name', this.get('subject'), true); // same as subject
			this.set('fax_3_address_type', 'FAX', true);
			this.set('fax_3_email_address', this.get('fax_3_original_display_name') + '@' + this.get('home_fax_number'));

			addressbook_long += 16;
			addressbook_mv.push(4);
		} else {
			// clear previous values
			this.set('fax_3_original_display_name', '');
			this.set('fax_3_address_type', '');
			this.set('fax_3_email_address', '');
		}

		this.set('address_book_long', addressbook_long);
		if (addressbook_mv.length > 0) {
			this.set('address_book_mv', addressbook_mv.sort());
		} else {
			this.set('address_book_mv', null);
		}

		this.endEdit();
	},

	/**
	 * Called by the store after the record was opened successfully.
	 * When an contact has been attached as a mail item and the user
	 * opens the contact we want to give the user the option to store
	 * the contact in the default contact folder.
	 *
	 * @private
	 */
	afterOpen: function()
	{
		if (this.isSubMessage()) {
			this.beginEdit();
			this.set('attach_num', []);
			this.set('message_flags', Zarafa.core.mapi.MessageFlags.MSGFLAG_READ);
			this.set('entryid', '');
			this.endEdit();
			this.phantom = true;

			var messageClass = this.get('message_class');
			if (Ext.isEmpty(messageClass)) {
				return;
			}

			var folder = container.getHierarchyStore().getDefaultFolderFromMessageClass(messageClass);
			if (Ext.isEmpty(folder)) {
				return;
			}

			// Set store/parent entryid, to create the record in the correct store and folder.
			this.beginEdit();
			this.set('store_entryid', folder.get('store_entryid'));
			this.set('parent_entryid', folder.get('entryid'));
			this.endEdit();

		}
		return Zarafa.contact.ContactRecord.superclass.afterOpen.call(this, arguments);
	},

	/**
	 * Builds URL to download contact as RFC6350-formatted stream with vcf extension.
	 * @param {Boolean} allAsZip (optional) True to downloading all the attachments as ZIP
	 * @return {String} URL for downloading message as file.
	 */
	getDownloadMessageUrl: function(allAsZip)
	{
		var url = container.getBaseURL();
		url = Ext.urlAppend(url, 'load=download_contact');
		url = Ext.urlAppend(url, 'storeid=' + this.get('store_entryid'));
		url = Ext.urlAppend(url, 'entryid=' + this.get('entryid'));

		return url;
	}
});

Zarafa.core.data.RecordFactory.addFieldToMessageClass('IPM.Contact', Zarafa.contact.ContactRecordFields);
Zarafa.core.data.RecordFactory.setSubStoreToMessageClass('IPM.Contact', 'attachments', Zarafa.core.data.IPMAttachmentStore);
Zarafa.core.data.RecordFactory.setBaseClassToMessageClass('IPM.Contact', Zarafa.contact.ContactRecord);
/*
 * #dependsFile client/zarafa/core/data/RecordFactory.js
 * #dependsFile client/zarafa/contact/DistlistMemberStore.js
 */
Ext.namespace('Zarafa.contact');

/**
 * @class Zarafa.contact.DistListRecordFields
 * Array of {@link Ext.data.Field field} configurations for the
 * {@link Zarafa.core.data.IPMRecord IPMRecord} object.
 * These fields will be available in all 'IPM.DistList' type messages.
 */
Zarafa.contact.DistListRecordFields = [
	{name: 'fileas'},
	{name: 'dl_name'},
	{name: 'subject'},
	{name: 'display_name'},
	{name: 'sensitivity', type: 'int', defaultValue: Zarafa.core.mapi.Sensitivity.NONE},
	{name: 'address_type', type: 'string', defaultValue: 'SMTP'},
	{name: 'private', type: 'boolean', defaultValue: false}
];

/**
 * @class Zarafa.contact.DistlistRecord
 * @extends Zarafa.core.data.IPMRecord
 *
 * An extension to the {@link Zarafa.core.data.IPMRecord IPMRecord} specific to records which are
 * used as Distribution Lists
 */
Zarafa.contact.DistlistRecord = Ext.extend(Zarafa.core.data.IPMRecord, {
	/**
	 * Convert this distribution list record into a {@link Zarafa.core.data.IPMRecipientRecord recipient}
	 * which can be used for composing news mails.
	 *
	 * @param {Zarafa.core.mapi.RecipientType} recipientType (optional) The recipient type which should
	 * be applied to this recipient. Defaults to {@link Zarafa.core.mapi.RecipientType#MAPI_TO}.
	 * @return {Zarafa.core.data.IPMRecipientRecord} The recipientRecord for this addressbook item
	 */
	convertToRecipient: function(recipientType)
	{
		var recipientRecord = Zarafa.core.data.RecordFactory.createRecordObjectByCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_RECIPIENT, {
			entryid: Zarafa.core.EntryId.wrapContactProviderEntryId(this.get('entryid'), Zarafa.core.mapi.ObjectType.MAPI_DISTLIST),
			object_type: Zarafa.core.mapi.ObjectType.MAPI_DISTLIST,
			display_type: Zarafa.core.mapi.DisplayType.DT_DISTLIST,
			display_type_ex: Zarafa.core.mapi.DisplayType.DT_DISTLIST,
			display_name: this.get('display_name'),
			email_address: this.get('fileas'),
			address_type: 'MAPIPDL',
			recipient_type: recipientType || Zarafa.core.mapi.RecipientType.MAPI_TO
		});

		return recipientRecord;
	},

	/**
	 * Convert this {@link Zarafa.contact.DistlistRecord DistlistRecord} into a {@link Zarafa.contact.DistlistMemberRecord DistlistMemberRecord}
	 * which can be used as record inside {@link Zarafa.contact.DistlistMemberStore}.
	 * @return {Zarafa.contact.DistlistMemberRecord} The distribution list member for distlist item.
	 */
	convertToDistlistMember: function()
	{
		return Zarafa.core.data.RecordFactory.createRecordObjectByCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_DISTLIST_MEMBER, {
			entryid: this.get('entryid'),
			address_type: 'MAPIPDL',
			// mapi_parseoneoff will fail if we don't give email_address
			email_address: this.get('fileas'),
			distlist_type: Zarafa.core.mapi.DistlistType.DL_DIST,
			display_name: this.get('display_name')
		});
	},

	/**
	 * Returns whether the IPMRecord supports the use of recipients or not (See {@link #supportsSubStore}).
	 * @return {Boolean} True if recipients are supported.
	 */
	supportsMembers: function()
	{
		return this.supportsSubStore('members');
	},

	/**
	 * Creates a Folder store for the {@link Zarafa.core.data.IPMRecord IPMRecord} (See {@link #createSubStore}).
	 * @return {Zarafa.core.data.IPMRecipientStore} The new Folder store.
	 */
	createMemberStore: function()
	{
		return this.createSubStore('members');
	},

	/**
	 * Set the Member store for the {@link Zarafa.core.data.IPMRecord record} (See {@link #setSubStore}).
	 * @param {Zarafa.core.data.IPMRecipientStore} memberStore The Member store.
	 * @return {Zarafa.core.data.IPMRecipientStore} The Member store.
	 */
	setMemberStore: function(memberStore)
	{
		return this.setSubStore('members', memberStore);
	},

	/**
	 * Get the Members store for the {@link Zarafa.core.data.IPMRecord IPMRecord} (See {@link #getSubStore}).
	 * @return {Zarafa.core.data.IPMRecipientStore} The Members store.
	 */
	getMemberStore: function()
	{
		return this.getSubStore('members');
	},

	/**
	 * Helper function to return names of all members of distribution list.
	 * @return {String} comma separated member names
	 */
	getMemberNames: function()
	{
		var store = this.getSubStore('members');
		var names = [];

		store.each(function(member) {
			var name = member.get('display_name');

			if(!Ext.isEmpty(name)) {
				names.push(name);
			} else {
				names.push(member.get('email_address'));
			}
		}, this);

		return names.join('; ');
	}
});

Zarafa.core.data.RecordFactory.addFieldToMessageClass('IPM.DistList', Zarafa.contact.DistListRecordFields);
Zarafa.core.data.RecordFactory.setSubStoreToMessageClass('IPM.DistList', 'members', Zarafa.contact.DistlistMemberStore);
Zarafa.core.data.RecordFactory.setBaseClassToMessageClass('IPM.DistList', Zarafa.contact.DistlistRecord);
/*
 * #dependsFile client/zarafa/core/data/RecordFactory.js
 * #dependsFile client/zarafa/core/mapi/IconIndex.js
 */
Ext.namespace('Zarafa.note');

/**
 * @class Zarafa.note.NoteRecordFields
 * Array of {@link Ext.data.Field field} configurations for the
 * {@link Zarafa.core.data.IPMRecord IPMRecord} object.
 * These fields will be available in all 'IPM.StickyNote' type messages.
 */
Zarafa.note.NoteRecordFields = [
	{name: 'icon_index', type: 'int', defaultValue:Zarafa.core.mapi.IconIndex['note_yellow']},
	{name: 'color', type: 'int', defaultValue:Zarafa.core.mapi.NoteColor['note_yellow']}
];

Zarafa.core.data.RecordFactory.addFieldToMessageClass('IPM.StickyNote', Zarafa.note.NoteRecordFields);

/**
 * @class Zarafa.calendar.NoteRecord
 * @extends Zarafa.core.data.IPMRecord
 *
 * An extension to the {@link Zarafa.core.data.IPMRecord IPMRecord} specific to Sticky Notes.
 */
Zarafa.note.NoteRecord = Ext.extend(Zarafa.core.data.IPMRecord, {
	/**
	 * Generate the subject from the first 28 characters of the notes body.
	 * if these 28 characters of note's body contains new line character than it should be truncated.
	 * truncation will be replaced with ellipsis ('...').
	 * @private
	 */
	generateSubject: function()
	{
		var text = this.get('body');

		// truncate body if it exceeds maximum subject length.
		if(text.length > 28) {
			text = Ext.util.Format.ellipsis(text, 28);
		}

		// we should break subject on new lines
		text = text.split(/\r\n|\r|\n/)[0];

		this.set('subject', text);

		return text;
	}
});
Zarafa.core.data.RecordFactory.setBaseClassToMessageClass('IPM.StickyNote', Zarafa.note.NoteRecord);/*
 * #dependsFile client/zarafa/core/data/RecordFactory.js
 * #dependsFile client/zarafa/core/data/MessageRecord.js
 */
Ext.namespace('Zarafa.advancesearch');

/**
 * @class Zarafa.advancesearch.AdvanceSearchRecordFields
 * Array of {@link Ext.data.Field field} configurations for the
 * {@link Zarafa.core.data.IPMRecord IPMRecord} object.
 * These fields will be available in all 'IPM.Search' type messages.
 */
Zarafa.advancesearch.AdvanceSearchRecordFields = [
	{name: 'searchdate', type: 'date', dateFormat: 'timestamp', defaultValue: null, sortDir: 'DESC'},
	{name: 'duedate', type: 'date', dateFormat: 'timestamp', defaultValue: null, sortDir: 'DESC'},
	{name: 'task_duedate', type: 'date', dateFormat: 'timestamp', defaultValue: null, sortDir: 'DESC'},
];

Zarafa.core.data.RecordFactory.addFieldToMessageClass('IPM.Search', Zarafa.advancesearch.AdvanceSearchRecordFields);
Zarafa.core.data.RecordFactory.setBaseClassToMessageClass('IPM.Search', Zarafa.core.data.MessageRecord);

// Add the field to all IPM (and IPM child) records
Zarafa.core.data.RecordFactory.addFieldToMessageClass('IPM', Zarafa.advancesearch.AdvanceSearchRecordFields);

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

/**
 * @class Zarafa.calendar.AppointmentRecordFields
 *
 * Array of {@link Ext.data.Field field} configurations for the
 * {@link Zarafa.core.data.IPMRecord IPMRecord} object.
 * These fields will be available in all 'IPM.Appointment' type messages.
 */
Zarafa.calendar.AppointmentRecordFields = [
	{name: 'importance', type: 'int', defaultValue: Zarafa.core.mapi.Importance.NORMAL},
	{name: 'sensitivity', type: 'int', defaultValue: Zarafa.core.mapi.Sensitivity.NONE},
	{name: 'startdate', type: 'date', dateFormat: 'timestamp', allowBlank: false, sortDir: 'DESC'},
	{name: 'duedate', type: 'date', dateFormat: 'timestamp', allowBlank: false, sortDir: 'DESC'},
	{name: 'basedate', type: 'date', dateFormat: 'timestamp', defaultValue: null},
	{name: 'recurring', type: 'boolean', defaultValue: false},
	{name: 'recurring_reset', type: 'boolean', defaultValue: false},
	{name: 'recurring_pattern', type: 'string'},
	{name: 'startdate_recurring', type: 'date', dateFormat: 'timestamp', defaultValue: null, sortDir: 'DESC'},
	{name: 'enddate_recurring', type: 'date', dateFormat: 'timestamp', defaultValue: null, sortDir: 'DESC'},
	{name: 'exception', type: 'boolean', defaultValue: false},
	{name: 'reply_time', type: 'date', dateFormat: 'timestamp', defaultValue: null},
	{name: 'reply_name', type: 'string', defaultValue: ''},
	{name: 'busystatus', type: 'int', defaultValue: Zarafa.core.mapi.BusyStatus.BUSY},
	{name: 'label', type: 'int', defaultValue: Zarafa.core.mapi.AppointmentLabels.NONE},
	{name: 'request_sent', type: 'boolean', defaultValue: false},
	{name: 'alldayevent', type: 'boolean', defaultValue: false},
	{name: 'private', type: 'boolean', defaultValue: false},
	{name: 'meeting', type: 'int', defaultValue: Zarafa.core.mapi.MeetingStatus.NONMEETING},
	{name: 'location', type: 'string'},
	{name: 'onlinemeetingurl', type: 'string'},
	{name: 'duration', type: 'int'},
	{name: 'auxiliary_flags', type: 'int'},
	{name: 'responsestatus', type: 'int', defaultValue: Zarafa.core.mapi.ResponseStatus.RESPONSE_NONE},
	{name: 'reminder', type: 'boolean'},
	{name: 'reminder_minutes', type: 'int'},
	{name: 'reminder_time', type: 'date', dateFormat: 'timestamp', defaultValue: null},
	{name: 'flagdueby', type: 'date', dateFormat: 'timestamp', defaultValue: null},
	{name: 'commonstart', type: 'date', dateFormat: 'timestamp', allowBlank: false},
	{name: 'commonend', type: 'date', dateFormat: 'timestamp', allowBlank: false},
	{name: 'commonassign'},
	{name: 'counter_proposal', type: 'boolean', defaultValue: false},
	// TODO: Move all Recurrence properties into a single object
	{name: 'recurrence_type', mapping: 'type', type: 'int', forceProtocol: true},
	{name: 'recurrence_subtype', mapping: 'subtype', type: 'int', forceProtocol: true},
	{name: 'recurrence_everyn', mapping: 'everyn', type: 'int', forceProtocol: true},
	{name: 'recurrence_regen', mapping: 'regen', type: 'int', forceProtocol: true},
	{name: 'recurrence_weekdays', mapping: 'weekdays', type: 'int', forceProtocol: true},
	{name: 'recurrence_month', mapping: 'month', type: 'int', forceProtocol: true},
	{name: 'recurrence_monthday', mapping: 'monthday', type: 'int', forceProtocol: true},
	{name: 'recurrence_nday', mapping: 'nday', type: 'int', forceProtocol: true},
	{name: 'recurrence_term', mapping: 'term', type: 'int', forceProtocol: true},
	{name: 'recurrence_numoccur', mapping: 'numoccur', type: 'int', forceProtocol: true},
	{name: 'recurrence_numexcept', mapping: 'numexcept', type: 'int', forceProtocol: true},
	{name: 'recurrence_numexceptmod', mapping: 'numexceptmod', type: 'int', forceProtocol: true},
	{name: 'recurrence_start', mapping: 'start', type: 'date', dateFormat: 'timestamp', forceProtocol: true, defaultValue: null},
	{name: 'recurrence_end', mapping: 'end', type: 'date', dateFormat: 'timestamp', forceProtocol: true, defaultValue: null},
	{name: 'recurrence_startocc', mapping: 'startocc', type: 'int', forceProtocol: true},
	{name: 'recurrence_endocc', mapping: 'endocc', type: 'int', forceProtocol: true},
	// TODO: Move all Timezone properties into a single object
	{name: 'timezone', type: 'int', forceProtocol: true},
	{name: 'timezone_unk', mapping: 'unk', type: 'int', forceProtocol: true},
	{name: 'timezone_timezonedst', mapping: 'timezonedst', type: 'int', forceProtocol: true},
	{name: 'timezone_dstendmonth', mapping: 'dstendmonth', type: 'int', forceProtocol: true},
	{name: 'timezone_dstendweek', mapping: 'dstendweek', type: 'int', forceProtocol: true},
	{name: 'timezone_dstendday', mapping: 'dstendday', type: 'int', forceProtocol: true},
	{name: 'timezone_dstendhour', mapping: 'dstendhour', type: 'int', forceProtocol: true},
	{name: 'timezone_dststartmonth', mapping: 'dststartmonth', type: 'int', forceProtocol: true},
	{name: 'timezone_dststartweek', mapping: 'dststartweek', type: 'int', forceProtocol: true},
	{name: 'timezone_dststartday', mapping: 'dststartday', type: 'int', forceProtocol: true},
	{name: 'timezone_dststarthour', mapping: 'dststarthour', type: 'int', forceProtocol: true},
	{name: 'timezone_iana', type: 'string', forceProtocol: true}
];

/**
 *	You are programmatically manipulating appointment items in Microsoft Outlook
 *	and expect the message class of all appointment items to be IPM.Appointment,
 *	but the message class of recurring exceptions is set to IPM.OLE.CLASS.{00061055-0000-0000-C000-000000000046}
 *	http://support.microsoft.com/kb/183024
 *
 *	IPM.OLE.Class	: The exception item of a recurrence series
 */
Zarafa.core.data.RecordFactory.addFieldToMessageClass('IPM.OLE.CLASS.{00061055-0000-0000-C000-000000000046}', Zarafa.calendar.AppointmentRecordFields);
Zarafa.core.data.RecordFactory.addFieldToMessageClass('IPM.OLE.CLASS.{00061055-0000-0000-C000-000000000046}', Zarafa.core.data.MessageRecordFields);

Zarafa.core.data.RecordFactory.addFieldToMessageClass('IPM.Appointment', Zarafa.calendar.AppointmentRecordFields);
Zarafa.core.data.RecordFactory.addFieldToMessageClass('IPM.Appointment', Zarafa.core.data.MessageRecordFields);
Zarafa.core.data.RecordFactory.addListenerToMessageClass('IPM.Appointment', 'createphantom', Zarafa.core.data.MessageRecordPhantomHandler);

Zarafa.core.data.RecordFactory.addListenerToMessageClass('IPM.Appointment', 'createphantom', function(record, data) {
	record.beginEdit();

	if (Ext.isNumber(record.get('duration'))) {
		var startDate = record.get('startdate') || record.get('commonstart');
		var dueDate = record.get('duedate') || record.get('commonend');
		if (startDate && dueDate) {
			record.set('duration', (dueDate - startDate) / (60 * 1000));
		}
	}

	var settings = container.getSettingsModel();

	if (!data || !Ext.isDefined(data.reminder)) {
		var reminder = false;
		var store = container.getHierarchyStore().getById(record.get('store_entryid'));
		if (!store || !store.isPublicStore()) {
			reminder = settings.get('zarafa/v1/contexts/calendar/default_reminder');
		}
		record.set('reminder', reminder);
	}

	if (!data || !Ext.isDefined(data.reminder_minutes)) {
		if (record.get('alldayevent')) {
			record.set('reminder_minutes', settings.get('zarafa/v1/contexts/calendar/default_allday_reminder_time'));
		} else {
			record.set('reminder_minutes', settings.get('zarafa/v1/contexts/calendar/default_reminder_time'));
		}
	}

	if (record.get('reminder')) {
		var startDate = record.get('startdate');
		if (Ext.isDate(startDate)) {
			record.set('reminder_time', startDate);
			record.set('flagdueby', startDate.add(Date.MINUTE, -record.get('reminder_minutes')));
		}
	}

	record.set('timezone_iana', Intl.DateTimeFormat().resolvedOptions().timeZone);

	record.endEdit();
});

/**
 * @class Zarafa.calendar.AppointmentRecord
 * @extends Zarafa.core.data.MessageRecord
 *
 * An extension to the {@link Zarafa.core.data.MessageRecord MessageRecord} specific to Appointment Request/Response Messages.
 */
Zarafa.calendar.AppointmentRecord = Ext.extend(Zarafa.core.data.MessageRecord, {

	/**
	 * The base array of ID properties which is copied to the {@link #idProperties}
	 * when the record is being created.
	 * @property
	 * @type Array
	 * @private
	 */
	baseIdProperties: [ 'entryid', 'store_entryid', 'parent_entryid', 'basedate', 'attach_num' ],

	/**
	 * Compare this {@link Zarafa.core.data.IPMRecord record} instance with another one to see
	 * if they are the same IPM Message from the server (i.e. The entryid matches).
	 *
	 * @param {Zarafa.core.data.IPMRecord} record The IPMRecord to compare with
	 * @return {Boolean} True if the records are the same.
	 */
	equals: function(record)
	{
		if (Zarafa.calendar.AppointmentRecord.superclass.equals.apply(this, arguments) === true) {
			// For recurring appointments we must also check if we have a match on the occurrence (through the basedate).
			return (this.get('basedate') === record.get('basedate'));
		}
		return false;
	},

	/**
	 * Function will get the recurrence information from (@link Zarafa.core.data.IPMRecord IPMRecord) and
	 * will generate the recurring pattern string that can be saved in recurring_pattern property of {@link Zarafa.core.data.IPMRecord IPMRecord}.
	 * @return {String} Recurring pattern string that can be saved in {@link Zarafa.core.data.IPMRecord IPMRecord}.
	 */
	generateRecurringPattern: function()
	{
		// Start formatting the properties in such a way we can apply
		// them directly into the recurrence pattern.
		var type = this.get('recurrence_type');
		var everyn = this.get('recurrence_everyn');
		var start = this.get('recurrence_start').toUTC();
		var end = this.get('recurrence_end').toUTC();
		var term = this.get('recurrence_term');
		var numocc = this.get('recurrence_numoccur');
		var startocc = this.get('recurrence_startocc');
		var endocc = this.get('recurrence_endocc');
		var pattern;
		var occSingleDayRank = false;
		var occTimeRange = (startocc !== 0 && endocc !== 0);

		switch (type) {
			case Zarafa.common.recurrence.data.RecurrenceType.DAILY:
				if (everyn == 1) {
					type = _('workday');
					occSingleDayRank = true;
				} else if (everyn == (24 * 60)) {
					type = _('day');
					occSingleDayRank = true;
				} else {
					everyn /= (24 * 60);
					type = _('days');
					occSingleDayRank = false;
				}
				break;
			case Zarafa.common.recurrence.data.RecurrenceType.WEEKLY:
				if (everyn == 1) {
					type = _('week');
					occSingleDayRank = true;
				} else {
					type = _('weeks');
					occSingleDayRank = false;
				}

				// Append selected week days related information, if any
				type += _(' on ') + this.prepareWeekDaysString();
				break;
			case Zarafa.common.recurrence.data.RecurrenceType.MONTHLY:
				if (everyn == 1) {
					type = _('month');
					occSingleDayRank = true;
				} else {
					type = _('months');
					occSingleDayRank = false;
				}
				break;
			case Zarafa.common.recurrence.data.RecurrenceType.YEARLY:
				if (everyn <= 12) {
					everyn = 1;
					type = _('year');
					occSingleDayRank = true;
				} else {
					everyn = everyn / 12;
					type = _('years');
					occSingleDayRank = false;
				}
				break;
		}

		var tmpStart = start.clone();
		tmpStart.setHours(startocc / 60);
		tmpStart.setMinutes(startocc % 60);
		// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
		startocc = tmpStart.formatDefaultTime();
		tmpStart.setHours(endocc / 60);
		tmpStart.setMinutes(endocc % 60);
		// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
		endocc = tmpStart.formatDefaultTime();

		// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
		start = start.format(_('d/m/Y'));
		// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
		end = end.format(_('d/m/Y'));

		// Based on the properties, we need to generate the recurrence pattern string.
		// This is obviously very easy since we can simply concatenate a bunch of strings,
		// however this messes up translations for languages which order their words
		// differently.
		// To improve translation quality we create a series of default strings, in which
		// we only have to fill in the correct variables. The base string is thus selected
		// based on the available properties.
		if (term == Zarafa.common.recurrence.data.RecurrenceEnd.NEVER) {
			if (occTimeRange) {
				if (occSingleDayRank) {
					pattern = String.format(_('Occurs every {0} effective {1} from {2} to {3}.'), type, start, startocc, endocc);
				} else {
					pattern = String.format(_('Occurs every {0} {1} effective {2} from {3} to {4}.'), everyn, type, start, startocc, endocc);
				}
			} else {
				if (occSingleDayRank) {
					pattern = String.format(_('Occurs every {0} effective {1}.'), type, start);
				} else {
					pattern = String.format(_('Occurs every {0} {1} effective {2}.'), everyn, type, start);
				}
			}
		} else if (term == Zarafa.common.recurrence.data.RecurrenceEnd.N_OCCURRENCES) {
			if (occTimeRange) {
				if (occSingleDayRank) {
					pattern = String.format(ngettext('Occurs every {0} effective {1} for {2} occurrence from {3} to {4}.', 'Occurs every {0} effective {1} for {2} occurrences from {3} to {4}.', numocc),
								type, start, numocc, startocc, endocc);
				} else {
					pattern = String.format(ngettext('Occurs every {0} {1} effective {2} for {3} occurrence from {4} to {5}.', 'Occurs every {0} {1} effective {2} for {3} occurrences {4} to {5}.', numocc),
								everyn, type, start, numocc, startocc, endocc);
				}
			} else {
				if (occSingleDayRank) {
					pattern = String.format(ngettext('Occurs every {0} effective {1} for {2} occurrence.', 'Occurs every {0} effective {1} for {2} occurrences.', numocc),
								type, start, numocc);
				} else {
					pattern = String.format(ngettext('Occurs every {0} {1} effective {2} for {3} occurrence.', 'Occurs every {0} {1} effective {2} for {3} occurrences.', numocc),
								everyn, type, start, numocc);
				}
			}
		} else if (term == Zarafa.common.recurrence.data.RecurrenceEnd.ON_DATE) {
			if (occTimeRange) {
				if (occSingleDayRank) {
					pattern = String.format(_('Occurs every {0} effective {1} until {2} from {3} to {4}.'), type, start, end, startocc, endocc);
				} else {
					pattern = String.format(_('Occurs every {0} {1} effective {2} until {3} from {4} to {5}.'), everyn, type, start, end, startocc, endocc);
				}
			} else {
				if (occSingleDayRank) {
					pattern = String.format(_('Occurs every {0} effective {1} until {2}.'), type, start, end);
				} else {
					pattern = String.format(_('Occurs every {0} {1} effective {2} until {3}.'), everyn, type, start, end);
				}
			}
		}

		return pattern;
	},

	/**
	 * Generates meeting time details which will be added to meeting response body.
	 * The preview panel's MeetingInfo component shows When/Where from MAPI
	 * properties, so the body only contains the user's response text.
	 * @param {String} responseText The user's comment text
	 * @param {Boolean} is_html Whether responseText is HTML
	 * @return {String} generated body message
	 */
	generateMeetingTimeInfo: function(responseText, is_html)
	{
		var messageBody = responseText || '';
		if (messageBody && !is_html) {
			messageBody = Ext.util.Format.htmlEncode(messageBody).replace(/\n/g, '<br>');
		}
		return messageBody;
	},

	/**
	 * Sends a requests to accept/decline a incoming meeting request.
	 *
	 * @param {Zarafa.core.mapi.ResponseStatus} responseType accept/decline/propose new time
	 * @param {String} meetingTimeInfo Meeting Time Information shown in message body.
	 * @param {Boolean} noResponse true if no response should be send to organizer else false
	 * @private
	 */
	sendMeetingRequestResponse: function(responseType, meetingTimeInfo, sendResponse)
	{
		if (Ext.isDefined(responseType)) {
			switch(responseType)
			{
				case Zarafa.core.mapi.ResponseStatus.RESPONSE_TENTATIVE:
				case Zarafa.core.mapi.ResponseStatus.RESPONSE_ACCEPTED:
					this.addMessageAction('action_type', 'acceptMeetingRequest');
					break;
				case Zarafa.core.mapi.ResponseStatus.RESPONSE_DECLINED:
					this.addMessageAction('action_type', 'declineMeetingRequest');
					break;
			}

			this.addMessageAction('responseType', responseType);
			this.addMessageAction('meetingTimeInfo', meetingTimeInfo);
			this.addMessageAction('mti_html', true);
			this.addMessageAction('sendResponse', sendResponse);

			this.getStore().save(this);
		}
	},

	/**
	 * Respond to a meeting request with the correct {@link Zarafa.core.mapi.ResponseStatus}.
	 *
	 * @param {Zarafa.core.mapi.ResponseStatus} responseType accept/decline/tentative
	 * @param {String} comment user's extra comments to response
	 * @param {Boolean} sendResponse send response to organizer
	 */
	respondToMeetingRequest: function(responseType, comment, com_is_html, sendResponse)
	{
		// The meeting time info in responses is stored as PR_BODY
		// (plain text), so never apply HTML encoding here.
		// Handle the legacy 3-argument call where com_is_html is
		// actually the sendResponse flag.
		// Skip the When/Where header — the preview panel's MeetingInfo
		// component already shows it from the MAPI properties.
		var actualSendResponse = (sendResponse !== undefined) ? sendResponse : com_is_html;
		this.sendMeetingRequestResponse(responseType,
			this.generateMeetingTimeInfo(comment, false),
			actualSendResponse);
	},

	/**
	 * Proposes new time for received meeting request
	 *
	 * @param {Zarafa.core.mapi.ResponseStatus} responseType tentative accept/decline
	 * @param {String} comment user's extra comment in propose new time dialog
	 * @param {Date} startDate Proposed start time for the meeting
	 * @param {Date} endDate Proposed end time for the meeting
	 */
	proposeNewTimeToMeetingRequest: function(responseType, comment, startDate, endDate)
	{
		this.set('counter_proposal', true);

		this.addMessageAction('proposed_starttime', startDate);
		this.addMessageAction('proposed_endtime', endDate);

		this.sendMeetingRequestResponse(responseType, this.generateProposeNewTimeBody(comment, startDate, endDate), true);
	},

	/**
	 * Generates body for propose new time MR mail.
	 *
	 * @param {String} comment user's extra comments in propose new time dialog
	 * @param {Date} startDate Proposed start time for the meeting
	 * @param {Date} endDate Proposed end time for the meeting
	 */
	generateProposeNewTimeBody: function (comment, startDate, endDate)
	{
		var proposeNewTimeBody = comment + '\n\n\n-----------\n' + _('New Meeting Time Proposed') + ':\n';
		// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
		proposeNewTimeBody += startDate.formatDefaultTime(_('l jS F Y {0}')) + ' - ';
		// # TRANSLATORS: See http://docs.sencha.com/extjs/3.4.0/#!/api/Date for the meaning of these formatting instructions
		proposeNewTimeBody += endDate.formatDefaultTime(_('l jS F Y {0}')) + '\n';

		return proposeNewTimeBody;
	},

	/**
	 * Function cancels Meeting invitation and sends Meeting Cancellation message.
	 * @param {String} Meeting Time Information shown in message body.
	 */
	cancelInvitation: function(comment, com_is_html)
	{
		this.addMessageAction('action_type', 'cancelInvitation');
		this.addMessageAction('meetingTimeInfo', this.generateMeetingTimeInfo(comment, com_is_html));
		this.addMessageAction('mti_html', true);

		var store = this.getStore();
		store.remove(this);
		store.save(this);
	},

	/**
	 * Forward the meeting request to additional recipients. A proper
	 * IPM.Schedule.Meeting.Request is sent to each recipient and the
	 * organizer receives a forward notification.
	 *
	 * @param {Object[]} recipients Array of recipient objects with at least
	 * display_name and smtp_address/email_address properties.
	 */
	forwardMeetingRequest: function(recipients)
	{
		var prefix = container.getSettingsModel().get('zarafa/v1/contexts/mail/use_english_abbreviations') ?
			'Fwd' : _('Fwd');
		this.addMessageAction('action_type', 'forwardMeetingRequest');
		this.addMessageAction('forwardRecipients', recipients);
		this.addMessageAction('forwardSubjectPrefix', prefix + ': ');

		this.getStore().save(this);
	},

	/**
	 * Function which cancel/decline/deletes Meeting and sends Meeting decline message to organizer.
	 * @param {String} sendUpdateFlag flag which decides messageAction for the deleting item
	 */
	declineMeeting: function(sendUpdateFlag)
	{
		var store = this.getStore();

		if(sendUpdateFlag){
			this.addMessageAction('action_type', 'declineMeeting');
		}

		store.remove(this);
		store.save(this);
	},

	/**
	 * Function is used to check if duedate property value of
	 * {@link Zarafa.calendar.AppointmentRecord AppointmentRecord} is in past or not.
	 * @return {Boolean} true if meeting/appointment is in past else false.
	 */
	isAppointmentInPast: function()
	{
		var dueDate = this.get('duedate');

		// find the correct due date for recurring appointments
		if  (this.isRecurring()) {
			dueDate = this.get('enddate_recurring');
		}

		if(Ext.isDate(dueDate) && dueDate.getTime() < (new Date().getTime())) {
			return true;
		}

		return false;
	},

	/**
	 * @return {Boolean} Returns true if the {@link Zarafa.core.data.AppointmentRecord AppointmentRecord} has
	 * been sent to the invitees.
	 */
	isMeetingSent: function()
	{
		return this.isMeetingOrganized() && this.get('request_sent') === true;
	},

	/**
	 * @return {Boolean} Returns true if the {@link Zarafa.core.data.AppointmentRecord AppointmentRecord} has
	 * meeting status received.
	 */
	isMeetingReceived: function()
	{
		return this.get('meeting') === Zarafa.core.mapi.MeetingStatus.MEETING_RECEIVED &&
			   this.get('responsestatus') !== Zarafa.core.mapi.ResponseStatus.RESPONSE_ORGANIZED;
	},

	/**
	 * @return {Boolean} Returns true if the {@link Zarafa.core.data.AppointmentRecord AppointmentRecord} has
	 * meeting status organized.
	 */
	isMeetingOrganized: function()
	{
		return this.get('meeting') === Zarafa.core.mapi.MeetingStatus.MEETING &&
			   this.get('responsestatus') === Zarafa.core.mapi.ResponseStatus.RESPONSE_ORGANIZED;
	},

	/**
	 * @return {Boolean} Returns true if the {@link Zarafa.core.data.AppointmentRecord AppointmentRecord} has
	 * meeting status nonmeeting.
	 */
	isMeeting: function()
	{
		var meeting = this.get('meeting');
		return (meeting && meeting !== Zarafa.core.mapi.MeetingStatus.NONMEETING);
	},

	/**
	 * @return {Boolean} Returns true if the {@link Zarafa.core.data.AppointmentRecord AppointmentRecord} has
	 * meeting status meeting canceled.
	 */
	isMeetingCanceled: function()
	{
		var meeting = this.get('meeting');
		return (meeting === Zarafa.core.mapi.MeetingStatus.MEETING_CANCELED || meeting === Zarafa.core.mapi.MeetingStatus.MEETING_RECEIVED_AND_CANCELED);
	},

	/**
	 * @return {Boolean} Returns true if the {@link Zarafa.core.data.AppointmentRecord AppointmentRecord} has
	 * meeting status meeting received and canceled.
	 */
	isMeetingReceivedAndCanceled: function()
	{
		return this.get('meeting') === Zarafa.core.mapi.MeetingStatus.MEETING_RECEIVED_AND_CANCELED &&
			   this.get('responsestatus') !== Zarafa.core.mapi.ResponseStatus.RESPONSE_ORGANIZED;
	},

	/**
	 * @return {Boolean} Returns true when the organizer requires a response from the attendee
	 */
	isMeetingResponseRequired: function()
	{
		return this.isMeetingReceived() && this.get('responsestatus') !== Zarafa.core.mapi.ResponseStatus.RESPONSE_NONE;
	},

	/**
	 * @return {Boolean} Returns true if the {@link Zarafa.core.data.AppointmentRecord AppointmentRecord} is
	 * recurring appointment.
	 */
	isRecurring: function()
	{
		return this.get('recurring') === true && !this.hasIdProp('basedate');
	},

	/**
	 * @return {Boolean} Returns true if the {@link Zarafa.core.data.AppointmentRecord AppointmentRecord} is
	 * recurring occurrence appointment.
	 */
	isRecurringOccurrence: function()
	{
		return Ext.isDate(this.get('basedate'));
	},

	/**
	 * @return {Boolean} Returns true if the {@link Zarafa.core.data.AppointmentRecord AppointmentRecord} is
	 * recurring occurrence appointment.
	 */
	isRecurringException: function()
	{
		return Ext.isDate(this.get('basedate')) && this.get('exception') === true;
	},

	/**
	 * Convenience method for determining if the message is a sub message of another message.
	 * For appointment record we need to also check if message is not an exception/occurrence of recurring appointment.
	 * @return {Boolean} True if this message is a sub message.
	 */
	isSubMessage: function()
	{
		// Recurring occurrences can never be embedded message
		if(this.isRecurringOccurrence()) {
			return false;
		}

		return Zarafa.calendar.AppointmentRecord.superclass.isSubMessage.apply(this, arguments);
	},

	/**
	 * @return {Boolean} Returns true if this
	 */
	hasRecurringExceptions: function()
	{
		// The appointment is not open, we don't know if there is an exception or not
		if (!this.isOpened()) {
			return undefined;
		}

		// If the appointment is not recurring, or this is an occurrence, there are no exceptions.
		if (!this.isRecurring()) {
			return false;
		}

		// If the appointment is recurring and having any exceptions.
		if (this.isRecurring() && this.get('recurrence_numexcept') > 0) {
			return true;
		}

		// Search if the recurring appointment has an exception attachment.
		var subStore = this.getSubStore('attachments');
		if (!subStore) {
			return false;
		}

		return subStore.findBy(function(attach) { return attach.isRecurrenceException(); }) >= 0;
	},

	/**
	 * Called by the store after the record was opened successfully.
	 * @private
	 */
	afterOpen: function()
	{
		if (this.isRecurring()) {
			// Recurring appointments can be opened as series or as occurrence.
			// When opening as a series, we actually have all the data for a single
			// occurrence (the one the user selected from the UI), which we are overriding
			// with the data about the series. However, the occurrence and series have
			// slight differences. The series doesn't have a basedate, and the startdate
			// and duedate properties are the values from the first occurrence.
			// When the record is being opened, it might already have been hooked to a
			// UI componennt, which at this moment has been initialized with the data
			// from the occurrence. Force all fields which will likely to be different
			// between the occurrence and series to be marked as modified, forcing the
			// UI to reinitialize the components which belong to it.
			this.modified = this.modified || {};
			this.modified['startdate'] = this.get('startdate');
			this.modified['duedate'] = this.get('duedate');
			this.modified['basedate'] = this.get('basedate');
			if (this.trackUpdateModifications === true) {
				this.updateModifications = this.updateModifications || {};
				this.updateModifications['startdate'] = this.get('startdate');
				this.updateModifications['duedate'] = this.get('duedate');
				this.updateModifications['basedate'] = this.get('basedate');
			}
		}

		Zarafa.calendar.AppointmentRecord.superclass.afterOpen.call(this);

		this.opened = !this.isRecurring();
		this.openedSeries = !this.opened;

		this.updateMeetingRecipients();
	},

	/**
	 * @return {Boolean} true if the record has been fully loaded.
	 */
	isOpened: function()
	{
		if (!this.isRecurring()) {
			return this.opened === true;
		} else {
			return this.openedSeries === true;
		}
	},

	/**
	 * @return {Boolean} true if the record is copied record.
	 */
	isCopied: function ()
	{
		return (this.get('auxiliary_flags') & Zarafa.core.mapi.AppointmentAuxiliaryFlags.auxApptFlagCopied) > 0;
	},

	/**
	 * Add action to Message Action list. When the added MessageAction is 'Send',
	 * then this will automatically {@link #generateMeetingTimeInfo update the meetingTimeInfo} as well.
	 * @param {String} name The action name to add to the list.
	 * @param {String} value The value attached to the action name
	 */
	addMessageAction: function(name, value)
	{
		Zarafa.calendar.AppointmentRecord.superclass.addMessageAction.apply(this, arguments);
		if (name === 'send') {
			var is_html = !!this.get('isHTML');
			this.addMessageAction('meetingTimeInfo', this.generateMeetingTimeInfo(this.getBody(), is_html));
			this.addMessageAction('mti_html', true);
		}
		if (name === 'meetingTimeInfo') {
			// If the record has not been opened yet, the body will not be part of the
			// meetingTimeInfo. In this case the server needs to add this.
			if(!this.isOpened()){
				this.addMessageAction('append_body', true);
			} else {
				this.deleteMessageAction('append_body');
			}
		}
	},

	/**
	 * Called by Extjs whenever {@link #endEdit} has been called (or {@link #set} without {@link #editing} enabled).
	 * If this {@link #hasMessageAction messageAction} 'send' has been set, then this will automatically
	 * {@link #generateMeetingTimeInfo update the meetingTimeInfo} as well.
	 * @override
	 * @private
	 */
	afterEdit: function()
	{
		if (this.isMeeting() && this.hasMessageAction('send')) {
			var is_html = !!this.get('isHTML');
			this.addMessageAction('meetingTimeInfo', this.generateMeetingTimeInfo(this.getBody(), is_html));
			this.addMessageAction('mti_html', true);
		}
		this.set('timezone_iana', Intl.DateTimeFormat().resolvedOptions().timeZone);
		Zarafa.calendar.AppointmentRecord.superclass.afterEdit.apply(this, arguments);
	},

	/**
	 * This will update all timezone properties inside this record, the properties
	 * will be initialized according to the timezone information which is currently
	 * valid for this browser (See {@link Date#getTimezoneStruct}).
	 */
	updateTimezoneInformation: function()
	{
		var tz = Date.getTimezoneStruct();

		this.beginEdit();
		this.set('timezone', tz.timezone);
		this.set('timezone_timezonedst', tz.timezonedst);
		this.set('timezone_dstendmonth', tz.dstendmonth);
		this.set('timezone_dstendweek', tz.dstendweek);
		this.set('timezone_dstendday', tz.dstendday);
		this.set('timezone_dstendhour', tz.dstendhour);
		this.set('timezone_dststartmonth', tz.dststartmonth);
		this.set('timezone_dststartweek', tz.dststartweek);
		this.set('timezone_dststartday', tz.dststartday);
		this.set('timezone_dststarthour', tz.dststarthour);
		this.endEdit();
	},

	/**
	 * This will update the {@link Zarafa.calendar.AppointmentRecord AppointmentRecord} recipients
	 * based on the current {@link Zarafa.core.mapi.MeetingStatus 'meeting' status}.
	 * If this appointment is a {@link #isMeeting meeting} then the organizer will be added into
	 * the recipients table otherwise all recipients will be removed.
	 */
	updateMeetingRecipients: function()
	{
		var recipientStore = this.getSubStore('recipients');
		if (!Ext.isDefined(recipientStore)) {
			return;
		}

		if (this.isMeeting()) {
			var existingOrganizer = false;
			// find index of organizer's record
			var orgIndex = recipientStore.findBy(function(record, id) {
				if(record.isMeetingOrganizer()) {
					existingOrganizer = record;
					return true;
				}
			}, this);

			// Check if organizer is available but mis-matched with record props
			if (orgIndex !== -1 && this.isOrganizerChanged(existingOrganizer.get('entryid'))) {
				// First remove and change organizer according to updated properties
				recipientStore.remove(existingOrganizer, true);
				orgIndex = -1;
			}

			if (orgIndex < 0) {
				var props = {
					recipient_type: Zarafa.core.mapi.RecipientType.MAPI_ORIG,
					recipient_flags: Zarafa.core.mapi.RecipientFlags.recipSendable | Zarafa.core.mapi.RecipientFlags.recipOrganizer,
					recipient_trackstatus: Zarafa.core.mapi.ResponseStatus.RESPONSE_ORGANIZED
				};

				// Check if the sent_representing_* properties are there,
				// if that is the case the organizer is the represented user,
				// otherwise the real sender is the organizer,
				if (this.get('sent_representing_entryid')) {
					Ext.apply(props, {
						display_name: this.get('sent_representing_name'),
						smtp_address: this.get('sent_representing_email_address'),
						address_type: this.get('sent_representing_address_type'),
						entryid: this.get('sent_representing_entryid')
					});
				} else {
					Ext.apply(props, {
						display_name: this.get('sender_name'),
						smtp_address: this.get('sender_email_address'),
						address_type: this.get('sender_address_type'),
						entryid: this.get('sender_entryid')
					});
				}

				// no organizer found then, add one
				// Apply fake ID to prevent is being marked as phantom
				var organizer = Zarafa.core.data.RecordFactory.createRecordObjectByCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_RECIPIENT, props, -1);

				// Act like a dummy store load operation to add organizer in recipient sub store
				// without any changes being registered.
				var existingRecords = [];
				recipientStore.each(function(rec){
					// Copy the record to get a separate instance instead of referring the same instance.
					existingRecords.push(rec.copy());
				});
				existingRecords.push(organizer);

				recipientStore.loadRecords({records: existingRecords}, undefined, true);

				// Register for an update event of ShadowStore to add organizer every time
				// when server responded about recipient(s) related changes
				container.getShadowStore().on('update', this.addOrganizerAfterUpdate, this);
			}

			// Apply sorting to the RecipientStore as we want
			// the organizer to be sorted to the start of the store,
			// and the rest to be applied on the rowid.
			recipientStore.sortBy('ASC', function(a, b) {
				var aOrganizer = a.isMeetingOrganizer();
				var bOrganizer = b.isMeetingOrganizer();

				if (aOrganizer !== bOrganizer) {
					return aOrganizer ? -1 : 1;
				} else {
					return a.get('rowid') - b.get('rowid');
				}
			});

		} else if (recipientStore.getCount() > 0) {
			// Normal appointments don't have any recipients.
			recipientStore.removeAll();
		}
	},

	/**
	 * Helper function to check if available organizer into recipient-sub-store is according to
	 * parent record's sent_representing_* or sender_* properties or not.
	 * @param {String} organizerId EntryID of current organizer
	 * @return {Boolean} True if organizer is same, false if organizer is changed
	 */
	isOrganizerChanged: function(organizerId)
	{
		var isRepresentingId = Zarafa.core.EntryId.compareEntryIds(organizerId, this.get('sent_representing_entryid'));
		var isSenderId = Zarafa.core.EntryId.compareEntryIds(organizerId, this.get('sender_entryid'));
		return !isRepresentingId || !isSenderId;
	},

	/**
	 * Event handler executed when update event will be fired if a Record has been updated.
	 * {@link #updateMeetingRecipients} method will be called to add organizer into the recipient sub store,
	 * while server will send modified recipient set.
	 * @param {Zarafa.core.data.ShadowStore} store The store from which any record(s) gets updated
	 * @param {Ext.data.Record} record The Record that was updated
	 * @param {String} operation The update operation being performed
	 */
	addOrganizerAfterUpdate: function(store, record, operation)
	{
		if(operation === Ext.data.Record.COMMIT) {
			// Unregister the update event from shadow store as it will be registered again if there is no organizer found
			container.getShadowStore().un('update', this.addOrganizerAfterUpdate, this);
			this.updateMeetingRecipients();
		}
	},

	/**
	 * Update the current appointment to a meeting, this will update the 'meeting'
	 * and 'responsestatus' properties to the proper values.
	 */
	convertToMeeting: function()
	{
		this.beginEdit();
		this.set('meeting', Zarafa.core.mapi.MeetingStatus.MEETING);
		this.set('responsestatus', Zarafa.core.mapi.ResponseStatus.RESPONSE_ORGANIZED);
		this.updateMeetingRecipients();
		this.endEdit();
	},

	/**
	 * Update the current meeting to a normal appointment, this will update the 'meeting'
	 * and 'responsestatus' properties to the proper values.
	 */
	convertToAppointment: function()
	{
		this.beginEdit();
		this.set('meeting', Zarafa.core.mapi.MeetingStatus.NONMEETING);
		this.set('responsestatus', Zarafa.core.mapi.ResponseStatus.RESPONSE_NONE);
		this.updateMeetingRecipients();
		this.endEdit();
	},

	/**
	 * Function is used to convert a series record to an occurrence record.
	 * When we are opening a single occurrence from series record then we need to modify some properties
	 * that are different on occurrence record and also need to add basedate as {@link #idProperties}.
	 * so the record can be correctly identified as an occurrence record.
	 * @return {Zarafa.core.data.IPMRecord} record that can be used to open occurrence of a series.
	 */
	convertToOccurrenceRecord: function()
	{
		// only convert it when its a series record
		if(this.hasIdProp('basedate') === false) {
			var cloneRec = this.copy();

			// For occurrences the the 'recurring' property must
			// always be false (as the occurrence itself doesn't recur.
			cloneRec.set('recurring', false);

			// For occurrences, the 'basedate' is part of the unique id
			cloneRec.addIdProp('basedate');

			// set delegate properties if needed
			if(!cloneRec.userIsStoreOwner()) {
				var storeRecord = container.getHierarchyStore().getById(cloneRec.get('store_entryid'));
				if(storeRecord) {
					cloneRec.setDelegatorInfo(storeRecord, true);
				}
			}

			return cloneRec;
		}

		return this;
	},

	/**
	 * Function is used to convert an occurrence record to a series record.
	 * Series and occurrence records has some silghtly differences in properties so we need to reset some
	 * properties which are only valid for occurrence record not for series record.
	 * Also we need to remove basedate property from {@link #idProperties} so record will be correctly identified
	 * as a series record.
	 * @return {Zarafa.core.data.IPMRecord} record that can be used to open whole series.
	 */
	convertToSeriesRecord: function()
	{
		// only convert it when its a occurrence record
		if(this.hasIdProp('basedate') === true) {
			var cloneRec = this.copy();

			// Series always have the 'recurring' property to true
			// (occurrences themselves have this property set to false
			cloneRec.set('recurring', true);
			// The 'basedate' is not used in series
			cloneRec.set('basedate', '');
			// A series cannot be an exception
			cloneRec.set('exception', false);

			// Remove the 'basedate' from the unique id
			cloneRec.removeIdProp('basedate');

			return cloneRec;
		}

		return this;
	},

	/**
	 * Function is used to convert 'recurrence_weekdays' {@link Zarafa.common.recurrence.data.RecurrenceSubtype subtype}
	 * property into a comma separated list which contains selected week days. To display weekday information
	 * as a part of recurring pattern.
	 * @return {String} A string containing selected week days separated by comma.
	 */
	prepareWeekDaysString: function()
	{
		var weekDaysObject = new Ext.util.MixedCollection();
		var weekStart = container.getSettingsModel().get('zarafa/v1/main/week_start');
		var checkedWeekDays = [];

		// Dynamically prepare an object which contains mapping between day name and its value according to the configured week start setting.
		for (var i = 0; i < Date.dayNames.length; i++) {
			weekDaysObject.add({
				dayName: Date.dayNames[(weekStart + i) % 7],
				dayValue: Math.pow(2, (weekStart + i) % 7)
			});
		}

		// Get the property value from record to prepare array of selected week days.
		var weekdays = this.get('recurrence_weekdays');
		weekDaysObject.each(function(dayObject) {
			if(!!(dayObject.dayValue & weekdays)){
				checkedWeekDays.push(dayObject.dayName);
			}
		});

		// Check if there is more than one days are selected, to append 'and' word before last week day
		if(checkedWeekDays.length > 1) {
			var lastWeekDayIndex = checkedWeekDays.length - 1;
			checkedWeekDays[lastWeekDayIndex] = _('and ') + checkedWeekDays[lastWeekDayIndex];
		}

		return (checkedWeekDays.length === 2) ? checkedWeekDays.join(" ") : checkedWeekDays.join(", ");
	},

	/**
	 * Builds URL to download appointment as RFC2445-formatted stream with ics extension.
	 * @param {Boolean} allAsZip (optional) True to downloading all the attachments as ZIP
	 * @return {String} URL for downloading message as file.
	 */
	getDownloadMessageUrl: function(allAsZip)
	{
		var url = container.getBaseURL();
		url = Ext.urlAppend(url, 'load=download_appointment');
		url = Ext.urlAppend(url, 'storeid=' + this.get('store_entryid'));
		url = Ext.urlAppend(url, 'entryid=' + this.get('entryid'));

		return url;
	}
});

Zarafa.core.data.RecordFactory.setBaseClassToMessageClass('IPM.Appointment', Zarafa.calendar.AppointmentRecord);
Zarafa.core.data.RecordFactory.setBaseClassToMessageClass('IPM.OLE.CLASS.{00061055-0000-0000-C000-000000000046}', Zarafa.calendar.AppointmentRecord);
/*
 * #dependsFile client/zarafa/core/data/RecordFactory.js
 * #dependsFile client/zarafa/core/data/MessageRecord.js
 */
Ext.namespace('Zarafa.mail');

/**
 * @class Zarafa.mail.MailRecordFields
 * Array of {@link Ext.data.Field field} configurations for the
 * {@link Zarafa.core.data.IPMRecord IPMRecord} object.
 * These fields will be available in all 'IPM.Note' type messages.
 */
Zarafa.mail.MailRecordFields = [
	{name: 'importance', type: 'int', defaultValue: Zarafa.core.mapi.Importance.NORMAL},
	{name: 'private', type: 'boolean', defaultValue: false},
	{name: 'sensitivity', type: 'int', defaultValue: Zarafa.core.mapi.Sensitivity.NONE},
	{name: 'flag_status'},
	{name: 'flag_icon'},
	{name: 'reply_time', type: 'date', dateFormat: 'timestamp', defaultValue: null},
	{name: 'reply_requested'},
	{name: 'response_requested'},
	{name: 'source_message_info'},
	{name: 'reminder', type: 'boolean', defaultValue: false},
	{name: 'reminder_time', type:'date', dateFormat:'timestamp', defaultValue: null},
	{name: 'flag_request'},
	{name: 'flag_due_by', type:'date', dateFormat:'timestamp', defaultValue: null},
	{name: 'flag_complete_time', type:'date', dateFormat:'timestamp', defaultValue: null},
	{name: 'block_status', type: 'int', defaultValue: undefined},
	{name: 'stubbed', type: 'boolean', defaultValue: false},
	{name: 'startdate', type: 'date', dateFormat: 'timestamp', defaultValue: null},
	{name: 'duedate', type: 'date', dateFormat: 'timestamp', defaultValue: null},
	{name: 'user_image', type: 'string'},
	{name: 'depth', type: 'int', defaultValue: 0},
	{name: 'conversation_count', type: 'int', defaultValue: 0},
	{name: 'conversation_id'},
	{name: 'folder_name'}
];

Zarafa.mail.MailRecordPhantomHandler = function(record) {
	var readReceipt = container.getSettingsModel().get('zarafa/v1/contexts/mail/always_request_readreceipt');
	record.set('read_receipt_requested', readReceipt);
};

Zarafa.core.data.RecordFactory.addFieldToMessageClass('REPORT.IPM.Note', Zarafa.mail.MailRecordFields);
Zarafa.core.data.RecordFactory.addFieldToMessageClass('REPORT.IPM.Note', Zarafa.core.data.MessageRecordFields);
Zarafa.core.data.RecordFactory.setSubStoreToMessageClass('REPORT.IPM.Note', 'reply-to', Zarafa.core.data.IPMRecipientStore);

Zarafa.core.data.RecordFactory.addFieldToMessageClass('IPM.Note', Zarafa.mail.MailRecordFields);
Zarafa.core.data.RecordFactory.addFieldToMessageClass('IPM.Note', Zarafa.core.data.MessageRecordFields);
Zarafa.core.data.RecordFactory.setSubStoreToMessageClass('IPM.Note', 'reply-to', Zarafa.core.data.IPMRecipientStore);

Zarafa.core.data.RecordFactory.addFieldToMessageClass('IPM.Schedule', Zarafa.mail.MailRecordFields);

Zarafa.core.data.RecordFactory.addListenerToMessageClass('IPM.Note', 'createphantom', Zarafa.core.data.MessageRecordPhantomHandler);
Zarafa.core.data.RecordFactory.addListenerToMessageClass('IPM.Note', 'createphantom', Zarafa.mail.MailRecordPhantomHandler);

Zarafa.core.data.RecordFactory.addDefaultValueToMessageClass('IPM.Note', 'message_flags', Zarafa.core.mapi.MessageFlags.MSGFLAG_UNSENT);
Zarafa.core.data.RecordFactory.addDefaultValueToMessageClass('REPORT.IPM.Note', 'message_flags', Zarafa.core.mapi.MessageFlags.MSGFLAG_UNSENT);

Zarafa.core.data.RecordFactory.setBaseClassToMessageClass('REPORT.IPM.Note', Zarafa.core.data.MessageRecord);
Zarafa.core.data.RecordFactory.setBaseClassToMessageClass('IPM.Note', Zarafa.core.data.MessageRecord);
/*
 * #dependsFile client/zarafa/core/data/RecordFactory.js
 * #dependsFile client/zarafa/core/data/MessageRecord.js
 */
Ext.namespace('Zarafa.task');

/**
 * @class Zarafa.task.TaskRecordFields
 * Array of {@link Ext.data.Field field} configurations for the
 * {@link Zarafa.core.data.IPMRecord IPMRecord} object.
 * These fields will be available in all 'IPM.Task' type messages.
 */
Zarafa.task.TaskRecordFields = [
	{name: 'importance', type: 'int', defaultValue: Zarafa.core.mapi.Importance.NORMAL},
	{name: 'message_flags', type: 'int', defaultValue: Zarafa.core.mapi.MessageFlags.MSGFLAG_READ},
	{name: 'last_modification_time', type: 'date', dateFormat: 'timestamp', defaultValue: null},
	{name: 'recurring', type: 'boolean'},
	{name: 'startdate', type: 'date', dateFormat: 'timestamp', defaultValue: null},
	{name: 'duedate', type: 'date', dateFormat: 'timestamp', defaultValue: null},
	{name: 'commonstart', type: 'date', dateFormat: 'timestamp', defaultValue: null},
	{name: 'commonend', type: 'date', dateFormat: 'timestamp', defaultValue: null},
	{name: 'taskstate', type: 'int', defaultValue: Zarafa.core.mapi.TaskState.NORMAL},
	{name: 'taskmode', type: 'int', defaultValue: Zarafa.core.mapi.TaskMode.NOTHING},
	{name: 'tasksoc', type: 'boolean', defaultValue: true},
	{name: 'taskupdates', type: 'boolean', defaultValue: true},
	{name: 'reminder', type: 'boolean', defaultValue: false},
	{name: 'reminderdate', type: 'date', dateFormat: 'timestamp', defaultValue: null},
	{name: 'owner'},
	{name: 'status', type: 'int', defaultValue: Zarafa.core.mapi.TaskStatus.NOT_STARTED},
	{name: 'percent_complete', type: 'float', defaultValue: 0},
	{name: 'complete', type: 'boolean', defaultValue: false},
	{name: 'sensitivity', type: 'int', defaultValue: Zarafa.core.mapi.Sensitivity.NONE},
	{name: 'private', type: 'boolean', defaultValue: false},
	{name: 'totalwork', type: 'int', defaultValue: 0},
	{name: 'actualwork', type: 'int', defaultValue: 0},
	{name: 'mileage'},
	{name: 'billing_information'},
	{name: 'companies'},
	{name: 'date_completed', type: 'date', dateFormat: 'timestamp', defaultValue: null},
	{name: 'reminder', type: 'boolean', defaultValue: false},
	{name: 'reminder_time', type: 'date', dateFormat: 'timestamp', defaultValue: null},
	{name: 'hide_attachments', type: 'boolean', defaultValue: false},
	{name: 'ownership', type: 'int', defaultValue:Zarafa.core.mapi.TaskOwnership.NEWTASK},
	{name: 'conversation_topic'},
	{name: 'task_assigner'},
	{name: 'task_assigned_time', type: 'date', dateFormat: 'timestamp', defaultValue: null},
	{name: 'taskhistory', type: 'int', defaultValue:Zarafa.core.mapi.TaskHistory.NONE},
	{name: 'task_goid'},
	{name: 'tasklastuser'},
	{name: 'flag_status'},
	{name: 'flag_icon'},
	{name: 'flag_request'},
	{name: 'flag_due_by', type:'date', dateFormat:'timestamp', defaultValue: null},
	{name: 'flag_complete_time', type:'date', dateFormat:'timestamp', defaultValue: null},
	{name: 'task_acceptance_state', type: 'int', defaultValue:Zarafa.core.mapi.TaskAcceptanceState.NOT_DELEGATED},
	{name: 'updatecount', type: 'int'},
	{name: 'task_not_found', type: 'boolean'}
];

Zarafa.core.data.RecordFactory.addFieldToMessageClass('IPM.Task', Zarafa.task.TaskRecordFields);
Zarafa.core.data.RecordFactory.setSubStoreToMessageClass('IPM.Task', 'reply-to', Zarafa.core.data.IPMRecipientStore);
Zarafa.core.data.RecordFactory.addListenerToMessageClass('IPM.Task', 'createphantom', function(record, data) {
	record.beginEdit();

	if (!data || !Ext.isDefined(data.reminder)) {
		var reminder = false;
		var store = container.getHierarchyStore().getById(record.get('store_entryid'));
		if (!store || !store.isPublicStore()) {
			reminder = container.getSettingsModel().get('zarafa/v1/contexts/task/default_reminder');
		}
		record.set('reminder', reminder);
	}

	if (record.get('reminder')) {
		var time = new Date().clearTime().add(Date.MINUTE, container.getSettingsModel().get('zarafa/v1/contexts/task/default_reminder_time'));
		record.set('reminder_time', time);
	}

	record.endEdit();
});

/**
 * @class Zarafa.task.TaskRecord
 * @extends Zarafa.core.data.MessageRecord
 *
 * An extension to the {@link Zarafa.core.data.MessageRecord MessageRecord} specific to Task Request/Response Messages.
 */
Zarafa.task.TaskRecord = Ext.extend(Zarafa.core.data.MessageRecord, {
	/**
	 * @return {Boolean} true if the {@link Zarafa.core.data.TaskRecord TaskRecord} is task request or
	 * task is not normal task else false.
	 */
	isTaskRequest: function()
	{
		return (this.get('taskmode') !== Zarafa.core.mapi.TaskMode.NOTHING) || this.isMessageClass('IPM.TaskRequest');
	},

	/**
	 * Function is used to determine that task has been assigner copy.
	 *
	 * @returns {boolean} true if task is assigner copy else false.
	 */
	isTaskOrganized: function ()
	{
		return this.get('taskstate') === Zarafa.core.mapi.TaskState.ACCEPT ||
			this.get('taskstate') === Zarafa.core.mapi.TaskState.DECLINE;
	},

	/**
	 * Function is used to determine that task has been delegated/assigned by the user. Here user
	 * rol has task assigner
	 *
	 * @returns {boolean} true if user has assigned task to assignee else false.
	 */
	isTaskDelegated: function()
	{
		return this.get('ownership') === Zarafa.core.mapi.TaskOwnership.DELEGATEDTASK;
	},

	/**
	 * Function is used to determine user is task owner.
	 *
	 * @returns {boolean} true if user is task owner/assignee of this task else false
	 */
	isTaskOwner: function ()
	{
		return this.get('ownership') === Zarafa.core.mapi.TaskOwnership.OWNTASK;
	},

	/**
	 * Function is used to determine task is received task from assigner.
	 *
	 * @returns {boolean} true if task is received task from assignor else false.
	 */
	isTaskReceived: function()
	{
		return this.get('taskstate') === Zarafa.core.mapi.TaskState.OWNER;
	},

	/**
	 * Function is used to determine that task is assigned task to assignee by user(assigner).
	 *
	 * @returns {boolean} true if task is assigned by assigner else false.
	 */
	isTaskAssigned: function ()
	{
		return this.get('taskhistory') === Zarafa.core.mapi.TaskHistory.ASSIGNED;
	},

	/**
	 * Function is used to determine that task is accepted by user(assignee).
	 *
	 * @returns {boolean} true if task is accepted by assignee else false.
	 */
	isTaskAccepted: function ()
	{
		return this.get('taskhistory') === Zarafa.core.mapi.TaskHistory.ACCEPTED;
	},

	/**
	 * Function is used to determine that task is updated by user(assignee).
	 *
	 * @returns {boolean} true if task is updated by assignee else false.
	 */
	isTaskUpdated: function ()
	{
		return this.get('taskhistory') === Zarafa.core.mapi.TaskHistory.UPDATED;
	},

	/**
	 * Function is used to identify the task is normal task.
	 *
	 * @returns {boolean} true if task is not assigned task else false.
	 */
	isNormalTask: function ()
	{
		var taskState = this.get('taskstate');

		return (taskState === Zarafa.core.mapi.TaskState.NORMAL ||
		taskState === Zarafa.core.mapi.TaskState.OWNER_NEW);
	},

	/**
	 * Function is used to identify the task was declined by the user(assignee).
	 *
	 * @returns {boolean} true if task is declined task else false.
	 */
	isTaskDeclined: function ()
	{
		return (this.get('taskstate') === Zarafa.core.mapi.TaskState.DECLINE &&
			this.get('taskhistory') === Zarafa.core.mapi.TaskHistory.DECLINED);
	},

	/**
	 * Function is used to determine that is assigned but not
	 * sent to assignee by assigner.
	 *
	 * @returns {boolean} true if task is draft assigned task else false.
	 */
	isDraftAssignedTask: function()
	{
		return (this.get('taskstate') === Zarafa.core.mapi.TaskState.OWNER_NEW &&
				this.get('taskmode') === Zarafa.core.mapi.TaskMode.REQUEST);
	},

	/**
	 * Function is used to determine that task in not yet accepted or declined by
	 * assignee.
	 *
	 * @return {Boolean} true if user as an assignee yet not accepted or declined task
	 * else false;
	 */
	isTaskNotResponded: function ()
	{
		return (this.isTaskOwner() && this.isTaskReceived() && this.isTaskAssigned());
	},

	/**
	 * Generates task request response comment information which will be added to task request response body.
	 * @return {String} generated body message.
	 */
	generateTaskCommentsInfo: function (commentText)
	{
		var taskCommentsInfo = commentText || '';
		if (Ext.isEmpty(this.get('body'))) {
			return taskCommentsInfo;
		}

		taskCommentsInfo += '\n---------\n' + this.get('body');
		return taskCommentsInfo;
	},

	/**
	 * Respond to a task request with the correct {@link Zarafa.core.mapi.TaskMode}.
	 *
	 * @param {Zarafa.core.mapi.ResponseStatus} responseType accept/decline
	 */
	respondToTaskRequest: function(responseType, comments, editResponse)
	{
		this.sendTaskRequestResponse(responseType, this.generateTaskCommentsInfo(comments), editResponse);
	},

	/**
	 * Sends a requests to accept/decline a incoming task request.
	 *
	 * @param {Zarafa.core.mapi.ResponseStatus} responseType accept/decline/propose new time
	 * @param {Boolean} editResponse true if no response should be send to organizer else false
	 * @private
	 */
	sendTaskRequestResponse: function(responseType, comments, editResponse)
	{
		if (Ext.isDefined(responseType)) {
			switch(responseType)
			{
				case Zarafa.core.mapi.TaskMode.ACCEPT:
					this.addMessageAction('action_type', 'acceptTaskRequest');
					this.set('taskhistory', Zarafa.core.mapi.TaskHistory.ACCEPTED, true);
					break;
				case Zarafa.core.mapi.TaskMode.DECLINE:
					this.addMessageAction('action_type', 'declineTaskRequest');
					this.set('taskhistory', Zarafa.core.mapi.TaskHistory.DECLINED, true);
					break;
			}

			this.addMessageAction('response_type', responseType);
			if (editResponse) {
				this.addMessageAction('task_comments_info', comments);
			}
			this.getStore().save(this);
		}
	},

	/**
	 * Function will set the message action either 'declineAndDelete' when assignee
	 * wants to delete task from task folder and send decline task response to assigner,
	 * 'completeAndDelete' when assignee wants to delete task and send complete task response
	 * to assigner or silently delete selected task/task request record from grid.
	 *
	 * @param {String} action which is either 'declineAndDelete', 'completeAndDelete' or 'delete'.
	 */
	deleteIncompleteTask: function (action)
	{
		if (action !== 'delete') {
			this.addMessageAction('action_type', action);
		}

		var store = this.getStore();
		store.remove(this);
		store.save(this);
	},

	/**
	 * Update the current task to a task request.
	 */
	convertToTaskRequest: function()
	{
		this.beginEdit();
		this.set('taskstate', Zarafa.core.mapi.TaskState.OWNER_NEW);
		this.set('taskmode', Zarafa.core.mapi.TaskMode.REQUEST);
		this.endEdit();
	}
});
Zarafa.core.data.RecordFactory.setBaseClassToMessageClass('IPM.Task', Zarafa.task.TaskRecord);
/*
 * #dependsFile client/zarafa/core/data/RecordFactory.js
 */
Ext.namespace('Zarafa.calendar');
/**
 * @class Zarafa.calendar.MeetingRequestRecordFields
 *
 * Array of {@link Ext.data.Field field} configurations for the
 * {@link Zarafa.core.data.IPMRecord IPMRecord} object.
 * These fields will be available in all 'IPM.Schedule' type messages.
 */
Zarafa.calendar.MeetingRequestRecordFields = [
	{name: 'conflictinfo'},
	{name: 'appointment_not_found', type: 'boolean', defaultValue: false},
	{name: 'counter_proposal', type: 'boolean', defaultValue: false},
	{name: 'meetingtype', type: 'number', defaultValue: Zarafa.core.mapi.MeetingType.MEETING_NONE},
	{name: 'proposed_start_date', type:'date', dateFormat:'timestamp', defaultValue: null},
	{name: 'proposed_end_date', type:'date', dateFormat:'timestamp', defaultValue: null},
	{name: 'proposed_duration', type: 'number'},
	{name: 'appointment_entryid'},
	{name: 'appointment_store_entryid'},
	{name: 'appointment_parent_entryid'},
	{name: 'appointment_basedate', type: 'date', dateFormat: 'timestamp', defaultValue: null},
	{name: 'appointment_duedate', type:'date', dateFormat:'timestamp'},
	{name: 'appointment_startdate', type:'date', dateFormat:'timestamp'},
	{name: 'appointment_recurring', type: 'boolean' },
	{name: 'appointment_recurring_pattern', type: 'string'},
	{name: 'appointment_startdate_recurring', type: 'date', dateFormat: 'timestamp'},
	{name: 'appointment_enddate_recurring', type: 'date', dateFormat: 'timestamp'},
	{name: 'appointment_exception', type: 'boolean', defaultValue: false},
	{name: 'appointment_location', type: 'string'},
	{name: 'updatecounter', type: 'int', defaultValue: 0},
	{name: 'meeting_updated', type: 'boolean', defaultValue: false},
	{name: 'goid', type: 'string'},
	{name: 'goid2', type: 'string'}
];

Zarafa.core.data.RecordFactory.addFieldToMessageClass('IPM.Schedule', Zarafa.calendar.MeetingRequestRecordFields);
Zarafa.core.data.RecordFactory.addFieldToMessageClass('IPM.Schedule', Zarafa.core.data.MessageRecordFields);
Zarafa.core.data.RecordFactory.setSubStoreToMessageClass('IPM.Schedule', 'reply-to', Zarafa.core.data.IPMRecipientStore);
Zarafa.core.data.RecordFactory.addListenerToMessageClass('IPM.Schedule', 'createphantom', Zarafa.core.data.MessageRecordPhantomHandler);

/**
 * @class Zarafa.calendar.MeetingRequestRecord
 * @extends Zarafa.calendar.AppointmentRecord
 *
 * An extension to the {@link Zarafa.calendar.AppointmentRecord AppointmentRecord} specific to Meeting Request/Response Messages.
 */
Zarafa.calendar.MeetingRequestRecord = Ext.extend(Zarafa.calendar.AppointmentRecord, {
	/**
	 * @return {Boolean} Returns true, overridden from {@link Zarafa.calendar.AppointmentRecord}
	 * as Meeting Requests in your inbox are always received.
	 */
	isMeetingReceived: function()
	{
		return true;
	},

	/**
	 * @return {Boolean} Returns true if the {@link Zarafa.core.data.MessageRecord MessageRecord} is a
	 * meeting response message.
	 */
	isMeetingRequestResponse: function()
	{
		return this.isMessageClass('IPM.Schedule.Meeting.Resp', true);
	},

	/**
	 * @return {Boolean} Returns true if the {@link Zarafa.core.data.MessageRecord MessageRecord} is a
	 * meeting cancellation response message.
	 */
	isMeetingRequestCanceled: function()
	{
		return this.isMessageClass('IPM.Schedule.Meeting.Canceled', true);
	},

	/**
	 * @return {Boolean} Returns true if the {@link Zarafa.core.data.MessageRecord MessageRecord} is a
	 * meeting request message.
	 */
	isMeetingRequest: function()
	{
		return this.isMessageClass('IPM.Schedule.Meeting.Request', true);
	},

	/**
	 * @return {Boolean} Returns true if the {@link Zarafa.core.data.MeetingRequestRecord MeetingRequestRecord} is a
	 * recurring meeting.
	 * @overridden
	 */
	isRecurring: function()
	{
		return this.get('appointment_recurring') === true;
	},

	/**
	 * @return {Boolean} Returns true if the {@link Zarafa.core.data.MeetingRequestRecord MeetingRequestRecord} is a
	 * recurring occurrence meeting.
	 * @overridden
	 */
	isRecurringOccurrence: function()
	{
		return Ext.isDate(this.get('appointment_basedate'));
	},

	/**
	 * @return {Boolean} Returns true if the {@link Zarafa.core.data.MeetingRequestRecord MeetingRequestRecord} is a
	 * recurring exception meeting.
	 * @overridden
	 */
	isRecurringException: function()
	{
		return Ext.isDate(this.get('appointment_basedate')) && this.get('appointment_exception') === true;
	},

	/**
	 * @return {Boolean} Returns true if the {@link Zarafa.core.data.MessageRecord MessageRecord} is and out of date
	 * meeting request/response.
	 */
	isMeetingOutOfDate: function()
	{
		return this.get('meetingtype') === Zarafa.core.mapi.MeetingType.MEETING_OUT_OF_DATE;
	},

	/**
	 * Function is used to check if duedate property value of
	 * {@link Zarafa.calendar.MeetingRequestRecord MeetingRequestRecord} is in past or not.
	 * @return {Boolean} true if meeting request/response is in past else false.
	 */
	isAppointmentInPast: function()
	{
		// @FIXME this actually depends on appointment_duedate property which is not returned for all meeting objects
		var dueDate = this.get('appointment_duedate');

		if(Ext.isDate(dueDate) && dueDate.getTime() < (new Date().getTime())) {
			return true;
		}

		return false;
	},

	/**
	 * Create a new {@link Zarafa.core.data.IPMRecord IPMRecord}. This record can be used to get all the properties
	 * of meeting record associated with this {@link Zarafa.calendar.MeetingRequestRecord MeetingRequestRecord}.
	 * @param {Boolean} viewAllProposals (optional) pass true if you want to show
	 * {@link Zarafa.calendar.dialogs.CounterProposalGrid CounterProposalGrid} with all the proposals.
	 * @return {Zarafa.core.data.IPMRecord} record which should be used to open
	 * {@link Zarafa.calendar.dialogs.AppointmentContentPanel AppointmentContentPanel}.
	 */
	convertToAppointmentRecord: function(viewAllProposals)
	{
		// get entryids of the corresponding meeting in calendar
		var appointmentEntryid = this.get('appointment_entryid');
		var appointmentParentEntryid = this.get('appointment_parent_entryid');
		var appointmentStoreEntryid = this.get('appointment_store_entryid');
		var appointmentBasedate = this.get('appointment_basedate');

		if(Ext.isEmpty(appointmentEntryid) || Ext.isEmpty(appointmentStoreEntryid)) {
			return;
		}

		if(!Ext.isBoolean(viewAllProposals)) {
			viewAllProposals = true;
		}

		var record = Zarafa.core.data.RecordFactory.createRecordObjectByMessageClass('IPM.Appointment', {
			entryid: appointmentEntryid,
			store_entryid: appointmentStoreEntryid,
			parent_entryid: appointmentParentEntryid,
			basedate: appointmentBasedate,
			counter_proposal: viewAllProposals,

			/*
			 * we need to provide some properties initially to the appointment record so when its set first time in
			 * the record dialog, checks which are using initial === true will work correct otherwise
			 * these properties will only be available after record is opened and at that time
			 * initial will be false
			 */
			meeting: Zarafa.core.mapi.MeetingStatus.MEETING,
			responsestatus: Zarafa.core.mapi.ResponseStatus.RESPONSE_ORGANIZED,
			startdate: this.get('appointment_startdate'),
			duedate: this.get('appointment_duedate')
		}, appointmentEntryid);

		return record;
	},

	/**
	 * This will update the {@link Zarafa.calendar.AppointmentRecord AppointmentRecord} recipients
	 * based on the current {@link Zarafa.core.mapi.MeetingStatus 'meeting' status}.
	 * If this appointment is a {@link #isMeeting meeting} then the organizer will be added into
	 * the recipients table otherwise all recipients will be removed. Overridden here by empty function
	 * as we don't need this functionality in {@link Zarafa.calendar.MeetingRequestRecord MeetingRequestRecord}.
	 * @hide
	 */
	updateMeetingRecipients: Ext.emptyFn,

	/**
	 * Meeting requests and responses live in mail folders and should be exported as
	 * RFC822 .eml streams (and zipped together with regular mails when multiple items
	 * are selected), not as ICS like a calendar appointment. Override the
	 * {@link Zarafa.calendar.AppointmentRecord AppointmentRecord} ICS URL with the
	 * mail-style eml/ZIP URL from {@link Zarafa.core.data.IPMRecord IPMRecord}.
	 * @param {Boolean} allAsZip (optional) True to download all selected messages as ZIP
	 * @return {String} URL for downloading message as eml file or ZIP archive.
	 */
	getDownloadMessageUrl: function(allAsZip)
	{
		var url = container.getBaseURL();
		url = Ext.urlAppend(url, 'load=download_message');
		url = Ext.urlAppend(url, 'storeid=' + this.get('store_entryid'));

		if (!allAsZip) {
			url = Ext.urlAppend(url, 'entryid=' + this.get('entryid'));
		} else {
			url = Ext.urlAppend(url, 'AllAsZip=true');
		}
		return url;
	}
});
Zarafa.core.data.RecordFactory.setBaseClassToMessageClass('IPM.Schedule', Zarafa.calendar.MeetingRequestRecord);
/*
 * #dependsFile client/zarafa/core/data/RecordFactory.js
 * #dependsFile client/zarafa/core/data/MessageRecord.js
 */
Ext.namespace('Zarafa.task');

Zarafa.core.data.RecordFactory.addFieldToMessageClass('IPM.TaskRequest', Zarafa.task.TaskRecordFields);
Zarafa.core.data.RecordFactory.addFieldToMessageClass('IPM.TaskRequest', Zarafa.core.data.MessageRecordFields);
Zarafa.core.data.RecordFactory.addListenerToMessageClass('IPM.TaskRequest', 'createphantom', Zarafa.core.data.MessageRecordPhantomHandler);
Zarafa.core.data.RecordFactory.setSubStoreToMessageClass('IPM.TaskRequest', 'reply-to', Zarafa.core.data.IPMRecipientStore);

/**
 * @class Zarafa.task.TaskRequestRecord
 * @extends Zarafa.task.TaskRecord
 *
 * An extension to the {@link Zarafa.core.data.MessageRecord MessageRecord} specific to
 * Task Request/Response Messages.
 */
Zarafa.task.TaskRequestRecord = Ext.extend(Zarafa.task.TaskRecord, {

	/**
	 * Function is used to determine that {@link Zarafa.task.TaskRequestRecord TaskRequest}
	 * is received from assigner.
	 *
	 * @return {boolean} True to task request is assigned to user by the assigner else false.
	 */
	isReceivedTaskRequestFromAssigner: function ()
	{
		return (this.isTaskOwner() && this.isTaskAssigned() &&
		this.get('taskmode') === Zarafa.core.mapi.TaskMode.REQUEST &&
		this.get('task_acceptance_state') === Zarafa.core.mapi.TaskAcceptanceState.DELEGATION_UNKNOWN);
	}
});

Zarafa.core.data.RecordFactory.setBaseClassToMessageClass('IPM.TaskRequest', Zarafa.task.TaskRequestRecord);