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 witout 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. Everytime 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();
	},

	/**
	 * Everytime 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').format(_('G:i')),
					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/ext-js/3-4/#!/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 seperately for the different busy statuses. So you will get a
	 * different track for tentative, busy and outofoffice. By displaying them on top of eachother
	 * the outofoffice is more important than busy and busy more important than tentative.
	 *
	 * @param {Ext.data.Store} store The store which raised the event
	 * @oaram {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 directlly 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 ammount 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 te 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 devide 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);
