/*
 * Copyright 2005 - 2009  Zarafa B.V.
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License, version 3, 
 * as published by the Free Software Foundation with the following additional 
 * term according to sec. 7:
 *  
 * According to sec. 7 of the GNU Affero General Public License, version
 * 3, the terms of the AGPL are supplemented with the following terms:
 * 
 * "Zarafa" is a registered trademark of Zarafa B.V. The licensing of
 * the Program under the AGPL does not imply a trademark license.
 * Therefore any rights, title and interest in our trademarks remain
 * entirely with us.
 * 
 * However, if you propagate an unmodified version of the Program you are
 * allowed to use the term "Zarafa" to indicate that you distribute the
 * Program. Furthermore you may use our trademarks where it is necessary
 * to indicate the intended purpose of a product or service provided you
 * use it in accordance with honest practices in industrial or commercial
 * matters.  If you want to propagate modified versions of the Program
 * under the name "Zarafa" or "Zarafa Server", you may only do so if you
 * have a written permission by Zarafa B.V. (to acquire a permission
 * please contact Zarafa at trademark@zarafa.com).
 * 
 * The interactive user interface of the software displays an attribution
 * notice containing the term "Zarafa" and/or the logo of Zarafa.
 * Interactive user interfaces of unmodified and modified versions must
 * display Appropriate Legal Notices according to sec. 5 of the GNU
 * Affero General Public License, version 3, when you propagate
 * unmodified or modified versions of the Program. In accordance with
 * sec. 7 b) of the GNU Affero General Public License, version 3, these
 * Appropriate Legal Notices must retain the logo of Zarafa or display
 * the words "Initial Development by Zarafa" if the display of the logo
 * is not reasonably feasible for technical reasons."
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *  
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 */

/**
 * --Calendar Day View--
 * @type	View
 * @classDescription	This view can be used for appointement
 * list module to display the calendar items
 * 
 * +-------+------------+------------+
 * | wk 51 | Mon 18 Dec | Tue 19 Dec |
 * +-------+------------+------------+
 * |  9:00 |------------|------------|
 * +-------+------------+------------+
 * | 10:00 |------------|------------|
 * +-------+------------+------------+
 * | 11:00 |------------|------------|
 * +-------+------------+------------+
 * 
 * DEPENDS ON:
 * |------> view.js
 * |----+-> *listmodule.js
 * |    |----> listmodule.js
 */
CalendarDayView.prototype = new View;
CalendarDayView.prototype.constructor = CalendarDayView;
CalendarDayView.superclass = View.prototype;


function CalendarDayView(moduleID, element, events, data)
{
	this.element = element;
	
	this.moduleID = moduleID;
	this.events = events;
	this.data = data;

	this.initView();
	this.setData(data, false);
	
}

CalendarDayView.prototype.setData = function(data)
{
	this.startdate = data["startdate"];
	this.duedate = data["duedate"];
	this.selecteddate = data["selecteddate"];

	this.days = new Array();
	for(var i = new Date(this.startdate); i.getTime() < this.duedate; i.addDays(1))
	{
		this.days.push(i.getTime());
	}
	
	this.setDayHeaders();
}

// Initializes the static background. This is not date-dependant
CalendarDayView.prototype.initView = function()
{
	this.headerElement = dhtml.addElement(this.element, "div", "header", this.moduleID+"_header");
	this.headerElement.style.width = 100+"%";
	this.weekNumberElement = dhtml.addElement(this.headerElement,"span","","week_number");
	this.alldayEventElement = dhtml.addElement(this.headerElement, "div", "alldayevent");
	this.alldayElement = dhtml.addElement(this.alldayEventElement, "div", "allcontainer");
	this.timelinetopElement = dhtml.addElement(this.headerElement, "div", "timelinetop");
	
	var workdayStart = webclient.settings.get("calendar/workdaystart");
	var workdayEnd = webclient.settings.get("calendar/workdayend");
	
	if(!workdayStart)
		workdayStart = 9*60;
	if(!workdayEnd)
		workdayEnd = 17*60;
	
	//extra title
	var prevTitle, nextTitle;
	switch(parseInt(this.data["days"],10)){
		case 1:
			prevTitle = _("Previous day");
			nextTitle = _("Next day");
			break;
		default:
			prevTitle = _("Previous week");
			nextTitle = _("Next week");
			break;
	}
	var extraElement = dhtml.addElement("","span");
	// previous button 
	var prevButton = dhtml.addElement(extraElement,"span","prev_button","",NBSP);
	dhtml.addEvent(this.moduleID,prevButton,"click",eventDayViewClickPrev);
	prevButton.title = prevTitle;
	
	// title string
	this.rangeElement = dhtml.addElement(extraElement,"span","","");
	
	// next button
	var nextButton =dhtml.addElement(extraElement,"span","next_button","",NBSP);
	dhtml.addEvent(this.moduleID,nextButton,"click",eventDayViewClickNext);
	nextButton.title = nextTitle;

	webclient.getModule(this.moduleID).setExtraTitle(extraElement);
	
	for(var i = 0; i < this.data["days"]; i++)
	{
		var dayElement = dhtml.addElement(this.headerElement, "div", "date");
	}
	
	this.daysElement = dhtml.addElement(this.element, "div", false, "days");
	
	var timelineElement = dhtml.addElement(this.daysElement, "div", "timeline");
	
	for(var i = 0; i < 24; i++)
	{
		var timeElement = dhtml.addElement(timelineElement, "div", "time");
		switch(parseInt(webclient.settings.get("calendar/vsize", 2))) {
		    case 1: timeElement.style.height = "25px"; break;
		    default: timeElement.style.height = "45px"; break;
		    case 3: timeElement.style.height = "65px"; break;
		}

		// Parsing time to be compatible with US time display (ex. 10:00 AM)
		var timeString = strftime_gmt(_("%H:%M"), ( (i*60*60) ));

		var timePortions = timeString.split(":");
		var timePortions2 = timePortions[1].split(" ");
		var hour = timePortions[0];
		var mins = timePortions2[0];
		timeElement.innerHTML = hour + "<sup>"+mins+"</sup>";
	}
	
	for(var i = 0; i < this.data["days"]; i++)
	{
		var dayElement = dhtml.addElement(this.daysElement, "div", "day", "day_" + i);

		for(var j = 0; j < 24; j++)
		{
			var brightness = "light";
			if(i==5 || i==6 || j * 60 < workdayStart || j * 60 >= workdayEnd)
				brightness = "dark";

			var firstHalfElement = dhtml.addElement(dayElement, "div", "top" + brightness, "day_" + i + "_time" + (j * 60));
			firstHalfElement.offsetTime = j*60;
			firstHalfElement.offsetDay = i;
			switch(parseInt(webclient.settings.get("calendar/vsize", 2))) {
			    case 1: firstHalfElement.style.height = "13px"; break;
			    default: firstHalfElement.style.height = "23px"; break;
			    case 3: firstHalfElement.style.height = "33px"; break;
			}
			dragdrop.addTarget(this.daysElement, firstHalfElement, "appointment", true, true);
			dhtml.addEvent(this.moduleID, firstHalfElement, "click", eventCalendarDayViewAddAppointmentDelayed);
			dhtml.addEvent(this.moduleID, firstHalfElement, "dblclick", eventCalendarDayViewDblClick);
			
			brightness = "light";
			if(i==5 || i==6 || j * 60 + 30 < workdayStart || j* 60 + 30 >= workdayEnd)
				brightness = "dark";
				
			var secondHalfElement = dhtml.addElement(dayElement, "div", "bottom" + brightness, "day_" + i + "_time" + (j * 60 + 30));
			secondHalfElement.offsetTime = j*60+30;
			secondHalfElement.offsetDay = i;
			switch(parseInt(webclient.settings.get("calendar/vsize", 2))) {
			    case 1: secondHalfElement.style.height = "12px"; break;
			    default: secondHalfElement.style.height = "22px"; break;
			    case 3: secondHalfElement.style.height = "32px"; break;
			}
			dragdrop.addTarget(this.daysElement, secondHalfElement, "appointment", true, true);
			dhtml.addEvent(this.moduleID, secondHalfElement, "click", eventCalendarDayViewAddAppointmentDelayed);
			dhtml.addEvent(this.moduleID, secondHalfElement, "dblclick", eventCalendarDayViewDblClick);
		}
		// Add an element at 24:00 which we use for positioning items that end at 00:00. You can't actually
		// see it.
		var endElem = dhtml.addElement(dayElement, "div", "", "day_" + i + "_time1440");
		endElem.offsetDay = i;
		endElem.offsetTime = 24 * 60;
	}

	// add keyboard event
	var module = webclient.getModule(this.moduleID);
	webclient.inputmanager.addObject(module, module.element);
	
	//Dont bind event if it is already binded earlier.
	if (!webclient.inputmanager.hasEvent(module, "keydown", eventCalendarDayViewKeyboard)) {
		webclient.inputmanager.bindEvent(module, "keydown", eventCalendarDayViewKeyboard);
	}

}

// Sets the date-dependant parts of the view
CalendarDayView.prototype.setDayHeaders = function()
{
	// Set the week number (wk XX)
	this.weekNumberElement.innerHTML = _("wk")+" "+this.selecteddate.getWeekNumber();

	var days = dhtml.getElementsByClassNameInElement(this.headerElement, "date", "div", true);
	
	// Set the day headers (mon 12 jan)
	for(var i = 0; i < days.length; i++) {
		var date = new Date(this.days[i]);
		var dayOfWeek = date.getDay();
		days[i].innerHTML = DAYS_SHORT[dayOfWeek] + " " + date.getDate() + " " + MONTHS_SHORT[date.getMonth()];
		// select today in view.
		var todayCount = (new Date().getDay()== 0) ? 7 : new Date().getDay();
		if((todayCount-1 == i  && date.isSameDay(new Date())) || (this.data["days"]==1 && date.isSameDay(new Date()))){
			dhtml.addClassName(days[i], "selectedToday");
			dhtml.addClassName(this.daysElement.childNodes[i+1], "selectedToday");
		}else {
			dhtml.removeClassName(days[i], "selectedToday");
			dhtml.removeClassName(this.daysElement.childNodes[i+1], "selectedToday");
		}
	}

	// Set the range header ('1 january - 7 janary')
	var titleString = "";
	switch(parseInt(this.data["days"],10)){
		case 1:
			titleString = this.selecteddate.getDate()+" "+MONTHS[this.selecteddate.getMonth()]+" "+this.selecteddate.getFullYear();
			break;
		default:
			var startDateObj = new Date(this.startdate);
			var dueDateObj = new Date(this.duedate-ONE_DAY);			
			titleString = startDateObj.getDate()+" "+MONTHS[startDateObj.getMonth()]+" - "+dueDateObj.getDate()+" "+MONTHS[dueDateObj.getMonth()];
			break;
	}
	this.rangeElement.innerHTML = titleString;

	this.clearAppointments();

}

CalendarDayView.prototype.resizeView = function()
{
	// clears the variable which is being used for max height of allDayElement.
	this.maxPosTop = 0;
	// Position all-day items so that we know the height of the all-day container
	this.positionAllAlldayItems();
	
	// Set main viewing window size
	this.daysElement.style.width = (this.headerElement.clientWidth - 1 > 0?this.headerElement.clientWidth - 1:3) + "px";
	this.daysElement.style.height = (this.element.clientHeight - this.daysElement.offsetTop - 1 > 0?this.element.clientHeight - this.daysElement.offsetTop - 1:3) + "px";

	// Set day widths
	// Header
	var days = dhtml.getElementsByClassNameInElement(this.headerElement, "date", "div", true);
	var width = (this.headerElement.offsetWidth - 73) / this.data["days"];
	var leftPosition = 50;
	
	if(width > 0) {
		for(var i = 0; i < this.data["days"]; i++)
		{
			days[i].style.width = width + "px";
			days[i].style.left = leftPosition + "px";
			
			leftPosition += days[i].clientWidth + 1;
		}
		
		// Days
		var days = dhtml.getElementsByClassNameInElement(this.daysElement, "day", "div", true);
		leftPosition = 50;
		for(var i = 0; i < this.data["days"]; i++)
		{
			days[i].style.width = width + "px";
			days[i].style.left = leftPosition + "px";
			
			if(i == this.data["days"] - 1) {
				days[i].style.width = (width + (this.headerElement.clientWidth - leftPosition - width - 18)) + "px";
			}
			
			leftPosition += days[i].clientWidth + 1;
		}
	}
	
	var workdayStart = webclient.settings.get("calendar/workdaystart");
	var workDayStartElem = dhtml.getElementById("day_0_time"+workdayStart, "div", this.daysElement);
	if(workDayStartElem) {
		this.daysElement.scrollTop = workDayStartElem.offsetTop;
	}

	this.positionItems();

	this.updateTargets();

}

// Updates all the targets by looking at the x,y coordinates of each day and
// updating the all the cells of those days seperately (instead of through
// dragdrop.updateTargets() )
CalendarDayView.prototype.updateTargets = function()
{
	var vsize;
	
	switch(parseInt(webclient.settings.get("calendar/vsize", 2))) {
	    case 1: vsize = 15; break;
	    default: vsize = 25; break;
	    case 3: vsize = 35; break;
    }
	
	for(var i = 0; i < this.data["days"]; i++) {
		var topleft = dhtml.getElementTopLeft(dhtml.getElementById("day_" + i + "_time0"));
		var x = topleft[0];
		var y = topleft[1];
		
		for(var j = 0; j < 24; j++) {
			dragdrop.updateTarget("appointment", "day_" + i + "_time" + (j * 60), x, y + (j*2*vsize));
			dragdrop.updateTarget("appointment", "day_" + i + "_time" + (j * 60 + 30), x, y + (j*2*vsize+vsize));
		}
	}
}

CalendarDayView.prototype.clearAppointments = function()
{
	// Remove old items
	var prevItems = dhtml.getElementsByClassNameInElement(this.element, "appointment", "div");
	for(var i = 0; i < prevItems.length; i++) {
		dhtml.deleteElement(prevItems[i]);
	}
		
	// Remove old all-day items
	var prevItems = dhtml.getElementsByClassNameInElement(this.headerElement, "allday_appointment", "div");
	for(var i = 0; i < prevItems.length; i++) {
		dhtml.deleteElement(prevItems[i]);
	}
		
}

// Open the initial view, return entryids of all objects
CalendarDayView.prototype.execute = function(items, properties, action)
{
	this.clearAppointments();
	
	var entryids = new Array();

	for(var i = 0; i < items.length; i++){
		var itemStart = parseInt(items[i].getElementsByTagName("startdate")[0].getAttribute("unixtime"),10)*1000;
		var itemDue = parseInt(items[i].getElementsByTagName("duedate")[0].getAttribute("unixtime"),10)*1000;
		// Start of appointment is before end of time period and the end of appointment is before start of time period
		if(this.duedate > itemStart && itemDue > this.startdate){
			var entry = this.addItemWithoutReposition(items[i], properties, action);
		}	
		if(entry) {
			entryids[entry['id']] = entry['entryid'];
		}
	}

	this.positionItems();

	this.updateTargets();

	// We must resize because there may be new allday items which change the size of the main
	// pane
	this.resizeView();

	return entryids;
}

// Add an item, only used internally (within this file)
CalendarDayView.prototype.addItemWithoutReposition = function(item, properties, action)
{
	var result = false;

	// The 'days' div
	var days = dhtml.getElementsByClassNameInElement(this.daysElement, "day", "div", true);
	var entryid = dhtml.getXMLValue(item,"entryid");

	if(entryid) {
		var startdate = item.getElementsByTagName("startdate")[0];
		
		if(startdate && startdate.firstChild) {
			var unixtime = startdate.getAttribute("unixtime");
			var elemID = entryid + "_" + unixtime;
			
			if(dhtml.getElementById(elemID))
				return; // we already have this item, ignore it. 
			
			if(unixtime) {
				// All day event (real all-day event or item which spans multiple days)
				if(this.isAllDayItem(item)) {
					this.createAllDayItem(dhtml.addElement(this.alldayElement, "div", false, "" + elemID), item);
				} else {
					// Calculate on which day the start date of the item is
					var day = Math.floor((unixtime - (this.days[0]/1000))/86400);
					this.createItem(days[day], dhtml.addElement(days[day], "div", false, "" + elemID), item, day);
				}

				result = new Object();
				result["id"] = elemID;
				result["entryid"] = entryid;
			}
		}
	}
	
	return result;
}
// Called when a single item must be added (ie after adding a new calendar item)
CalendarDayView.prototype.addItem = function(item, properties, action)
{
	var result = true;
	var startTime = item.getElementsByTagName("startdate")[0].getAttribute("unixtime")*1000;
	var dueTime = item.getElementsByTagName("duedate")[0].getAttribute("unixtime")*1000;
	// Start of appointment is before end of time period and the end of appointment is before start of time period
	if(this.duedate > startTime && dueTime > this.startdate){
		var result = this.addItemWithoutReposition(item,properties,action);
		this.positionItems();
		dragdrop.updateTargets("appointment");
	}
	
	this.resizeView();
	
	return result;
}

// Called when a single item is deleted
CalendarDayView.prototype.deleteItems = function(items)
{
	return false; // fallback to total refresh
}

// Called when a single item is updated
CalendarDayView.prototype.updateItem = function(element, item, properties)
{
	entry = new Object();

	var days = dhtml.getElementsByClassNameInElement(this.daysElement, "day", "div", true);
	
	var isAllDayElement = element.type == "allday" ? true : false;
	var isAllDayItem = isAllDayElement;
	var itemStartdate, itemUnixTime, itemDay;
	
	if(item) {
		isAllDayItem = this.isAllDayItem(item);
		itemStartdate = item.getElementsByTagName("startdate")[0];
		itemUnixTime = itemStartdate.getAttribute("unixtime"); 
		itemDay = Math.floor((itemUnixTime - (this.days[0]/1000))/86400);
		
		// Item switched to a day which is beyond the view
		if(itemDay >= days.length || itemDay < 0) {
			dhtml.deleteElement(element);
			entry["id"] = "";
			entry["entryid"] = item.getElementsByTagName("entryid")[0].firstChild.nodeValue;
			return entry;
		}
	
		// Relocate to correct day	
		days[itemDay].appendChild(element);

		element.innerHTML = "";
		
		if(isAllDayItem) {
			this.createAllDayItem(element, item);
		} else {
			this.createItem(element.parentNode, element, item, itemDay);
		}
		
		entry["id"] = element.id;
		entry["entryid"] = item.getElementsByTagName("entryid")[0].firstChild.nodeValue;
	}
	else {
		entry = false;
	}
	
	var oldParent = element.parentNode;
	
	if(isAllDayItem || (isAllDayItem != isAllDayElement)) {
		if(isAllDayItem) 
			this.alldayElement.appendChild(element);
		else
			days[itemDay].appendChild(element);

		this.positionAllAlldayItems();
		this.positionItemsOnDay(oldParent);
	}
	
	if(!isAllDayItem)
		this.positionItemsOnDay(element.parentNode);

	return entry;
}

// Create a standard item in the appointment item list (but don't position it)
CalendarDayView.prototype.createItem = function(daysElement, appointment, item, daynr)
{
	if (typeof daysElement == "undefined"){
		return; // it could happen that this function is called for an item that doesn't belong on this date
				// in that case daysElement is undefined so we ignore this item
	}

	appointment.style.display = "none";
	
	appointment.is_disabled = dhtml.getTextNode(item.getElementsByTagName("disabled_item")[0],0) != 0; // for private items, no events may be added to this item
	
	if (!appointment.is_disabled){
		if (this.events && this.events["row"]){
			dhtml.setEvents(this.moduleID, appointment, this.events["row"]);
		}
	
		dhtml.addEvent(this.moduleID, appointment, "mousedown", eventCalendarDayViewOnClick);
	}
	var className = "ipm_appointment appointment";

	// Label is also used further on in the code
	var label = dhtml.getTextNode(item.getElementsByTagName("label")[0]);
	className += this.convertLabelNrToString(label);
	
	appointment.className = className;
	if (!appointment.is_disabled){
		dragdrop.addDraggable(appointment, "appointment", true, APPOINTMENT_RESIZABLE);
	}

	var privateAppointment = item.getElementsByTagName("private")[0];
	var privateSensitivity = item.getElementsByTagName("sensitivity")[0];
	if(privateAppointment && privateAppointment.firstChild && privateSensitivity && privateSensitivity.firstChild) {
		if(privateAppointment.firstChild.nodeValue == "1") {
			dhtml.addElement(appointment, "span", "private", false, NBSP);
		} else if(privateSensitivity.firstChild.nodeValue == "2") {
			dhtml.addElement(appointment, "span", "private", false, NBSP);
		}
	}
	var reminderSet = item.getElementsByTagName("reminder")[0];
	if (reminderSet && reminderSet.firstChild && reminderSet.firstChild.nodeValue == "1"){
		dhtml.addElement(appointment, "span", "reminder", false, NBSP);
	}
	
	var meeting = item.getElementsByTagName("meeting")[0];
	if(meeting && meeting.firstChild) {
		var responseStatus = dhtml.getTextNode(item.getElementsByTagName("responsestatus")[0], 0);
		appointment.meetingrequest = responseStatus; // store responseStatus in DOM tree
		switch(meeting.firstChild.nodeValue)
		{
			case "1":
			case "3":
			case "5":
				dhtml.addElement(appointment, "span", "meetingrequest", false, NBSP);
				break;
		}
	}
	
	var recurring = item.getElementsByTagName("recurring")[0];
	if(recurring && recurring.firstChild) {
		var exception = item.getElementsByTagName("exception")[0];
		
		if(exception && exception.firstChild) {
			dhtml.addElement(appointment, "span", "recurring_exception", false, NBSP);			
		} else if(recurring.firstChild.nodeValue == "1") {
			dhtml.addElement(appointment, "span", "recurring", false, NBSP);
		}
		
		// Basedate is used for saving
		var basedate = item.getElementsByTagName("basedate")[0];
		if(basedate && basedate.firstChild) {
			appointment.setAttribute("basedate", basedate.getAttribute("unixtime"));
		}
	}
	
	var subject = item.getElementsByTagName("subject")[0];
	if(subject && subject.firstChild) {
		appointment.innerHTML += subject.firstChild.nodeValue;
		appointment.subject = subject.firstChild.nodeValue;
	}else{
		appointment.innerHTML += "&nbsp;";
		appointment.subject = NBSP;
	}
	
	var location = item.getElementsByTagName("location")[0];
	if(location && location.firstChild) {
		appointment.innerHTML += " (" + location.firstChild.nodeValue + ")";
		appointment.location = location.firstChild.nodeValue;
	}
	
	var startdate = item.getElementsByTagName("startdate")[0];
	var duedate = item.getElementsByTagName("duedate")[0];
	
	appointment.start = startdate.getAttribute("unixtime");
	appointment.end = duedate.getAttribute("unixtime");
	
	if(startdate && startdate.firstChild && duedate && duedate.firstChild) {
		var starttime = startdate.getAttribute("unixtime");
		var duetime = duedate.getAttribute("unixtime");
		if (duetime==starttime) duetime = (starttime*1)+60; // make duetime +1 minute when equals to starttime
		
		// Normalize reversed start/end
		if(starttime > duetime) {
			var tmp = starttime;
			starttime = duetime;
			duetime = tmp;
		}

		var start_date = new Date(starttime * 1000);
		var due_date = new Date(duetime * 1000);
		
		appointment.offsetStartTime = start_date.getHours() * 60 + start_date.getMinutes();
		appointment.offsetDay = daynr;

		var startDateId = daysElement.id + "_time" + (start_date.getHours() * 60);
		if(start_date.getMinutes() >= 30) {
			startDateId = daysElement.id + "_time" + (start_date.getHours() * 60 + 30);
		}

		var dueDateId = daysElement.id + "_time" + (due_date.getHours() * 60);
		if(due_date.getMinutes() > 0 && due_date.getMinutes() <= 30) {
			dueDateId = daysElement.id + "_time" + (due_date.getHours() * 60 + 30);
		} else if(due_date.getMinutes() > 30) {
			dueDateId = daysElement.id + "_time" + ((due_date.getHours() + 1) * 60);			
		}
		
		appointment.offsetEndTime = due_date.getHours() * 60 + due_date.getMinutes();

		var startDateElem = dhtml.getElementById(startDateId);
		var endDateElem = dhtml.getElementById(dueDateId);
		appointment.style.top = startDateElem.offsetTop + "px";
		appointment.style.width = (daysElement.clientWidth - 15) + "px";
		
		var height = endDateElem.offsetTop - startDateElem.offsetTop - 6;
		if(height < 0) {
			// Height is negative, so the item ends tomorrow. Show the item until the end of today.
			height += daysElement.clientHeight;
		}

		appointment.style.height = height + "px";
		
		// This attribute is used for saving. 
		appointment.setAttribute("starttime", startDateId);

		var busyStatus;
		var bstext = dhtml.getTextNode(item.getElementsByTagName("busystatus")[0]);
		switch(bstext)
		{
			case "0":
				busyStatus += " free";
				break;
			case "1":
				busyStatus += " tentative";
				break;
			case "3":
				busyStatus += " outofoffice";
				break;
			default:
				busyStatus += " busy";
				break;
		}

		// Next we are going to set up the duration bar that will indicate how long the appointment will run.
		var durationBar = dhtml.addElement(appointment, "div", "durationbar "+busyStatus, false);

		var barStartDate = new Date(start_date.getTime());
		var barDueDate = new Date(due_date.getTime());

		var barStartDateMinutes = barStartDate.getMinutes();
		var barDueDateMinutes = barDueDate.getMinutes();

		// Set the date to either 0 min or 30 min, this is because the appointment block snaps to 
		// halfhour slots and we want the start date of that block.
		barStartDate.setMinutes( barStartDateMinutes - (barStartDateMinutes % 30) );
		if(barDueDateMinutes > 30){
			barDueDate.setMinutes(60);
		}else if(barDueDateMinutes > 0){
			barDueDate.setMinutes(30);
		}else{
			barDueDate.setMinutes(0);
		}

		// The blockTimeDiff is the number of seconds that the block covers (not the appointment)
		var blockTimeDiff = barDueDate.getTime() - barStartDate.getTime();
		// The offsetDate is the number of seconds after the start of the block.
		var offsetAppointmentStartDate = start_date.getTime() - barStartDate.getTime();
		var offsetAppointmentDueDate = due_date.getTime() - barStartDate.getTime();
		// The deltaAppointmentStart and Due will be used to convert the seconds into pixels
		var deltaAppointmentStart = offsetAppointmentStartDate / blockTimeDiff;
		var deltaAppointmentDue = offsetAppointmentDueDate / blockTimeDiff;

		// For some reason I have to recalculate the height of the block here
		var blockHeight = parseInt(endDateElem.offsetTop, 10) - parseInt(startDateElem.offsetTop, 10);
		// Calculating the number of pixels where the duration bar needs to start and end
		// The height value is calculated earlier on in this function
		var durationBarStart = Math.round(blockHeight * deltaAppointmentStart);
		var durationBarEnd = Math.round(blockHeight * deltaAppointmentDue); // Using "End" now instead of "Due"

		/** 
		 * If the appointment ends at xx:59 we should make it look like it ends on the next whole 
		 * hour. Multiday appointments split up in two parts ranging from xx:xx till 23:59 and 0:00 
		 * till yy:yy. This way the duration block will stretch to the end.
		 */
		if(due_date.getMinutes() == 59){
			durationBarEnd = blockHeight;
		}

		// If the durationBar becomes too small, force the bar to show at least one pixel
		if(durationBarEnd - durationBarStart < 2){
			durationBar.style.height = "1px";
		}

		durationBar.style.top = durationBarStart + "px";
		durationBar.style.bottom = (blockHeight - durationBarEnd) + "px";
	}
	
	appointment.type = "normal";
}

/**
 * Function will convert label number to a classname
 * @param value = int label number
 * @return string classname  
 */ 
CalendarDayView.prototype.convertLabelNrToString = function(value)
{
	var className = "";
	//Appointment labels
	if(value){
		switch(value){
			case "1": className = " label_important"; break;
			case "2": className = " label_work"; break;
			case "3": className = " label_personal"; break;
			case "4": className = " label_holiday"; break;
			case "5": className = " label_required"; break;
			case "6": className = " label_travel_required"; break;
			case "7": className = " label_prepare_required"; break;
			case "8": className = " label_birthday"; break;
			case "9": className = " label_special_date"; break;
			case "10": className = " label_phone_interview"; break;
			default: className = " label_none"; break;
		}
	}
	else{
		className = " label_none";
	}
	return className;
}

// Create an all day item (but don't position it)
CalendarDayView.prototype.createAllDayItem = function(appointElement, item)
{
	// reset events thay may still be there when converting from a 'normal' appointment
	appointElement.events = new Array();
	appointElement.style.cursor = "";
	
	var subject = item.getElementsByTagName("subject")[0];
	var startUnixtime = parseInt(item.getElementsByTagName("startdate")[0].getAttribute("unixtime"),10);
	var endUnixtime = parseInt(item.getElementsByTagName("duedate")[0].getAttribute("unixtime"),10);
	var duration = dhtml.getTextNode(item.getElementsByTagName("duration")[0]);
	var label = dhtml.getTextNode(item.getElementsByTagName("label")[0]);
	var alldayevent = dhtml.getTextNode(item.getElementsByTagName("alldayevent")[0]);
	var busystatus = dhtml.getTextNode(item.getElementsByTagName("busystatus")[0]);
	var location = dhtml.getTextNode(item.getElementsByTagName("location")[0],"");
	if(location){
		location = " ("+location+")";
	}
	
	if(startUnixtime >= this.duedate/1000 || endUnixtime <= this.startdate/1000)
	    return; // Item we received is not within our viewing range

	//body of item
	dhtml.deleteAllChildren(appointElement);

	var recurring = item.getElementsByTagName("recurring")[0];
	if(recurring && recurring.firstChild) {
		var exception = item.getElementsByTagName("exception")[0];
		
		if(exception && exception.firstChild) {
			dhtml.addElement(appointElement, "span", "recurring_exception", false, NBSP);			
		} else if(recurring.firstChild.nodeValue == "1") {
			dhtml.addElement(appointElement, "span", "recurring", false, NBSP);
		}
		
		// Remember basedate
		var basedate = item.getElementsByTagName("basedate")[0];
		if(basedate && basedate.firstChild) {
			appointElement.setAttribute("basedate", basedate.getAttribute("unixtime"));
		}
	}

	dhtml.addTextNode(appointElement,dhtml.getTextNode(subject, NBSP)+location);
	appointElement.setAttribute("start",startUnixtime);
	appointElement.setAttribute("end",endUnixtime);
	appointElement.setAttribute("alldayevent",alldayevent);
	//style
	var className = "ipm_appointment allday_appointment";
	switch(busystatus){
		case "0":
			className += " free";
			break;
		case "1":
			className += " tentative";
			break;
		case "3":
			className += " outofoffice";
			break;
		default:
			className += " busy";
			break;
	}
	dhtml.addClassName(appointElement, className);
	dhtml.addClassName(appointElement, this.convertLabelNrToString(label));

	//addevents
	appointElement.is_disabled = dhtml.getTextNode(item.getElementsByTagName("disabled_item")[0],0) != 0;		// for private items, no events may be added to this item 
	if(!appointElement.is_disabled) {
		if (this.events && this.events["row"]){ 
			dhtml.setEvents(this.moduleID, appointElement, this.events["row"]); 
		}
		dhtml.addEvent(this.moduleID, appointElement, "click", eventCalendarDayViewOnClick);
		dhtml.addEvent(this.moduleID, appointElement, "keyup", eventCalendarDayViewKeyboard);
	}

	appointElement.type = "allday";
}

// Position all items (called after an addition/removal/change)
CalendarDayView.prototype.positionItems = function()
{
	var days = dhtml.getElementsByClassNameInElement(this.daysElement, "day", "div", true);
	
	if(days[0]){
		for(var i = 0; i < this.data["days"]; i++)
		{
			this.positionItemsOnDay(days[i]);
		}
		this.positionAllAlldayItems();
	}
}

// Reposition all allday items
CalendarDayView.prototype.positionAllAlldayItems = function()
{	
	//position all "all day events"
	var dayList = new Object();
	var headerElement = this.headerElement;
	var alldayeventElement = this.alldayEventElement;
	var timelinetopElement = this.timelinetopElement;
	
	var headDays = dhtml.getElementsByClassNameInElement(this.headerElement,"date","div",true);
	var allDayItems = dhtml.getElementsByClassNameInElement(this.alldayElement,"allday_appointment","div",true);

	allDayItems.sort(this.sortAlldayItems);

	for(i in headDays){
		dayList[i] = new Object();
	}
	if(allDayItems.length > 0){
		for(i in allDayItems){
			var realStart = allDayItems[i].getAttribute("start");
			var realEnd = allDayItems[i].getAttribute("end");
			var isAllDayEvent = allDayItems[i].getAttribute("alldayevent");
			var startTime = timeToZero(parseInt(realStart,10));
			var endTime = timeToZero(parseInt(realEnd,10));
			if(startTime < Math.floor(this.startdate/1000)){
				startTime = Math.floor(this.startdate/1000);
			}
			if(endTime > Math.floor(this.duedate/1000)){
				endTime = Math.floor(this.duedate/1000);
			}
	
			/**
			 * If the IF-statement is true the appointment runs from 0:00 day X till
			 * 0:00 day Y and can be considered a real ALL DAY event. When this is 
			 * not the case and the appointment runs from mid-day till mid-day the 
			 * duration has to be calculated differently. The mid-day appointment 
			 * has to be placed over all days it covers.
			 */
	
			// If event is not all day event.
			if(isAllDayEvent == 0){ 
				if(realStart == startTime && realEnd == endTime){
					var durationInDays = Math.ceil((endTime-startTime)/86400);
				}else{
					//calculate the start and end time in hours and minutes, if they are 00:00 then 
					//dont add one extra day in endTime and calculate durationInDays
					var startHourMin = (new Date(realStart * 1000)).getHours() + (new Date(realStart * 1000)).getMinutes();
					var endHourMin = (new Date(realEnd * 1000)).getHours() + (new Date(realEnd * 1000)).getMinutes();
					 	
					// If the time is 00:00 and dates are diff 
					if(startHourMin == 0 && endHourMin == 0){
						var durationInDays = Math.ceil((endTime-startTime)/86400);
					}else{
						var durationInDays = Math.ceil((endTime+(24*60*60)-startTime)/86400);
					}
				}
			}else{
				var durationInDays = Math.ceil((endTime-startTime)/86400);
			}
			var startDay = Math.floor((startTime - (this.days[0]/1000))/86400);
			var headerDayElement = dhtml.getElementsByClassNameInElement(this.headerElement,"date","div", true)[startDay];
			var posTop = 0;
			
			// You could have an item with 'AllDayEvent == true' but start == end.
			if(durationInDays == 0)
				durationInDays = 1;
			
			for(var j=0;j<durationInDays;j++){
				if(dayList[startDay+j]){
					while(dayList[startDay+j][posTop]=="used"){
						posTop++;
					}
				}
			}
			//flag used in dayList
			for(var j=0;j<durationInDays;j++){
				if(!dayList[startDay+j]){
					dayList[startDay+j] = new Object();
				}
				dayList[startDay+j][posTop]="used";
			}
			if(headerDayElement){
				//set style of item
				allDayItems[i].style.left = headerDayElement.offsetLeft - 50 + "px";
				allDayItems[i].style.width = (headerDayElement.offsetWidth*durationInDays) - 6 + "px";
				allDayItems[i].style.height = "14px";
				allDayItems[i].style.top = (allDayItems[i].offsetHeight+2)*posTop+"px";		
			}
			//size the headers
			posTop++;
			
			// set the heighest posTop as maxPosTop, to get the proper height of allDayElement.
			if(!this.maxPosTop){
				this.maxPosTop = posTop;
			}else{
				if(this.maxPosTop <= posTop){
					this.maxPosTop = posTop;
				}
			}
			
			if(alldayeventElement.offsetHeight > posTop*19){
				posTop = 1;
			}
			
			// set posTop to available MaxPosTop.
			if(this.maxPosTop){
				posTop = this.maxPosTop;
			}
			
			headerElement.style.height = posTop*19+17+"px";
			timelinetopElement.style.height = posTop*19+16+"px";
			alldayeventElement.style.height = posTop*19+"px";
		}
	}else{
		// handle the case if allDayItems is not present
		if(alldayeventElement.offsetHeight >= 19){
			alldayeventElement.style.height = 19+"px";
			headerElement.style.height = 19+17+"px";
			timelinetopElement.style.height = 19+16+"px";
		}
	}
}

// Gets all overlapping items and returns them as an array
CalendarDayView.prototype.getOverLapping = function(slots, start, end)
{
	var overlapping = new Object();
	
	for(elementid in slots) {
		var slot = slots[elementid];
		
		if(slot.start < end && slot.end > start) 
			overlapping[elementid] = slot;
	}
	
	return overlapping;
}

// Propagates the 'maxdepth' value of slot with id 'elementid' to all its overlappers and their overlappers
CalendarDayView.prototype.propagateMaxSlot = function(slots, elementid, maxslot)
{
	// Loop protection
	if(slots[elementid].maxslot == maxslot)
		return;
		
	slots[elementid].maxslot = maxslot;
	
	var overlapping = this.getOverLapping(slots, slots[elementid].start, slots[elementid].end);
	
	for(elementid in overlapping) {
		this.propagateMaxSlot(slots, elementid, maxslot);
	}
}

/**
 * @todo implement multi dayevents in this function
 */ 
CalendarDayView.prototype.positionItemsOnDay = function(dayElement)
{
	var items = dhtml.getElementsByClassNameInElement(dayElement, "appointment", "div", true);

	// This is the array of all items, which will be increasingly built up
	// by this algorithm to calculate in which slot the items must go
	var slots = new Object();


	// Sort items by startdate, and length (longest first)
	items.sort(this.sortItems);

	/* Loop through all the items, doing the following:
	 * For each item, get all overlapping slots alread placed and try to place the
	 * new item in slot0 .. slotN (with slotN being the maxslot of overlapper 0)
	 * If there are no slots, set slot to N+1, and propagate a new maxslot (N+1)
	 * to all overlappers and their overlappers.
	 */
	for(var i = 0; i < items.length; i++)
	{
		/**
		 * Calculate the position times. To prevent 15min appointments from 
		 * overlapping the positioning time is rounded to the fill the complete 
		 * half hour slot.
		 */
		items[i].positionStartTime = (new Date(items[i].start*1000)).floorHalfhour()/1000;
		items[i].positionEndTime = (new Date(items[i].end*1000)).ceilHalfhour()/1000;

		var overlapping = this.getOverLapping(slots, items[i].positionStartTime, items[i].positionEndTime);
		var maxdepth = 0;
		var placed = false;
		var elementid = items[i].id;
		var slot = new Object;
			
		// Remember this info so we can do overlap checking later
		slot.start = items[i].positionStartTime;
		slot.end = items[i].positionEndTime;
		
		for(var overlapid in overlapping) { // this is more like an if(overlapping.length > 0)
			maxdepth = overlapping[overlapid].maxslot; // maxslot should be the same for all overlappers, so take the first
			
			// Try to put the item in the leftmost slot
			for(var slotnr = 0; slotnr < maxdepth; slotnr++) {
				var slotfree = true;
				for(var overlapper in overlapping) {
					if(overlapping[overlapper].slot == slotnr) {
						slotfree = false;
						break;
					}
				}
				
				// This slot is still free, so use that
				if(slotfree) {
					placed = true;
					slot.slot = slotnr;
					slot.maxslot = maxdepth;
					slots[elementid] = slot;
					break;
				}
			}
			break; // Yep, only go through this once
		}

		if(!placed) {
			// No free slots, add a new slot on the right
			slot.slot = maxdepth;
			slot.maxslot = 0; // will be updated by propagateMaxSlot 

			slots[elementid] = slot;
				
			// Propagate new 'maxslot' to all other overlapping slots (and their overlappers etc)
			this.propagateMaxSlot(slots, elementid, maxdepth + 1);
		}
	}		
	 
	/* After running through this algorithm for all items, the 'slots' array
	 * will contain both the slot number and the max. slot number for each item
	 * which we now use to position and resize the items
	 */

	// Position items
	for(i = 0; i < items.length; i++)
	{
		var item = items[i];
		var slot = slots[items[i].id];
		
		var width = (item.parentNode.clientWidth / slot.maxslot) - 15;
		item.style.width = width + "px";
		item.style.left = (slot.slot * (width + 15)) + "px";
		item.style.display = "";
	}
}

// Sort on starttime, if time is equal, sort on duration (longest first)
CalendarDayView.prototype.sortItems = function(itemA, itemB)
{
	if(itemA.start == itemB.start)
		return (itemB.end - itemB.start) - (itemA.end - itemA.start);
	if(itemA.start > itemB.start) return 1;
	if(itemA.start < itemB.start) return -1;
	
	return 0;
}

CalendarDayView.prototype.sortAlldayItems = function(itemA, itemB)
{
	var result = 0;
	var startA = parseInt(itemA.getAttribute("start"),10);
	var startB = parseInt(itemB.getAttribute("start"),10);
	var endA = parseInt(itemA.getAttribute("end"),10);
	var endB = parseInt(itemB.getAttribute("end"),10);
	var durationA = endA-startA;
	var durationB = endB-startB;
		
	//biggest duration on top
	if(durationA > durationB)	result = -1;
	if(durationA < durationB)	result = 1;
	
	//smallest starttime on top
	if(startA > startB)	result = 1;
	if(startA < startB)	result = -1;
	
	return result;
}

CalendarDayView.prototype.isAllDayItem = function(item)
{
	var allDayEvent = item.getElementsByTagName("alldayevent");
	var itemStart = item.getElementsByTagName("startdate")[0].getAttribute("unixtime")*1000;
	var itemDue = item.getElementsByTagName("duedate")[0].getAttribute("unixtime")*1000;

	if((dhtml.getTextNode(allDayEvent[0]) == 1) ||
		(timeToZero(itemStart/1000) != timeToZero(itemDue/1000))){
		
		var tempStartTime = new Date(itemStart);
		tempStartTime.addDays(1);
		if((new Date(itemStart).toTime() != "00:00") &&
			(new Date(itemDue).toTime() == "00:00") &&
			(timeToZero(tempStartTime.getTime()/1000) == timeToZero(itemDue/1000))){
			return false;
		}else{
			return true;
		}
	}else{
		return false;
	}
}

CalendarDayView.prototype.loadMessage = function()
{
	document.body.style.cursor = "wait";
}


CalendarDayView.prototype.deleteLoadMessage = function()
{
	document.body.style.cursor = "default";
}

function getTimeOffsetByPixelOffset(firstElement, offset)
{
	// Loop through the target elements and check if the endTarget is between
	// the element height.
	while(firstElement.offsetTop < offset) 
	{
		firstElement = firstElement.nextSibling;
	}

	endTargetTime = firstElement.offsetTime;
	
	return endTargetTime;
}

/**
 * Opening a create appointment dialog with startdate filled in.
 */
function eventCalendarDayViewDblClick(moduleObject, element, event){
	clearTimeout(moduleObject.quickElemTimeout);

	//deselect selected item
	eventCalendarDayViewOnClick(moduleObject,false,event);

	// delete quickElem only if it exists and isn't the same time
	if (moduleObject.quickElem && moduleObject.quickElem.getAttribute("timestamp")!=element.id){
	    event = new Object();
	    
		event.keyCode = 13;
		eventQuickAppointmentKey(moduleObject, moduleObject.quickElem, event);

		dhtml.deleteElement(moduleObject.quickElem);
		moduleObject.quickElem = null;
	}

	// Get time/day of new appointment
	var offsetDay = element.offsetDay;
	var offsetTime = element.offsetTime;

	var starttime = addUnixTimestampToUnixTimeStamp(moduleObject.viewController.viewObject.days[offsetDay] / 1000, offsetTime * 60);

	var uri = DIALOG_URL+"task=appointment_standard&storeid=" + moduleObject.storeid + "&parententryid=" + moduleObject.entryid + "&date=" + starttime;
	webclient.openWindow(moduleObject, "appointment", uri);
}

/**
 * Delaying the create quick appointment function to allow the user to double click.
 */
function eventCalendarDayViewAddAppointmentDelayed(moduleObject, element, event){
	clearTimeout(moduleObject.quickElemTimeout);
	moduleObject.quickElemTimeout = setTimeout(function(){
			eventCalendarDayViewAddAppointment(moduleObject, element, event);
		},
		parseInt(webclient.settings.get("calendar/delay_quick_appointment_after_click", 500),10)
	);
}

function eventCalendarDayViewAddAppointment(moduleObject, element, event)
{
	//deselect selected item
	eventCalendarDayViewOnClick(moduleObject,false,event);

	// delete quickElem only if it exists and isn't the same time
	if (moduleObject.quickElem && moduleObject.quickElem.getAttribute("timestamp")!=element.id){
		event = new Object();

		event.keyCode = 13;
		eventQuickAppointmentKey(moduleObject, moduleObject.quickElem, event);

		dhtml.deleteElement(moduleObject.quickElem);
		moduleObject.quickElem = null;
	}

	// when quickElem still exists, exit this function, nothing to do here
	if (!moduleObject.quickElem){
		moduleObject.quickElem = dhtml.addElement(null, "input", null, "quickappointment");
		moduleObject.quickElem.setAttribute("type", "text");
		element.appendChild(moduleObject.quickElem);
		moduleObject.quickElem.offsetDay =  element.offsetDay;
		moduleObject.quickElem.offsetTime = element.offsetTime;
		dhtml.addEvent(moduleObject, moduleObject.quickElem, "keypress", eventQuickAppointmentKey);

		// workaround for Firefox 1.5 bug with autocomplete and emtpy string: "'Permission denied to get property XULElement.selectedIndex' when calling method: [nsIAutoCompletePopup::selectedIndex]"
		moduleObject.quickElem.setAttribute("autocomplete","off");

		moduleObject.quickElem.style.height = (element.offsetHeight-10)+"px";
		moduleObject.quickElem.style.width = (element.offsetWidth-6)+"px";
		moduleObject.quickElem.style.zIndex = 99;
	} 

	if (moduleObject.quickElem){
		
		/**
		Note: In IE, giving focus manually to an element changes 'scrollTop' of whichever parentElement is having scroll. 
		So,it looks like scroll jumps when an element is selected.
		*/
		var days = dhtml.getElementById("days");
		if (days){
			//Get scrollTop into temporary variable and assign it back to scrolling element, after giving focus.
			var daysscrollTop = days.scrollTop;
			moduleObject.quickElem.focus();
			days.scrollTop = daysscrollTop;
		}
	}
}

function eventQuickAppointmentKey(moduleObject, element, event)
{
	if (moduleObject.quickElem){
		switch (event.keyCode) {
			case 13:
				var subject = moduleObject.quickElem.value;
				if (subject.trim()!=""){
					// Get time/day of new appointment
					var offsetDay = moduleObject.quickElem.offsetDay;
					var offsetTime = moduleObject.quickElem.offsetTime;

					var starttime = addUnixTimestampToUnixTimeStamp(moduleObject.viewController.viewObject.days[offsetDay] / 1000, offsetTime * 60);
					var endtime = addUnixTimestampToUnixTimeStamp(starttime, HALF_HOUR / 1000);

					var reminderTime = null; 
					if(webclient.settings.get("calendar/reminder","true") == "true"){
						reminderTime = webclient.settings.get("calendar/reminder_minutes", 15); 
					}

					moduleObject.createAppointment(starttime, endtime, subject, null, null, null, reminderTime);
				}
				dhtml.deleteElement(moduleObject.quickElem);
				moduleObject.quickElem = null;
				break;
			case 27:
				dhtml.deleteElement(moduleObject.quickElem);
				moduleObject.quickElem = null;
				break;
			
		}
	}
}

// Called with moduleObject == module, NOT this view object.
function eventAppointmentListDragDropTarget(moduleObject, targetElement, element, event)
{
	// We only use the targetElement to see which day we are in, for the start/end we
	// just look at the dragged element's height and location.
	var offsetDay = targetElement.offsetDay;
	
	var starttime = false;
	var endtime = false;
	
	// Date (day-month-year 00:00 hours)
	var dayStart = moduleObject.viewController.viewObject.days[offsetDay];
	
	// Parse start hour and start minute		
	var subject = element.subject;
	var location = element.location;
	
	// Get start/endtime just by looking at the pixel height of the top and bottom of
	// the element we are dropping
	var topOffset = element.offsetTop;
	var bottomOffset = element.offsetTop + element.clientHeight;
	var firstTargetElement = dhtml.getElementById("day_" + offsetDay + "_time0");

	var startTimeOffset = getTimeOffsetByPixelOffset(firstTargetElement, topOffset);
	var endTimeOffset = getTimeOffsetByPixelOffset(firstTargetElement, bottomOffset);

	var starttime = new Date(dayStart);
	starttime.addUnixTimeStampToDate(startTimeOffset * 60);
	var endtime = new Date(dayStart);
	endtime.addUnixTimeStampToDate(endTimeOffset * 60);

	var props = new Object();
	props["store"] = moduleObject.storeid;
	props["parent_entryid"] = moduleObject.entryid;
				
	props["entryid"] = moduleObject.entryids[element.id];
	props["startdate"] = starttime.getTime()/1000;
	props["duedate"] = endtime.getTime()/1000;
	props["commonstart"] = props["startdate"];
	props["commonend"] = props["duedate"];
	props["duration"] = (props["duedate"]-props["startdate"])/60;
	props["remindertime"] = parseInt(props["startdate"]);

	var basedate = (element.getAttribute("basedate") == "undefined" || !element.getAttribute("basedate")?false:element.getAttribute("basedate"));
	if(basedate) {
		props["basedate"] = basedate;
	}

	var occurrenceBaseDate = element.id.substring(element.id.indexOf("_")+1);
	var send = false;
	var moveOccAllowFlag = true;
	if (element.meetingrequest && parseInt(element.meetingrequest) == 1) { // 1 = olResponseOrganized
		if( endtime.getTime()>(new Date().getTime())) {
			send = confirm(_("Would you like to send an update to the attendees regarding changes to this meeting?"));
		}
	}else if(basedate){
		moveOccAllowFlag = confirmAppointMoved(moduleObject, element.offsetDay, targetElement.offsetDay);
	}
	if(moveOccAllowFlag){
		moduleObject.save(props, send);
		moduleObject.viewController.updateItem(element);
	}
}

function eventDayViewClickPrev(moduleObject, element, event)
{
	var datepickerlistmodule = webclient.getModulesByName("datepickerlistmodule")[0];
	var numberOfDays = (moduleObject.duedate-moduleObject.startdate)/ONE_DAY;
	var startdate;

	if(numberOfDays == 1){
		startdate = new Date(moduleObject.startdate);
		startdate.addDays(-1);
	} else {
		startdate = new Date(moduleObject.startdate);
		startdate.addDays(-7);
	}
	datepickerlistmodule.changeMonth(startdate.getMonth() + 1, startdate.getFullYear(), true);
	datepickerlistmodule.changeSelectedDate(startdate.getTime(),true);
}

function eventDayViewClickNext(moduleObject, element, event)
{
	var datepickerlistmodule = webclient.getModulesByName("datepickerlistmodule")[0];
	var numberOfDays = (moduleObject.duedate-moduleObject.startdate)/ONE_DAY;
	var startdate;
	
	if(numberOfDays == 1){
		startdate = new Date(moduleObject.startdate);
		startdate.addDays(1);
	} else {
		startdate = new Date(moduleObject.startdate);
		startdate.addDays(7);
	}
	
	datepickerlistmodule.changeMonth(startdate.getMonth() + 1, startdate.getFullYear(), true);
	datepickerlistmodule.changeSelectedDate(startdate.getTime(),true);
}

function eventCalendarDayViewOnClick(moduleObject, element, event)
{
	//deselect quick appointment
	if(moduleObject.quickElem){
		// submit new appointment when it has some text, the keyhandler will do this when keyCode == 13
		event = new Object();
		
		event.keyCode = 13;
		eventQuickAppointmentKey(moduleObject, moduleObject.quickElem, event);
	}
	
	//deselect old appointments
	while(moduleObject.selectedMessages.length > 0){
		var item = dhtml.getElementById(moduleObject.selectedMessages.pop());
		dhtml.removeClassName(item,"selected");
	}
	
	//select appointment
	if(element && typeof moduleObject.entryids[element.id] != "undefined"){
		moduleObject.selectedMessages.push(element.id);
		dhtml.addClassName(element,"selected");
	}
}

function eventCalendarDayViewKeyboard(moduleObject, element, event)
{
	if (typeof moduleObject != "undefined"){

		if (event.type == "keydown"){

			// get the right element
			if (moduleObject && moduleObject instanceof ListModule && typeof moduleObject.selectedMessages[0] != "undefined"){
				element = dhtml.getElementById(moduleObject.selectedMessages[0]);
			
				if (element){
					switch (event.keyCode){
						case 13: // ENTER
							eventListDblClickMessage(moduleObject, element, event);
							break;
						case 46: // DELETE
							moduleObject.deleteAppointments(moduleObject.getSelectedMessages(), true); // prompt for occurrence
							break;
					}
				}
			}
		}
	}
}

