/**
 * 
 * All content on this website (including text, images, source
 * code and any other original works), unless otherwise noted,
 * is licensed under a Creative Commons License.
 * 
 * http://creativecommons.org/licenses/by-nc-sa/2.5/
 * 
 * Copyright (C) Open-Xchange Inc., 2006-2011
 * Mail: info@open-xchange.com 
 * 
 * @author Jan Finsel <jan@open-xchange.org>
 * 
 */
register_Calendar();

var activeDate=new Date();
var activeDay=activeDate.getDate();
var activeMonth=activeDate.getMonth();
var activeYear=activeDate.getFullYear();
var calendarFolderPath, activeFolderOwner = 0;

/*********************************************************************************************************************/
var selectedAppointment=new Object();
var calendarSelection = new Selection();
var lastSelectedAppointment=null;

calendar_serializeid = function(x) {
	return x.folder + "." + x.id + "." + (x.recurrence_position || 0) 
	       + "." + (x.recurrence_id || 0) + "." + (x.created_by || 0);
};
calendar_makeID = function (x) {
	var mObj = { id: x[0], folder: x[1] };	
	if (x[2] != undefined) { 
		mObj["recurrence_position"]=x[2]; 
	}
	if (x[3] != undefined) {
		mObj["recurrence_id"]=x[3];
	}
	if (x[4] != undefined) {
        mObj["created_by"]=x[4];
    }
	return mObj;
};

/**
 * A function for converting arrays used by the current view to a proper object.
 * @param {Array} array an array of fields as used by the current view.
 * @type Object
 * @return An object with the array elements mapped to the proper field names.
 */
var convertAppointmentArray = identity;

// convertAppointmentArray for workweek-based views (day, workweek and custom)
function convertWorkweekArray(a) {
    return { id: a[0], folder_id: a[1], recurrence_position: a[2] || 0,
        recurrence_id: a[3], created_by: a[4], title: a[5], start_date: a[6],
        end_date: a[7], note: a[8], recurrence_type: a[9], users: a[10],
        full_time: a[11], shown_as: a[12], color_label: a[13], location: a[14],
        private_flag: a[15], participants: a[16], interval: a[17],
        categories: a[18] };
}

var calendarhovers=new Object();
function register_Calendar() {
	
	registerView("calendar",null,
	function () { 
		register("OX_New_Search", calendarSearch);
        register("OX_All_Categories", setCalCategories);
        register("OX_Add_Categories", addCalCategories);
        register("OX_Delete_All_Categories", deleteCalCategories);
        ox.api.folder.get({
            folder: activefolder,
            success: function(data) {
                activeFolderOwner = data.created_by;
            }
        });
	},
	function () { 
		unregister("OX_New_Search", calendarSearch); 
		unregister("OX_All_Categories", setCalCategories);
        unregister("OX_Add_Categories", addCalCategories);
        unregister("OX_Delete_All_Categories", deleteCalCategories);
	},
	function () {
	    triggerEvent('OX_Delete_Search');
	},
	function() {
        ox.api.folder.get({
            folder: activefolder,
            success: function(data) {
                activeFolderOwner = data.created_by;
            }
        });
	});
	registerView("calendar/calendar/day",
		function() { 
			showNode("calendarWorkweek"); 
		}, 
		function() { 
		    convertAppointmentArray = convertWorkweekArray;
			triggerEvent("Selected", []);
			maxappointmentsperday=configGetKey("gui.calendar.day.numberofappointments");
			cwwdaysinweek = 1;
			var oDateLoc = new Date(Date.UTC(activeYear,activeMonth,activeDay,0,0,0));			    
			startofweek=oDateLoc.getUTCDay();
			if (oMiniCalendar) oMiniCalendar.setSelectedByDate(activeYear, activeMonth, activeDay);
			
			unregister("OX_Create_Object",default_calendar_create_object);
			register("OX_Create_Object",default_calendar_workweek_create_object);		
 			cwwenable();
            displayNameDayView();       
            calendarFolderPath.drawDOMNode("calendarofworkweekview", activefolder);
			showCalendarWorkweek();
			register("OX_Add_Flag", calendarAddTag);
			if (calendarhovers["daywwcustom"] && configGetKey("gui.effects.hover.calendar")) {
				calendarhovers["daywwcustom"].enable();
				calendarhovers["daywwcustom2"].enable(); 
			}
			register("OX_Print", calendar_printDay);
		 }, 
		 function() {
		 	if (calendarhovers["daywwcustom"]) { 
		 		calendarhovers["daywwcustom"].disable(); 
		 		calendarhovers["daywwcustom2"].disable(); 
		 	}
		 	unregister("OX_Create_Object",default_calendar_workweek_create_object);		 	
 			register("OX_Create_Object",default_calendar_create_object);
 			cwwdisable();
 			calendarFolderPath.clear();
		 	unregister("OX_Add_Flag", calendarAddTag);
		 	unregister("OX_Print", calendar_printDay);
            convertAppointmentArray = identity;
		 },
		function() { 
			hideNode("calendarWorkweek"); 
		},
		function() { 
			var oDateLoc = new Date(Date.UTC(activeYear,activeMonth,activeDay,0,0,0));	
			startofweek=oDateLoc.getUTCDay();
			displayNameDayView();
			calendarFolderPath.drawDOMNode("calendarofworkweekview", activefolder);		
			showCalendarWorkweek();
		}
	);
	registerView("calendar/calendar/workweek",
		function() { 
			showNode("calendarWorkweek"); 
		}, 
		function() { 
            convertAppointmentArray = convertWorkweekArray;
			triggerEvent("Selected", []);
 			maxappointmentsperday=configGetKey("gui.calendar.workweek.numberofappointments");
			cwwdaysinweek = configGetKey("gui.calendar.workweek.countdays");
			startofweek=configGetKey("gui.calendar.workweek.startday");
			unregister("OX_Create_Object",default_calendar_create_object);
			register("OX_Create_Object",default_calendar_workweek_create_object);	
			calendarFolderPath.drawDOMNode("calendarofworkweekview", activefolder);	 	
			displayNameDayView();		
 			cwwenable();
			showCalendarWorkweek();		
			if(calendarhovers["daywwcustom"] && configGetKey("gui.effects.hover.calendar")) { 
				calendarhovers["daywwcustom"].enable(); 
				calendarhovers["daywwcustom2"].enable(); 
			}
			register("OX_Add_Flag", calendarAddTag);
			register("OX_Print", calendar_printWorkweek);
		}, 
		function() {
		    unregister("OX_Print", calendar_printWorkweek);
 			if (calendarhovers["daywwcustom"]) { 
 				calendarhovers["daywwcustom"].disable(); 
 				calendarhovers["daywwcustom2"].disable(); 
 			}
			unregister("OX_Create_Object",default_calendar_workweek_create_object);		 	
 			register("OX_Create_Object",default_calendar_create_object);
 			calendarFolderPath.clear();
			cwwdisable()
		 	unregister("OX_Add_Flag", calendarAddTag);	
            convertAppointmentArray = identity;
		},
        function() { 
			hideNode("calendarWorkweek");
		},
		function() {
			displayNameDayView();
			calendarFolderPath.drawDOMNode("calendarofworkweekview", activefolder);	
			showCalendarWorkweek();
		}
	);
	registerView("calendar/calendar/custom",
		function() { showNode("calendarWorkweek"); }, 
		function() { 
            convertAppointmentArray = convertWorkweekArray;
			triggerEvent("Selected", []);
 			maxappointmentsperday=configGetKey("gui.calendar.custom.numberofappointments");
			cwwdaysinweek = configGetKey("gui.calendar.custom.countdays");
			var oDateLoc = new Date(Date.UTC(activeYear,activeMonth,activeDay,0,0,0));	
			startofweek=oDateLoc.getUTCDay();
			unregister("OX_Create_Object",default_calendar_create_object);
			register("OX_Create_Object",default_calendar_workweek_create_object);
			calendarFolderPath.drawDOMNode("calendarofworkweekview", activefolder);		 	
			displayNameDayView();		
 			cwwenable();
			showCalendarWorkweek();
			if (calendarhovers["daywwcustom"] && configGetKey("gui.effects.hover.calendar")) { 
				calendarhovers["daywwcustom"].enable(); 
				calendarhovers["daywwcustom2"].enable(); 
			}		
			register("OX_Add_Flag", calendarAddTag);
		 }, 
		 function() {
 			if (calendarhovers["daywwcustom"]) { 
 				calendarhovers["daywwcustom"].disable(); 
 				calendarhovers["daywwcustom2"].disable(); 
 			}
			unregister("OX_Create_Object",default_calendar_workweek_create_object);		 	
 			register("OX_Create_Object",default_calendar_create_object);
			cwwdisable()
			calendarFolderPath.clear();
		 	unregister("OX_Add_Flag", calendarAddTag);	
            convertAppointmentArray = identity;
		 },
		function() { 
			hideNode("calendarWorkweek");
		},
		function() {
			var oDateLoc = new Date(Date.UTC(activeYear,activeMonth,activeDay,0,0,0));	
			startofweek=oDateLoc.getUTCDay();
			calendarFolderPath.drawDOMNode("calendarofworkweekview", activefolder);
			displayNameDayView();		
			showCalendarWorkweek();
		}
	);
	var firstmonthview=false;
	registerView("calendar/calendar/month",
		function() { 
			showNode("calendarMonth");
			$("calendarMonth").style.display="block"; 
		}, 
		function() {
		    convertAppointmentArray = function(object) {
                return {
                    id: object[0], // 1
                    folder: object[1], // 20
                    folder_id: object[1], // 20
                    recurrence_position: object[2] || 0, // 207
                    recurrence_id: object[3], // 206
                    created_by: object[4], // 2
                    title: object[5], // 200
                    start_date: object[6], // 201
                    end_date: object[7], // 202
                    note: object[8], // 203
                    recurrence_type: object[9], // 209
                    participants: object[10], // 221
                    users: object[10], // 221
                    full_time: object[11], // 401
                    shown_as: object[12], // 402
                    color_label: object[13], // 102
                    location: object[14], // 400
                    categories: object[15], // 100
                    private_flag: object[16] // 101
                };
		    };
		    
			triggerEvent("Selected", []);
			if(!firstmonthview) { 
				firstmonthview=true;
				var hover=new Hover($("calmain"),OXAppointmentHover.getContent().node);
				calendarhovers["month"]=hover;	
				hover.setSize(OXAppointmentHover.contentobject.node);
				hover.getTarget = function (node) {
                    try {
    					while (node) {
    						if(node.oxcalobj) {
                                return node.parentNode ? node : null;
                            }
    						node = node.parentNode;
    					}
                    } catch (e) { /*see default implementation*/ }
                };
				hover.onShow = function (node) {
					OXAppointmentHover.actualHover=this;
					var splitid=node.oxcalobj.split("-");
					return OXAppointmentHover.refillContent(splitid[1],splitid[0],splitid[2]);	
				} 
			} 
			if(configGetKey("gui.effects.hover.calendar") && calendarhovers["month"]) {
				calendarhovers["month"].enable();
			}
			calendarFolderPath.drawDOMNode("calendarofmonthview", activefolder);
			displayNameDayView();	  
			register('OX_Refresh',refreshMonthView);
		  	register("OX_Add_Flag", calendarAddTag);
			resizeEvents.register("Resized",resizeWeekSections);
			unregister("OX_Create_Object",default_calendar_create_object);
			register("OX_Create_Object",default_calendar_workweek_create_object);		
		  	loadMonthView();
		  	register("OX_Print", calendar_printMonth);
		 }, 
		 function() {
            unregister("OX_Print", calendar_printMonth);
		 	if (calendarhovers["month"]) { 
		 		calendarhovers["month"].disable(); 
		 	}
		 	unregister('OX_Refresh',refreshMonthView);
		 	unregister("OX_Add_Flag", calendarAddTag);
			register("OX_Create_Object",default_calendar_create_object);
			unregister("OX_Create_Object",default_calendar_workweek_create_object);		 	
		 	resizeEvents.unregister("Resized",resizeWeekSections);
		 	calendarFolderPath.clear();
		 	convertAppointmentArray = identity;
		 },
		function() { 
			hideNode("calendarMonth");
			$("calendarMonth").style.display="none";
		},
		function() {
			calendarFolderPath.drawDOMNode("calendarofmonthview", activefolder);
			displayNameDayView();
			refreshMonthView(true);		
            resizeEvents.register("Resized",resizeWeekSections);	
		}
	);
	registerView("calendar/calendar/week",
		function() { 
			showNode("calendarWeek");
			$("calendarWeek").style.display=""; }, 
		function() {
		    convertAppointmentArray = function(a) {
		        return { id: a[0], folder_id: a[1],
		            recurrence_position: a[2] || 0, title: a[3],
		            start_date: a[4], end_date: a[5], note: a[6],
		            recurrence_type: a[7], users: a[8], full_time: a[9],
		            shown_as: a[10], color_label: a[11], location: a[12],
		            private_flag: a[13], recurrence_id: a[14],
		            created_by: a[15], participants: a[16], categories: a[17] };
		    };
			triggerEvent("Selected", []);
			calendarFolderPath.drawDOMNode("calendarofweekview", activefolder);
			displayNameDayView(); 	
			unregister("OX_Create_Object",default_calendar_create_object);
			register("OX_Create_Object",default_calendar_workweek_create_object);		
			objWeekCalendarTool.init();
			register("OX_Add_Flag", calendarAddTag);
			register("OX_Print", calendar_printWeek);
		 }, 
		 function() {
            unregister("OX_Print", calendar_printWeek);
		 	if(calendarhovers["week"]) { 
		 		calendarhovers["week"].disable(); 
		 	}
			register("OX_Create_Object",default_calendar_create_object);
			unregister("OX_Create_Object",default_calendar_workweek_create_object);	
		 	unregister("OX_Add_Flag", calendarAddTag);
		 	calendarFolderPath.clear();
		 	convertAppointmentArray = identity;
		},
		function() { 
			hideNode("calendarWeek");
			$("calendarWeek").style.display="none"; },
		function() {
			calendarFolderPath.drawDOMNode("calendarofweekview", activefolder);
			displayNameDayView();
			objWeekCalendarTool.init();
		}
	);
	
	var calendarTeamViewChangeTeamPopup = null;
	// store team view specific settings in ui config
	var storeTeamViewSettings = function(param) {
		var grid = getCalendarGrid("teamview");
		if (typeof(grid) != undefined) {
			configSetKey("gui.calendar.teamview.zoom", grid.zoom);
		}
	}
	
	/*
	 * TeamView
	 */
	var teamView_show = function(intervalLength, zoom, view, firstDayOfWeek) {
	    return function() {
	        // show node
    	    showNode("calendarTeamDay");
    	    // add quick config
    	    jQuery.quickConfig("#teamViewCalendarGrid", "#teamViewQuickConfig", "#teamViewQuickConfigLink");
    	    // get grid
            var grid = getCalendarGrid("teamview");
            // get last zoom level from config
            if (configContainsKey("gui.calendar.teamview.zoom")) {
                grid.zoom = configGetKey("gui.calendar.teamview.zoom");
            } else {
            	grid.zoom = zoom;
            }
            grid.applyView(view, firstDayOfWeek || 1);
            //grid.setFirstDayOfWeek(firstDayOfWeek || 1);
            //grid.setGridIntervalLength(intervalLength);
            grid.setParentDOMNode($("teamViewCalendarGrid"));
            grid.setSelectionChangedEvent("SubSelected");
            grid.setTeamMemberChangedEvent("OX_Teammember_Changed");
            grid.setZoomLevelChangedEvent("OX_Zoom_Level_Changed");
            // align with week month?
            //if (view == "MONTH" || view == "WORKWEEK") {
            //    grid.setGridIntervalToWeek(null, intervalLength);
            //} 
            grid.validateOrUpdate();
            register("OX_Zoom_Level_Changed", storeTeamViewSettings);
            calendarGridHandlers.registerAll();
	    };
	};
	
	var teamView_enter = function() {
	    // trigger selection event
	    setTimeout(function () {
	        triggerEvent("SubSelected", []);
	    }, 10);
	    // update current team
        var grid = getCalendarGrid("teamview");
        grid.loadDefaultTeam();
        if (configGetKey("gui.effects.hover.calendar") && calendarhovers["teamday"]) {
            calendarhovers["teamday"].enable();
        }
    };
    
    var teamView_leave = function() {
        if (calendarhovers["teamday"]) {
            calendarhovers["teamday"].disable();
        }
    };
    
    var teamView_hide = function() {
        hideNode("calendarTeamDay");
        $("calendarTeamDay").style.display = "none";
        unregister("OX_Zoom_Level_Changed", storeTeamViewSettings);
        calendarGridHandlers.unregisterAll();
    };
	
	registerView("calendar/team/day",
	        teamView_show(1, 200, "DAY"),
	        teamView_enter,
	        teamView_leave,
	        teamView_hide,
	        null
	        );
	
	registerView("calendar/team/workweek",
			function() {
	        	teamView_show(configGetKey("gui.calendar.workweek.countdays") || 5, 100,
	        		"WORKWEEK", configGetKey("gui.calendar.workweek.startday"))();
			},
	        teamView_enter,
	        teamView_leave,
	        teamView_hide,
	        null
	        );
	
	registerView("calendar/team/week",
            teamView_show(7, 100, "WEEK"),
            teamView_enter,
            teamView_leave,
            teamView_hide,
            null
            );
	
	registerView("calendar/team/month",
	        teamView_show(28, 75, "MONTH"),
            teamView_enter,
            teamView_leave,
            teamView_hide,
            null
	        );

    registerView("calendar/team/custom",
            function() {
                // config not available now
                var days = configGetKey("gui.calendar.custom.countdays");
                teamView_show(days, 100, "CUSTOM")();
            },
            teamView_enter,
            teamView_leave,
            teamView_hide,
            null
            );

	function calendarteamweekrefresh() {
		displayNameDayView();
		loadTeamWeek();
	}

	registerView("calendar/list",
		function() { 
			showNode("calendarList");
			$("calendarList").style.display=""; 
		}, 
		function() {
            convertAppointmentArray = function(a) {
                return { id: a[0], folder_id: a[1],
                    recurrence_position: a[2] || 0, recurrence_id: a[3],
                    created_by: a[4], recurrence_type: a[5], private_flag: a[6],
                    title: a[7], start_date: a[8], end_date: a[9],
                    location: a[10], color_label: a[11], participants: a[12],
                    full_time: a[13], users: a[14] };
            };
			register("OX_Print",calendar_printList); 
			register("OX_Add_Flag", calendarAddTag);
		}, 
		 function() {
		 	calendarFolderPath.clear();
		 	disableCalendarGrid();
		 	unregister("OX_Print",calendar_printList);
		 	unregister("OX_Add_Flag", calendarAddTag);
		 	convertAppointmentArray = identity;
		 },
		function() { 
			hideNode("calendarList");
			$("calendarList").style.display="none";
		}
	);
	registerView("calendar/list/day",
		null, 
		function() {
			calendarFolderPath.drawDOMNode("calendaroflistview", activefolder);
			cwwdaysinweek = 1; 
			if(!calendarGrid){
				initCalenderLiveGridOnLoad();
			}
			initLiveGridCalendar("day");
		 }, 
		 null,
		 null,
		 function() {
		 	calendarFolderPath.drawDOMNode("calendaroflistview", activefolder);
		 	displayNameDayView();
		 	initLiveGridCalendar("day"); 
		 }
	);
	registerView("calendar/list/workweek",
		null, 
		function() {
			calendarFolderPath.drawDOMNode("calendaroflistview", activefolder);
			cwwdaysinweek = configGetKey("gui.calendar.workweek.countdays");
            startofweek=configGetKey("gui.calendar.workweek.startday");
			if(!calendarGrid){
				initCalenderLiveGridOnLoad();
			}
			initLiveGridCalendar("workweek");
		 }, 
		 null,
		 null,
		 function() { 		 	
		 	calendarFolderPath.drawDOMNode("calendaroflistview", activefolder);
		 	displayNameDayView();
		 	initLiveGridCalendar("workweek");
		 }
	);
	registerView("calendar/list/month",
		null, 
		function() {
			calendarFolderPath.drawDOMNode("calendaroflistview", activefolder);
			if(!calendarGrid){
				initCalenderLiveGridOnLoad();
			}
			initLiveGridCalendar("month");
		 }, 
		 null,
		 null,
		 function() {
		 	calendarFolderPath.drawDOMNode("calendaroflistview", activefolder);
		 	displayNameDayView();
		 	initLiveGridCalendar("month"); 
		 }
	);
	registerView("calendar/list/week",
		null, 
		function() {
			calendarFolderPath.drawDOMNode("calendaroflistview", activefolder);
			if(!calendarGrid){
				initCalenderLiveGridOnLoad();
			}
			initLiveGridCalendar("week");
		 }, 
		 null,
		 null,
		 function() {
		 	calendarFolderPath.drawDOMNode("calendaroflistview", activefolder);
		 	displayNameDayView();
		 	initLiveGridCalendar("week"); 
		 }
	);
    registerView("calendar/list/custom",
        null, 
        function() {
            calendarFolderPath.drawDOMNode("calendaroflistview", activefolder);
            cwwdaysinweek = configGetKey("gui.calendar.custom.countdays");
            var oDateLoc = new Date(Date.UTC(activeYear,activeMonth,activeDay,0,0,0));  
            startofweek=oDateLoc.getUTCDay();
            if(!calendarGrid){
                initCalenderLiveGridOnLoad();
            }
            initLiveGridCalendar("custom");
         }, 
         null,
         null,
         function() {
            calendarFolderPath.drawDOMNode("calendaroflistview", activefolder);
            displayNameDayView();
            initLiveGridCalendar("custom"); 
         }
    );
	registerView("calendar/list/search",
		null, 
		function() { 
			if (!calendarGrid){
				initCalenderLiveGridOnLoad();
			}
			initLiveGridCalendar("search");
			register("OX_Delete_Search", calendarDeleteSearch);			
			if (lastCalendarSearch) {			   
               setTimeout(function() {
               	    calendarFolderPath.nodeId="calendaroflistview";
                	calendarFolderPath.drawSearch(lastCalendarSearch.module, lastCalendarSearch.searchstring);
                },0);
            }			
		 }, 
		 function() {
		 	calendarFolderPath.clear();	 	
	 		unregister("OX_Delete_Search", calendarDeleteSearch);
		 },
		 null
	);
	registerView("calendar/detail",
	    // SHOW
		function() { 
			showNode("calendarAppointmentDetail");
			$("calendarAppointmentDetail").style.display="";
	    },
		// ENTER
		function() { 
			register("OX_Refresh",reloadAppDetailView);
			register("OX_AcceptDeny_Changed",reloadAppDetailView);
			register("OX_Confirmation_Change",caldetail_change_Confirm);
			register("OX_Add_Flag", calendarAddTag);
			// added "lastSelectedAppointment" so that this also works with the
			// team view. This view (correctly) clears its selection when leaving
			// the view. so: last selected appointment or normal selection?
			var selectedApp = selectedAppointment || lastSelectedAppointment;
			// flatten array
			if (selectedApp.length) selectedApp = selectedApp[0];
			// id?
			if (selectedApp.id) {
			    // restore selectedAppointment, since the next function uses global magic
			    selectedAppointment = selectedApp;
				detailAppointmentOnload();
			}
			if (typeof selectedAppointment == 'undefined' || selectedAppointment == '') {
				triggerEvent("OX_New_Error", 4, _("No Appointment selected."));
			}
        },
        // LEAVE
        function() {
        	unregister("OX_Refresh",reloadAppDetailView);
        	unregister("OX_AcceptDeny_Changed",reloadAppDetailView);
        	unregister("OX_Confirmation_Change",caldetail_change_Confirm);
        	unregister("OX_Add_Flag", calendarAddTag);
        	objAppDetailParticipants = null;
        },
		function() { 			
			hideNode("calendarAppointmentDetail");
			$("calendarAppointmentDetail").style.display="none";
		}
	);
	registerView("calendar/detail/overview",
		function() {
			register("OX_Print",calendar_printDetail);
			$("tabAppointmentDetail1").style.display="block";
			$("panelAppointmentDetail1").className="tabPanelFirstHi tabPanelHiColor background-color-content font-style-lable border-color-content-default";			
		},
		null, 
		null,
		function() {
			unregister("OX_Print",calendar_printDetail);
			$("tabAppointmentDetail1").style.display="none";
			$("panelAppointmentDetail1").className="tabPanelFirst tabPanelColors font-color-disabled background-color-additional-content border-color-design font-weight-default";			
		}
	);
	registerView("calendar/detail/participant",
		function() {
			$("tabAppointmentDetail2").style.display="block";
			$("panelAppointmentDetail2").className="tabPanelHi tabPanelHiColor background-color-content font-style-lable border-color-content-default";			
		}, 
		function() { 
			refAppDetailParticipantTab.enableGrid();
			currentAppointmentDetailTab = 'PARTICIPANT';
		 }, 
		function() {
			if(a_d_participantGrid && a_d_participantGrid.storage) {
				refAppDetailParticipantTab.disableGrid();
			}
		},
		function() {
			$("tabAppointmentDetail2").style.display="none";
			$("panelAppointmentDetail2").className="tabPanel tabPanelColors font-color-disabled background-color-additional-content border-color-design font-weight-default";			
		}
	);
	var attachmentGridLoaded = false;
	registerView("calendar/detail/attachment",
		function() {
			$("tabAppointmentDetail3").style.display="block";
			$("panelAppointmentDetail3").className="tabPanelHi tabPanelHiColor background-color-content font-style-lable border-color-content-default";			
		}, 
		function() {
			if(!attachmentGridLoaded)
			{
				makeDetailAppointmentAttachmentGrid();
				attachmentGridLoaded = true;
			}
			else
				gridatt.enableGrid();
			gridatt.getAttachments(selectedAppointment.id,selectedAppointment.folder);				
		}, 
		 function() {
		 	if(attachmentGridLoaded)
				gridatt.disableGrid();
		},
 		function() {
			$("tabAppointmentDetail3").style.display="none";
			$("panelAppointmentDetail3").className="tabPanel tabPanelColors font-color-disabled background-color-additional-content border-color-design font-weight-default";			
		}

	);				
}
var ctd_tv_members;
register("Loaded",function() {
	// preparing currently selected members for later use in new appointment, see US532
	register("OX_Teammember_Changed",
			function(members) { 
				var _mem = [];
				for (var i in members) {
					_mem.push([0, 0, members[i].display_name, 0, members[i].id, 0, 0, members[i].type]);
				}
				ctd_tv_members = {};				
				ctd_tv_members["data"] = _mem;
				ctd_tv_members["module"] = "contacts";
			}
	);
	
    calendarFolderPath = new FolderPath("calendar");

    var ids = ["arrowForCWVOptions", "arrowForCWWVOptions",
        "arrowForCMVOptions", 
        "arrowForCLVOptions", "arrowForCDetailVOptions"];
    for (var i in ids) {
        var menu = new PulldownMenu($(ids[i]));
        menu.addItem((new MenuItem(_("Options"), showOptions))
                      .setIcon("img/folder/settings.gif"));
    }

    function showOptions() { 
        var elem=currentpath2.join("/");
        if(elem.indexOf("calendar/calendar") == 0 || elem.indexOf("calendar/detail") == 0 || elem.indexOf("calendar/list") == 0) { 
            ox.UIController.setModule({
                module: "configuration",
                view :"configuration/calendar/preferences",
                success: function() {
                    ox.widgets.configTree.get("configuration/calendar").open();
                    ox.widgets.configTree.selection.click("configuration/calendar/preferences", false,
                        false, true);
                }
            });
        } else if(elem.indexOf("calendar/team") == 0) { 
            ox.UIController.setModule({
                module: "configuration",
                view :"configuration/calendar/teams",
                success: function() {
                    ox.widgets.configTree.get("configuration/calendar").open();
                    ox.widgets.configTree.selection.click("configuration/calendar/teams", false,
                        false, true);
                }
            });
        }   
    }
});

/*********************************************************************************************************************/
var calendar_activeallfolders=true;
registerPrintView("calendar/list/day");
registerPrintView("calendar/list/workweek");
registerPrintView("calendar/list/month");
registerPrintView("calendar/list/week");
registerPrintView("calendar/list/search");
registerPrintView("calendar/detail/overview");

function calendar_printList() {
	globalprint.printLiveGrid(calendarGrid);
}

//Templates
register("OX_Configuration_Loaded", 
	function() {
		// just enable template printing if bundle is installed and enabled
		if (configContainsKey("modules.calendar.printing") && 
				configGetKey("modules.calendar.printing") == true) {
            registerPrintView("calendar/calendar/day");
			registerPrintView("calendar/calendar/workweek");
			registerPrintView("calendar/calendar/week");
			registerPrintView("calendar/calendar/month");
		}
	}
)

function calendar_printDay() {
    ox.api.calendar.print({ view: "day" });
}

function calendar_printWorkweek() {
    ox.api.calendar.print({ view: "workweek" });
}

function calendar_printWeek() {
    ox.api.calendar.print({ view: "week" });
}

function calendar_printMonth() {
    ox.api.calendar.print({ view: "month" });
}

function default_calendar_create_object(module,folder) {
	if (module=="calendar") {
		var d=new Date();
		var myinterval=15;
		d.setUTCDate(1);
		d.setUTCMonth(activeMonth);
		d.setUTCFullYear(activeYear);
		d.setUTCDate(activeDay);
		d.setUTCHours(d.getHours());
		var mymin=d.getUTCMinutes();
		if(mymin % myinterval) {
			mymin=mymin - (mymin % myinterval)+ myinterval;
		}
		d.setUTCMinutes(mymin);
		calendar_createNewAppointment(d, folder, false, 
				(currentpath2.join("/").match(/^calendar\/team\//) ? ctd_tv_members : null));	
	}
}

function calendar_createNewAppointment(startdate,infolder,wholeday,oFromMail,endDate) {

    // open window
    ox.api.calendar.compose({
        params: {
            folder: infolder,
            start_date: startdate !== undefined ? startdate.getTime() : undefined,
            end_date: endDate !== undefined ? endDate.getTime() : undefined,
            full_time: wholeday
        },
        data: oFromMail ? { sFromModule: oFromMail.module, oFromMail: oFromMail.data } : {}
    });
}

var detailAppointmentLoading = false;
	
function reloadAppDetailView(opt_fn, target_folder) {
	
	function cb_reloadAppDetail() {
		detailAppointmentLoading = false;		
		if(currentCalendarTagProgress)
			endProgressCalendarTagging();
		if(opt_fn)
			opt_fn();
	}
	
	if(!detailAppointmentLoading) {
		detailAppointmentLoading = true;	
		
		triggerEvent("OX_Switch_View", "calendar/detail/overview");	
		
		if(selectedAppointment.length && selectedAppointment.length == 1) {
			selectedAppointment = selectedAppointment[0];
		}
		if(selectedAppointment.id)
		{			
			if(target_folder && activefolder != target_folder) {
				var cb_setActiveFolder = function(node) {
					
					triggerEvent("Selected", [selectedAppointment]);
					detailAppointmentOnload(cb_reloadAppDetail);
				}
				selectedAppointment.folder = target_folder;
				ox.api.ui.setFolder(target_folder,cb_setActiveFolder);
			} else {
				detailAppointmentOnload(cb_reloadAppDetail);
			}
												
		}
	}
}

registerModule("calendar", _("Calendar"), 30);
registerModuleView("calendar","Calendar",5,{x:0,y:0,ww:1,hw:1});
//TODO
register("OX_New_OXObject",function(mymodule,folder) {
	if(mymodule== "calendar") {
		calendar_createNewAppointment(new Date(Date.UTC(activeYear, activeMonth, activeDay,0,0,0)),folder,false);
	}
});
register("OX_Calendar_Edit", openAppointmentPopup);
register("OX_Calendar_Delete", deleteAppointment);
var currentCalendarRange = "";
var currentCalendarView = "CALENDAR";

function displayNameDayView () {
    ox.api.folder.get({
        folder: activefolder,
        success: displayNameDayViewCb
    });
}

function calendar_getAllFoldersAttribute () {	
	return configGetKey("gui.calendar.allfolders") && calendar_activeallfolders;
}

function displayNameDayViewCb (folder) {
	calendar_activeallfolders = false;
	if (folder.type == 1 || folder.type == 3){
        calendar_activeallfolders = folder.type == 1 ? true : false;
    }	
	if (folder.created_by) {
		internalCache.getUsers([folder.created_by], function(cbObj){
			if(folder.type == 1 || folder.type == 3) {
				if (folder.type == 1) {
					$("allAppointmentListViewDiv").style.visibility = "visible";
					$("allAppointmentsWeekViewDiv").style.visibility = "visible";
					$("allAppointmentsWorkWeekViewDiv").style.visibility = "visible";
					$("allAppointmentsMonthViewDiv").style.visibility = "visible";
					calendar_activeallfolders=true;
				} else {
					$("allAppointmentListViewDiv").style.visibility = "hidden";
					$("allAppointmentsWeekViewDiv").style.visibility = "hidden";
					$("allAppointmentsWorkWeekViewDiv").style.visibility = "hidden";
					$("allAppointmentsMonthViewDiv").style.visibility = "hidden";
					calendar_activeallfolders=false;
				}
			} else if (folder.type == 2) {
				$("allAppointmentListViewDiv").style.visibility = "hidden";
				$("allAppointmentsWeekViewDiv").style.visibility = "hidden";
				$("allAppointmentsWorkWeekViewDiv").style.visibility = "hidden";
				$("allAppointmentsMonthViewDiv").style.visibility = "hidden";
				calendar_activeallfolders=false;
			}
		});
	}
}

function showAllAppointments(){
	if(calendar_getAllFoldersAttribute()){
		$("allAppointmentListView").checked = "";
		configSetKey("gui.calendar.allfolders",false);
		if(currentpath2[1] == "list") {
			initLiveGridCalendar(currentpath2[2]);
		}
	} else {
		$("allAppointmentListView").checked = "checked";
		configSetKey("gui.calendar.allfolders",true);
		if(currentpath2[1] == "list") {
			initLiveGridCalendar(currentpath2[2]);
		} 		
	}
}

function openMailToApp(oDate, oMailObjects, sMod) {
	
	// for PIM this feature isn't allowed, so triggering the upsell dialog
	if (corewindow.configGetKey('participants.showDialog') == false) {  
		corewindow.triggerEvent('Feature_Not_Available','modules/calendar/new/add_participants', window);
		return;
	}
	
	if (sMod == "contacts") {
		var aIds=new Array();
		for(var indx=0; indx<oMailObjects.length; indx++) {
			aIds.push({id:oMailObjects[indx].id,folder:oMailObjects[indx].folder || oMailObjects[indx].folder_id});
		}
		json.put(AjaxRoot + '/contacts?action=list&session='+session+"&columns="+"1,20,500,555,524,556,557",aIds,null,function (arg){
			calendar_createNewAppointment(oDate,configGetKey("folder.calendar"),false,{module:sMod,data:arg.data});		
		});
	} else if (sMod == "mail") {
		if (oMailObjects.length > 5) {
			oMailObjects = oMailObjects.slice(0,4);
		}
		var collection={};
        collection.objects = oMailObjects;
        collection.columns = OXMailMapping.GETPLAIN;
        OXCache.newRequest(null, "mail", collection, null,
        function (daten) {
            if (daten.objects.length) {
                calendar_createNewAppointment(oDate,configGetKey("folder.calendar"),false,{module:sMod,data:daten.objects});
            }
        });
	} else {
		calendar_createNewAppointment(oDate,configGetKey("folder.calendar"),false,{module:sMod,data:oMailObjects});
	}
}

function openAppointmentPopup(e) {
	triggerEvent("OX_Clear_All_Edit_Lines");	
	var selappoint;

	function edit_whole_series() {
		var app_id = selappoint.recurrence_id || selappoint.id;
		ox.api.calendar.edit({
			params: {
				id: app_id,
				recurrence_position: 0,
				folder: selappoint.folder
			},
			id: selappoint
		});
	}
	function edit_single_series() {
		ox.api.calendar.edit({
			params: {
				id: selappoint.id,
				recurrence_position: selappoint.recurrence_position,
				folder: selappoint.folder,
				singleappointment: "yes"
			},
			id: selappoint
		});
	}
	function edit_single_appointment() {
		ox.api.calendar.edit({
			params: {
				id: selappoint.id,
				recurrence_position: 0,
				folder: selappoint.folder
			},
			id: selappoint
		});
	}
	
	if(selectedAppointment.length){
		selappoint=selectedAppointment[0];
	} else {
		selappoint=selectedAppointment;
	}
    if (!menucheckRight("WRITE")) {
        triggerEvent("OX_New_Error", 4,
            _("You do not have write permission for this object."));
    } else if (selappoint.recurrence_position > 0 && selappoint.recurrence_id) {
		newConfirm(_("Edit Appointment"),_("Do you want to edit the whole series or a single series appointment? Please note when changing a serial appointment, exceptions to the series are reset."),AlertPopup.SERIESBUTTON,null,null,null,null,edit_whole_series,edit_single_series);
	} else {
		edit_single_appointment();
	}
	if (e) { stopEvent(e) };
}

function getLastCalendarView() {
    return menulastviews.calendar || getDefaultCalendarView();
}

function deleteAppointment(){
    
    // the team view has its own handling of deleting appointments
    if (activemodule == "calendar" && currentpath2[1] == "team") {
        calendarGrid_deleteSelectedAppointments();
        return;
    }
    
    var todelete=[];
    if (selectedAppointment.length){
        todelete = selectedAppointment;
    } else { 
        todelete[0] = selectedAppointment;
    }
    
    // sadly the grid selection doesn't provide enough data to determinate 
    // the group status, that's why we have to iterate through the storage :-|
    if (currentpath2[1] == "list") {
        todelete = [];
        calendarGrid.storage.newIterate(selectedAppointment, emptyFunction, function(id, object) { 
            todelete.push({ id: object[0], folder_id: object[1],
                recurrence_position: object[2], recurrence_id: object[3],
                created_by: object[4], participants: object[12] });
        });
    }

    // get latest timestamp
    // start with "now"
	var timestamplocal = new Date().getTime()
	if (activemodule == "calendar") {
		if (currentpath2[1] == "list" && calendarGrid.storage.timestamp) {
		    // get storage timestamp
			timestamplocal = calendarGrid.storage.timestamp;
		} else if (lastUpdateOfCalendarTimestamp) {
		    // get global timestamp
			timestamplocal = lastUpdateOfCalendarTimestamp;
		}
	}
	var deletionWarning = false;
	for(var i = 0; i<todelete.length;i++){
		if (todelete[i].id.toString().indexOf("_")  != -1) {
			todelete[i].id = todelete[i].id.substring(0,todelete[i].id.indexOf("_"));
		}
		
		// user is owner and it's a group app. we need to display a warning later
		if (deletionWarning == false && todelete[i].created_by == config.identifier && 
		        todelete[i].participants.length > 1) {
		    deletionWarning = true;
		}
	}
	var isseries=false;
	for(var x=0; x<todelete.length; x++) {	    
		if (todelete[x].recurrence_position > 0 && todelete[x].recurrence_id != null) {
			isseries=true;
			break;
		}
	}
	
	var fn_afterDelete_Appoitment = function(cb) {
		triggerEvent("OX_Refresh_Mini_Calendar");
		triggerEvent("OX_After_Delete_Appointment");
		for(var i=0; i<cb.length;i++){
			if(cb[i] && cb[i].error){
				newServerError(cb[i],4);	
			}
		}
		window.setTimeout(function() {
			if(currentpath[0] == "calendar") {
				if(currentpath2[1] == "calendar") {
					window.setTimeout("storageCache.update()", 300);
				} else if(currentpath2[1] == "list") {
					if(currentpath2[2] != "search") {
						storageCache.update();
					} else {
						calendarSearch(lastCalendarSearch.module, lastCalendarSearch.searchstring, null, null, lastCalendarSearch.folder);
					}
				} else if(currentpath2[1] == "detail") {
					triggerEvent("OX_Switch_View", getLastCalendarView());
				}
			} 
			selectedAppointment={};	
			triggerEvent("Selected",new Array());
		},0);
	}
	
	function delete_single_series() {
		var multipleArray = [];
		for(var i = 0; i < todelete.length; i++){
			multipleArray[i] = {action : "delete", module : "calendar", timestamp : timestamplocal, data : todelete[i]};
		}
		json.put(AjaxRoot + "/multiple?session="+session +"&continue=true", multipleArray, null, fn_afterDelete_Appoitment);	
	}
	function delete_whole_series() {
		var multipleArray = [];
		var deleteobject=new Object();
		var newtodelete=new Array();
		for(var i = 0; i < todelete.length; i++){	
			if(!deleteobject[todelete[i].folder+":"+todelete[i].id]) {
				deleteobject[todelete[i].folder+":"+todelete[i].id]=true;
				newobject=new Object();
				newobject.id=todelete[i].recurrence_id || todelete[i].id;
				newobject.folder=todelete[i].folder;
				newtodelete.push(newobject);
			}
		}
		for (var i=0; i<newtodelete.length; i++) {	
			multipleArray[i] = { action : "delete", module : "calendar", timestamp : timestamplocal, data : newtodelete[i]};
		}
		json.put(AjaxRoot + "/multiple?session=" + session +"&continue=true", multipleArray, null, fn_afterDelete_Appoitment);
	}
	
	var message = _("Are you sure you want to delete the selected items?");
	if (todelete.length==1) {
		if (isseries) {
			message = _("Do you want to delete the whole series or a single series appointment ?");
		} else {
			message = _("Are you sure you want to delete the selected item?");
		}
	} else if (isseries) {
	    message = newConfirm(_("Delete Appointment"), _("Do you want to delete the whole series or a single series appointments?"),AlertPopup.SERIESBUTTON,null,null,null,null,delete_whole_series,delete_single_series);
	}
	
	// user is owner and it's a group appointment. warn the owner that the app will be deleted 
	// for all participants.
	if (deletionWarning === true) {
	    message += "\n\n" + _("Please note: You are the creator of this appointment. If you delete this appointment it will be removed for all participants.");
	}
	
	if (isseries) {
        newConfirm(_("Delete Appointment"), message, AlertPopup.SERIESBUTTON, null, null, null, null, delete_whole_series, delete_single_series);
    } else {
        newConfirm(_("Delete Appointment"), message, AlertPopup.YESNO, null, null, delete_whole_series, null, null);
    }
	
}
function cancelConflict(){
	$("appointmentConflictWindow").style.display = "none";
	storageCache.update();
}
function ignoreConflict(){
	if(dragObjDayView){
		dragAppointmentDayView.ignore_conflicts = true;
		json.put(AjaxRoot + "/calendar?action=update&session=" + session + "&id=" + dragObjDayView.id + "&timestamp=" + lastUpdateOfCalendarTimestamp + "&folder=" + dragObjDayView.folder, dragAppointmentDayView, null, function(cb){
				if(!cb.error && !cb.data.conflicts){
					storageCache.update();
				}
			});
	}
	dragObjDayView = null;
	$("appointmentConflictWindow").style.display = "none";
}

var cal_numOfObj = null; // last shown number of objects in head line
/**
 * change the number of objects in the folder shown in the headline of each view
 * @param num {Integer} the number to shown in the head line
 */
function calendar_HeaderUpdate(num) {
	if (cal_numOfObj == num) return; // nothing changed so nothing to render
	cal_numOfObj = num;
	calendarFolderPath.additionalNode=document.createTextNode(" (" + cal_numOfObj + ")");
	var view = "calendarofworkweekview";
	switch (currentpath2.join("/")) {
		case "calendar/calendar/month": 
			view = "calendarofmonthview"; 
			break;
		case "calendar/calendar/week":
			view = "calendarofweekview";
			break;
		case "calendar/list/day":
		case "calendar/list/workweek":
		case "calendar/list/month":
		case "calendar/list/week":
			view = "calendaroflistview";
			break;
	}
	calendarFolderPath.drawDOMNode(view, activefolder);
}

function setCalCategories(action, category) {
	if (currentview.name.match(/team/) && calendarGridHandlers) {
		calendarGridHandlers.setCategories(action, category);
	
	} else {
		var objects = [];
		if (Object.prototype.toString.call(selectedAppointment) == "[object Array]") {
			objects = clone(selectedAppointment);
		} else {
			objects = [ clone(selectedAppointment) ];
		}
		
		for (var i = 0; i < objects.length; i++) {
            objects[i].module = "calendar";
            objects[i].folder_id = objects[i].folder;
            delete objects[i].folder;
        }
		
		var collection = { columns: ["created_by", "users", "title", "location",
                                     "start_date", "end_date", "categories"],
                           objects: objects };
		OXCache.newRequest(null, "calendar", collection, null, 
		function (data) {
        	var cats = [], apps = data.objects;
            for (var i in apps) {
            	if (apps[i].categories) {
            		cats.push(apps[i].categories);
            	}
            }            
            function update(list, str) {
            	// update appointments
            	var multiple = [], $l = apps.length;
                for (var i = 0; i < $l; i++) {
                    var request = {
                        "action": "update",
                        "module": "calendar",
                        "folder": apps[i].folder_id,
                        "id": apps[i].id,
                        "data": {
                        	"categories": (str || apps[i].categories || null)
                        },
                        "timestamp": apps[i].timestamp
                    };
                    // add recurrence_position?
                    if (apps[i].recurrence_position) {
                        request.recurrence_position = apps[i].recurrence_position;
                    }
                    multiple.push(request);
                }
                json.put(AjaxRoot + "/multiple?session=" + session, multiple, null, function(response) {            	
                	if (currentpath[0] == "calendar") {
        				if (currentpath2[1] == "calendar") {
        					window.setTimeout("storageCache.update()", 300);
        				} else if (currentpath2[1] == "list") {
        					storageCache.update();
        				} else if (currentpath2[1] == "detail") {
        					triggerEvent("OX_Switch_View", getLastCalendarView());
        				}        				
        				//triggerEvent("Selected", [selectedAppointment]);
        				
        				// don't ask. as long as all modules doesn't use the cache we have
        				// to force an update here. otherwise re-selection of a previous
        				// edit object fails with old data. highly needs to be recoded!
        				OXCache.newRequest(null, "calendar", { columns: [ "categories"],
        		            objects: apps }, null, emptyFunction, true);
        			}
                });
            }
            
            if (action == "add") { 
            	// iterate over all apps and add the new category to 
            	// each of it
            	for (var i in apps) {
                	if (apps[i].categories) {
                		apps[i].categories = ox.categories.utils.paramToString(            			
                    			ox.categories.getByString(
                    					(category[0] + ", " + apps[i].categories), 
                    						ox.categories.match.ALL, true), "name");
                	} else {
                		apps[i].categories = category[0];
                	}
                }
            	update();
            	
            } else if (action == "delete_all") { 
            	// just in case, delete the old categories
            	for (var i in apps) {
            		delete (apps[i].categories);
                }
            	update(cats, null);
            	
            } else {
            	// open dialog
            	ox.categories.ui.open(cats.join(","), update);
            }
        	
        });
	}
}

function addCalCategories(categories) {
	setCalCategories("add", categories);
}

function deleteCalCategories(category) {
	setCalCategories("delete_all", category);
}


/*
 *	Simple Calendar API
 */

// calendar namespace
ox.calendar = { ui: {}, debug: false };

// helper
ox.calendar.MINUTE = 60 * 1000;
ox.calendar.HOUR = ox.calendar.MINUTE * 60;
ox.calendar.DAY = ox.calendar.HOUR * 24;
ox.calendar.WEEK = ox.calendar.DAY * 7;

ox.calendar.floor = function(timestamp, step) {
	timestamp = timestamp || 0;
	step = step || ox.calendar.HOUR;
	return Math.floor(timestamp / step) * step;
};

ox.calendar.now = function() {
	return (new Date()).getTime();
};

// get
ox.calendar.get = function(folder_id, id, recurrence_position, callback) {

	// folder_id set?
	if (!folder_id) {
		// set default folder
		folder_id = ox.calendar.getDefaultFolder();
	}
	
	if (typeof folder_id != "undefined" && typeof id != "undefined" && typeof recurrence_position != "undefined") {
		
		// ensure default value
		recurrence_position = recurrence_position || 0;
		
		// server request
		ox.JSON.get(AjaxRoot + "/calendar?action=get&session=" + session + "&id=" + id +
            "&folder=" + folder_id +
            (recurrence_position ? "&recurrence_position=" + recurrence_position
                                 : ""),
			// success handler
			function(response) {
				// debug?
				if (ox.calendar.debug) { console.info("ox.calendar.get", clone(response)); }
				// got a response?
				if (response && response.data) {
					// callback
					if (typeof callback == "function") { callback(response); }
				}
			}
		);
	} else {
		// invalid function call
		console.error(
			"ox.calendar.get(folder_id : int, id : int, recurrence_position : int, [callback : function]). Invalid parameters.", 
			folder_id, id, recurrence_position, callback
		);
	}
};

// get multiple
ox.calendar.getMultiple = function(apps, callback) {
	// is array?
	if (apps && apps.length) {
		// prepare "multiple" request
		var multiple = [], i = 0, $l = apps.length;
		for (; i < $l; i++) {
			var request = {
                "action": "get",
                "module": "calendar"
			};
			// objects or flat list?
			if (typeof apps[i] == "number") {
				app.id = apps[i];
			}
			else {
				var app = apps[i];
				request.folder = app.folder_id || ox.calendar.getDefaultFolder();
                request.id = app.id;
                request.recurrence_position = app.recurrence_position || 0;
			}
			multiple.push(request);
		}
		// server request
		ox.JSON.put(AjaxRoot + "/multiple?session=" + session, multiple, function(response) {
			if (response && typeof callback == "function") {
				callback(response);
			}
		});
    }
};

// create appointment
ox.calendar.create = function(app, callback) {
	if (app) {
		// folder not set?
		if (!app.folder_id) {
			if (configContainsKey("folder.calendar")) {
				// set to default folder
				app.folder_id = configGetKey("folder.calendar");
			}
		}
		// alarm not set?
		if (!app.alarm) {
			// default alarm time set?
			if (configContainsKey("gui.calendar.default_reminder")) {
				// set to default
				app.alarm = configGetKey("gui.calendar.default_reminder");
			}
		}
		// mandatory fields defined?
		if (typeof app.start_date != "undefined" && typeof app.end_date != "undefined" && typeof app.folder_id != "undefined") {
			// server request
			ox.JSON.put(AjaxRoot + "/calendar?action=new&session=" + session, app,
				// success
				function(response) {
					if (response && response.data) {
						// update storage?
						if (response.data.id) { storageCache.update(); }
						// callback
						if (typeof callback == "function") { callback(response); }
					}
				}
			);
		}
		else {
			// invalid function call
			console.error(
				"ox.calendar.create(appointment : object, [callback : function]). " + 
				"Mandatory fields are: folder_id, start_date, end_date.", app
			);
		}
	}
};

ox.calendar.ui.create = function(app, callback) {
	// call API
	ox.calendar.create(app, function(response) {
		// ignore conflict handler
		var ignore = function() {
			// set ignore flag
			app.ignore_conflicts = true;
			// try again
			ox.calendar.create(app, function(response) {
				// callback
				if (typeof callback == "function") { callback(response); }
			});
		};
		// conflict?
		if (response.data.conflicts) {
			// show conflict dialog
			ConflictsPopup.open(app, response.data.conflicts, null, null, ignore);
		}
		else {
			// callback
			if (typeof callback == "function") { callback(response); }
		}
	});
};

// new
ox.calendar.ui.editNew = function(param) {
	// mandatory field: folder_id
	var app = {};
	// param is number or string?
	if (typeof param == "number" || typeof param == "string") {
		// copy folder_id
		app.folder_id = param;
	} else if (typeof param == "object") {
		// copy object
		app = param;
	}
	// folder_id not set?
	if (typeof app.folder_id == "undefined") {
		// default folder available?
		if (configContainsKey("folder.calendar")) {
			// set to default folder
			app.folder_id = configGetKey("folder.calendar");
		}
	}
	// folder_id set?
	if (typeof app.folder_id != "undefined") {
		// continuation
		var cont = function() {
			// start date not set?
			if (typeof app.start_date == "undefined") {
				app.start_date = ox.calendar.floor(ox.calendar.now()) + 1; // default: next hour
			}
			// end date not set?
			if (typeof app.end_date == "undefined") {
				app.end_date = app.start_date + 2 * ox.calendar.HOUR; // default length: two hours
			}			
			// open new window
			ox.api.calendar.compose({
				params: {
					folder: app.folder_id,
					start_date: app.start_date,
					end_date: app.end_date
				}
			});
		};
		// has general write privileges?
		rightshandler.checkRights("calendar", null, app, cont, function() {});
	}
	else {
		// invalid function call
		console.error(
			"ox.calendar.ui.editNew(appointment : object | folder_id : int). " + 
			"Mandatory fields are: folder_id.", app
		);
	}
};

// checks whether the appointment is a recurring appointment
ox.calendar.isRecurrence = function(app) {
	return app.recurrence_position > 0 && app.recurrence_id == app.id;
};

ox.calendar.isSeries = function(app) {
	return !app.recurrence_position && app.recurrence_type > 0;
};

ox.calendar.isSingle = function(app) {
	return app.recurrence_type == 0 || typeof app.recurrence_type == "undefined";
};

ox.calendar.getDefaultFolder = function() {
	// default folder available?
	return configContainsKey("folder.calendar") ? configGetKey("folder.calendar") : null;
};

// edit
ox.calendar.ui.edit = function(app) {
	if (app) {
		// clear edit lines
		triggerEvent("OX_Clear_All_Edit_Lines");
		// dialog handlers
		var edit = function() {
			ox.api.calendar.edit({
				params: {
					id: app.id,
					folder: app.folder_id,
					recurrence_position: 0
				}
			});
		};
		var editRecurrence = function() {
			ox.api.calendar.edit({
				params: {
					id: app.id,
					folder: app.folder_id,
					recurrence_position: app.recurrence_position,
					singleappointment: "yes"
				}
			});
		};
		// write privileges?
		menuhasRightandLoad([app], "WRITE",
			// yes
			function () {
				// is recurrence?
				if (ox.calendar.isRecurrence(app)) {
					// show dialog
					newConfirm(_("Edit Appointment"),
						_("Do you want to edit the whole series or a single series appointment? Please note when changing a serial appointment, exceptions to the series are reset."),
						AlertPopup.SERIESBUTTON, null, null, null, null, edit, editRecurrence
					);
				}
				else {
					// edit single appointment/series
					edit();
				}
			},
			// no
			function() {
				// trigger event
				triggerEvent("OX_New_Error", 4, _("You do not have write permission for this object."));
			}
		);
	}
};

// update
ox.calendar.update = function(app, callback) {
	
	if (app) {
		
		var data = app.data, timestamp = app.timestamp;
		
		// mandatory fields defined?
		if (data && typeof timestamp != "undefined" && typeof data.id != "undefined" && 
			typeof data.folder_id != "undefined" && typeof data.recurrence_position != "undefined") {
	
			// adjust request data
			var requestData = clone(app.data);
			
			// clean up first? (exceptions)
			if (requestData.recurrence_position > 0 && requestData.id != requestData.recurrence_id) {
				requestData.recurrence_id = requestData.id;
				requestData.recurrence_position = 0;
				requestData.recurrence_type = 0;
			}
			
			// is series?
			if (ox.calendar.isSeries(requestData)) {
				// remove id & position
				delete requestData.recurrence_id;
				delete requestData.recurrence_position;
				delete requestData.sequence;
			}
			else {
				// single appointment or recurrence
				// remove recurrence information
				delete requestData.recurrence_id;
				delete requestData.recurrence_start;
				delete requestData.recurrence_date_position;
				delete requestData.days;
				delete requestData.day_in_month;
				delete requestData.month;
				delete requestData.interval;
				delete requestData.occurrences;
				delete requestData.until;
				delete requestData.sequence;
				// update single appointment?
				if (ox.calendar.isSingle(requestData)) {
					// remove type
					delete requestData.recurrence_position;
					delete requestData.recurrence_type;
				}
				else {
					// make an exception
					requestData.recurrence_type = 0;
				}
			}
			
			// debug?
			if (ox.calendar.debug) { console.info("ox.calendar.update: requestData", requestData); }
			
			// server request
			ox.JSON.put(AjaxRoot + "/calendar?action=update&session=" + session + "&id=" + data.id +
				"&folder=" + data.folder_id + "&timestamp=" + timestamp,
				// data
				requestData,
				// success handler
				function(response) {
					// debug?
					if (ox.calendar.debug) { console.info("ox.calendar.update", response); }
					// response?
					if (response && response.data) {
						// update timestamp
						if (response.timestamp) {
							app.timestamp = response.timestamp;
						}
						// callback?
						if (typeof callback == "function") { callback(response); }
					}
				}
			);
		}
		else {
			// invalid function call
			console.error(
				"ox.calendar.update(appointment : object{ data : object, timestamp : int }, [callback : function]). " +
				"data must include id, folder_id, and recurrence_position.", app
			);
		}
	}
};

// UI: update
ox.calendar.ui.update = function(appointment, changes, callback) {
	
	// ensure changes
	changes = changes || {};
	
	var ONE_DAY = ox.calendar.DAY;
	
	if (typeof appointment != "undefined") {
		
		// force notification
		changes.notification = true;
		
		var updateOriginal = function() {
			// set recurrence position
			changes.recurrence_position = 0;
			update();
		};
	
		var updateRecurrence = function() {
			// ensure recurrence position
			changes.recurrence_position = appointment.recurrence_position;
			update();
		};
		
		var dayFloor = function(timestamp) {
			return Math.floor(timestamp/ONE_DAY)*ONE_DAY;
		};
		
		var update = function() {
			// get appointment
			ox.calendar.get(appointment.folder_id, appointment.id, changes.recurrence_position, function(app) {
				// update series? (check with "appointment", not "changes")
				if (appointment.recurrence_position > 0) {
					// start or end changed?
					if (typeof changes.start_date != "undefined" || typeof changes.end_date != "undefined") {
						// copy
						changes.start_date =  changes.start_date || appointment.start_date;
						changes.end_date =  changes.end_date || appointment.end_date;
						// adjust start date
						var shift = changes.start_date - appointment.start_date;
						app.data.start_date += shift;
						changes.start_date = app.data.start_date;
						// adjust end date
						var shift = changes.end_date - appointment.end_date;
						app.data.end_date += shift;
						changes.end_date = app.data.end_date;
					}
				}
				// merge changes
				for (var key in changes) { app.data[key] = changes[key]; }
				// debug?
				if (ox.calendar.debug) { console.info("changes merged", app, changes); }
				// now update
				ox.calendar.update(app, function(response) {
					// conflict?
					if (response.data.conflicts) {
						// ignore handler
						var ignore = function() {
							// set ignore flag
							app.data.ignore_conflicts = true;
							// update again
							ox.calendar.update(app, function(response) {
								if (response.data.id) {
									storageCache.update(triggerEvent("OX_Refresh_Mini_Calendar"));
								}
								// callback?
								if (typeof callback == "function") { callback(response); }
							});
						};
						// show dialog
						ConflictsPopup.open(app, response.data.conflicts, null, null, ignore);
					} else {
						// update storage & mini calendar
						storageCache.update(triggerEvent("OX_Refresh_Mini_Calendar"));
						// callback?
						if (typeof callback == "function") { callback(response); }
					}
				});
			});
		};
		
		var cancel = function() { };
		
		// write privileges?
		menuhasRightandLoad([appointment], "WRITE", function () {
			// debug?
			if (ox.calendar.debug) { console.info("write privileges for", clone(appointment)); }
			// single or series?
			if (ox.calendar.isRecurrence(appointment)) {
				// update single appointment within a series
				// helper
				var equals = function(timestamp1, timestamp2) {
					var diff= timestamp1 - timestamp2; 
					return diff < ONE_DAY && diff > -ONE_DAY && (new Date(timestamp1)).getUTCDate() == (new Date(timestamp2)).getUTCDate();
				};
				// debug?
				if (ox.calendar.debug) { console.info("updating exception/recurrences", appointment); }
				// part of series?
				if(!appointment.start_date | !changes.start_date || equals(appointment.start_date, changes.start_date)) {
					// dialog A
					newConfirm(
						_("Edit Appointment"),
						_("Do you want to edit the whole series or a single series appointment? Please note when changing a serial appointment, exceptions to the series are reset."),
						AlertPopup.SERIESBUTTON, null, cancel, null, null, updateOriginal, updateRecurrence, cancel
					);
				} else {
					// dialog B
					newConfirm(
						_("Edit Appointment"),
						_("By changing the date of this appointment you are creating an appointment exception to the series. Do you want to continue?"),
						AlertPopup.CONTINUECANCEL, updateRecurrence, cancel, null, null, updateOriginal, null, cancel, null, updateRecurrence
					);
				}
			} else {
				// debug?
				if (ox.calendar.debug) { console.info("updating original", appointment); }
				// update
				updateOriginal();
			}
		});
	
	} else {
		// invalid function call
		console.error(
			"ox.calendar.ui.update(appointment : object, [changes : object], [callback : function]). " +
			"Invalid parameters.", appointment, changes
		);
	}
};

fileloaded();