Ext.namespace('Zarafa.calendar.ui.canvas');

/**
 * @class Zarafa.calendar.ui.canvas.CalendarBoxView
 * @extends Zarafa.calendar.ui.AbstractCalendarBoxView
 *
 * A canvas-based implementation of the calendar box view. This implementation creates two canvas DOM elements on the parent view's
 * header and body. These canvas elements are then used by child appointment views to render to.
 *
 * Note that this class implements rendering only. Functionality
 * common to both implementations can be found in {@link Zarafa.calendar.ui.AbstractCalendarBoxView AbstractCalendarBoxView}.
 *
 * The canvas rendering is build in 3 layers. This means that for drawing the header, 3 canvas elements will
 * be used, the same as with the body. In total this means 6 canvas elements are rendered for the calendar.
 *
 * Using layering we can optimize the rendering of the calendar. Whenever an appoint changes, we are not
 * interested in redrawing the complete calendar and all appointments in it (This would become a performance
 * issue for calendars with many appointments). So with 3 layers we can create the following setup:
 *
 *  - Layer 1 : The calendar
 *    This contains the calendar background. For the header this means the background color, and the
 *    name of the date which is being displayed in the color (optionally additional markup is applied
 *    in case the rendered date is 'today'). For the body this means that all timestrips are rendered
 *    into the calendar (using the correct theme color).
 *  - Layer 2 : The appointments
 *    This contains all the appointments which are available in the calendar. None of the appointments
 *    are allowed to overlap with another appointment (The time can obviously overlap, but in that case
 *    the appointments are placed side-by-side inside the same column). Whenever a single appointment
 *    changes, we only have to redraw that single appointment (since it doesn't overlap with any other
 *    appointment, we can be sure that redrawing the appointment will not affect other appointments).
 *  - Layer 3 : The selections
 *    The most volatile layer which displays appointment outlines (black borders) and dragHandles
 *    (small boxes on the edges), which are positioned exactly on top of an appointment on Layer 2.
 *    Whenever the selection changes, we only require that Layer 3 is cleared an redrawn (or when a
 *    appointment is selected, but no other appointment is deselected, then only the outline will have
 *    to be drawn.
 */
Zarafa.calendar.ui.canvas.CalendarBoxView = Ext.extend(Zarafa.calendar.ui.AbstractCalendarBoxView, {
	/**
	 * @cfg {Number} expandButtonRadius The radius for the expand button which appears when not
	 * all appointments could be rendered for that particular day. The button is a round button,
	 * with an triangle inside.
	 */
	expandButtonRadius : 8,

	/**
	 * @cfg {Number} expandButtonMargin The margins around the expand button which appears
	 * when not all appointments could be rendered for that particular day. The button is a round button,
	 * with an triangle inside.
	 */
	expandButtonMargin : 2,

	/**
	 * The <canvas> element for the top-row in the calendar which contains the
	 * dayname for each individual column. Since the header does not contain appointments,
	 * we don't need extra layers for displaying the appointments.
	 * @property
	 * @type Ext.Element
	 */
	headerBackgroundCanvas : undefined,

	/**
	 * The <canvas> element for the body in the calendar which contains the
	 * timestrips and the appointments.
	 * This element is 'layer 1' for the body, containing only the background.
	 * @property
	 * @type Ext.Element
	 */
	bodyBackgroundCanvas : undefined,

	/**
	 * The <canvas> element for the body in the calendar which contains the
	 * timestrips and the appointments.
	 * This element is 'layer 2' for the body, containing only the appointments.
	 * @property
	 * @type Ext.Element
	 */
	bodyAppointmentCanvas : undefined,

	/**
	 * The <canvas> element for the body in the calendar which contains the
	 * timestrips and the appointments.
	 * This element is 'layer 3' for the body, containing only the selections.
	 * @property
	 * @type Ext.Element
	 */
	bodySelectionCanvas : undefined,

	/**
	 * The Appointment over which the cursor is currently hovering.
	 * @property
	 * @type Zarafa.calendar.ui.canvas.AppointmentBoxView
	 */
	appointmentOver : undefined,

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

		Ext.applyIf(config, {
			baseCls : 'zarafa-calendar',
			enableDD : true,
			ddGroup : 'dd.mapiitem',
			bodyDropConfig : {
				ddGroup : 'dd.mapiitem',
				headerMode : false,
				selectingSnapMode : Zarafa.calendar.data.SnapModes.DAY,
				draggingSnapMode : Zarafa.calendar.data.SnapModes.NONE
			}
		});

		Zarafa.calendar.ui.canvas.CalendarBoxView.superclass.constructor.call(this, config);
	},

	/**
	 * Renders the view. Generates the body and header layers.
	 * @param {Ext.Element} container The Ext.Element into which the view must be rendered.
	 * @private
	 * @override
	 */
	render : function(container)
	{
		Zarafa.calendar.ui.canvas.CalendarBoxView.superclass.render.call(this, container);

		this.create('canvas', this.header, 'headerBackgroundCanvas', 'zarafa-canvas zarafa-canvas-layer-1 zarafa-canvas-header-background');
		
		// Create styling elements. We will take the styles of these elements to draw the canvas.
		// This way we can set these styles in the css files.
		this.create('div', this.headerBackgroundCanvas, 'headerBackgroundCanvasStylingElement', 'zarafa-styling-element');
		this.create('div', this.headerBackgroundCanvas, 'headerBackgroundCanvasStylingElementCurrentDay', 'zarafa-styling-element-current-day');
		this.headerBackgroundCanvasStylingElement.styling = {
			font : this.headerBackgroundCanvasStylingElement.getStyle('font'),
			fontSize : parseInt(this.headerBackgroundCanvasStylingElement.getStyle('font-size'), 10),
			paddingTop : this.headerBackgroundCanvasStylingElementCurrentDay.getPadding('t'),
			paddingLeft : this.headerBackgroundCanvasStylingElementCurrentDay.getPadding('l'),
			paddingRight : this.headerBackgroundCanvasStylingElementCurrentDay.getPadding('r')
		};
		this.headerBackgroundCanvasStylingElementCurrentDay.styling = {
			backgroundColor : this.headerBackgroundCanvasStylingElementCurrentDay.getStyle('background-color'),
			color : this.headerBackgroundCanvasStylingElementCurrentDay.getStyle('color')
		};

		this.create('canvas', this.body, 'bodyBackgroundCanvas', 'zarafa-canvas zarafa-canvas-layer-1');
		this.create('canvas', this.body, 'bodyAppointmentCanvas', 'zarafa-canvas zarafa-canvas-layer-2');
		this.create('canvas', this.body, 'bodySelectionCanvas', 'zarafa-canvas zarafa-canvas-layer-3');

		// Hook mouse events to the body.
		this.mon(this.getBodyActionCanvas(), {
			'mousemove': this.onBodyMove,
			'mouseout': this.onBodyOut,
			'click': this.onBodyClick,
			'dblclick': this.onBodyDblClick,
			'contextmenu': this.onBodyContextMenu,
			scope: this
		});
	},

	/**
	 * Create a new {@link Zarafa.calendar.ui.AppointmentView Appointment} object
	 * for the given {@link Zarafa.core.data.IPMRecord record}.
	 * @param {Zarafa.core.data.IPMRecord} record The record for which the AppointmentView
	 * must be created.
	 * @private
	 */
	createAppointment : function(record)
	{
		return new Zarafa.calendar.ui.canvas.AppointmentBoxView({
			parentView: this,
			record : record,
			calendarColorScheme : this.calendarColorScheme
		});
	},

	/**
	 * Create an Appointment Proxy object which can represent the selected text
	 * Must be implemented by the subclasses.
	 * @return Zarafa.calendar.ui.AbstractDateRangeView
	 * @protected
	 */
	createAppointmentProxy : function()
	{
		return new Zarafa.calendar.ui.canvas.AppointmentProxy({ showTime : false });
	},

	/**
	 * Returns the canvas element on which all user-interactions for the body take place.
	 * This is the element on which event handlers are being attached.
	 * @return {Ext.Element} The calendar body element.
	 */
	getBodyActionCanvas : function()
	{
		return this.bodySelectionCanvas;
	},

	/**
	 * Returns the canvas element which must be used to draw Appointments for the body ({@link #bodyAppointmentCanvas Layer2}).
	 * @return {Ext.Element} The calendar body element
	 */
	getBodyAppointmentCanvas : function()
	{
		return this.bodyAppointmentCanvas;
	},

	/**
	 * Returns the canvas element which must be used to draw Appointment Selections for the body ({@link #bodySelectionCanvas Layer3}).
	 * @return {Ext.Element} The calendar body element
	 */
	getBodySelectionCanvas : function()
	{
		return this.bodySelectionCanvas;
	},

	/**
	 * Returns the canvas element on which all user-interactions for the header take place.
	 * This is the element on which event handlers are being attached.
	 * @return {Ext.Element} The calendar header element.
	 */
	getHeaderActionCanvas : function()
	{
		return this.headerSelectionCanvas;
	},

	/**
	 * The {@link Zarafa.calendar.ui.CalendarMultiView CalendarMultiView} view has a header area that automatically
	 * resizes when its child views require more space. In the days view for instance, appointments that span
	 * more than 24 hours are laid out in the header. This view however has a header with a fixed height.
	 * @return {Number} height in pixels the calendar view needs to properly lay out its header.
	 */
	getDesiredHeaderHeight : function()
	{
		return this.rendered ? parseInt(this.headerBackgroundCanvasStylingElement.getStyle('height'), 10) : 0;
	},

	/**
	 * Sets the text on the headers for each day column. The header title is generated using the {@link #getDayHeaderTitle} function.
	 * @param {Array} dayPositions The array of {@link Zarafa.calendar.data.DayLayoutPosition LayoutPositions} for the various days.
	 * @private
	 */
	drawDayHeaders : function()
	{
		var width = this.header.getWidth();
		var height = this.header.getHeight();

		// First resize the canvas elements for the body, this will effectively clear
		// all the contents and make them available for drawing the new contents into it.
		Zarafa.resizeCanvas(this.headerBackgroundCanvas, width, height);

		// Get context from the canvas.
		var context = this.headerBackgroundCanvas.dom.getContext("2d");
		context.save();

		// Fill the header with the header color.
		context.fillStyle = this.calendarColorScheme.base;
		context.fillRect(0, 0, width, height);

		// Draw a header for each day of the week. We just draw the outlines of each
		// box in the colorscheme's border color and draw text in the center that tells us
		// what day of the week that column represents.
		context.fillStyle = this.headerBackgroundCanvasStylingElement.getStyle('color');
		context.font = this.headerBackgroundCanvasStylingElement.getStyle('font');

		var dayWidth = width / this.numDaysInWeek;
		var startDate = this.getVisibleDateRange().getStartDate().clone();
		var lineWidth = context.lineWidth;

		// Set the startDate to 12:00 to prevent problems when the DST switch
		// occurs at 00:00 (like in Brasil).
		startDate.setHours(12);

		for (var i = 0; i < this.numDaysInWeek; i++) {
			var date = startDate.add(Date.DAY, i).clearTime();
			var left = Math.round(dayWidth * i);
			var right = Math.round(dayWidth * (i + 1)) + lineWidth;
			var boxWidth = right - left;

			// Draw header text
			var text = this.getDayHeaderTitle(date, boxWidth);
			context.drawText(text, left + this.headerBackgroundCanvasStylingElement.getPadding('l'), height - Math.ceil((height-parseInt(this.headerBackgroundCanvasStylingElement.getStyle('font-size')))/2, 10));
		}

		context.restore();
	},

	/**
	 * Draws an expand button. These buttons are shown when there is not enough
	 * space in a day box to show all the appointments for a given day, as a
	 * visual hint to the user that not all information is shown.
	 * The button can be clicked to zoom to that particular day.
	 * @param {CanvasRenderingContext2D} context canvas drawing context.
	 * The context should be positioned at the top-left position of the dayBox.
	 * @param {Object} dayBox day box configuration.
	 * @private
	 */
	drawExpandButton : function(context, dayBox)
	{
		// Calculate daybox position.
		var boxWidth = dayBox.right - dayBox.left;
		var boxHeight = dayBox.bottom - dayBox.top;

		// Determine the centre of the button
		var x = boxWidth - this.expandButtonRadius - this.expandButtonMargin;
		var y = boxHeight - this.expandButtonRadius - this.expandButtonMargin;

		// Draw a triangle pointing down
		context.beginPath();
		var radius = this.expandButtonRadius - 2;
		for (var i = 0; i < 4; i++) {
			var x1 = Math.sin(i * 2 / 3 * Math.PI) * radius + x;
			var y1 = Math.cos(i * 2 / 3 * Math.PI) * radius + y;

			if (i === 0) {
				context.moveTo(x1, y1);
			} else {
				context.lineTo(x1, y1);
			}
		}
		context.closePath();

		context.fillStyle = this.calendarColorScheme.base;
		context.fill();
		context.strokeStyle = 'white';
		context.stroke();
	},

	/**
	 * Draws a dayBox in which the appointments for a particular day can be drawn.
	 * @param {CanvasRenderingContext2D} context rendering context.
	 * @param {Object} dayBox day box configuration.
	 * @private
	 */
	drawDayBox : function(context, dayBox)
	{
		var boxWidth = dayBox.right - dayBox.left;
		var boxHeight = dayBox.bottom - dayBox.top;

		// Move the context to the correct position of the dayBox.
		context.save();
		context.translate(dayBox.left, dayBox.top);

		// Draw Header background.
		var backgroundColor = context.convertHexRgbToDecRgba(this.calendarColorScheme.base, 0.2);
		var color;
		if (dayBox.today) {
			backgroundColor = this.headerBackgroundCanvasStylingElementCurrentDay.getStyle('background-color');
			color = this.headerBackgroundCanvasStylingElementCurrentDay.getStyle('color');
			context.font = this.headerBackgroundCanvasStylingElementCurrentDay.getStyle('font');
		} else {
			color = this.headerBackgroundCanvasStylingElement.getStyle('color');
			color = 'black';
			context.font = this.headerBackgroundCanvasStylingElement.getStyle('font');
		}

		context.fillStyle = backgroundColor;
		context.fillRect(0, 0, boxWidth, this.dayHeaderHeight);

		// Draw header text.
		context.fillStyle = color;
		context.drawText(
			dayBox.date.format(_("jS")), 
			this.headerBackgroundCanvasStylingElement.getPadding('l'), 
			this.dayHeaderHeight -  Math.ceil((this.dayHeaderHeight -parseInt(this.headerBackgroundCanvasStylingElement.getStyle('font-size')))/2) - 1
		);

		// Determine the background color of the day box body area.
		if (dayBox.active) {
			switch (dayBox.busyStatus) {
				case Zarafa.core.mapi.BusyStatus.FREE:
					context.fillStyle = 'white';
					break;
				case Zarafa.core.mapi.BusyStatus.BUSY:
					context.fillStyle = '#d7e2f1';
					break;
				case Zarafa.core.mapi.BusyStatus.TENTATIVE:
					context.fillStyle = this.calendarColorScheme.stripnormal;
					break;
				case Zarafa.core.mapi.BusyStatus.OUTOFOFFICE:
					context.fillStyle = '#e7d7ef';
					break;
				default:
					break;
			}
		} else {
			// Set the color for non-active days (in this view these are the days of other months)
			context.fillStyle = context.convertHexRgbToDecRgba(this.calendarColorScheme.base, 0.1);
		}

		context.fillRect(0, this.dayHeaderHeight, boxWidth, boxHeight - this.dayHeaderHeight + 1);

		// If the day box should be rendered tentative (striped) fill the body rect with a dashed pattern fill.
		if (dayBox.busyStatus === Zarafa.core.mapi.BusyStatus.TENTATIVE) {
			context.fillStyle = context.createPattern(Zarafa.calendar.ui.IconCache.getDashedImage(), 'repeat');
			context.fillRect(0, this.dayHeaderHeight, boxWidth, boxHeight - this.dayHeaderHeight + 1);
		}

		// The border around the day box
		context.beginPath();
		if ( dayBox.left ){
			context.moveTo(0.5, boxHeight - 0.5);
			context.lineTo(0.5, 0.5);
		} else {
			context.moveTo(0.5, 0.5);
		}
		if ( dayBox.top ){
			context.lineTo(boxWidth - 0.5, 0.5);
		}
		context.strokeStyle = this.calendarColorScheme.base;
		context.stroke();

		// Draw expand button.
		if (dayBox.overflow) {
			this.drawExpandButton(context, dayBox);
		}

		context.restore();
	},

	/**
	 * Draws the boxes that represent the days in the box view. Each box is drawn using drawDayBox().
	 * @private
	 */
	drawDays : function()
	{
		var todayBox;
		var width = this.body.getWidth();
		var height = this.body.getHeight();

		// First resize the canvas elements for the body, this will effectively clear
		// all the contents and make them available for drawing the new contents into it.
		Zarafa.resizeCanvas(this.bodyBackgroundCanvas, width, height);
		Zarafa.resizeCanvas(this.bodyAppointmentCanvas, width, height);
		Zarafa.resizeCanvas(this.bodySelectionCanvas, width, height);

		// Get canvas drawing context.
		var context = this.bodyBackgroundCanvas.dom.getContext("2d");
		context.save();

		// Draw the individual day boxes. If a day box represents the current day (today) it is drawn with a special
		// header color. Because the outline of that day also needs to be highlighted that day box is stored and later
		// has its outlines repainted.
		for (var i = 0, len = this.dayBoxConfigurations.length; i < len; i++) {
			var dayBox = this.dayBoxConfigurations[i];

			this.drawDayBox(context, dayBox);
			if (dayBox.today) {
				todayBox = dayBox;
			}
		}

		if (Ext.isDefined(todayBox)) {
			var boxWidth = todayBox.right - todayBox.left;
			var boxHeight = todayBox.bottom - todayBox.top;
			var lineWidth = context.lineWidth;
			context.lineWidth = 3;

			context.strokeStyle = this.headerBackgroundCanvasStylingElementCurrentDay.getStyle('background-color');
			context.strokeRect(todayBox.left + (lineWidth / 2), todayBox.top + (lineWidth / 2), boxWidth - lineWidth, boxHeight - lineWidth);
		}

		context.restore();
	},

	/**
	 * Tests if a given mouse event is over a header. Used to implement the
	 * functionality where the user clicks on the header of a day strip zoom to that day.
	 * @param {Ext.EventObject} event ExtJS event object
	 * @return {Boolean} True iff the user clicked on the header area
	 * @private
	 */
	eventInHeader : function(event)
	{
		// Calculate the x,y position of the mouse relative the the body canvas.
		var element = this.getBodyActionCanvas();
		var x = event.getPageX() - element.getX();
		var y = event.getPageY() - element.getY();

		// Check for each day box if the position is inside its header.
		for (var i = 0, len = this.dayBoxConfigurations.length; i < len; i++) {
			var dayBox = this.dayBoxConfigurations[i];

			if (x >= dayBox.left && x <= dayBox.right &&
				y >= dayBox.top && y <= (dayBox.top + this.dayHeaderHeight)) {
					return true;
			}
		}

		return false;
	},

	/**
	 * Tests if a given mouse event is over a daybox. Used to implement the functionality where the
	 * user clicks on the body of a day box.
	 * @param {Ext.EventObject} event ExtJS event object
	 * @return {Boolean} True iff the user clicked on the header area
	 * @private
	 */
	eventInDay : function(event)
	{
		// Calculate the x,y position of the mouse relative the the body canvas.
		var element = this.getBodyActionCanvas();
		var x = event.getPageX() - element.getX();
		var y = event.getPageY() - element.getY();

		// Check for each day box if the position is inside its header.
		for (var i = 0, len = this.dayBoxConfigurations.length; i < len; i++) {
			var dayBox = this.dayBoxConfigurations[i];

			if (x >= dayBox.left && x <= dayBox.right &&
				y >= dayBox.top && y <= dayBox.bottom) {
					return true;
			}
		}

		return false;
	},

	/**
	 * Tests if a given mouse event is over the expand button on a day box.
	 * Used to implement the functionality where the user clicks on an expand
	 * button to zoom to the day.
	 * @param {Ext.EventObject} event ExtJS event object
	 * @return {Boolean} True iff the user clicked on the expand button.
	 * @private
	 */
	eventInExpandButton : function(event)
	{
		// Calculate the x,y position of the mouse relative the the body canvas.
		var element = this.getBodyActionCanvas();
		var x = event.getPageX() - element.getX();
		var y = event.getPageY() - element.getY();

		// Check for each day box if the position is inside the overflow button.
		for (var i = 0, len = this.dayBoxConfigurations.length; i < len; i++) {
			var dayBox = this.dayBoxConfigurations[i];
			var insideBox = (x >= dayBox.left && x <= dayBox.right &&
							 y >= dayBox.top && y <= dayBox.bottom);

			// If the event is not inside this dayBox, we can continue
			// to the next dayBox.
			if (!insideBox) {
				continue;
			}

			// So the event was inside this box, but if it has not overflowed,
			// we can simply return return false now.
			if (!dayBox.overflow) {
				return false;
			}

			// So the event was inside this box, the box is overflown,
			// so we have a chance that the event was on the expand button.
			// Lets do the math.

			// Calculate the offset from the center of the expand button.
			var tx = (dayBox.right - this.expandButtonRadius - this.expandButtonMargin) - x;
			var ty = (dayBox.bottom - this.expandButtonRadius - this.expandButtonMargin) - y;

			// Pythagoras to get the distance from the center.
			var dist = Math.sqrt(tx * tx + ty * ty);

			// Since the button is circular, if the distance is smaller than the circle
			// radius the mouse is over the button.
			return (dist <= this.expandButtonRadius);
		}

		return false;
	},

	/**
	 * Determine over which area of the {@link Zarafa.calendar.ui.canvas.AppointmentBoxView appointment}
	 * the given {@link Ext.EventObject event} took place, and which cursor type corresponds to that location.
	 *
	 * @param {Zarafa.calendar.ui.canvas.AppointmentBoxView} appointment The appointment to check
	 * @param {Ext.EventObject} event The event object
	 * @return {String} The cursor type
	 * @private
	 */
	getCursorForBodyAppointment : function(appointment, event)
	{
		if (appointment.eventOverBodyStartHandle(event)) {
			return 'w-resize';
		} else if (appointment.eventOverBodyDueHandle(event)) {
			return 'e-resize';
		} else {
			return 'move';
		}
	},

	/**
	 * Event handler for the mouse move event on the calendar body. Checks if the mouse is over an appointment or
	 * daybox header/expand button and adjusts the cursor accordingly.
	 * @param {Ext.EventObject} event ExtJS event object
	 * @private
	 */
	onBodyMove : function(event)
	{
		var cursor = 'default';
		var over = false;

		// Determine if the event is over the header area or the expand button,
		// if that is the case, we can be 100% sure that the event didn't
		// occur over any appointment.
		if (this.eventInHeader(event) || this.eventInExpandButton(event)) {
			cursor = 'pointer';
		} else {
			// Check if the event is over an appointment.
			// Our first check should be if we are still over the same
			// appointment as before. If that is not the case, we should
			// loop over all appointments to look for the appointment we
			// are currently hovering over.
			if (this.appointmentOver && this.appointmentOver.eventOverBody(event)) {
				// We are still over the same appointment as before,
				// we don't need to search any further.
				over = this.appointmentOver;
			} else {
				// We can break the loop as soon as we find the first appointment
				// on which we are hovering. Since appointments do not overlap,
				// we cannot be hovering over multiple appointments at the same time.
				for (var i = 0, len = this.appointments.length; i < len; i++) {
					var appointment = this.appointments[i];

					// The body start and due handlers are placed over the body,
					// so it is not possible for the mouse to be over the start handle,
					// but not over the body. We can thus add a quick check to
					// see if the event is over the body, and only then determine
					// over which part of the body the mouse is actually over.
					if (appointment.eventOverBody(event)) {
						over = appointment;
						break;
					}
				}
			}
		}

		if (over) {
			// Determine which cursor should be used for the current
			// position over the given appointment
			cursor = this.getCursorForBodyAppointment(over, event);
		}

		// Check if the cursor is hovering over a different appointment then before.
		if (this.appointmentOver !== over) {
			// We previously were hovering over an appointment,
			// so fire the appointmentmouseout event
			if (this.appointmentOver) {
				this.fireEvent('appointmentmouseout', this, this.appointmentOver.getRecord(), event);
			}

			// Update the reference for next time.
			this.appointmentOver = over;

			// We are hovering over a different appointment,
			// so fire the appointmentmouseover event
			if (over) {
				this.fireEvent('appointmentmouseover', this, over.getRecord(), event);
			}
		}

		this.getBodyActionCanvas().dom.style.cursor = cursor;
	},

	/**
	 * Event handler for the mouse out event on the calendar body.
	 * Checks if we were previously over an appointment, and fire the 'appointmentmouseout'
	 * event in that case.
	 * @param {Ext.EventObject} event ExtJS event object
	 * @private
	 */
	onBodyOut : function(event)
	{
		if (this.appointmentOver && !this.appointmentOver.eventOverBody(event)) {
			this.fireEvent('appointmentmouseout', this, this.appointmentOver.getRecord(), event);
			this.appointmentOver = false;
		}
	},

	/**
	 * Event handler for the mouse click event on the calendar body. Appointment mouse down events are handled
	 * by the drag/drop handler, so this only checks for user clicks on day box headers and expand buttons.
	 * @param {Ext.EventObject} event ExtJS event object
	 * @private
	 */
	onBodyClick : function(event)
	{
		if (this.eventInHeader(event) || this.eventInExpandButton(event)) {
			var xy = event.getXY();
			var date = this.screenLocationToDate(xy[0], xy[1]);

			this.fireEvent('dayclick', this, date);
		}
	},

	/**
	 * Generic event handler for the mousemove event, this will check over which part of
	 * the calendar the event has occurred, and will call {@link #onBodyMove}.
	 * @param {Ext.EventObject} event The event object
	 */
	onMouseMove : function(event)
	{
		this.onBodyMove(event);
	},

	/**
	 * This will determine on which {@link Zarafa.core.data.IPMRecord record}
	 * the event was fired.
	 * @param {Ext.EventObject} event The event for which the selection must be updated.
	 * @return {Zarafa.core.data.IPMRecord} The record (if any) on which
	 * the event was fired.
	 * @private
	 */
	getRecordForEvent : function(event)
	{
		// Check if the mouse is over one of the appointments.
		// We can be sure that the event didn't occur on an appointment,
		// if the event didn't even occur inside a dayBox.
		if (this.eventInDay(event)) {
			for (var i = 0, len = this.appointments.length; i < len; i++) {
				var appointment = this.appointments[i];
				if (appointment.eventOverBody(event)) {
					return appointment.getRecord();
				}
			}
		}
	},

	/**
	 * Event handler for the doubleclick event
	 * @param {Ext.EventObject} event ExtJS event object
	 * @private
	 */
	onBodyDblClick : function(event)
	{
		var record = this.getRecordForEvent(event);

		// Fire dblclick event
		this.fireEvent('dblclick', this, event, record);
	},

	/**
	 * Event handler for the right-button mouse click event on the calendar body. It checks to see if there's
	 * an appointment under the mouse, and if not selects the day the mouse is over.
	 * @param {Ext.EventObject} event ExtJS event object
	 * @private
	 */
	onBodyContextMenu : function(event)
	{
		var record = this.getRecordForEvent(event);
		var range;

		if (!record) {
			var xy = event.getXY();
			range = this.screenLocationToDateRange(xy[0], xy[1]);
		}

		// Fire the calendar's context menu event.
		this.fireEvent('contextmenu', this, event, record, range);
	},

	/**
	 * Event handler which is fired when the {@link #selectionModel} fires the
	 * {@link Zarafa.calendar.ui.AppointmentSelectionModel#appointmentdeselect appointmentdeselect} event.
	 * This will clear the 3rd layers ({@link #bodySelectionCanvas} and {@link #headerSelectionCanvas},
	 * and will force an update for all previously selected records, which need to redraw their
	 * selection outline.
	 * @param {Zarafa.calendar.ui.AppointmentSelectionModel} selectionModel The selection model which fired the event.
	 * @param {Zarafa.core.data.IPMRecord} oldRecord The record which was deselected.
	 * @private
	 * @override
	 */
	onAppointmentDeselect : function(selectionModel, oldRecord)
	{
		Zarafa.calendar.ui.canvas.CalendarBoxView.superclass.onAppointmentDeselect.call(this, selectionModel, oldRecord);

		// Resize the selection canvas elements, this forces it to be cleared (event when the size did not change).
		Zarafa.resizeCanvas(this.bodySelectionCanvas, this.bodySelectionCanvas.getWidth(), this.bodySelectionCanvas.getHeight());

		if (selectionModel.hasSelection()) {
			for (var i = 0, len = this.appointments.length; i < len; i++) {
				var appointment = this.appointments[i];
				appointment.setSelected(appointment.isSelected());
			}
		}
	},

	/**
	 * Event handler which is fired when the {@link #selectionModel} fires the
	 * {@link Zarafa.calendar.ui.AppointmentSelectionModel#selectionclear selectionclear} event.
	 * This will clear the 3rd layers ({@link #bodySelectionCanvas} and {@link #headerSelectionCanvas}.
	 * @param {Zarafa.calendar.ui.AppointmentSelectionModel} selectionModel The selection model which fired the event.
	 * @private
	 * @override
	 */
	onAppointmentSelectionClear : function(selectionModel)
	{
		Zarafa.calendar.ui.canvas.CalendarBoxView.superclass.onAppointmentSelectionClear.call(this, selectionModel);

		// Resize the selection canvas elements, this forces it to be cleared (event when the size did not change).
		Zarafa.resizeCanvas(this.bodySelectionCanvas, this.bodySelectionCanvas.getWidth(), this.bodySelectionCanvas.getHeight());
	},

	/**
	 * Removes a child view from this view.
	 * @param {Zarafa.core.ui.View} child child view to be added
	 * @param {Boolean} destroy if true, destroy the child view
	 * @override
	 */
	removeChildView : function(child, destroy)
	{
		// If the childview was the appointment we are hovering over,
		// we need to reset the appointmentOver property.
		if (this.appointmentOver && this.appointmentOver === child) {
			this.fireEvent('appointmentmouseout', this, this.appointmentOver.getRecord());
			this.appointmentOver = false;
		}

		return Zarafa.calendar.ui.canvas.CalendarBoxView.superclass.removeChildView.apply(this, arguments);
	},

	/**
	 * Lays out the view. This function is called after {@link #render} and is used
	 * to update the view to the latest situation. When an appointment, or setting
	 * has been changed, the {@link #layout} function must change the look to reflect
	 * the new changes.
	 * @protected
	 */
	onLayout : function()
	{
		// Perform greedy coloring on the appointments. This calculates how overlapping appointments are laid out
		// in rows on the view.
		this.doGreedyColoring(this.appointments, true);

		// Parent lay out.
		Zarafa.calendar.ui.canvas.CalendarBoxView.superclass.onLayout.call(this);
		
		// Check if we have a light or dark color scheme
		var isDarkColor = Zarafa.core.ColorSchemes.getLuma(this.calendarColorScheme.base) < 155;
		if ( !isDarkColor ){
			this.headerBackgroundCanvasStylingElement.addClass('light-background');
		} else {
			this.headerBackgroundCanvasStylingElement.removeClass('light-background');
		}
		
		// Update the height of the body.
		var height = this.parentView.scrollable.getHeight();
		this.body.setHeight(height);

		// Calculate the positions of all dayBoxes, and start layout
		// to position the div elements.
		this.calculateDayBoxConfigurations();
		this.drawDayHeaders();
		this.drawDays();
	}
});