/*
 * #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: '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}
];

/**
 *	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.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 occurence (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 proeprty 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/ext-js/3-4/#!/api/Date for the meaning of these formatting instructions
		startocc = tmpStart.format(_('G:i'));
		tmpStart.setHours(endocc / 60);
		tmpStart.setMinutes(endocc % 60);
		// # TRANSLATORS: See http://docs.sencha.com/ext-js/3-4/#!/api/Date for the meaning of these formatting instructions
		endocc = tmpStart.format(_('G:i'));

		// # TRANSLATORS: See http://docs.sencha.com/ext-js/3-4/#!/api/Date for the meaning of these formatting instructions
		start = start.format(_('d/m/Y'));
		// # TRANSLATORS: See http://docs.sencha.com/ext-js/3-4/#!/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_OCCURENCES) {
			if (occTimeRange) {
				if (occSingleDayRank) {
					pattern = String.format(ngettext('Occurs every {0} effective {1} for {2} occurence from {3} to {4}.', 'Occurs every {0} effective {1} for {2} occurences from {3} to {4}.', numocc),
								type, start, numocc, startocc, endocc);
				} else {
					pattern = String.format(ngettext('Occurs every {0} {1} effective {2} for {3} occurence from {4} to {5}.', 'Occurs every {0} {1} effective {2} for {3} occurences {4} to {5}.', numocc),
								everyn, type, start, numocc, startocc, endocc);
				}
			} else {
				if (occSingleDayRank) {
					pattern = String.format(ngettext('Occurs every {0} effective {1} for {2} occurence.', 'Occurs every {0} effective {1} for {2} occurences.', numocc),
								type, start, numocc);
				} else {
					pattern = String.format(ngettext('Occurs every {0} {1} effective {2} for {3} occurence.', 'Occurs every {0} {1} effective {2} for {3} occurences.', 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.
	 * @return {String} generated body message.
	 */
	generateMeetingTimeInfo : function(responseText)
	{
		var messageBody = responseText || '';
		var startDate = this.get('startdate');
		var dueDate = this.get('duedate');
		var meetingLocation = this.get('location') || '';
		var recurringPattern = this.get('recurring_pattern') || '';

		var meetingTimeInfo = _('When') + ': ';

		if (recurringPattern) {
			meetingTimeInfo += recurringPattern + '\n';
		} else {
			// # TRANSLATORS: See http://docs.sencha.com/ext-js/3-4/#!/api/Date for the meaning of these formatting instructions
			meetingTimeInfo += startDate.format(_('l jS F Y G:i')) + ' - ';
			// # TRANSLATORS: See http://docs.sencha.com/ext-js/3-4/#!/api/Date for the meaning of these formatting instructions
			meetingTimeInfo += dueDate.format(_('l jS F Y G:i')) + '\n';
		}

		meetingTimeInfo += _('Where') + ': '  + meetingLocation + '\n\n';
		meetingTimeInfo += '*~*~*~*~*~*~*~*~*~*\n\n' + messageBody;

		return meetingTimeInfo;
	},

	/**
	 * 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('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, sendResponse)
	{
		this.sendMeetingRequestResponse(responseType, this.generateMeetingTimeInfo(comment), sendResponse);
	},

	/**
	 * 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);
	},

	/**
	 * Genereates 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/ext-js/3-4/#!/api/Date for the meaning of these formatting instructions
		proposeNewTimeBody += startDate.format(_('l jS F Y G:i')) + ' - ';
		// # TRANSLATORS: See http://docs.sencha.com/ext-js/3-4/#!/api/Date for the meaning of these formatting instructions
		proposeNewTimeBody += endDate.format(_('l jS F Y G:i')) + '\n';

		return proposeNewTimeBody;
	},

	/**
	 * Function cancels Meeting invitation and sends Meeting Cancellation message.
	 * @param {String} Meeting Time Information shown in message body.
	 */
	cancelInvitation : function(meetingTimeInfo)
	{
		this.addMessageAction('action_type', 'cancelInvitation');
		this.addMessageAction('meetingTimeInfo', this.generateMeetingTimeInfo(meetingTimeInfo));

		var store = this.getStore();
		store.remove(this);
		store.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 occurence appointment.
	 */
	isRecurringOccurence : function()
	{
		return Ext.isDate(this.get('basedate'));
	},

	/**
	 * @return {Boolean} Returns true if the {@link Zarafa.core.data.AppointmentRecord AppointmentRecord} is
	 * recurring occurence 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/occurence of recurring appointment.
	 * @return {Boolean} True if this message is a sub message.
	 */
	isSubMessage : function()
	{
		// Recurring occurences can never be embedded message
		if(this.isRecurringOccurence()) {
			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 exeption or not
		if (!this.isOpened()) {
			return undefined;
		}

		// If the apointment is not recurring, or this is an occurence, 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 occurence.
			// When openening as a series, we actually have all the data for a single
			// occurence (the one the user selected from the UI), which we are overriding
			// with the data about the series. However, the occurence and series have
			// slight differences. The series doesn't have a basedate, and the startdate
			// and duedate properties are the values from the first occurence.
			// 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 occurence. Force all fields which will likely to be different
			// between the occurence 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') {
			this.addMessageAction('meetingTimeInfo', this.generateMeetingTimeInfo(this.getBody()));
		}
		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')) {
			this.addMessageAction('meetingTimeInfo', this.generateMeetingTimeInfo(this.getBody()));
		}
		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 occurence record.
	 * When we are opening a single occurence from series record then we need to modify some properties
	 * that are different on occurence record and also need to add basedate as {@link #idProperties}.
	 * so the record can be correctly identified as an occurence record.
	 * @return {Zarafa.core.data.IPMRecord} record that can be used to open occurence of a series.
	 */
	convertToOccurenceRecord : function()
	{
		// only convert it when its a series record
		if(this.hasIdProp('basedate') === false) {
			var cloneRec = this.copy();

			// For occurences the the 'recurring' property must
			// always be false (as the occurence itself doesn't recur.
			cloneRec.set('recurring', false);

			// For occurences, 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 occurence record to a series record.
	 * Series and occurence records has some silghtly differences in properties so we need to reset some
	 * properties which are only valid for occurence 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
			// (occurences 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);
