/* Copyright 2009 Palm, Inc.  All rights reserved. */

var FREETIME_STR = "freetime";	// Do NOT Localize
var TimelineTapOffset = 4; 
var DayAssistant = Class.create({
								
	reminderMenuItemId: 5,
	scrollDelay: 500,
								
	initialize: function() {
		this.appMenuModel = { visible:true, 
									label:$L('Calendar'), 
									items: [ Mojo.Menu.editItem,
											{label:$L('New'), items: [{label:$L('Event'), command:'newtimed', disabled:false},
	                													{label:$L('All day event'), command:'newallday', disabled:false}] 
	                													 				
											},											
											{label:$L('Sync Now'), command:'sync', id: 2},
											{label:$L('Show today'), command:'today', id: 3},
											{label:$L('Jump to...'), command:'jumpto', id: 4},
											{label:$L('Missed reminders...'), command:'reminders', id: 5},
											{label:$L('Preferences & Accounts'), command:Mojo.Menu.prefsCmd, disabled: false},
											{label:$L('Help'), command:Mojo.Menu.helpCmd, disabled:false}]
								};
		this.colors = {
				'cal-color-blue': 	{background: 'rgb(145, 211, 234)', border: 'rgb(99, 165, 188)',	text: 'rgb(5, 32, 41)'},
				'cal-color-green': 	{background: 'rgb(140, 240, 140)', border: 'rgb(77, 206, 77)',	text: 'rgb(1, 52, 1)'},
				'cal-color-yellow': {background: 'rgb(232, 227, 124)', border: 'rgb(184, 188, 88)',	text: 'rgb(54, 51, 0)'},
				'cal-color-gray': 	{background: 'rgb(186, 186, 186)', border: 'rgb(140, 140, 140)',text: 'rgb(25, 25, 25)'},
				'cal-color-orange': {background: 'rgb(251, 180, 116)', border: 'rgb(212, 132, 61)',	text: 'rgb(48, 18, 00)'},
				'cal-color-pink': 	{background: 'rgb(245, 156, 188)', border: 'rgb(209, 104, 147)',text: 'rgb(40, 0, 0)'},
				'cal-color-red': 	{background: 'rgb(255, 151, 151)', border: 'rgb(224, 98, 98)',	text: 'rgb(45, 8, 21)'},
				'cal-color-purple': {background: 'rgb(217, 183, 255)', border: 'rgb(177, 128, 232)',text: 'rgb(38, 22, 56)'},
				'cal-color-teal': 	{background: 'rgb(114, 223, 210)', border: 'rgb(68, 177, 164)',	text: 'rgb(0, 42, 36)'},
			};
			
		this.PREV_DAY = 1;
		this.THIS_DAY = 2;
		this.NEXT_DAY = 3;
		this.active = false;
		this.pendingRender = false;
		this.pendingCalendarSettingsUpdate = false;
		//this.pendingOpenScratchEventInDetails = false;
		this.snapEffectCount = 0;

		
		this.hourHeight = 48;
		this.halfHourHeight = 24;
		this.fifteenMinuteHeight = 12;
		
		this.totalWidth = 320;
		this.totalHeight = this.hourHeight * 24;
		// To make sure that the 11pm timeline can be scrolled above the command menu buttons, add an extra timeline
		// at the bottom
		this.totalHeight += this.hourHeight;
		
		this.updateSceneScrollerSizeHandler = this.updateSceneScrollerAtReSize.bindAsEventListener(this);
	},
	
	getEventChangeCallback: function(response) {
		if (response.changed) {
			this.clearCache();
			if (this.scrolling == 0)
				this.updateAll();
		}
	},
	
	putdayDataInCache:function(response){
		var numDays = response.days.size();
		for (var i = 0; i < numDays; i++) {
			var day = {};
			day.days = [];
			day.days.push(response.days[i]);
			this.calcEventCompression(day.days[0]);
			var dayDate = day.days[0].date;
			this.addToCache(dayDate, day);
		
		}
	},
	
	getEventsCallback: function(hideFastScroll, response) {
		//Mojo.Log.info("day-assistant: getEventsCallback " );
		this.currentQuery =null;
		if(this.pendingQuery)
		{
			
			this.currentQuery=this.pendingQuery.apply();
			this.pendingQuery=null;
			this.putdayDataInCache(response);			
			return;
		}

		if(this.eventUpdatingStateOn){
			this.eventUpdatingStateOn=false;
			this.eventUpdatingId==null;
			
			
			if(this.scratchEvent && this.scratchEvent.saved)
				this.resetScratchEvent();
				//this.eventBeingUpdated =false;
				
		}
	
		var numDays = response.days.size();
		//var openScratchEventInDetailsId = undefined;
		
		for (var i = 0; i < numDays; i++) {
			var day = {};
			day.days = [];
			day.days.push(response.days[i]);
			this.calcEventCompression(day.days[0]);
			var dayDate = day.days[0].date;
			this.addToCache(dayDate, day);
			
			var dayDisplayed = this.isDayDisplayed(dayDate);
			
			if (dayDisplayed) {
				if (dayDisplayed == this.THIS_DAY) {
					this.updateTapHandlers();
				}
					
				if (this.active) {
					// If we have a new inline event, and the event has been saved,
					// then we clear it from the display. 
					if (this.scratchEvent) {
						//Mojo.Log.info("*** getEventsCallback: SCRATCH EVENT PRESENT");
						if (this.scratchEvent.saved) {
							//Mojo.Log.info("*** Clearing scratch event from renderDay");
							//if (this.pendingOpenScratchEventInDetails) {
							//	openScratchEventInDetailsId = this.scratchEvent.id;
							//	this.pendingOpenScratchEventInDetails = false;
						//	}
							this.resetScratchEvent();
						}
					}
					this.renderDay(dayDisplayed, day, hideFastScroll);
				} else {
					// The events have changed, while the Day View is not active.
					// Set this flag so we know to re-render ourselves upon
					// activate
					this.pendingRender = true;
				}
	  		}
			
			// Only toggle the compression if the day is THIS_DAY, since THIS_DAY
			// is the only affected by this.tempUncompressed
			if (this.tempUncompressed && dayDisplayed == this.THIS_DAY) {
				response.days[i].events.each(function(event){
					var idModel = parseFromId(event.id);
					if (this.draggedEventId == idModel.id) {
						this.tempUncompressed = false;
						this.toggleCompression(true, event.top);
					}
				}, this);
			}
		}
		
		if (this.isAllDisplayedInCache()) {
			this.hideFastScrollView();
			this.controller.get('dv_scrim_next').hide();
			this.controller.get('dv_scrim').hide();
			this.controller.get('dv_scrim_prev').hide();
		}
		
	
	},
	
	getEventsFailCallback: function(response) {
	  	Mojo.Log.info("FAILURE IN THE SERVICE **********************************************************");
		this.hideFastScrollView();
	},
	
	isDayDisplayed: function(day) {
		if (this.listeningToScroller)
			return 0;
			
		var dayDateKey = this.keyFromDate(day);
		var thisDay = getCurrentDateTime();
		if (dayDateKey == this.keyFromDate(thisDay))
			return this.THIS_DAY;
		if (dayDateKey == this.keyFromDate(thisDay.addDays(-1)))
			return this.PREV_DAY;
		if (dayDateKey == this.keyFromDate(thisDay.addDays(2)))
			return this.NEXT_DAY;
		return 0;
	},
	
	isAllDisplayedInCache: function() {
		/*
			
		var thisDay = getCurrentDateTime();
		if (this.getFromCache(thisDay) == null) 
			return false;
		thisDay.addDays(-1);
		if (this.getFromCache(thisDay) == null) 
			return false;
		thisDay.addDays(2);
		if (this.getFromCache(thisDay) == null) 
			return false;*/
		
		return true;
	},
	
	buildViewHeader: function() {
		var hdr = {};
		hdr.viewPrefix = 'dv';	// Do NOT Localize
		
		var hdrHtml = Mojo.View.render({object: hdr, template: 'shared/calendar_view_header'});
		this.controller.get('dv_header_container1').update(hdrHtml);
	},
		
	buildDayTitle: function(doScrim) {
		var today = Date.today().clearTime();
		var dayDateNoTime = new Date(this.dayDate).clearTime();
		
		if (dayDateNoTime.compareTo(today) == 0) {
			var template = new Template($L("#{todayStr}, #{dateStr}"));
			var todayStr= Mojo.Format.formatRelativeDate(today, {date: "short"}).capitalize();
			
			var templateModel = {};
			templateModel.todayStr = todayStr;
			templateModel.dateStr = Mojo.Format.formatDate(this.dayDate, $L("EEE MMM d"));
			this.controller.get('dv_title').update(template.evaluate(templateModel)); // Localize this date format string
		} else {
			this.controller.get('dv_title').update(Mojo.Format.formatDate(this.dayDate, $L("EEE MMM d, yyyy"))); // Localize this date format string
		}
		
		if (doScrim) {
			this.controller.get('dv_scrim_weekday').update(Mojo.Format.formatDate(this.dayDate, $L("EEEE"))); // Localize this date format string
			this.controller.get('dv_scrim_month').update(Mojo.Format.formatDate(this.dayDate, $L("MMMM"))); // Localize this date format string
			this.controller.get('dv_scrim_day').update(this.dayDate.getDate()); // Localize this date format string
		}
	},
	
	updateTimeFormat: function() {
		var prefs = getPrefs();
		if (prefs) {
			this.startHour = new Date(prefs.startTimeOfDay).getUTCHours();
			this.endHour = new Date(prefs.endTimeOfDay).getUTCHours();
		}
		
		var timeFormat = getTimeFormat();
		if (!this.currentTimeFormat || (timeFormat != this.currentTimeFormat)) {
			this.currentTimeFormat = timeFormat;
		}	
	},
	
	updateEmptySpacer: function(whichDay, resize) {
		//Mojo.Log.info("day-assistant: updateEmptySpacer whichDay %d", whichDay);
		
		var spacerHeight = 0;
		
		// NOTE: The height of the allDayEvents includes the height of the header
		// See the padding-top setting in dv_allday_col_wrapper class in day.css
		var allDayHeight = this.divs[whichDay].alldayevents.getHeight();
		// Determine the height of the fixed header
		if (this.divs[whichDay].alldayevents_scroller.style.display != 'none') {
			spacerHeight = allDayHeight;
			if (allDayHeight == 0) {
				// This means the view isn't active, but there are all day events to account for.
				// So we need to set pendingUpdateEmptySpacer, so that updateEmptySpacer will be
				// called on activate()
				this.pendingUpdateEmptySpacer = true;
			}
		} else {
			// If the there are no all day events for this day, then we need to 
			// account for the height of the header and the height of the all-day-separator
			// which is currently 7 pixels high
			spacerHeight = this.controller.get('dv_view_header').getHeight() + 7;
		}
		
		var thisDay = this.days[whichDay].date.clearTime();
		var dayData = this.getFromCache(thisDay.getTime());
		var newHeight = 1200;

		if (dayData && this.compressed) {
			// Incorporate extra space after 11pm timeline so that it can be scrolled above the 
			// command menu buttons
			newHeight = Math.floor(dayData.days[0].timelines[24].topCompress) + this.hourHeight;
		}				
		
		var scrollerHeight = this.controller.window.innerHeight-spacerHeight;

		if (newHeight < scrollerHeight)
			newHeight = scrollerHeight;
		
		if (this.divs[whichDay].eventsCanvas.offsetHeight != newHeight) {
			this.divs[whichDay].eventsCanvas.setAttribute('height', newHeight+'px');
			// Fixes NOV-88057, NOV-90191
			// setting the height attribute of the canvas, clears the canvas so we need to redraw day
			if (resize) {
				this.renderDay(whichDay, dayData, false, true);
			}
		}
		
		this.tds[whichDay].emptySpacer.setStyle({height: spacerHeight+'px'});
		
		// this.controller.window.innerHeight is the height of the scene not 
		// including the system bar at the top of the screen
		this.tds[whichDay].scroller.setStyle({height: scrollerHeight+'px'});
						
		// Validate the scroll positions since the height of the scrollers have changed
		if (this.tds[whichDay].scroller.mojo)
			this.tds[whichDay].scroller.mojo.validateScrollPosition();		
	},
	
	eraseDay: function(whichDay, top) {
		var ctx = this.divs[whichDay].ctxEvents;
		// Erase the entire cavas
		ctx.fillStyle = "rgb(228, 228, 226)";
		if (top)
			ctx.fillRect(0, top, this.totalWidth, this.totalHeight - top);
		else
			ctx.fillRect(0, 0, this.totalWidth, this.totalHeight);
	},
	
	eraseAllDay: function(whichDay) {
		var ctx = this.divs[whichDay].ctxAllDayEvents;
	
		// Erase the entire cavas
		ctx.fillStyle = "rgb(200, 199, 199)";
		ctx.fillRect(0, 0, this.totalWidth, this.divs[whichDay].alldayevents_list.height);
	},
	
	draw3Tile: function(ctx, imageData, x, y, dx, dy) {
		ctx.drawImage(imageData.imageLeft, x, y, imageData.sxLeft, dy);
		ctx.drawImage(imageData.imageCenter, x+imageData.sxLeft, y, dx-imageData.sxLeft-imageData.sxRight, dy);
		ctx.drawImage(imageData.imageRight, x+dx-imageData.sxRight, y, imageData.sxRight, dy);
	},
	
	compressPos: function(expanded, compressed) {
		if (this.animating) {
			if (this.compressed) {
				// uncompressed to compressed
				return expanded - (expanded - compressed) * this.animationValue;
			} else {
				// compressed to uncompressed
				return compressed + (expanded - compressed) * this.animationValue;
			}
		} else if (this.compressed)
			return compressed;
		else
			return expanded;
	},
	getXIndexForTimeText:function(ctx,i,text)
	{		
			
			ctx.font = "bold 14px Prelude";
			

			var metrics = ctx.measureText(text);
			if (i >= this.startHour && i < this.endHour) 
				ctx.fillStyle = "rgb(85, 85, 85)";
			else 
				ctx.fillStyle = "rgb(119, 119, 119)";
	
			return ((20 - metrics.width) / 2 + 4);		
	}	,
		
	renderTimeLines: function(whichDay, tops) {
		var ctx = this.divs[whichDay].ctxEvents;
		
		for (var i = 0; i < 25; i++) {
			if (this.animating && !tops.timelines[i].animatible)
				continue;
		
			var top = i * this.hourHeight;
			if (tops)
				top = this.compressPos(tops.timelines[i].top, tops.timelines[i].topCompress);
			
			var hidden = (top < 0) || (this.animating && (tops.timelines[i].topCompress < 0));	
			if (!hidden) {
				// Draw time line
				if ((i == this.startHour) || (i == this.endHour)) {
					ctx.fillStyle = "rgb(159, 159, 158)";
					ctx.fillRect(0, top, this.totalWidth, 2);
				}
				else {
					ctx.fillStyle = "rgb(179, 179, 178)";
					ctx.fillRect(0, top, this.totalWidth, 1);
				}
				
				if (i < 24) {
					var text = String(i);
					if (this.currentTimeFormat == "HH12") {
						if (i == 0) 
							text = '12';
						if (i > 12) 
							text = String(i - 12);
					}
					ctx.strokeText(text, this.getXIndexForTimeText(ctx, i, text), top + 15);
				}
			}
		}
	},
	
	renderBusyTime: function(whichDay, busy) {
		if (this.animating && !busy.animatible)
			return;
	
		var ctx = this.divs[whichDay].ctxEvents;
		var marginLeft = 0;
		var marginRight = 0;
		
		var top = this.compressPos(busy.top, busy.topCompress);
		var height = (busy.end_decimal - busy.start_decimal) * this.hourHeight / 100;
		var left = marginLeft;
		var width = this.totalWidth - marginLeft - marginRight;
		
		this.repeatImage(ctx, this.busyImage, 12, 12, left, top, width, height);
	},
	
	repeatImage: function(ctx, image, sw, sh, dx, dy, dw, dh) {
		var cols = Math.floor(dw / sw);
		var dwLastCol = dw % sw;
		var rows = Math.floor(dh / sh);
		var dhLastRow = dh % sh;
		
		for (var y = 0; y < rows; y++) {
			for (var x = 0; x < cols; x++) {
				ctx.drawImage(image, dx + x * sw, dy + y * sh, sw, sh);
			}
			if (dwLastCol != 0)
				ctx.drawImage(image, 0, 0, dwLastCol, sh, dx + cols * sw, dy + y * sh, dwLastCol, sh);
		}
		if (dhLastRow != 0) {
			for (var x = 0; x < cols; x++) {
				ctx.drawImage(image, 0, 0, sw, dhLastRow, dx + x * sw, dy + rows * sh, sw, dhLastRow);
			}
			if (dwLastCol != 0)
				ctx.drawImage(image, 0, 0, dwLastCol, dhLastRow, dx + cols * sw, dy + rows * sh, dwLastCol, dhLastRow);
		}
	},
	
	getFreeDimensions: function(free, compressed) {
		if (compressed) {
			return {
				left: 0,
				top: free.topCompress,
				width: this.totalWidth,
				height: this.hourHeight
			};
		} else {
			var ctx = this.divs[this.THIS_DAY].ctxEvents;
			ctx.font = "bold 12px Prelude";
			var freeTextWidth = ctx.measureText(free.displayText).width;
			var buttonWidth = freeTextWidth + this.free3Button.sxLeft + this.free3Button.sxRight;
			var buttonHeight = this.free3Button.syCenter;
			if (buttonHeight < this.hourHeight)
				buttonHeight = this.hourHeight;
			return {
				left: this.totalWidth - buttonWidth - 20,
				top: (free.top + free.height / 2 - buttonHeight / 2),
				width: buttonWidth,
				height: buttonHeight
			};
		}
	},
	
	renderFreeTime: function(whichDay, free) {
		if (free.duration > 150) {
			var ctx = this.divs[whichDay].ctxEvents;
			
			var top = this.compressPos(free.top, free.topCompress);
			var height = this.compressPos(free.height, this.hourHeight);
			
			if (this.compressed || this.animating) {
				ctx.drawImage(this.freeImageFull,  /*			
				this.draw3Tile(ctx,
							  this.free3Tile,
							  */
							  0, 
							  top, 
							  this.totalWidth, 
							  height);
			}
			
			ctx.font = "bold 12px Prelude";
			var freeTextWidth = ctx.measureText(free.displayText).width;
			
			var buttonWidth = freeTextWidth + this.free3Button.sxLeft + this.free3Button.sxRight;
			var buttonLeft = this.totalWidth - buttonWidth - 20;
			var buttonTop = top + height / 2 - this.free3Button.syCenter / 2;
			this.draw3Tile(ctx, this.free3Button, buttonLeft, buttonTop, buttonWidth, this.free3Button.syCenter);
			
			ctx.fillStyle = "rgb(102, 102, 102)";
			ctx.strokeText(free.displayText, buttonLeft + this.free3Button.sxLeft, buttonTop + 18);
		}
	},
	
	getEventDimensions: function(event, topPos) {
		var marginLeft = 28;
		var marginRight = 0;
		var eventMaxWidth = this.totalWidth - marginLeft - marginRight;
		
		var top = topPos || this.compressPos(event.top, event.topCompress);
		var height = ((event.end_decimal - event.start_decimal) * this.hourHeight / 100);
		var left = marginLeft;
		var width = eventMaxWidth;
		if (event.overlap_count > 1) {
			left = (event.overlap_index * (eventMaxWidth / event.overlap_count)) + marginLeft;
			width = eventMaxWidth / event.overlap_count;
		}
		return {left: left, top: top, width: width, height: height};
	},

		chopText:function(buf1,maxWidth,context){
		var buf2;
		var index=0;
		
		//lets first check for this context what is the max num chars that can fit into
		//the max available width	 
		//taking a sample char of B 
		
		var met =context.measureText("B");
		var numChars = Math.round(maxWidth/met.width)-1;
		
		buf2 = buf1.substring(0, numChars);
		var metrics = context.measureText(buf2);
		if (metrics.width >= maxWidth) 
			numChars=this.minusNumChars(buf1,numChars,context,maxWidth);
		else 
			numChars=this.plusNumChars(buf1,numChars,context,maxWidth);
		
		return numChars;
	},
	
	plusNumChars:function(buf1,numChars,context,maxWidth){
		
		var length =buf1.length;
		while(numChars<=length){
			numChars =numChars+1;
			buf2 =buf1.substring(0, numChars);
			var metrics = context.measureText(buf2);
			if (metrics.width >= maxWidth) {
				numChars--;
				break;
			}
				
			
		}
		return numChars;
	},
	
	minusNumChars:function(buf1,numChars,context,maxWidth){
		
		var length =buf1.length;
		while(numChars>length){
			numChars =numChars-1;
			buf2 =buf1.substring(0, numChars);
			var metrics = context.measureText(buf2);
			if (metrics.width < maxWidth) {
				numChars++;
				break;
			}			
			
		}
		return numChars;
	},
    getStringToDisplay:function(container,context,maxWidth,doChop){
		
		var buf = container.textToBeDisplayed;
		var index =buf.length;
		var buf1=buf;
		var didTruncation =false;
	
	
		//if there is a carriage return or '\n' in the parsed text put the rest of text in nexzt line
		if (!container.noParsechar) 
		{
			var pos1 = buf1.indexOf('\n', 0);
			var pos2 = buf1.indexOf('\r', 0);
			var pos;
			if (pos1 == -1) 
				pos = pos2;
			else {
				if (pos2 == -1) 
					pos = pos1;
				else 
					pos = (pos1 <= pos2) ? pos1 : pos2;
			}
			if (pos != -1) {
				didTruncation = true;
				buf1 = buf1.substring(0, pos + 1);
				index = pos;
			}
		}
		
		container.truncateForEllipsis = true;
		
		do{
			var metrics = context.measureText(buf1);
			
			//maxWidth-8 cuts it off fairly close to the edge, but gives a little padding
			if (metrics.width >= maxWidth-8) {
				didTruncation = true;
				if (index == 0 || container.noParseChar || doChop) {
					//we can enter this case only if we tried going through the string completely
					//before and found no space or carriage return or tab characters 
					//in that case just chop it without any intelligent logic
					container.noParsechar = true;
					index = this.chopText(buf1, maxWidth-15, context);
					buf1 = buf.substring(0, index + 1);
					break;
					
				}
				
				//this is the case when the buffer text is wider
				//than available width
				var j = index - 1;
				
				//find the previous space or line feed or tap or carriage return from current location
				while (buf.charAt(j) != ' ' && buf.charAt(j) != '\n' && buf.charAt(j) != '\r' && buf.charAt(j) != '\t' && j > 0) {
					j--;
				}
				
				index = j;
				if (index > 0) 
					buf1 = buf.substring(0, index + 1);
				
			} else {
				// If the width of the text is less than the maxWidth minus the space needed
				// for the ellipsis, then set truncateForEllipsis to false (used in fillText below).
				// NOTE: We do it here since we have the metrics.width.  Calling context.measureText is 
				// expensive...
				if (metrics.width < maxWidth-15)
					container.truncateForEllipsis = false;
				break;
			}
		}while(index>-1);
		
		//get the text which needs to be displayed in the current line
		container.displayText =buf1;

				
		if (didTruncation) {
			
			//this is the case when we have chopped off some text to be displayed and there is some
			//remaining text to be displayed
			container.textToBeDisplayed = container.textToBeDisplayed.substring(index + 1, container.textToBeDisplayed.length);
		}
		else {
			//this is the case when we didnt have to parse. text fit in the available width
			
			container.textToBeDisplayed = "";
			container.addEllipsis = false;
		}
		
	
	},

	fillText:function(text, context,x,y,width,bottom,lineHeight){
		if (!text)
			return;
			
		var container ={
			displayText:"",
			textToBeDisplayed:text,
			addEllipsis:true
		}
		//calculate the lineHeight
		
		while(container.textToBeDisplayed !="" && y <bottom)
		{		
			
			var displayString;
			var nextY=y+lineHeight;
			
			if (nextY >= bottom && container.textToBeDisplayed != "")
			{
				//this is the last line, we are attaching ellipsis, so just chop
				
				this.getStringToDisplay(container,context,width,true/*doChop*/);
				displayString=container.displayText;
				if(container.addEllipsis) {
					var displayStringLength = displayString.length;
					//this is the last line that can be displayed so add the ellipsis
					
					// If the text + the width of the ellipsis is wider than the width,
					// then we need to truncate the string by 3 characters to 
					// accomodate the ellipsis
					if (container.truncateForEllipsis)
						displayStringLength -= 3;
					displayString = displayString.substring(0, displayStringLength);
					displayString += "...";
				}
			}
			else
			{
				this.getStringToDisplay(container,context,width,false/*do chop*/);
				displayString=container.displayText;
			}
			context.strokeText(displayString, x, y);
			y=nextY;
			
		}
	},
	
	
	
	renderEvent: function(whichDay, event, selected, ghost) {
		//Mojo.Log.info("day-assistant: renderEvent");
		if (this.animating && !event.animatible)
			return;
	
		var ctx = this.divs[whichDay].ctxEvents;
		var marginLeft = 28;
		var marginRight = 0;
		var eventMaxWidth = this.totalWidth - marginLeft - marginRight;
		
		var top = this.compressPos(event.top, event.topCompress);
		var height = ((event.end_decimal - event.start_decimal) * this.hourHeight / 100);
		var left = marginLeft;
		var width = eventMaxWidth;
		if (event.overlap_count > 1) {
			left = (event.overlap_index * (eventMaxWidth / event.overlap_count)) + marginLeft;
			width = eventMaxWidth / event.overlap_count;
		}
		ctx.fillStyle = this.colors[event.calendarColor].border;
		ctx.fillRect(left, top, width, height);
		ctx.fillStyle = this.colors[event.calendarColor].background;
		ctx.fillRect(left+1, top+1, width-2, height-2);
		
		// highlights
		ctx.fillStyle = "rgba(255, 255, 255, 0.9)";
		ctx.fillRect(left+1, top+1, width-2, 1);
		ctx.fillStyle = "rgba(255, 255, 255, 0.3)";
		ctx.fillRect(left+1, top+2, width-2, 1);
		ctx.fillStyle = "rgba(255, 255, 255, 0.6)";
		ctx.fillRect(left+1, top+1, 1, height-2);
		ctx.fillRect(left+width-2, top+1, 1, height-2);

		ctx.fillStyle = "rgba(255, 255, 255, 0.2)";
		ctx.fillRect(left+1, top+1, width-1, 11);
		
		ctx.fillStyle = "rgba(255, 255, 255, 0.3)";
		ctx.fillRect(left+1, top+height-2, width-2, 1);
		ctx.fillStyle = "rgba(255, 255, 255, 0.20)";
		ctx.fillRect(left+2, top+1, 1, height-2);
		ctx.fillRect(left+width-3, top+1, 1, height-2);
		
		ctx.fillStyle = "rgba(255, 255, 255, 0.10)";
		ctx.fillRect(left+1, top+height-3, width-2, 1);
		
		var textTop = top + 30;
		
		// Subject
		ctx.fillStyle = this.colors[event.calendarColor].text;
		if (selected)
			ctx.fillStyle = "rgb(255, 255, 255)";
		ctx.font = "bold 14px Prelude";
		bottom = textTop;
		var subjectTop = top+15;
		if (bottom >= (top + height))
			subjectTop = top+17;	// no room for location or note, therefore vertically center subject text
		this.fillText(event.subject, ctx, left+4, subjectTop, width, bottom, 15 /*line height*/);	
		
		// Location
		if (event.location && (event.location.length > 0) && (textTop < (top + height))) {
			ctx.font = "italic bold 14px Prelude";
			bottom = textTop+15;
			this.fillText(event.location,ctx,left+4,textTop,width,bottom,15 /*line height*/);		
			textTop += 15;
		}
		
		// Notes
		if (event.note && (event.note.length > 0) && (textTop < (top + height))) {
			ctx.font = "12px Prelude";
			bottom = top+height;
			this.fillText(event.note, ctx, left+4, textTop-2, width, bottom-2, 12 /*line height*/);			
		}
		
		if (ghost) {
			var selectedColor = "rgba(228, 228, 226, 0.85)"
			ctx.fillStyle = selectedColor;
			ctx.fillRect(left, top, width, height);
		} else if (this.event_being_held && this.event_being_held.id == event.id) {
			//Mojo.Log.info("Dragging style");
			var selectedColor = "rgba(2, 2, 2, 0.25)"
			ctx.fillStyle = selectedColor;
			ctx.fillRect(left, top, width, height);
		} else if (selected) {
			var selectedColor = "rgba(4, 4, 4, 0.5)"
			ctx.fillStyle = selectedColor;
		  ctx.fillRect(left, top, width, height);
		} else if (this.eventUpdatingStateOn && this.eventUpdatingId==event.id) {
			var selectedColor = "rgba(4, 4, 4, 0.5)"
			ctx.fillStyle = selectedColor;
		  ctx.fillRect(left, top, width, height);
		}
	},
	
	getAllDayDimensions: function(index, event) {
		var marginLeft = 4;
		var marginRight = 4;
		//Incorporates the padding-top of the first all day event 
		//from the old css style .dv-allday-event:first-child
		var marginTop = 10;
		var eventHeight = 24;
		// Incorporates the padding-top 1px + padding-bottom 9px from 
		// the old css style dv-allday-event
		var totalHeight = 34;
		
		return {
			top : (totalHeight * index + marginTop),
			height : eventHeight,
			left : marginLeft,
			width : (this.totalWidth - marginLeft - marginRight)
		};
	},
	
	renderAllDayEvent: function(whichDay, index, event, selected) {
		var ctx = this.divs[whichDay].ctxAllDayEvents;
		var dim = this.getAllDayDimensions(index, event);

		ctx.fillStyle = "rgb(200, 199, 199)";
		ctx.fillRect(dim.left, dim.top, dim.width, dim.height);
			
		this.draw3Tile(ctx, 
					   this.colors[event.calendarColor].allday, 
					   dim.left, 
					   dim.top, 
					   dim.width, 
					   this.colors[event.calendarColor].allday.syCenter);
		
		// Subject
		ctx.fillStyle = this.colors[event.calendarColor].text;
		if (selected)
			ctx.fillStyle = "rgb(255, 255, 255)";
		ctx.font = "bold 14px Prelude";
		this.fillText(event.subject, ctx, dim.left+10, dim.top+17, dim.width-10, dim.top+30, 15 /*line height*/);
		
		if (selected) {
			var selectedColor = "rgba(4, 4, 4, 0.5)"
			ctx.fillStyle = selectedColor;
			ctx.fillRect(dim.left, dim.top, dim.width, dim.height);
		}
	},
	
	calcEventCompression: function(day) {
		var i;
		
		// Make sure that free times, busy times, and events are sorted by start times
		day.freeTimes = day.freeTimes.sortBy(function(f) { return f.start_decimal; })
		day.events = day.events.sortBy(function(e) { return e.start_decimal; })
		day.busyTimes = day.busyTimes.sortBy(function(b) { return b.start_decimal; })

		day.animationTop = 0;
		
		var compressDelta = 0;
		var maxFreeDuration = 0;
		for (i = 0; i < day.freeTimes.length; i++) {
			var free = day.freeTimes[i];
			free.height = (free.end_decimal - free.start_decimal) * this.hourHeight / 100;
			
			if (free.height > maxFreeDuration)
				maxFreeDuration = free.height;
			
			if (free.duration > 150) {
				var template;
				var templateModel = {};
				templateModel.hour = parseInt(free.duration / 100.0);
				templateModel.min = (((free.duration % 100) / 100.0) * 60).toFixed(0);
				if (templateModel.min != 0) 
					template = new Template($L("#{hour} HRS, #{min} MINS FREE"));
				else 
					template = new Template($L("#{hour} HRS FREE"));
				
				free.displayText = template.evaluate(templateModel);
	
				free.top = free.start_decimal * this.hourHeight / 100;
				free.topCompress = free.top - compressDelta;
				
				if (day.animationTop == 0)
					day.animationTop = free.top;
				
				compressDelta += free.height - this.hourHeight;
			}
		}
		day.maxFreeDuration = maxFreeDuration;


		var currentFree = this.getFirstFree(day.freeTimes);
		var prevFree = null;
		day.timelines = [];
		compressDelta = 0;
		for (i = 0; i < 24; i++) {
			if (currentFree && (i >= currentFree.start_decimal / 100)) {
				compressDelta += currentFree.height - this.hourHeight;
				prevFree = currentFree;
				currentFree = this.getNextFree();
			}
			var topCompress = i * this.hourHeight - compressDelta;
			if (prevFree && (i >= prevFree.start_decimal / 100)  && (i < prevFree.end_decimal / 100))
				topCompress = -1;

			day.timelines.push({top: (i * this.hourHeight), topCompress: topCompress, animatible: ((i * this.hourHeight) > day.animationTop)});
			if (i == 23) {
				// Add extra time line for midnight
				day.timelines.push({top: (24 * this.hourHeight), topCompress: 24 * this.hourHeight - compressDelta, animatible: true});
			}
		}
		
		
		compressDelta = 0;
		currentFree = this.getFirstFree(day.freeTimes);
		for (i = 0; i < day.events.length; i++) {
			var event = day.events[i];
			
			event.top = event.start_decimal * this.hourHeight / 100;
			event.animatible = (event.top > day.animationTop);
			
			while (currentFree && (event.start_decimal > currentFree.start_decimal)) {
				compressDelta += currentFree.height - this.hourHeight;
				currentFree = this.getNextFree();
			}
			
			event.topCompress = event.top - compressDelta;
		}
		
		compressDelta = 0;
		currentFree = this.getFirstFree(day.freeTimes);
		for (i = 0; i < day.busyTimes.length; i++) {
			var busy = day.busyTimes[i];
			
			busy.top = busy.start_decimal * this.hourHeight / 100;
			busy.animatible = (busy.top > day.animationTop);
			
			while (currentFree && (busy.start_decimal > currentFree.start_decimal)) {
				compressDelta += currentFree.height - this.hourHeight;
				currentFree = this.getNextFree();
			}
			
			busy.topCompress = busy.top - compressDelta;
		}
	},
	
	getFirstFree: function(freeTimes) {
		this._freeTimes = freeTimes;
		this._freeTimeIndex = -1;
		return this.getNextFree();
	},
	
	getNextFree: function() {
		var free = null;
		do {
			this._freeTimeIndex++;
			free = (this._freeTimeIndex < this._freeTimes.length) ? this._freeTimes[this._freeTimeIndex] : null;
		} while (free && (free.duration <= 150));
		return free;
	},
	
	renderEmptyDay: function(whichDay) {
		//Mojo.Log.info("day-assistant: renderEmptyDay");
		this.eraseDay(whichDay);
		this.eraseAllDay(whichDay);
		this.renderTimeLines(whichDay);
	},
	
	renderDay: function(whichDay, data, scrollToDefault, skipUpdateEmptySpacer) {
		//Mojo.Log.info("day-assistant: renderDay");
		var i;
		
		if (!data) {
			this.renderEmptyDay(whichDay);
			return;
		}																	   
		
		// The data should equal 1 day worth of data
		var day = data.days[0];
		if (day) {
			this.controller.get('ampm_indicator').hide();
			this.controller.get('current_time').hide();
			if (!this.animating) {
				// ** ALL DAY EVENTS
				// Determine whether there are any all day events
				if (day.allDayEvents.size() > 0) {
					var newHeight = 34 * day.allDayEvents.size();
					
					// Set the height of the canvas
					if (this.divs[whichDay].alldayevents_list.offsetHeight != newHeight) {
						this.divs[whichDay].alldayevents_list.height = newHeight;
						this.divs[whichDay].alldayevents_list.style.height = newHeight+"px";
					}
					
					this.eraseAllDay(whichDay);
					
					// Render each allDay
					for (i = 0; i < day.allDayEvents.length; i++) {
						this.renderAllDayEvent(whichDay, i, day.allDayEvents[i]);
					}
					
					// Show the canvas
					this.divs[whichDay].alldayevents_list.show();
					// Make all day events scroller visible (this is needed for updateEmptySpacer)
					this.divs[whichDay].alldayevents_scroller.setStyle({display: 'block'});
					this.divs[whichDay].alldayevents_scroller.mojo.scrollTo(undefined, 0);
				}
				else {
					// Set the height of the canvas to 0... not sure if this is necessary,
					// since we hide the canvas below.
					if (this.divs[whichDay].alldayevents_list.offsetHeight != 0) {
						this.divs[whichDay].alldayevents_list.height = 0;
						this.divs[whichDay].alldayevents_list.style.height = "0px";
					}
					// Hide the canvas
					this.divs[whichDay].alldayevents_list.hide();
					
					// Hide all day events scroller (this is needed for updateEmptySpacer)
					if (this.divs[whichDay].alldayevents_scroller.style.display != 'none')
						this.divs[whichDay].alldayevents_scroller.style.display = 'none';
				}
				
				// IMPORTANT: Call updateEmptySpacer AFTER the alldayevents divs have been updated
				// Update the height of the empty_space based on whether the all day list is visible
				// skipUpdateEmptySpacer is specified when we need to refresh the canvas after growing it, prior to beginning the 
				// animation to expand the compressed free time.  Calling updateEmptySpacer() shrinks the canvas back to the 
				// compressed size, and screws up the scrolling.  This is a hack, and we should clean it up properly after the release.
				if( !this.eventUpdatingStateOn && !skipUpdateEmptySpacer)
					this.updateEmptySpacer(whichDay, false);
			}

			if (this.animating)
				this.eraseDay(whichDay, day.animationTop);
			else
				this.eraseDay(whichDay);
			
			//render the start and end of day first
			var startHeight, endHeight;
			
			if (!this.compressed) {
				startHeight=this.startHour*this.hourHeight;			
			    endHeight=(24-this.endHour)*this.hourHeight;
				var y=this.endHour*this.hourHeight;
			}
			else {
				startHeight=this.calculatePosWithCompression(day,this.startHour);
				y=this.calculatePosWithCompression(day,this.endHour);
				h=day.timelines[24].topCompress;
				endHeight=h-y;
			}
			
			var ctx = this.divs[whichDay].ctxEvents;
			ctx.fillStyle = "#cececc";
			
			// Draw shaded area before start of day
			if(this.animating) {
				// If we're animating, we need to be careful to preserve the data above day.animationTop.
				if(startHeight > day.animationTop) {
					ctx.fillRect(0, day.animationTop, this.totalWidth, startHeight - day.animationTop);
				}
			} else {
				ctx.fillRect(0, 0, this.totalWidth, startHeight);
			}
			
			// Draw shaded area after end of day
			ctx.fillRect(0,y, this.totalWidth, endHeight);
			
			
			// Busy times
			for (i = 0; i < day.busyTimes.length; i++) {
				this.renderBusyTime(whichDay, day.busyTimes[i]);
			}
			
			if (this.compressed) {
				// Free times
				for (i = 0; i < day.freeTimes.length; i++) {
					this.renderFreeTime(whichDay, day.freeTimes[i]);
				}
				
				this.renderTimeLines(whichDay, day);
			} else {
				this.renderTimeLines(whichDay, day);
				
				// Free times
				for (i = 0; i < day.freeTimes.length; i++) {
					this.renderFreeTime(whichDay, day.freeTimes[i]);
				}
			}
			
			// Events
			this.days[whichDay].firstEventTimelineIndex = -1;
			for (i = 0; i < day.events.length; i++) {
				this.renderEvent(whichDay, day.events[i]);
				
				// Determine the time of the first event, used to set the default time for the day
				var startTimeline = day.events[i].start_decimal / 100;
				if ((this.days[whichDay].firstEventTimelineIndex == -1) ||
					(startTimeline < this.days[whichDay].firstEventTimelineIndex))
				{
					this.days[whichDay].firstEventTimelineIndex = startTimeline;
				}
			}
			
			if (!this.animating) {
				// Since the default time of day is dependent on the first event of the day, update the defaultTime here.
				this.setDefaultTimeforDay(new Date(this.days[whichDay].date).clearTime(), false, whichDay);
				
				if (whichDay == this.THIS_DAY) {
					if (scrollToDefault  || this.forceScrollToDefault) {
					    this.forceScrollToDefault=false;
						this.scrollToDefault(whichDay);
					}
				}
				else {
					//Mojo.Log.info("RENDERDAY : CALLING SCROLLTODEFAULT WHICHDAY %d", whichDay);
					this.scrollToDefault(whichDay);
				}
				//now that we have finished rendering the timelines and number 
				//render am pm
				if (this.currentTimeFormat == "HH12") {
				//now show the am/pm indicators
					this.calculatePosToDrawAmPm(Math.abs(this.tds[this.THIS_DAY].scroller.mojo.getScrollPosition().top));
					this.controller.get('ampm_indicator').show();
				}

			}
		}
	},
	
	buildDay: function() {
		//Mojo.Log.info("&&&&&&&& buildDay BEGIN");
		this.dayDate = getCurrentDateTime();
		this.days[this.THIS_DAY].date = getCurrentDateTime();
		this.days[this.PREV_DAY].date = getCurrentDateTime().addDays(-1).clearTime();
		this.days[this.NEXT_DAY].date = getCurrentDateTime().addDays(1).clearTime();
		//Mojo.Log.info("PREV_DAY date: %s, defaultTime %s", Luna.Format.formatDate(this.days[this.PREV_DAY].date, {date:"short"}), Luna.Format.formatDate(this.days[this.PREV_DAY].defaultTime, {time:"short"}));
		//Mojo.Log.info("THIS_DAY date: %s, defaultTime %s", Luna.Format.formatDate(this.days[this.THIS_DAY].date, {date:"short"}), Luna.Format.formatDate(this.days[this.THIS_DAY].defaultTime, {time:"short"}));
		//Mojo.Log.info("NEXT_DAY date: %s, defaultTime %s", Luna.Format.formatDate(this.days[this.NEXT_DAY].date, {date:"short"}), Luna.Format.formatDate(this.days[this.NEXT_DAY].defaultTime, {time:"short"}));
		//Mojo.Log.info("&&&&&&&& buildDay END");
	},
	
	updateTapHandlers: function() {
		//Mojo.Log.info("day-assistant: updateTapHandlers");
		var thisDay = this.days[this.THIS_DAY].date.clearTime();
		var dayData = this.getFromCache(thisDay.getTime());
		
		this.tapHandler.deactivate();
		this.tapHandler.removeAllHandlers();
		this.tapHandlerCompressed.deactivate();
		this.tapHandlerCompressed.removeAllHandlers();
		this.tapHandlerAllDay.deactivate();
		this.tapHandlerAllDay.removeAllHandlers();
		
		if (dayData) {
			var day = dayData.days[0];
			for (i = 0; i < day.events.length; i++) {
				var event = day.events[i];

				var eventDim = this.getEventDimensions(event, event.top);
				this.tapHandler.addHandler(eventDim.left, 
											 eventDim.top, 
											 eventDim.left + eventDim.width,
											 eventDim.top + eventDim.height,
											 this.handleEventsTap.bind(this, event),
											 this.showEventSelection.bind(this, event),
											 this.clearEventSelection.bind(this, event),
											 this.handleEventsHold.bind(this, event),
											 this.handleEventsDragStart.bind(this),
											 this.handleEventsDragging.bind(this),
											 this.handleEventsDragEnd.bind(this));

				eventDim = this.getEventDimensions(event, event.topCompress);
				this.tapHandlerCompressed.addHandler(eventDim.left, 
											 eventDim.top, 
											 eventDim.left + eventDim.width,
											 eventDim.top + eventDim.height,
											 this.handleEventsTap.bind(this, event),
											 this.showEventSelection.bind(this, event),
											 this.clearEventSelection.bind(this, event),
											 this.handleEventsHold.bind(this, event),
											 this.handleEventsDragStart.bind(this),
											 this.handleEventsDragging.bind(this),
											 this.handleEventsDragEnd.bind(this));
			}
			
			for (i = 0; i < day.freeTimes.length; i++) {
				var free = day.freeTimes[i];
				
				if (free.duration <= 150)
					continue;

				var freeDim = this.getFreeDimensions(free, false);
				this.tapHandler.addHandler(freeDim.left, 
											 freeDim.top, 
											 freeDim.left + freeDim.width,
											 freeDim.top + freeDim.height,
											 this.handleFreeTap.bind(this, free));

				freeDim = this.getFreeDimensions(free, true);
				this.tapHandlerCompressed.addHandler(freeDim.left, 
											 freeDim.top, 
											 freeDim.left + freeDim.width,
											 freeDim.top + freeDim.height,
											 this.handleFreeTap.bind(this, free));
			}
			
			for (i = 0; i < day.allDayEvents.length; i++) {
				var event = day.allDayEvents[i];
				var dim = this.getAllDayDimensions(i, event);
				this.tapHandlerAllDay.addHandler(dim.left, 
											 	dim.top, 
											 	dim.left + dim.width,
											 	dim.top + dim.height,
											 	this.handleAllDayTap.bind(this, i, event),
											  this.showAllDayEventSelection.bind(this, i, event),
											  this.clearAllDayEventSelection.bind(this, i, event));
			}
			
			var free = day.freeTimes[i];
			if (this.compressed)
				this.tapHandlerCompressed.activate();
			else
				this.tapHandler.activate();
			this.tapHandlerAllDay.activate();
		}
	},
	
	// *** OBSERVER CALLBACKS
	currentDateTimeUpdated: function(scrollToDefault) {
		//Mojo.Log.info("day-assistant: currentDateTimeUpdated");
		
		this.buildDay();
		
		this.updateTapHandlers();
		
		this.buildDayTitle();
		
		if (!this.changingDays) {
			if (!this.scratchEventActive) {
				this.updateFromCache(true);
				if (scrollToDefault) {
					this.scrollToDefault(this.THIS_DAY);
				}
			}
		}
	},
	
	timeFormatUpdated: function() {
		// The time format has updated, we need to re-render each day in the Day View
		this.updateTimeFormat();
		this.updateFromCache(false);
	},
	
	timeChangeUpdated:function(){
		//Mojo.Log.info("day view:timeChangeUpdated");
		this.buildDayTitle();
	},
	
	dayChanged: function() {
		//Mojo.Log.info("day view:dayChanged");
		this.buildDayTitle();
	},
	
	currentCalendarUpdated: function() {
		var calMgr = getCalendarsManager();
		if (calMgr.currentCal == 'all') {
			this.controller.get('dv_current_calendar').update($L("All"));
			this.controller.get('dv_calendar_source').setAttribute('class', 'header-cal-source');
		} else {
			var syncSource = calMgr.getCalSyncSource("id"+calMgr.currentCal);
			if (syncSource == "Local")
				// Special case the local calendar to always show Palm since Palm Profile always
				// gets truncated and looks ugly
				this.controller.get('dv_current_calendar').update($L('Palm'));
			else
				this.controller.get('dv_current_calendar').update(calMgr.getCalAccountName("id"+calMgr.currentCal));
			this.controller.get('dv_calendar_source').setAttribute('class', 'header-cal-source ' + calMgr.getCalColorStyle("id"+calMgr.currentCal));
		}
		
		// Check to see if the calendar is read only.
		if (calMgr.isCalendarReadOnly("id"+calMgr.currentCal)) {
			this.readOnlyCalendar = true;
		} else {
			this.readOnlyCalendar = false;
		}
		
		this.appMenuModel.items[1].disabled = this.readOnlyCalendar;
		this.appMenuModel.items[1].items[0].disabled = this.readOnlyCalendar;
		this.appMenuModel.items[1].items[1].disabled = this.readOnlyCalendar;

		this.controller.modelChanged(this.appMenuModel);
	},
	
	calendarSettingsUpdated: function() {
		// The colors have been updated, so we clear the cache and update everything
		this.pendingCalendarSettingsUpdate = true;
	},
	
	calendarListUpdated: function() {
		// Do nothing
	},
	
	calendarPrefsUpdated: function() {
		//Mojo.Log.info('day-assistant: calendarPrefsUpdated');
		
		// The prefs have been updated.  The startTime and endTime
		// prefs only affect the timelines
		this.updateTimeFormat();
		this.updateFromCache(false);
	},
	
	wrapGetEvents:function(startDay,endDay){
		console.error("################## Query for start and end,"+startDay+","+endDay);
		return getCalendarService().getEvents(getCurrentCal(), 
									   startDay.getTime(), 
									   endDay.getTime(), 
									   this.getEventsCallback.bind(this, false), 
									   this.controller, 
									   this.getEventsFailCallback.bind(this),
									   {freeTimes: true});
	},
	
	preProcessGetEvents:function(startDay,endDay){
		//console.error("************ preprocessing get events");
		
		if(!this.currentQuery){
			//push the request into current queue
			//and do the query
			
			//only the queries in current queue will be posted to dbus and will need to  be cleaned up
			//when getEventsCallBack comes in.
			this.currentQuery=this.wrapGetEvents(startDay,endDay);
			}
		else{
			//put the request in pending queue of length 3
			
			this.pendingQuery = this.wrapGetEvents.bind(this,startDay,endDay);
			
		}
		
	},
	
	
	// ***
	updateFromCache: function(scrollToDefault) {
		//Mojo.Log.info("day-assistant: updateFromCache");
		var thisDay = getCurrentDateTime().set({hour: 0, minute: 0, second: 0, millisecond: 0});
		var prevDay = getCurrentDateTime().addDays(-1).set({hour: 0, minute: 0, second: 0, millisecond: 0});
		var nextDay = getCurrentDateTime().addDays(1).set({hour: 0, minute: 0, second: 0, millisecond: 0});
		var startDay = null;
		var endDay = null;
		
		dayData = this.getFromCache(thisDay.getTime());
		if (dayData) {
			this.renderDay(this.THIS_DAY, dayData, scrollToDefault);
		} else {
			this.renderEmptyDay(this.THIS_DAY);
			startDay = thisDay;
			endDay = thisDay;
		}
		
		var dayData = this.getFromCache(prevDay.getTime());
		if (dayData) {
			this.renderDay(this.PREV_DAY, dayData, true);
		} else {
			this.renderEmptyDay(this.PREV_DAY);
			startDay = prevDay;
			if(!endDay)
				endDay = prevDay;
		}
		
		dayData = this.getFromCache(nextDay.getTime());
		if (dayData) {
			this.renderDay(this.NEXT_DAY, dayData, true);
		} else {
			this.renderEmptyDay(this.NEXT_DAY);
			if (!startDay)
				startDay = nextDay;
			endDay = nextDay;
		}
		
		if (startDay) {
			this.preProcessGetEvents(startDay,endDay);
			} else {
			this.hideFastScrollView();
		}
	},
	
	hideFastScrollView: function() {
		this.fastScrollScrim.hide();
		if (this.menuFadeDiv)
			this.menuFadeDiv.show();
		//var transition = this.controller.prepareTransition(Mojo.Transition.crossFade);
		//this.controller.get('dv_scrim').hide();
		//transition.run();
		this.fastScroll = false;
	},
	
	updateAll: function() {
		var prevDay = getCurrentDateTime().addDays(-1);
		var nextDay = getCurrentDateTime().addDays(1);
		this.preProcessGetEvents(prevDay,nextDay);
	},
	
	resetPos: function() {
		this.settingSnapIndex = true;
		this.scrollContainer.mojo.setSnapIndex(1, false);
		this.settingSnapIndex = false;
	},
	
	updateTimer:function(refreshNeeded){
		var now = new Date();
		var whichDay = this.dayDate;
		
		//get currenttime and calculate the next time it needs to fire
	    if (this.updateUITimeoutID) {
                this.controller.window.clearTimeout(this.updateUITimeoutID);
            }
		if (now.getDate() != whichDay.getDate() ||
		now.getFullYear() != whichDay.getFullYear() ||
		now.getMonth() != whichDay.getMonth() ) {
			this.controller.get('current_time').hide();
			return;
		}		
		
		 var minutes =now.getMinutes();
		 
		 var nextScheduleMillis =( 60 -(minutes%60))*60*1000;
		 this.updateUITimeoutID = this.controller.window.setTimeout(this.updateTimer.bind(this,true), nextScheduleMillis);
		 this.drawNowIndicator(now,refreshNeeded);		 
	},
	
	//this function takes care of calculating the
	//y pixel at which the arrow needs to be drawn
	//depending on the current time (i.e NOW)
	
	drawNowIndicator:function(now,refreshNeeded){
		if(this.animating)
			return;
		var img = new Image();
		img.src = "images/current-time-indicator.png";

		nowHours = now.getHours();
		nowMins= now.getMinutes();
		var data = this.getFromCache(this.days[this.THIS_DAY].defaultTime);
		
		if(refreshNeeded)
		{
			this.controller.get('current_time').hide();
			
		}
		
		if(this.compressed && data)
		{
			
			var day= data.days[0];
			
			yPixel=this.calculatePosWithCompression(day,nowHours);
			if (day.timelines[nowHours].topCompress != -1) {
				//this is a normal timeline so we need to do the quarter hour math
				//first quarter hour
				//do nothing we alredy have the right yPixel value;
				
				yPixel=day.timelines[nowHours].topCompress;
				
			}
		}
		else{
			
				yPixel=nowHours * this.hourHeight;
			
		}
		//yPixel = Math.floor(yPixel-(img.height/2));
		
		this.controller.get('current_time').setStyle({	
			top: yPixel+'px'
		}).show();

	},
	
	calculatePosWithCompression:function(day,hour)
	{
		var scrollToPos = day.timelines[hour].topCompress;
		if (scrollToPos === -1) {
		
			//Back up to the last valid time line, use topCompress. 
			var adjustedHour = hour;
			while (day.timelines[adjustedHour].topCompress === -1 && adjustedHour > 0) {
				adjustedHour--;
			}
			scrollToPos = day.timelines[adjustedHour].topCompress;
			
			//Work backwards through the freeTimes array to find the top of the 
			//free time accordion that the current hour is in.										
			var freeTimesIndex = day.freeTimes.length - 1;
			if (freeTimesIndex >= 0) {
				while (day.freeTimes[freeTimesIndex].top == undefined || day.freeTimes[freeTimesIndex].top > day.timelines[hour].top) {
					freeTimesIndex--;
				}
				
				if (freeTimesIndex >= 0) {
					scrollToPos = day.freeTimes[freeTimesIndex].topCompress;
					
				}
			}
		}
		return scrollToPos;
	},
	
	
	scrollToDefault: function(whichDay) {
		if (this.tds[whichDay].scroller && this.tds[whichDay].scroller.mojo) {
			//Mojo.Log.info('BEFORE SCROLL TOP', this.tds[whichDay].scroller.mojo.getScrollPosition().top);
			var ctx = this.divs[whichDay].ctxEvents;
			//get the day from the cache, so we can access its timelines
			var data = this.getFromCache(this.days[whichDay].defaultTime);
			if(!data) {
				//if we don't have the day in the cache yet, use the default time
				this.tds[whichDay].scroller.mojo.scrollTo(undefined, -this.days[whichDay].defaultTime.getHours() * this.hourHeight);
				
				return;
			}
				
			//get the default hour's timeline position
			var day = data.days[0];
			var hour = this.days[whichDay].defaultTime.getHours();
			var scrollToPos = day.timelines[hour].top;
			
			if (this.compressed) {
				scrollToPos = this.calculatePosWithCompression(day, hour);	
			}
	
					
			this.tds[whichDay].scroller.mojo.scrollTo(undefined, -scrollToPos);
			this.updateTimer(false);
			//Mojo.Log.info('AFTER SCROLL TOP', this.tds[whichDay].scroller.mojo.getScrollPosition().top);	
		}
	},
	
	blurStageBound:function(){
		this.handleCommit();
	},
	
	setup: function() {
		//Mojo.Log.info("day-assistant:setup");
		this.clearCache();
		//this.controller.get('dv_scrim').hide();
		this.service = getCalendarService();
		this.forceScrollToDefault =true;
		this.reminders = getReminderManager();
		this.reminders.observeReminders('day', this);
		this.prevScrollPos=100000; //setting to some high initial value
		
		observeCurrentDateTime('day', this);
		observeTimeFormat('day', this);
		observeTimeChange('day', this);
		observeDayChange('day', this);
		
		this.blurStage = this.blurStageBound.bind(this); // used in activate/deactivate

		this.scrollContainer = this.controller.get('dv_scroll_container');
		this.sceneWidth = this.scrollContainer.getDimensions().width;
		this.compressed = true;
		this.tempUncompressed = false;
		this.dvScratchEventsDiv = this.controller.get('dv_scratch_events');
		this.scratchEventActive = false;
		this.scratchEvent = null;
		
		// Used to detect Orange key + tap to delete inline events
		this.orangeKeyDown = false;
	
		this.firstActivated = true;
		
		this.buildViewHeader();
		
		this.readOnlyCalendar = false;
		
		getCalendarsManager().observeCalendars('day', this);
		getPrefsManager().observeCalendarPrefs('day', this);
		
		this.dayDate = getCurrentDateTime();
		this.cacheRequests = [];
		
		this.serviceMonitorMsg = this.controller.serviceRequest('palm://com.palm.lunabus/signal', {
		        method: 'registerServerStatus',
		        parameters: {
		            "serviceName": "com.palm.calendar"
		        },
		        onSuccess: this.handleServiceStatus.bind(this)
		    });

		// Keeps track of the dates and default times for each day so that
		// the days are properly scrolled to the correct time for
		// each day during peeks
		this.days = [];
		this.days[this.PREV_DAY] = {};
		this.days[this.THIS_DAY] = {};
		this.days[this.NEXT_DAY] = {};
		// Set the dates
		this.days[this.THIS_DAY].date = getCurrentDateTime();
		var prevDay = getCurrentDateTime();
		prevDay.addDays(-1);
		this.days[this.PREV_DAY].date = prevDay;
		var nextDay = getCurrentDateTime();
		nextDay.addDays(1);
		this.days[this.NEXT_DAY].date = nextDay;
		// Set the firstEventTimelineIndex to -1
		this.days[this.PREV_DAY].firstEventTimelineIndex = -1;
		this.days[this.THIS_DAY].firstEventTimelineIndex = -1;
		this.days[this.NEXT_DAY].firstEventTimelineIndex = -1;
		
		this.free = [];
		this.free[this.PREV_DAY] = {};
		this.free[this.THIS_DAY] = {};
		this.free[this.NEXT_DAY] = {};
			
		this.tds = [];
		this.tds[this.PREV_DAY] = {};
		this.tds[this.THIS_DAY] = {};
		this.tds[this.NEXT_DAY] = {};
		
		this.divs = [];
		this.divs[this.PREV_DAY] = {};
		this.divs[this.THIS_DAY] = {};
		this.divs[this.NEXT_DAY] = {};

		this.cols = [];
		this.cols[this.PREV_DAY] = {};
		this.cols[this.THIS_DAY] = {};
		this.cols[this.NEXT_DAY] = {};
		this.cols[this.PREV_DAY].allDayCol = this.controller.get('dv_allday_col_prev');
		this.cols[this.THIS_DAY].allDayCol = this.controller.get('dv_allday_col');
		this.cols[this.NEXT_DAY].allDayCol = this.controller.get('dv_allday_col_next');
		
		this.snapElements = [];
		this.snapElements.push(this.controller.get("snap_element_prev"));
		this.snapElements.push(this.controller.get("snap_element_this"));
		this.snapElements.push(this.controller.get("snap_element_next"));
		this.horizScrollerModel = {scrollbars: false, mode: "horizontal-snap", snapIndex: 1};
		this.horizScrollerModel.snapElements = {x: this.snapElements, y: []};
		this.scrollerPos = 1;
		this.scrolling = 0;
		this.scrollToDefaultPending = false;
		this.controller.setupWidget('dv_scroll_container', {}, this.horizScrollerModel);	
		
		this.propertyChangedHandler = this.propertyChanged.bindAsEventListener(this);
		this.controller.listen('dv_scroll_container', Mojo.Event.propertyChanged, this.propertyChangedHandler);
		this.scrollStartingHandler = this.scrollStarting.bindAsEventListener(this);

		// set allday header background to black after the all-day header image loads
		// (this is a fix for a visual bug where, on first launch, the header shows up as all-black while the header image loads)
		this.controller.get("allday_bg_preloader").onload = function(){
			this.controller.get('dv_header_container2').style.backgroundColor='black';
		}.bind(this);

		this.scrollDataDiv = this.controller.get('dv_scroll_data');

	  	this.flickHandler = this.handleFlickEvents.bindAsEventListener(this);

		this.tds[this.THIS_DAY].scroller = this.controller.get('dv_day_scroller');
		this.tds[this.THIS_DAY].emptySpacer = this.controller.get('dv_empty_spacer');
		
		this.tds[this.PREV_DAY].scroller = this.controller.get('dv_day_scroller_prev');
		this.tds[this.PREV_DAY].emptySpacer = this.controller.get('dv_empty_spacer_prev');
		
		this.tds[this.NEXT_DAY].scroller = this.controller.get('dv_day_scroller_next');
		this.tds[this.NEXT_DAY].emptySpacer = this.controller.get('dv_empty_spacer_next');
	
		this.divs[this.PREV_DAY].ctxEvents = this.controller.get('dv_events_prev').getContext('2d');
		this.divs[this.THIS_DAY].ctxEvents = this.controller.get('dv_events').getContext('2d');
		this.divs[this.NEXT_DAY].ctxEvents = this.controller.get('dv_events_next').getContext('2d');
		this.divs[this.PREV_DAY].ctxEvents.font = "14px Prelude Bold";
		this.divs[this.THIS_DAY].ctxEvents.font = "14px Prelude Bold";
		this.divs[this.NEXT_DAY].ctxEvents.font = "14px Prelude Bold";
		
		this.divs[this.PREV_DAY].alldayevents = this.controller.get('dv_alldayevents_prev');
		this.divs[this.NEXT_DAY].alldayevents = this.controller.get('dv_alldayevents_next');
		this.divs[this.THIS_DAY].alldayevents = this.controller.get('dv_alldayevents');
		this.divs[this.PREV_DAY].alldayevents_scroller = this.controller.get('dv_alldayevents_scroller_prev');
		this.divs[this.NEXT_DAY].alldayevents_scroller = this.controller.get('dv_alldayevents_scroller_next');
		this.divs[this.THIS_DAY].alldayevents_scroller = this.controller.get('dv_alldayevents_scroller');
		this.divs[this.PREV_DAY].alldayevents_list = this.controller.get('dv_alldayevents_list_prev');
		this.divs[this.NEXT_DAY].alldayevents_list = this.controller.get('dv_alldayevents_list_next');
		this.divs[this.THIS_DAY].alldayevents_list = this.controller.get('dv_alldayevents_list');
		this.divs[this.PREV_DAY].ctxAllDayEvents = this.divs[this.PREV_DAY].alldayevents_list.getContext('2d');
		this.divs[this.NEXT_DAY].ctxAllDayEvents = this.divs[this.NEXT_DAY].alldayevents_list.getContext('2d');
		this.divs[this.THIS_DAY].ctxAllDayEvents = this.divs[this.THIS_DAY].alldayevents_list.getContext('2d');
		
		this.fastScrollScrim = this.controller.get('dv_scroll_scrim');
		this.fastScrollAnimateDiv = this.controller.get('dv_scrim_animate');
		this.fastScrollScrim.hide();

		this.busyImage = this.controller.get('busy_time_image');
		
		var allDayGray3Tile = {};
		allDayGray3Tile.imageLeft = this.controller.get('allday_event_gray_left');
		allDayGray3Tile.sxLeft = 12;
		allDayGray3Tile.syLeft = 24;
		allDayGray3Tile.imageRight = this.controller.get('allday_event_gray_right');
		allDayGray3Tile.sxRight = 12;
		allDayGray3Tile.syRight = 24;
		allDayGray3Tile.imageCenter = this.controller.get('allday_event_gray_center');
		allDayGray3Tile.sxCenter = 1;
		allDayGray3Tile.syCenter = 24;
		this.colors['cal-color-gray'].allday = allDayGray3Tile;
			
		var allDayGreen3Tile = {};
		allDayGreen3Tile.imageLeft = this.controller.get('allday_event_green_left');
		allDayGreen3Tile.sxLeft = 12;
		allDayGreen3Tile.syLeft = 24;
		allDayGreen3Tile.imageRight = this.controller.get('allday_event_green_right');
		allDayGreen3Tile.sxRight = 12;
		allDayGreen3Tile.syRight = 24;
		allDayGreen3Tile.imageCenter = this.controller.get('allday_event_green_center');
		allDayGreen3Tile.sxCenter = 1;
		allDayGreen3Tile.syCenter = 24;
		this.colors['cal-color-green'].allday = allDayGreen3Tile;
			
		var allDayOrange3Tile = {};
		allDayOrange3Tile.imageLeft = this.controller.get('allday_event_orange_left');
		allDayOrange3Tile.sxLeft = 12;
		allDayOrange3Tile.syLeft = 24;
		allDayOrange3Tile.imageRight = this.controller.get('allday_event_orange_right');
		allDayOrange3Tile.sxRight = 12;
		allDayOrange3Tile.syRight = 24;
		allDayOrange3Tile.imageCenter = this.controller.get('allday_event_orange_center');
		allDayOrange3Tile.sxCenter = 1;
		allDayOrange3Tile.syCenter = 24;
		this.colors['cal-color-orange'].allday = allDayOrange3Tile;
			
		var allDayPink3Tile = {};
		allDayPink3Tile.imageLeft = this.controller.get('allday_event_pink_left');
		allDayPink3Tile.sxLeft = 12;
		allDayPink3Tile.syLeft = 24;
		allDayPink3Tile.imageRight = this.controller.get('allday_event_pink_right');
		allDayPink3Tile.sxRight = 12;
		allDayPink3Tile.syRight = 24;
		allDayPink3Tile.imageCenter = this.controller.get('allday_event_pink_center');
		allDayPink3Tile.sxCenter = 1;
		allDayPink3Tile.syCenter = 24;
		this.colors['cal-color-pink'].allday = allDayPink3Tile;
			
		var allDayPurple3Tile = {};
		allDayPurple3Tile.imageLeft = this.controller.get('allday_event_purple_left');
		allDayPurple3Tile.sxLeft = 12;
		allDayPurple3Tile.syLeft = 24;
		allDayPurple3Tile.imageRight = this.controller.get('allday_event_purple_right');
		allDayPurple3Tile.sxRight = 12;
		allDayPurple3Tile.syRight = 24;
		allDayPurple3Tile.imageCenter = this.controller.get('allday_event_purple_center');
		allDayPurple3Tile.sxCenter = 1;
		allDayPurple3Tile.syCenter = 24;
		this.colors['cal-color-purple'].allday = allDayPurple3Tile;
			
		var allDayRed3Tile = {};
		allDayRed3Tile.imageLeft = this.controller.get('allday_event_red_left');
		allDayRed3Tile.sxLeft = 12;
		allDayRed3Tile.syLeft = 24;
		allDayRed3Tile.imageRight = this.controller.get('allday_event_red_right');
		allDayRed3Tile.sxRight = 12;
		allDayRed3Tile.syRight = 24;
		allDayRed3Tile.imageCenter = this.controller.get('allday_event_red_center');
		allDayRed3Tile.sxCenter = 1;
		allDayRed3Tile.syCenter = 24;
		this.colors['cal-color-red'].allday = allDayRed3Tile;
			
		var allDayTeal3Tile = {};
		allDayTeal3Tile.imageLeft = this.controller.get('allday_event_teal_left');
		allDayTeal3Tile.sxLeft = 12;
		allDayTeal3Tile.syLeft = 24;
		allDayTeal3Tile.imageRight = this.controller.get('allday_event_teal_right');
		allDayTeal3Tile.sxRight = 12;
		allDayTeal3Tile.syRight = 24;
		allDayTeal3Tile.imageCenter = this.controller.get('allday_event_teal_center');
		allDayTeal3Tile.sxCenter = 1;
		allDayTeal3Tile.syCenter = 24;
		this.colors['cal-color-teal'].allday = allDayTeal3Tile;
			
		var allDayYellow3Tile = {};
		allDayYellow3Tile.imageLeft = this.controller.get('allday_event_yellow_left');
		allDayYellow3Tile.sxLeft = 12;
		allDayYellow3Tile.syLeft = 24;
		allDayYellow3Tile.imageRight = this.controller.get('allday_event_yellow_right');
		allDayYellow3Tile.sxRight = 12;
		allDayYellow3Tile.syRight = 24;
		allDayYellow3Tile.imageCenter = this.controller.get('allday_event_yellow_center');
		allDayYellow3Tile.sxCenter = 1;
		allDayYellow3Tile.syCenter = 24;
		this.colors['cal-color-yellow'].allday = allDayYellow3Tile;
			
		var allDayBlue3Tile = {};
		allDayBlue3Tile.imageLeft = this.controller.get('allday_event_blue_left');
		allDayBlue3Tile.sxLeft = 12;
		allDayBlue3Tile.syLeft = 24;
		allDayBlue3Tile.imageRight = this.controller.get('allday_event_blue_right');
		allDayBlue3Tile.sxRight = 12;
		allDayBlue3Tile.syRight = 24;
		allDayBlue3Tile.imageCenter = this.controller.get('allday_event_blue_center');
		allDayBlue3Tile.sxCenter = 1;
		allDayBlue3Tile.syCenter = 24;
		this.colors['cal-color-blue'].allday = allDayBlue3Tile;
			
		this.free3Button = {};
		this.free3Button.imageLeft = this.controller.get('free_button_left');
		this.free3Button.sxLeft = 13;
		this.free3Button.syLeft = 27;
		this.free3Button.imageRight = this.controller.get('free_button_right');
		this.free3Button.sxRight = 13;
		this.free3Button.syRight = 27;
		this.free3Button.imageCenter = this.controller.get('free_button_center');
		this.free3Button.sxCenter = 1;
		this.free3Button.syCenter = 27;
		
		this.freeImageFull = this.controller.get('free_full');
	
		this.divs[this.PREV_DAY].eventsCanvas = this.controller.get('dv_events_prev');
		this.divs[this.THIS_DAY].eventsCanvas = this.controller.get('dv_events');
		this.divs[this.NEXT_DAY].eventsCanvas = this.controller.get('dv_events_next');
		this.tapHandler = new TapHandler(this.divs[this.THIS_DAY].eventsCanvas);
		this.tapHandler.setDefaultHandler(this.handleTimeLinesTap.bind(this, false));
		this.tapHandlerCompressed = new TapHandler(this.divs[this.THIS_DAY].eventsCanvas);
		this.tapHandlerCompressed.setDefaultHandler(this.handleTimeLinesTap.bind(this, true));
		this.tapHandlerAllDay = new TapHandler(this.divs[this.THIS_DAY].alldayevents_list);

		
		this.updateTimeFormat();
		
		// Determine the default time to scroll to.  
		this.setDefaultTimeforDay(new Date(this.days[this.THIS_DAY].date).clearTime(), true, this.THIS_DAY);
		this.setDefaultTimeforDay(new Date(this.days[this.PREV_DAY].date).clearTime(), true, this.PREV_DAY);	
		this.setDefaultTimeforDay(new Date(this.days[this.NEXT_DAY].date).clearTime(), true, this.NEXT_DAY);	
		
		this.menuModel = {
			visible:true,
			items: [
					{},
						{label:$L('Views'), 
						toggleCmd:'day', 
						items:[
							{label:$L('Day'), icon: 'menu-day', command:'day'}, 
							{label:$L('Week'), icon: 'menu-week', command:'week'}, 
							{label:$L('Month'), icon: 'menu-month', command:'month'}
					 ]},
					{},
					]};
			
		this.controller.setupWidget(Mojo.Menu.commandMenu, undefined, this.menuModel);
		this.controller.setupWidget(Mojo.Menu.appMenu, {omitDefaultItems:true}, this.appMenuModel);
		this.remindersUpdated(); 	// Update missed reminders menu item
		
		// Setup scrolling for days
		this.vertScrollerModel = {scrollbars: false, mode: "vertical"};
		this.controller.setupWidget(this.tds[this.THIS_DAY].scroller, {}, this.vertScrollerModel);	
		this.vertScrollerModelPrev = {scrollbars: false, mode: "vertical"};
		this.controller.listen(this.tds[this.THIS_DAY].scroller, Mojo.Event.scrollStarting, this.vertScrollerListener.bind(this));
		
		this.controller.setupWidget(this.tds[this.PREV_DAY].scroller, {}, this.vertScrollerModelPrev);	
		this.vertScrollerModelNext = {scrollbars: false, mode: "vertical"};
		this.controller.setupWidget(this.tds[this.NEXT_DAY].scroller, {}, this.vertScrollerModelNext);	
		
		// Set up scrolling for all day list
		this.controller.setupWidget(this.divs[this.THIS_DAY].alldayevents_scroller, undefined, {mode: 'vertical'});
		this.controller.setupWidget(this.divs[this.PREV_DAY].alldayevents_scroller, undefined, {mode: 'vertical'});
		this.controller.setupWidget(this.divs[this.NEXT_DAY].alldayevents_scroller, undefined, {mode: 'vertical'});

   		this.CalSelectorHandler = this.calSelector.bindAsEventListener(this);
		
		// IMPORTANT: Set up the height of the scrollers.  If this is not done
		// at setup, then the call to scrollToDefault (which occurs before 
		// getEventsCallback is called) will fail because the dimensions of the
		// scroller are not set up correctly
		this.updateEmptySpacer(this.THIS_DAY,false);
		this.updateEmptySpacer(this.PREV_DAY,false);
		this.updateEmptySpacer(this.NEXT_DAY,false);
		
		//the following is for when an event is edited/deleted in details view
		//and needs to be updated in the day view
		this.updatingEventHandler =this.setEventUpdated.bind(this);
   },
  	vertScrollerListener:function(event){
		var indicator =	this.controller.get('ampm_indicator');
		indicator.setStyle({color:'rgba(0,0,0,0)'});

		event.scroller.addListener({moved:this.vertScrollerMoved.bind(this)});
	},
	vertScrollerMoved:function(scrollEnding,position){
		
		if(scrollEnding && this.currentTimeFormat == "HH12")
		{
			this.calculatePosToDrawAmPm(Math.abs(this.tds[this.THIS_DAY].scroller.mojo.getScrollPosition().top));
		}
		
	},
  
  	handleServiceStatus: function(response) {
		if (response.connected) {
			this.eventChangedRequest = getCalendarService().getEventChange(this.getEventChangeCallback.bind(this), this.controller);
		} else {
			// Wake up the service, we need it running for changed notifications
			var controller = this.controller;
			setTimeout(function() {
				controller.serviceRequest("palm://com.palm.calendar/com/palm/luna/private" , { method: 'ping' });
			 }, 1000);
		}
	},
  
	ready: function() {
		this.dvScratchEventsDiv.update();
		this.dvScratchEventsDiv.hide();

		// Set the height of the canvas to 0... not sure if this is necessary,
		// since we hide the canvas below.
		this.divs[this.THIS_DAY].alldayevents_list.height = 0;
		// Hide the canvas
		this.divs[this.THIS_DAY].alldayevents_list.hide();
		// Hide all day events scroller (this is needed for updateEmptySpacer)
		this.divs[this.THIS_DAY].alldayevents_scroller.style.display = 'none';
				
		this.updateSceneScrollerSize();

		this.currentDateTimeUpdated();
		this.scrollToDefault(this.THIS_DAY);
		
		this.fastScrollScrim.show();
		
		// If the horizontal scroller is showing prev day, move to snapIndex 1
		var scrollPos = this.scrollContainer.mojo.getScrollPosition();
		if (scrollPos.left == 0) {
			this.scrollContainer.mojo.scrollTo(-321, 0, false);
		}
	},
	
	/*
	 * setDefaultTimeforDay - Figures out the default time to scroll to, for a specific
	 * scrollerbased on the following rules:
	 * 
	 * - If the scroller is THIS_DAY, and the day is TODAY, defaultTime should be NOW
	 * - If the day is n-o-t TODAY, and there are no events in the day,
	 * 		scroll to the Start of Day as defined by the Calendar Prefs
	 * - If the day is n-o-t TODAY, and there are events in the day,
	 * 		scroll to the first event of the day (rounded to whole hours, ex: If
	 * 		the first event is 11:30AM, the defaultTime will be 11AM)
	 * 
	 * @param day: Day
	 * @param dontNotify: ONLY VALID if whichDayInView is undefined
	 * @param whichDayInView: If undefined, setCurrentDateTime will be called, which means
	 * 												that the "day" parameter represents the date THIS_DAY WILL
	 * 												be, and NOT the day THIS_DAY is currently representing. If 
	 * 												defined, only the defaultTime setting is updated.
	 */
  setDefaultTimeforDay: function(day, dontNotify, whichDayInView) {
		var now = new Date();
		var today = Date.today();
		var curDateTime = new Date(day);
		
		//Mojo.Log.info("setDefaultTimeforDay whichDayInView %d", whichDayInView);
		var whichDay = whichDayInView;
		if (whichDay == undefined) {
			whichDay = this.THIS_DAY;
		}

		if (day.clearTime().equals(today.clearTime()) == true) {
			// If whichDay is THIS_DAY and the day represented is today, then we scroll to "now"
			//Mojo.Log.info("setDefaultTimeforDay: whichDayInView %d Setting Time for NOW", whichDayInView);
			curDateTime.set({hour: now.getHours()});
		} else {
			var firstEventTimelineIndex = this.days[whichDay].firstEventTimelineIndex;
			if (firstEventTimelineIndex != -1 && whichDayInView !== undefined) {
				// Otherwise, scroll to the first event of the day if there is one.
				curDateTime.setHours(firstEventTimelineIndex);
				//Mojo.Log.info("setDefaultTimeforDay: whichDayInView %d Setting Time for first event %s", whichDayInView, Luna.Format.formatDate(curDateTime, {time:"short"}));
			}
			else {
				// Scroll to the start of the day if there are no events on the day.
				// Start of day is set in Calendar Prefs
				//Mojo.Log.info("setDefaultTimeforDay: whichDayInView %d Setting Time for START OF DAY", whichDayInView);
				var prefs = getPrefs();
				if (prefs) {
					var startTime = new Date(prefs.startTimeOfDay);
					curDateTime.setHours(startTime.getUTCHours());
					curDateTime.setMinutes(startTime.getUTCMinutes());
				}
			}
		}
		
		
		
		// Store the default time for the day
		this.days[whichDay].defaultTime = curDateTime;
		
		// If it's the current day, then we also set the app wide
		// current date time setting.  
		if (whichDayInView == undefined) {
			//Mojo.Log.info("SETTING CURRENT DATE TIME TO DEFAULT")
			setCurrentDateTime(curDateTime, dontNotify);
		}
	},
  
	showJumpTo: function() {
		this.maybeCommitScratchEvent(true);
		this.controller.showDialog({template: 'shared/jumpto', assistant: new JumptoDialogAssistant(this)});
	},
	
	syncAllCalendars: function() {
		this.getCalendarAccountsRequest = getAccountsService().getAllAccountsForCalendarService(
		this.getCalendarAccountsCallback.bind(this), this.controller, false);
	},
	
	syncAllCallback: function(response) {
		Mojo.Controller.appController.showBanner({
			messageText: $L("Syncing accounts")},
			null /*launchArguments*/,
			"calendar-sync-all");
	},
	
	getCalendarAccountsCallback: function(response) {
		// Find all the EAS accounts if there are any
		this.easAccountIds = null;
		response.list.each(function(acct){
			//Mojo.Log.info("acct domain %s", acct.domain);
			if (acct.domain == 'eas') {
				// Keep track of the list of eas accounts for Sync Accounts button
				if (this.easAccountIds == null) {
					this.easAccountIds = new Array();
				}
				this.easAccountIds.push(acct.accountId);
			}
		}, this);

		getPimSyncService().syncAll(this.controller, this.easAccountIds, this.syncAllCallback.bind(this));
	},	
	
	// Commit the scratch event, if there is one and it has valid data associated with it.
	// Returns true if there was a scratch event (even if it was 'empty'), false otherwise.
	// If onlyResetOnCancel is truthy, then we only call resetScratchEvent() in the case where it's NOT being committed.
	// This has the effect of leaving the scratch event in the DOM until we get the "real" one back from teh service, so
	// that the user doesn't see it disappear and then reappear, which is pretty disturbing.
	maybeCommitScratchEvent: function(onlyResetOnCancel) {
		if (this.scratchEvent) {
			if (this.scratchEvent.id == undefined &&
					(this.scratchEventField.mojo.getValue() == "" || !this.scratchEventField.mojo.getValue())) {
				// If this is a new event, and the user hasn't entered anything, then we 
				// do not save the event.
				if(onlyResetOnCancel) {
					this.resetScratchEvent();
				}
			} else {
				// Commit the scratch event
				this.scratchEventCommitHandler(this.scratchEventField.mojo.getValue());
			}
			// Remove the scratch event
			if(!onlyResetOnCancel) {
				this.resetScratchEvent();
			}
			return true;
		}
		
		return false;
	},
	
	handleCommand: function(event) {
		if(event.type == Mojo.Event.command) {
			//Mojo.Log.info("**** handleCommand: event.command %s", event.command);
			
			// Do NOT call handleScratchEventCommand here...  ONLY call it
			// for commands we recognize, otherwise the scratch event will get committed
			// on commands like palm-show-app-menu, or palm-cut-md, and we
			// don't want that.
			if(event.command == 'month') {
				Event.stop(event);
				this.maybeCommitScratchEvent(true);
				this.handleMonthView();
			} else if(event.command == 'week') {
				Event.stop(event);
				this.maybeCommitScratchEvent(true);
				this.handleWeekView();
			} else if(event.command == 'day' || event.command == 'today') {
				Event.stop(event);
				this.maybeCommitScratchEvent(true);
				this.setDefaultTimeforDay(new Date().clearTime());
			} else if (event.command == Mojo.Menu.prefsCmd) {
				Event.stop(event);
				this.controller.stageController.pushScene('prefs');
				this.maybeCommitScratchEvent();
			} else if(event.command =="newtimed"){
				Event.stop(event);
				this.controller.stageController.pushScene('edit', 0, 0, false,this.getCalId()/*calendarId*/,0/*subject*/);
				this.maybeCommitScratchEvent();
			} else if(event.command=="newallday"){
				Event.stop(event);
				this.controller.stageController.pushScene('edit', 0, 0, true,this.getCalId(),0/*subject*/);
				this.maybeCommitScratchEvent();
			} else if(event.command == Mojo.Menu.helpCmd){
				this.maybeCommitScratchEvent(true);
				getAppManagerService().launchHelp(this.controller);
			} else if (event.command == 'jumpto') {
				Event.stop(event);
				this.showJumpTo(); // this may also commit the scratch event
			} else if (event.command == 'reminders') {
				Event.stop(event);
				this.maybeCommitScratchEvent();
				this.controller.stageController.pushScene('reminder-list');
			} else if (event.command == 'sync') {
				Event.stop(event);
				this.maybeCommitScratchEvent(true);
				this.syncAllCalendars();
			}				
		} else if(event.type == Mojo.Event.commandEnable && event.command == Mojo.Menu.prefsCmd) {
			// Enable prefs menuitem for this scene.
			event.stopPropagation();
		} else if(event.type == Mojo.Event.back){
			// If there's a scratch event, possibly commit it, and then override default back handling.
			if (this.maybeCommitScratchEvent(true)) {
				Event.stop(event);
			}
		}
			
	},
	
	initFlickTimings: function() {
		this.flickTimes = [];
		this.flickIndex = 0;
		this.lastFlick = null;
	},
	
	addFlickTime: function() {
		var maxTimings = 2;
		var now = new Date().getTime();
		if (this.lastFlick) {
			var flickTime = now - this.lastFlick;
			if (this.flickTimes.length < maxTimings) {
				this.flickTimes.push(flickTime);
			} else {
				this.flickTimes[this.flickIndex] = flickTime;
				this.flickIndex++;
				if (this.flickIndex >= this.flickTimes.length) {
					this.flickIndex = 0;
				}
			}
		}
		this.lastFlick = now;
	},
	
	handleFlickEvents: function(event) {
		var allDataCached = this.isAllDisplayedInCache();
		if (!allDataCached || (this.listeningToScroller && event.velocity.x)) {
			if (!allDataCached || this.scrolling != 0 || this.fastScroll) {
				
				if (Math.abs(this.scrolling) > 0 && !this.fastScrollScrim.visible()) {
					this.fastScrollAnimateDiv.hide();
					this.fastScroll = true;
					//var transition = this.controller.prepareTransition(Mojo.Transition.crossFade);
					this.fastScrollScrim.show();
					if (this.menuFadeDiv)
						this.menuFadeDiv.hide();
					//transition.run();
				} 
				this.addFlickTime();
				Event.stop(event);
				return Mojo.Gesture.CONSUMED_EVENT;
			}
		}	
	},
	
	propertyChanged: function(event) {
		//there is a weird case where this scroller event handler is called when 
		//typing an enter key on the text field in this scene
		//so to work around it.
		
		if(typeof event.value == "string")
			return;
			
		if (this.settingSnapIndex)
			return;

		// Change date displayed in header before we start scrolling.  
		if (this.scrollerPos != event.value) {

			if (!this.fastScrollScrim.visible())
				this.fastScrollScrim.show();
				
			var scrollerChange =  event.value - this.scrollerPos;
			this.scrolling += scrollerChange;
			this.scrollerPos = event.value;
			this.dayDate.addDays(scrollerChange);
			this.buildDayTitle();
			var thisDay = this.dayDate;
			if (this.getFromCache(thisDay) == null) 
				{
					//show the grey scrim
					this.controller.get('dv_scrim_next').show();
					console.error("&&&&&&&&&&&entered case where data not in cache");
					this.controller.get('dv_scrim').show();
					this.controller.get('dv_scrim_prev').show();
					
				}
				else
				{
					//show the grey scrim
					this.controller.get('dv_scrim_next').hide();
					console.error("&&&&&&&&&&&entered case where data in cache,"+thisDay);
					this.controller.get('dv_scrim').hide();
					this.controller.get('dv_scrim_prev').hide();
				}
		}
	},
	
	scrollStarting: function(event) {
		if (this.settingSnapIndex)
			return;
		
		// we need to remember the day we started on, since we need to handle scrolling and
		// committing scratch events differently, depending on whether or not we scroll to a different day.
		this.horizScrollStartDay = this.dayDate.getTime();
		
		this.prevScrollPos=100000;
		//Mojo.Log.info("day-assistant: scrollStarting");
		event.scroller.addListener(this);
		this.listeningToScroller =true;
		this.initFlickTimings();
	},
	
	doneScrolling: function(){
		//if (this.isAllDisplayedInCache())
		this.hideFastScrollView();
		this.lastFlick = null;
		this.listeningToScroller = false;
		this.scrolling = 0;
		this.scrollDelayUpdateId = null;
	},
	
	moved: function(scrollEnding, position) {
		var scrolledToNewDay;
		
		if (this.settingSnapIndex)
			return;
		
		scrolledToNewDay = !(this.horizScrollStartDay === this.dayDate.getTime());
		
		if(scrollEnding && this.scratchEvent) {
			// Commit the scratch event... And leave the scratch event div visible if we did not scroll to a new day.
			this.maybeCommitScratchEvent(!scrolledToNewDay);
		}
		
		var currentScrollPos = Math.abs(position.x);
		if (this.listeningToScroller && ((Math.abs(this.prevScrollPos - currentScrollPos) == 0) || scrollEnding)) {
			this.updateScrollResults(scrolledToNewDay);
		}
		this.prevScrollPos = currentScrollPos;
	},
	
	updateScrollResults: function(scrolledToNewDay) {
		this.resetPos();
		this.changingDays = true;
		
		this.setDefaultTimeforDay(new Date(this.dayDate).clearTime());
		this.changingDays = false;
		this.updateFromCache(scrolledToNewDay);

		this.scrollerPos = 1;
		this.doneScrolling();
	},

	calSelector: function(e) {
		Event.stop(e);
	    var listItems = buildCalendarsMenu(true /*includeAll*/, true /*includeReadOnly*/, true /*includeExcludedFromAll*/);
	 
	    this.controller.popupSubmenu({
	      onChoose: this.calendarChoose.bind(this),
	    	toggleCmd: getCurrentCal(), 
	      // placeNear: e.target,
		  manualPlacement:true,
		  popupClass:"cal-selector-popup",
	      items: listItems
	    });
	},
	 
	calendarChoose: function(value) {
		
		if (value) {
			if(value=="viewOptions")
			{
				this.controller.stageController.pushScene("prefs-options",this.acctsModel);
				return;
			}
			
			this.maybeCommitScratchEvent(true);
			
			setCurrentCal(value);
			this.clearCache();
			this.updateAll();
			WeekAssistant.cacheSize = 0;
			WeekAssistant.weekCache = new Hash();
		}
	},
  
  cleanup: function() {
		//Mojo.Log.info('day-assistant: cleanup');
 		
		this.reminders.stopObservingReminders('day');
		getCalendarsManager().stopObservingCalendars('day');
		getPrefsManager().stopObservingCalendarPrefs('day');
		
		this.resetScratchEvent();
	
		this.pendingQuery =null;
		if(this.currentQuery){
			this.currentQuery.cancel();
			this.currentQuery =null;
		}
		
		stopObservingCurrentDateTime('day');
		stopObservingTimeFormat('day');
		stopObservingTimeChange('day');
		stopObservingDayChange('day');
  },

  activate: function() {
		
		this.active = true;
		//this.resetPos(); // TEMP
		
		if (this.compressed)
			this.tapHandlerCompressed.activate();
		else
			this.tapHandler.activate();
		this.tapHandlerAllDay.activate();
		
		// Listen for resize events, so that we can size our scroller
		// appropriately.  Specific cases of importance:
		// - If updateEmptySpacer is called while the dashboard is up, then the scroller
		// height needs to be updated when the dashboard is brought down.
		
		Mojo.Event.listen(this.controller.window, 'resize', this.updateSceneScrollerSizeHandler);
		this.controller.listen('dv_scroll_container', Mojo.Event.scrollStarting, this.scrollStartingHandler);
		
		// Listen for stageDeactivate to confirm scratch events.  blurStage is bound once in setup.
		// TODO: It would be better to listen for stageDeactivate on the sceneElement, which can be done once in setup().
		this.controller.listen(this.controller.document, Mojo.Event.deactivate, this.blurStage);
		
		// Calendar Header
		this.headerTapHandler = this.showJumpTo.bindAsEventListener(this);
		this.controller.get('dv_view_header').observe(Mojo.Event.tap, this.headerTapHandler);
		
		// Calendar Picker
		this.calendarTapHandler = this.CalSelectorHandler.bindAsEventListener(this);
	 	this.controller.get('dv_calendar_source').observe(Mojo.Event.tap, this.calendarTapHandler);
		
		// The colors have been updated, so we clear the cache and update everything
		if (this.pendingCalendarSettingsUpdate == true) {
			this.clearCache();
			this.updateAll();
			WeekAssistant.cacheSize = 0;
			WeekAssistant.weekCache = new Hash();
			this.pendingCalendarSettingsUpdate = false;
		}
		
		if (this.pendingUpdateEmptySpacer) {
			// Tried to updateEmptySpacer while the Day View was not active
			// which prevents the scroller from being updated to the correct size.
			// SO we call updateEmptySpacer here.
			this.pendingUpdateEmptySpacer = undefined;
			this.updateSceneScrollerSize();
		}
		
		if (this.pendingRender == true) {
			this.updateFromCache(false);
			this.pendingRender = false;
		}
		
	  	this.scrollDataDiv.observe(Mojo.Event.flick, this.flickHandler);
 	  	this.fastScrollScrim.observe(Mojo.Event.flick, this.flickHandler);
		this.menuFadeDiv = this.controller.select('div.palm-menu-fade')[0];
		
		// Listen for keys
		this.keyUpHandler = this.handleKeyUp.bind(this);
		this.controller.listen(this.controller.sceneElement, Mojo.Event.keyup, this.keyUpHandler);
		
		this.keyDownHandler = this.handleKeyDown.bind(this);
		this.controller.listen(this.controller.sceneElement, Mojo.Event.keydown, this.keyDownHandler);
		this.updateTimer(); 	
  },
  
  deactivate: function() {
		
		this.active = false;
		
		this.tapHandler.deactivate();
		this.tapHandlerCompressed.deactivate();
		this.tapHandlerAllDay.deactivate();
		
		Mojo.Event.stopListening(this.controller.window, 'resize', this.updateSceneScrollerSizeHandler);
		this.controller.stopListening('dv_scroll_container', Mojo.Event.scrollStarting, this.scrollStartingHandler);
		
		this.controller.get('dv_view_header').stopObserving(Mojo.Event.tap, this.headerTapHandler);
		this.controller.get('dv_calendar_source').stopObserving(Mojo.Event.tap, this.calendarTapHandler);
 	
	  	this.controller.stopListening(this.controller.document, Mojo.Event.deactivate, this.blurStage);
	    this.scrollDataDiv.stopObserving(Mojo.Event.flick, this.flickHandler);
	  	this.fastScrollScrim.stopObserving(Mojo.Event.flick, this.flickHandler);
			
		this.controller.stopListening(this.controller.sceneElement, Mojo.Event.keyup, this.keyUpHandler);
		this.controller.stopListening(this.controller.sceneElement, Mojo.Event.keydown, this.keyDownHandler);
		if (this.updateUITimeoutID) {
                this.controller.window.clearTimeout(this.updateUITimeoutID);
				this.updateUITimeoutID=0;
         } 	
	},
	
	
	// *** EVENT HANDLERS ***
	
	handleKeyUp: function(event) {
		// Check for Orange key
		if (event.originalEvent.altKey || event.originalEvent.keyCode === 129) {
    	//Mojo.Log.info("handleKeyUp detected ORANGE key");
			this.orangeKeyDown = false;
 		} 
	},
	
	handleKeyDown: function(event) {
		// Check for Orange key
		if (event.originalEvent.altKey || event.originalEvent.keyCode === 129) {
    	//Mojo.Log.info("handleKeyDown detected ORANGE key");
			this.orangeKeyDown = true;
 		} 
	},
	
	updateSceneScrollerSize: function(event) {
		//Mojo.Log.info("*** updateSceneScrollerSize");
		this.updateEmptySpacer(this.THIS_DAY,false);
		this.updateEmptySpacer(this.PREV_DAY,false);
		this.updateEmptySpacer(this.NEXT_DAY,false);
	},
	
	updateSceneScrollerAtReSize: function(event) {
		//Mojo.Log.info("*** updateSceneScrollerSize");
		this.updateEmptySpacer(this.THIS_DAY,true);
		this.updateEmptySpacer(this.PREV_DAY,true);
		this.updateEmptySpacer(this.NEXT_DAY,true);
	},
	
	getCalId :function(){
		var defaultCalId;
		if (getCurrentCal() == 'all') {
			// Create an event in the calendar specified by the defaultCalId preference
			defaultCalId = getPrefsManager().getDefaultCalendar();
			
			if (defaultCalId == 0) {
				defaultCalId = getCalendarsManager().getLocalCalendarId();
			}
			
			// Check to see if the calendarId is a valid one
			if (getCalendarsManager().getCalName("id"+defaultCalId) == undefined) {
				// The default calendar doesn't exist anymore so let's just create it
				// in Local
				defaultCalId = getCalendarsManager().getLocalCalendarId();
			}
			
		}else{
			defaultCalId =getCurrentCal();
		}
		return "id"+defaultCalId;
	},
	
	// SCRATCH EVENTS
	createNewScratchEvent: function(targetDate) {
		// Create a new event.
		this.scratchEvent = new CalendarEvent();

		// Event with id 0 indicates new event
		this.scratchEvent.id = undefined;
		
		// If a targetDate was supplied, use that.			
		// Otherwise use the current date as the default date/time.
		var start = getCurrentDateTime();
		if (targetDate)
			start = new Date(targetDate);
			
		// Set the start time and end time.
		this.scratchEvent.startTimestamp = start.getTime();
		this.scratchEvent.endTimestamp = start.addMinutes(getPrefs().defaultEventDuration).getTime();
		this.scratchEvent.allDay = false;
		
		// Set the default alarm of the event
		this.scratchEvent.alarm = getPrefs().defaultEventReminder;
		
		// Create an event in the currently selected calendar.  If
		// ALL calendars are displayed then we create an event in the 
		// default calendar (specified by a preference)
		var defaultCalId = this.getCalId();
		this.scratchEvent.calendarId = defaultCalId;
		this.scratchEvent.calendarColor = getCalendarsManager().getCalColorStyle(defaultCalId);		
		this.scratchEvent.saved = false;
	},
	
	resetScratchEvent: function() {
		//Mojo.Log.info('day-assistant: resetScratchEvent');
		var scratchEventDiv = this.controller.get('scratch_event');
		if (scratchEventDiv) {
			// Stop listening for key and tap events on the scene now that the scratch event is being
			// removed

			//Luna.Log.info('****** scratchEventDiv IS NOT NULL', scratchEventDiv.up());
			if (this.scratchEventKeyEventHandler)
				Mojo.Event.stopListening(scratchEventDiv, "keyup", this.scratchEventKeyEventHandler);

			scratchEventDiv.remove();
		}
		
		this.dvScratchEventsDiv.update();
		this.dvScratchEventsDiv.hide();

		if (this.scratchEventTapHandler)
			Mojo.Event.stopListening(this.controller.sceneElement, Mojo.Event.tap, this.scratchEventTapHandler);
		if (this.scratchEventHoldHandler)	
			Mojo.Event.stopListening(this.controller.sceneElement, Mojo.Event.hold, this.scratchEventHoldHandler);

				
		this.scratchEvent = null;
		this.scratchEventActive = false;
		this.scratchEventField = undefined;
	},
	
	savedNewScratchEvent: function(response) {
		//Mojo.Log.info("savedNewScratchEvent");
		
		// Set the event saved to true, to indicate that the event was successfully saved.
		
		if (this.scratchEvent) {
			//Mojo.Log.info("!!!!!!!!!! day-assistant: savedNewScratchEvent: %s scratch %s", response.id,this.scratchEvent.id);
			
			if (response.id == this.scratchEvent.id || this.scratchEvent.id == undefined || this.scratchEvent.id.match(response.id)) {
		  
				// NOTE: Event Ids are strings NOT longs
				if (this.scratchEvent.id == undefined)
					this.scratchEvent.id = response.id + "";	// Do NOT Localize
				this.scratchEvent.saving = undefined;
		  	this.scratchEvent.saved = true;
		  }
		}
		// Don't call resetScratchEvent here otherwise there will be flicker in the
		// interval between removing the scratch event and us getting the notification
		// that the events query has been updated.
	},
	
	saveFailedNewScratchEvent: function(response) {
		//Mojo.Log.info('day-assistant: saveFailedNewScratchEvent');
		
		// TODO: Put up a notification here?
		
		// The save failed so we just remove the scratch event from the UI.
		// Remove the scratch event
		this.resetScratchEvent();
	},
	
	saveScratchEvent: function() {
		//Mojo.Log.info("day-assistant: saveScratchEvent");
			
		if (this.scratchEvent) {
			this.scratchEvent.saving = true;
			if (this.scratchEvent.id) {
				//existing event, so just update
				getCalendarService().updateEvent(this.scratchEvent, this.savedNewScratchEvent.bind(this), this.controller, this.saveFailedNewScratchEvent.bind(this));
			}
			else {
				//new event, so create it
				getCalendarService().setEvent(this.scratchEvent, this.savedNewScratchEvent.bind(this), this.controller, this.saveFailedNewScratchEvent.bind(this));
			}
		}
		// Don't call resetScratchEvent here, we need to keep it onscreen until the real
		// event is saved
		this.controller.get('scratch_event').addClassName('event-updated');
	},
	
	handleNewScratchEventCommit: function(newValue) {
		//Mojo.Log.info("handleNewScratchEventCommit: ");
		if (this.scratchEvent && this.scratchEvent.saved == false && !this.scratchEvent.saving) {
			if (newValue == null || newValue == "") {
				this.scratchEvent.subject = $L("New event");
				this.saveScratchEvent();
			} else {
				this.scratchEvent.subject = newValue;		
			  this.saveScratchEvent();
			}
		}
	},
	
	preCheckBeforeScratchEvent:function(newHour, newMinutes, eventTop) {
		// Only allow creation of inline events if a non readonly calendar is selected or All
		if (this.readOnlyCalendar == false) {
			this.createNewInlineEvent(newHour, newMinutes, eventTop);
		}
		
	},

	handleScratchEventKey: function(event) {
		//Mojo.Log.info("day-assistant: handleScratchEventKey");
		var chr = event.keyCode;
		if (Mojo.Char.isEnterKey(chr)) {
			this.maybeCommitScratchEvent(true);			
		}
	},
	
	handleScratchEventTap: function(event) {
		//Mojo.Log.info("day-assistant: handleScratchEventTap %s", event.target.id);
		if (event.target.up('div#scratch_event')) {
			
			if (event.target.id == "scratch_event_info") {
				Event.stop(event);
				
				// Remember the startTimeStamp of the new event
				var eventStartTime = new Date(this.scratchEvent.startTimestamp);
			
				// Save the event
				if (this.scratchEventField.mojo.getValue() == "" || this.scratchEventField.mojo.getValue() == null) {
					if (this.scratchEvent.id == undefined) {
						// The user hit the info icon in a new event without entering anything
						// for the subject
						
						// Postpone the call to resetScratchEvent until deactivate(),
						// so that the event doesn't disappear from view before the Details
						// scene is shown.
						//this.eventBeingUpdated = true;
				
						//for now just reset the scratch event until we figure out the problem with 
						//with scratch events not clearing up
						this.resetScratchEvent();
						// Launch the Event Details scene as if we were creating a new event
						
						this.controller.stageController.pushScene('edit', 0, eventStartTime, false /*allDay*/,this.getCalId(),null,false,this.updatingEventHandler);
					} else {
						var subject = $L('Event Name');
						
						// Visually we don't want the user to notice the removal of the
						// scratch event (for the brief moment between the reset and the
						// push of the edit scene.  So we temporarily replace the 
						// subject of the ORIGINAL event with the new subject
						//var eventdiv=this.controller.get(this.scratchEvent.id);
						//eventdiv.down("div.subject").update(subject);
						
						this.controller.stageController.pushScene('edit',this.scratchEvent.id,0,false,this.scratchEvent.calendarId,subject,true/*forceCommit*/,this.updatingEventHandler);
						this.resetScratchEvent();
					}
				} else if (	this.scratchEvent.id != 0 && 
							this.scratchEventField.mojo.getValue() == this.scratchEvent.origSubject) {
					var scratchEventId = this.scratchEvent.id;		
					// Nothing changed so no need to save the scratch event
					this.resetScratchEvent();	
					// Launch the edit scene with the current event
					this.controller.stageController.pushScene('edit', scratchEventId, eventStartTime, true,this.scratchEvent.calendarId,this.scratchEvent.subject,false,this.updatingEventHandler);
				} else {
					var eventid=(this.scratchEvent.id)?this.scratchEvent.id:0;
					
					if(this.scratchEvent.id == undefined) {
						// Postpone the call to resetScratchEvent until deactivate(),
						// so that the event doesn't disappear from view before the Details
						// scene is shown.
						//this.eventBeingUpdated = true;
						//for now just reset the scratch event until we figure
						//out the problem with resetting scratchevent
						
						
					} else {
						// Visually we don't want the user to notice the removal of the
						// scratch event (for the brief moment between the reset and the
						// push of the edit scene.  So we temporarily replace the 
						// subject of the ORIGINAL event with the new subject
						this.scratchEvent.subject =this.scratchEventField.mojo.getValue();
						//var eventdiv=this.controller.get(this.scratchEvent.id);
						//eventdiv.down("div.subject").update(this.scratchEvent.subject);
						
						//@todo -[poornima need to render the whole day scene for this ? 
						
					}
					this.controller.stageController.pushScene('edit', eventid, eventStartTime, false,this.scratchEvent.calendarId,this.scratchEventField.mojo.getValue(),true/*forceCommit*/,this.updatingEventHandler);
					//if (!this.eventBeingUpdated) {
						this.resetScratchEvent();
					//}
				}		
			}
		} else {
			//Mojo.Log.info("TAP OUTSIDE THE SCRATCH EVENT");
			Event.stop(event);
			this.handleCommit();
		}
	},
	
	handleCommit:function(){
		// Do not try to commit the scratch event again if we detect that the scratch event is 
		// either Being saved, or Has been already saved
		if (this.scratchEvent && this.scratchEvent.saved == false  && !this.scratchEvent.saving && this.scratchEventField) {
			if (this.scratchEventField.mojo.getValue() == "" || !this.scratchEventField.mojo.getValue()) {
	  		if (this.scratchEvent.id == undefined) {
	  			// The user tapped outside of the scratch event, didn't type anything for the
					// subject, so we cancel the new event
					this.resetScratchEvent();
				} else {
					// Save the edited event with "Event Name" as the subject
					this.scratchEvent.subject = $L("Event name");
					this.saveScratchEvent();
					
				}
			} else {
				this.scratchEvent.subject = this.scratchEventField.mojo.getValue();
				this.saveScratchEvent();
			}
		}
		
	},
	
	handleScratchEventHold: function(event) {
		//Mojo.Log.info("day-assistant: handleScratchEventHold %s ", event.target.id);
		if (event.target.up('div#scratch_event')) {
			Event.stop(event);
		} else {
			this.handleCommit();
		}
	},
	
	createNewInlineEvent: function(newHour, newMinutes, eventTop) {
		//Mojo.Log.info('***********day-assistant: createNewInlineEvent');
		var curDateTime = getCurrentDateTime();
		
		this.scratchEventActive = true;
		//this.pendingOpenScratchEventInDetails = false;
		
		curDateTime.set({hour: newHour, minute: newMinutes, second: 0, millisecond: 0});
		//setCurrentDateTime(curDateTime);
		
		this.createNewScratchEvent(curDateTime);
		
		// Calculate the bounds of the scratch event in the view
		var minutePercent = newMinutes / 60.0;
		
		this.scratchEvent.top = (eventTop + 1) + 'px';
		
		var eventsWidth = 292;
		var eventsParentWidth = 320;
		var eventsOffsetX = eventsParentWidth - eventsWidth;
		
		this.scratchEvent.left = eventsOffsetX + 'px';
		this.scratchEvent.width = eventsWidth + 'px';
		this.scratchEvent.textFieldWidth = (eventsWidth - 48) + 'px';
		var defaultEventDuration = getPrefs().defaultEventDuration;
		if(defaultEventDuration <=30)
			this.scratchEvent.halfHourClass = "half-hour-event";
			// Calculate the height of event based on the duration of the event
		this.scratchEvent.height = parseInt(( defaultEventDuration/ 60.0) * this.hourHeight) + 'px';
		
		// Add the current event to view
		var newEventHtml = Mojo.View.render({object: this.scratchEvent, template: 'day/day-scratchevent'});
		this.dvScratchEventsDiv.insert(newEventHtml);
		this.dvScratchEventsDiv.show();

		var tfAttrs = {
			hintText : $L('New event'),
			multi: 'false',
			modelProperty: 'subject',
			focus: true,
			requiresEnterKey: true,
			focusMode: Mojo.Widget.focusSelectMode,
		};
		// Setup the TruncTextField widget
		var scratchEventDiv = this.controller.get('scratch_event');
    this.controller.setupWidget('scratch_event_field', tfAttrs, this.scratchEvent);
		this.controller.instantiateChildWidgets(scratchEventDiv);
		this.scratchEventField = this.controller.get('scratch_event_field');
		this.scratchEventCommitHandler = this.handleNewScratchEventCommit.bind(this);
		this.scratchEventKeyEventHandler = this.handleScratchEventKey.bind(this);
		this.scratchEventTapHandler = this.handleScratchEventTap.bind(this)
		this.scratchEventHoldHandler = this.handleScratchEventHold.bind(this);
		
		// Listen for the key and tap events on the whole scene when a scratch event is present
		Mojo.Event.listen(scratchEventDiv, "keyup", this.scratchEventKeyEventHandler);
		Mojo.Event.listen(this.controller.get("dv_scratch_events"), Mojo.Event.tap, this.scratchEventTapHandler);
		Mojo.Event.listen(this.controller.get("dv_scratch_events"), Mojo.Event.hold, this.scratchEventHoldHandler);
	},
	
	// Handles taps for free times
	handleFreeTap: function(free) {
		var top = this.compressPos(free.top, free.topCompress);
		var height = this.compressPos(free.height, this.hourHeight);
		this.toggleCompression(true, top + height * 0.5);
	},
	
	showEventSelection: function(event) {
		//Mojo.Log.info("day-assistant: showEventSelection");
		this.renderEvent(this.THIS_DAY, event, true);
	},
	
	clearEventSelection: function(event) {
		//Mojo.Log.info("day-assistant: clearEventSelection");
		this.renderEvent(this.THIS_DAY, event, false);
	},
	
	showAllDayEventSelection: function(index, event) {
		this.renderAllDayEvent(this.THIS_DAY, index, event, true);
	},
	
	clearAllDayEventSelection: function(index, event) {
		this.renderAllDayEvent(this.THIS_DAY, index, event, false);
	},
	
	handleTimeLinesTap: function(compressed, event, x, y) {
		//Mojo.Log.info("handleTimeLinesTap");
		var newHour = -1;
		var newMinute;
		var eventTop;
		
		var dayNoTime = getCurrentDateTime().set({hour: 0, minute: 0, second: 0, millisecond: 0});
		var day = this.getFromCache(dayNoTime.getTime()).days[0];
		
		if (compressed) {
			for (i = 0; i < day.timelines.length; i++) {
				var timeline = day.timelines[i];
				var timelineBottom = timeline.topCompress + this.hourHeight - TimelineTapOffset;
				
				if (i == 23)
					timelineBottom = timeline.topCompress + this.hourHeight;
					
				if ((timeline.topCompress != -1) && (y > timeline.topCompress - TimelineTapOffset) && (y <= timelineBottom)) {
					newHour = i;
					eventTop = timeline.topCompress;
					newMinute = 0;
					
					// Disable code to create events on the half hour... 
					/*
					if ((y - timeline.topCompress) >= (this.hourHeight / 2)) {
						newMinute = 30;
						eventTop += this.hourHeight / 2;
					}
					*/
					break;
				}
			}
			
			// Check if tap was outside timelines (day is compressed smaller then the view)
			if (newHour == -1) {
				Event.stop(event);
				return;
			}
				
		} else {
			newHour = Math.floor(y / this.hourHeight);
			var timeline = day.timelines[newHour];
			if (newHour < 23 && y >= timeline.top + this.hourHeight - TimelineTapOffset) {
				// The user tapped just above the next timeline so round up to the next timeline
				newHour++;
			}
			eventTop = newHour * this.hourHeight;
			newMinute = 0;
			// Disable code to create events on the half hour... 
			/*
			if ((y % this.hourHeight) >= (this.hourHeight / 2)) {
				newMinute = 30;
				eventTop += this.hourHeight / 2;
			}
			*/
		}
		
	  	if (!this.scratchEvent) {
			this.preCheckBeforeScratchEvent(newHour, newMinute, eventTop);
 	 	} else {
 		   	if (this.scratchEvent) {
				if (this.scratchEvent.saved) {
					this.resetScratchEvent();
				}
			}
	  	}
		
		Event.stop(event);
	},
	
	handleAllDayTap: function(index, event) {
		this.deletedeventId = undefined;
		if (this.orangeKeyDown == true) {
			// Confirm with the user they want to delete the event
			this.eventToBeDeleted = event;
			this.allDayEventToBeDeletedIndex = index;
			this.deletedeventId = event.id;
			// Reset the orangeKeyDown to handle the case the user lets go
			// of the Orange key when the Delete confirmation dialog is up
			this.orangeKeyDown = false;
			getCalendarService().getEvent(event.id, this.gotEventInlineDelete.bind(this),this.gotEventInlineDeleteFailed.bind(this), this.controller);
		} else {
			// Show the Event Details for the tapped all day event
			this.controller.stageController.pushScene('edit', 
												  event.id, 
												  getCurrentDateTime(), 
												  true, 
												  event.calendarId, 
												  event.subject, 
												  false, 
												  this.updatingEventHandler);
		}
	},
	
	// Handles taps for events
	gotEventInlineDelete: function(response) {
		//Mojo.Log.info("gotEventInlineDelete");
		
		var targetEvent = response;
		// If the user is trying to delete a new event (cancelling the new event) 
		// [new event's parentId equals 0]
		// or
		// if the event is NOT a recurring event, show the standard delete warning
		if ((!targetEvent.rrule || targetEvent.rrule == "") && targetEvent.parentId <=0){
			this.controller.showDialog({
				template: 'shared/confirm-deleteevent',
				assistant: new DeleteEventConfirmDialogAssistant(this, targetEvent, targetEvent.startTimestamp),
				preventCancel: true
			});
		} else {
			// If the event is a recurring event, show the warning allowing users to
			// delete the series, the single event, or cancel
			var eventToBeDeletedStartime = parseFromId(this.deletedeventId);
			this.controller.showDialog({
				template: 'shared/confirm-deleterecurringevent',
				assistant: new DeleteEventConfirmDialogAssistant(this, targetEvent, eventToBeDeletedStartime.start),
				preventCancel: true
			});
		}
		// Re-render the event (un-tapped)
		if (this.eventToBeDeleted.allDay == true) {
			this.renderAllDayEvent(this.THIS_DAY, this.allDayEventToBeDeletedIndex, this.eventToBeDeleted, false);
		} else {
			this.renderEvent(this.THIS_DAY, this.eventToBeDeleted, false);
		}
		this.allDayEventToBeDeletedIndex = undefined;
		this.eventToBeDeleted = null;
	},
	
	gotEventInlineDeleteFailed: function(response) {
		//Mojo.Log.info("gotEventInlineDeleteFailed " + Object.toJSON(response));
		// Re-render the event (un-tapped)
		if (this.eventToBeDeleted.allDay == true) {
			this.renderAllDayEvent(this.THIS_DAY, this.allDayEventToBeDeletedIndex, this.eventToBeDeleted, false);
		} else {
			this.renderEvent(this.THIS_DAY, this.eventToBeDeleted, false);
		}
		this.allDayEventToBeDeletedIndex = undefined;
		this.eventToBeDeleted = null;
	},
	
	deletedEvent: function(response) {
		//Mojo.Log.info("deletedEvent " + Object.toJSON(response));
		// Remove the reminder for this event
		if (this.deletedeventId != undefined)
			getReminderManager().removeReminder(this.deletedeventId);
	},
	
	handleEventsTap: function(event, tapEvent) {
		//Mojo.Log.info("day-assistant: handleEventsTap event_being_held " + Object.toJSON(this.event_being_held));
		
		Event.stop(tapEvent);
		this.deletedeventId = undefined;
		if (this.event_being_held) {
			// NOTE: This code used to be in handleEventsHoldEnd, but we now check for
			// tap and hold event here.
			if (this.event_moved == false) {
				// If there is no event being dragged, then the user just wants to
				// edit the event inline
		  	var cachedEvent = this.getEventFromCache(this.event_being_held.id, false, getCurrentDateTime());
		  	
		  	if (cachedEvent == null) {
		  		// The event is no longer in the cache, so retrieve the event from the calendar service so we can set the new start time
					getCalendarService().getEvent(this.event_being_held.id, this.gotEventForUpdateSubject.bind(this), this.gotEventForUpdateSubjectFailed.bind(this), this.controller);
				} else {
					this.createEditScratchEvent(cachedEvent);
					this.insertEditScratchEvent();
					var evt = this.event_being_held;
					this.event_being_held = null;
					// Render the event without the "dragging" styling
					this.renderEvent(this.THIS_DAY, evt, false);
				}
			}
		} else {
			if (this.orangeKeyDown == true) {
				// Confirm with the user they want to delete the event
				//Mojo.Log.info("handleEventsTap: Orange key + tap = Delete event " + Object.toJSON(event));
				// Retrieve the event from the calendar service so we can figure
				// out whether it's recurring or not
				this.eventToBeDeleted = event;
				this.deletedeventId = event.id;
				// Reset the orangeKeyDown to handle the case the user lets go
				// of the Orange key when the Delete confirmation dialog is up
				this.orangeKeyDown = false;
				getCalendarService().getEvent(event.id, this.gotEventInlineDelete.bind(this),this.gotEventInlineDeleteFailed.bind(this), this.controller);
			} else {
				this.controller.stageController.pushScene('edit', 
													  event.id, 
													  new Date(event.startTimestamp), 
													  false, 
													  event.calendarId, 
													  event.subject, 
													  false, 
													  this.updatingEventHandler);
			}
		}
	},
	
	resetEventBeingDragged: function() {
		//Mojo.Log.info("resetEventBeingDragged");
		if (this.event_being_dragged) {
			//Mojo.Log.info("Clearing dvScratchEventsDiv");
			// Visually remove the treatment of "dragging" the item
			this.dvScratchEventsDiv.update();
			this.dvScratchEventsDiv.hide();
			
			// "Forget" the item being dragged, we're not dragging anymore
			this.event_being_dragged = null;
			
			var evt = this.event_being_held;
			this.event_being_held = null;
			// Re-render the event in its non-selected state
			this.renderEvent(this.THIS_DAY, evt, false);
		}
	},
	
	handleEventsHold: function(event) {
		//Mojo.Log.info("day-assistant: handleEventsHold %s", Object.toJSON(event));
		
		if (event.readonly == false) {
			// Keep track of the event being held. only if it is an editable event
			this.event_being_held = event;
			this.event_moved = false;
			
			this.renderEvent(this.THIS_DAY, event, false /*selected*/);
			
			// Toggle compression
			if (this.compressed) {
				this.tempUncompressed = true;
				this.toggleCompression(true, this.event_being_held.top);
			}
		}
	},
	
	handleEditScratchEventCommit: function(newValue) {
		//Mojo.Log.info("day-assistant: handleEditScratchEventCommit: ");
		if (this.scratchEvent && this.scratchEvent.saved == false  && !this.scratchEvent.saving) {
			if (newValue == null || newValue == "") 
				this.scratchEvent.subject = $L("Event name");
			else
				this.scratchEvent.subject = newValue;
			this.saveScratchEvent();
		}
	},
	
	insertEditScratchEvent: function() {
		// Add the scratch event to view
		var editEventHtml = Mojo.View.render({object: this.scratchEvent, template: 'day/day-scratchevent'});
		
		this.dvScratchEventsDiv.insert(editEventHtml);
		this.dvScratchEventsDiv.show();
		
		var tfAttrs = {
			hintText : $L('Event name'),
			multi: 'false',
			modelProperty: 'subject',
			focus: true,
			requiresEnterKey: true
		};
		
		// Setup the TruncTextField widget
		var scratchEventDiv = this.controller.get('scratch_event');
    	this.controller.setupWidget('scratch_event_field', tfAttrs, this.scratchEvent);
		this.controller.instantiateChildWidgets(scratchEventDiv);
		this.scratchEventField = this.controller.get('scratch_event_field');
		this.scratchEventCommitHandler = this.handleEditScratchEventCommit.bind(this);
		this.scratchEventKeyEventHandler = this.handleScratchEventKey.bind(this);
		this.scratchEventTapHandler = this.handleScratchEventTap.bind(this)
		this.scratchEventHoldHandler = this.handleScratchEventHold.bind(this);
		
		// Listen for the key and tap events on the whole scene when a scratch event is present
		Mojo.Event.listen(scratchEventDiv, "keyup", this.scratchEventKeyEventHandler);
		Mojo.Event.listen(this.controller.sceneElement, Mojo.Event.tap, this.scratchEventTapHandler);
		Mojo.Event.listen(this.controller.sceneElement, Mojo.Event.hold, this.scratchEventHoldHandler);
	},
	
	gotEventForUpdateSubject: function(response) {
		//Mojo.Log.info("day-assistant: gotEventForUpdateSubject");
		if (response) {
			//in case we dont have the event in the cache and end up querying using getEvent
			//we won't have the start decimal to draw the scratch event in the right position
			//so add it to the response
			response.start_decimal=this.getEventFromCache(this.event_being_held.id, false /*isAllDay*/, new Date(this.event_being_held.startTimestamp)).start_decimal;
			this.createEditScratchEvent(response);
			this.insertEditScratchEvent();
			this.event_being_held = null;
		}
	},
	
	gotEventForUpdateSubjectFailed: function(response) {
		//Mojo.Log.info("day-assistant: gotEventForUpdateSubjectFailed");
		this.event_being_held = null;
	},
	
	createEditScratchEvent: function(origEvent) {
		this.scratchEventActive = true;
		//this.pendingOpenScratchEventInDetails = false;
		
		// Create a scratch event to represent the event
		this.scratchEvent = Object.clone(origEvent);
		this.scratchEvent.origSubject = Object.clone(this.scratchEvent.subject);
		
		// Calculate the dimensions of the scratch event
		//var eventsWidth = this.divs[this.THIS_DAY].events.getWidth();
		//var eventsParentWidth = this.divs[this.THIS_DAY].events.parentNode.getWidth();
		var eventsWidth = 292;
		var eventsParentWidth = 320;
		var eventsOffsetX = eventsParentWidth - eventsWidth;

		var eventDim = this.getEventDimensions(this.event_being_held);
		this.scratchEvent.top = eventDim.top+'px';
		this.scratchEvent.left = eventsOffsetX + 'px';
		this.scratchEvent.height = eventDim.height+'px';
		this.scratchEvent.width = eventsWidth + 'px';
		if(parseInt(this.scratchEvent.height) <= this.halfHourHeight)
			this.scratchEvent.halfHourClass = "half-hour-event";
		this.scratchEvent.saved = false;
		this.scratchEvent.edited = false;
	},
	
	handleEventsHoldEnd: function(event) {
		//Mojo.Log.info('day-assistant: handleEventsHoldEnd');
		
		Event.stop(event);
		// If there is no event being dragged
		if (this.event_moved == false) {
			// Try to retrieve the event from the cache
			var cachedEvent = this.getEventFromCache(this.event_being_held.id, false /*isAllDay*/, new Date(this.event_being_held.startTimestamp));
			
			this.renderEvent(this.THIS_DAY, this.event_being_held, false);
			
			if (cachedEvent == null) {
	  			// The event is no longer in the cache, so retrieve the event from the calendar service so we can set the new start time
				getCalendarService().getEvent(this.event_being_held.id, 
											  this.gotEventForUpdateSubject.bind(this), 
											  this.gotEventForUpdateSubjectFailed.bind(this),
											  this.controller);
			} else {
				this.createEditScratchEvent(cachedEvent);
				this.insertEditScratchEvent();
			  this.event_being_held = null;
			}
		}
	},
	
	gotEventFailed:function(){
		this.controller.showDialog({
				template: 'shared/invalidEvent_warning',
				assistant: new InvalidEventWarning(this),
				preventCancel: true
			});
	},
	
	deletedEventHandle: function(){
		//update all again.
		this.updateAll();
		
	},
	
	createDraggingEvent: function(origEvent) {
		// Create a scratch event to represent the event
		this.draggingEvent = Object.clone(origEvent);
		
		// Calculate the dimensions of the scratch event
		var eventDim = this.getEventDimensions(origEvent);
		this.draggingEvent.top = eventDim.top+'px';
		this.draggingEvent.left = eventDim.left + 'px';
		this.draggingEvent.height = eventDim.height+'px';
		this.draggingEvent.width = eventDim.width + 'px';
		var draggingEventHtml = Mojo.View.render({object: this.draggingEvent, template: 'day/day-draggingevent'});
		
		this.dvScratchEventsDiv.insert(draggingEventHtml);
		this.dvScratchEventsDiv.show();
	},
	
	handleEventsDragStart: function(event) {
		//Mojo.Log.info("day-assistant: handleEventsDragStart " + event.down.y);
			
		// Figure out is being dragged...  If no event was hit, we should just
		// ignore the drag event
		if (this.event_being_held) {
			this.event_moved = true;
			// Render the ghost of the event
			this.renderEvent(this.THIS_DAY, this.event_being_held, false, true /*ghost*/);
			// Create a scratch event to represent the event being dragged
			this.createDraggingEvent(this.event_being_held);
			this.event_being_dragged = this.controller.get("dragging_event");
			// Keep track of the initial hit point
			this.dragLastPointY = event.down.y;
			Event.stop(event);
			return Mojo.Gesture.CONSUMED_EVENT;
		}
	},
	
	// Move the element... NOTE: This assumes that the element is 
	// positioned absolutely
	moveElement: function(element, dy) {
		var newTop = parseInt(element.getStyle('top'), 10) + dy;
		//Mojo.Log.info("day-assistant: moveElement: %s %d",element.id,newTop)
		
		element.setStyle({top: newTop + 'px'});
		return newTop;
	},
	
	handleEventsDragging: function(event) {
		//Mojo.Log.info("day-assistant handleEventsDragging");
		if (this.event_being_dragged) {
			var target = this.event_being_dragged;
			
			var pointerY = event.move.y;
	    if (this.dragLastPointY != pointerY) {
				// Calculate the delta of the current point and the last point
		  	var elementPos = target.viewportOffset();
		  	var top = elementPos[1];
		  	var newOffsetY = pointerY - this.dragLastPointY;
				// Move the element based off of the delta
		  	this.moveElement(target, newOffsetY);
				this.dragLastPointY = pointerY;
			}
		
			return Mojo.Gesture.CONSUMED_EVENT;
		}
	},

	// Callback which gets called when the event is successfully retrieved from
	// the database
	gotEventForUpdateTime: function(response) {
		//Mojo.Log.info("day-assistant:gotEvent:");
		
		// Use the response as the event.
		var targetEvent = response;
		if (targetEvent.rrule == '' || !targetEvent.rrule) {
			// Reset the dragging state
			this.resetEventBeingDragged();
			// Calculate the duration of the event
			var duration = targetEvent.endTimestamp - targetEvent.startTimestamp;
			var startTime = new Date(targetEvent.startTimestamp);
			if (this.newHour == undefined && this.newMinutes == undefined) {
				// Calculate the new start time using the delta
				startTime.addMinutes(this.newTimeDeltaInMinutes);
			} else {
				// Set the new startTimestamp 
				startTime.set({
					hour: this.newHour,
					minute: this.newMinutes
				});
				
			}
		
			targetEvent.startTimestamp = startTime.getTime();
			targetEvent.endTimestamp = targetEvent.startTimestamp + duration;
			getCalendarService().setEvent(targetEvent, this.updatedEventTime.bind(this), this.controller);
		}
		else
		{
			this.doRecurrenceDialog(targetEvent);
		}
	},
	
	doRecurrenceDialog:function(targetEvent){
		this.toBeSavedEvent = targetEvent;
		startdate= new Date(targetEvent.startTimestamp);
		start = Mojo.Format.formatDate(startdate, {date: "long"});
		this.controller.showDialog({
			template: 'shared/recurrence-warning',
			assistant: new RecurrenceWarning(this,this.toBeSavedEvent.id),
			start:start
			
		});
	},
	
	saveEvent:function(parentid){
		if(parentid)
		{
			var currentRecurrenceInstanceTimes = parseFromId(this.event_being_held.id);	
			//Mojo.Log.info("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% parsed id is: %j",currentRecurrenceInstanceTimes);
			this.toBeSavedEvent.parentId =parentid;
			this.toBeSavedEvent.id=-1;
			this.toBeSavedEvent.rrule='';
			this.toBeSavedEvent.exdates= '';
			this.toBeSavedEvent.rruleModel = null;
			this.toBeSavedEvent.originalstartTimestamp =currentRecurrenceInstanceTimes.start;
			this.toBeSavedEvent.startTimestamp = currentRecurrenceInstanceTimes.start;
			this.toBeSavedEvent.endTimestamp = currentRecurrenceInstanceTimes.end;
			this.toBeSavedEvent.endValidity=0;
				
		} else if(parentid <=0 && this.toBeSavedEvent.isRecurringForever==true)
			//we need to do this because if the end validity returned from the 
			//service is a long value, it seems to get truncated when retuned to 
			//JS layer and hence while setting it back 
			this.toBeSavedEvent.endValidity = "Long.MAX_VALUE"; 		
		
		// Reset the dragging state
		this.resetEventBeingDragged();
		
		//we also need to assign the start and end time stamp 
		var startTime = new Date(this.toBeSavedEvent.startTimestamp);
		var duration = this.toBeSavedEvent.endTimestamp - this.toBeSavedEvent.startTimestamp;
		
		if (this.newHour == undefined && this.newMinutes == undefined) {
			// Calculate the new start time using the delta
			startTime.addMinutes(this.newTimeDeltaInMinutes);
		} else {
			// Set the new startTimestamp 
			startTime.set({
				hour: this.newHour,
				minute: this.newMinutes
			});
			
		}
		
		this.toBeSavedEvent.startTimestamp = startTime.getTime();
		this.toBeSavedEvent.endTimestamp = this.toBeSavedEvent.startTimestamp + duration;
		
		
		if(this.toBeSavedEvent.exdates)
			getRecurrenceRule().addExdateToRRule(this.toBeSavedEvent);
		getCalendarService().setEvent(this.toBeSavedEvent, this.updatedEventTime.bind(this), this.controller);

	},
	
	recurrenceCancel:function(){
		//Mojo.Log.info('inside day- recurrene canceled %o',this.draggedEventTop);
		this.event_being_held.top = this.event_being_held.origDragTop;
		
		// Reset the dragging state
		this.resetEventBeingDragged();
		
		// Re-render the day
		var day = this.getFromCache(getCurrentDateTime().set({hour: 0, minute: 0, second: 0, millisecond: 0}).getTime());
		this.renderDay(this.THIS_DAY, day, false);
		if (this.toBeSavedEvent) {
			this.toBeSavedEvent = undefined;
			this.draggedEventTop=undefined;
		}
	},
	
	// Callback which gets called when the event is successfully updated with
	// its new start time
	updatedEventTime: function(response) {
		//Mojo.Log.info("day-assistant: updatedEventTime");
		this.draggedEventId = response.id;
		if (this.toBeSavedEvent)
			this.toBeSavedEvent = undefined;
			
		// Need to update the tap handlers since an event moved so the rect
		// associated with the event is now different
		this.updateTapHandlers();
	},
	
	// From the vertical coordinate of a point, calculate within half hour granularity,
	// the nearest time span
	//we calculate this per our new rule where if it is four pixels closer to the next half hour,
	// we drag and drop in the next closest halfhour. Else in the previous half hour window
	ptToHalfHour: function(y) {
		var halfHourUp = Math.floor(y / this.halfHourHeight);	
		if ((y >= 0 && y % this.halfHourHeight <= this.halfHourHeight - TimelineTapOffset) || 
			(y < 0 && Math.abs(y % this.halfHourHeight) > TimelineTapOffset))
		{
			return halfHourUp;
		} else {
			return halfHourUp + 1;
		}	
	},
	
	handleEventsDragEnd: function(event) {
		//Mojo.Log.info("day-assistant handleEventsDragEnd");
		
		if (this.event_being_dragged) {
			var itemTop = this.event_being_dragged.positionedOffset().top;
			
			// Calculate how much "time" the user has moved the event.  When the event is retrieved
			// we use this delta to calculate the new start time.  Can not calculate the new start
			// time here, because for events that overlap days, this.dragStartHour may not reflect
			// the actual start time of the event
			this.newTimeDeltaInMinutes = this.ptToHalfHour(itemTop-this.event_being_held.top) * 30;
			
			// Set these to undefined since we use newTimeDeltaInMinutes to determing the new start time
			// in gotEventForUpdateTime
			this.newHour = undefined;
			this.newMinutes = undefined;
			
			// Show the event in its new position.  At this point, the CalendarEvent in the
			// database hasn't been updated with the new time, but we can move the event
			// visually first
			this.event_being_held.origDragTop = this.event_being_held.top;
			
			this.event_being_held.top = itemTop;
			this.renderEvent(this.THIS_DAY, this.event_being_held, false);

			// Retrieve the event from the calendar service so we can set the new start time
			getCalendarService().getEvent(this.event_being_held.id, this.gotEventForUpdateTime.bind(this),this.gotEventFailed.bind(this), this.controller);
			
			return Mojo.Gesture.CONSUMED_EVENT;
		}
	},
	
	
	// ** MENU **
  handleWeekView: function(e) {
  	//Mojo.Log.info("go to week view");
		var sController = this.controller.stageController;
		sController.popScene();
		sController.pushScene({name: "week", transition: Mojo.Transition.crossFade, disableSceneScroller: true});
  },
  
  handleMonthView: function(e) {
  	//Mojo.Log.info("go to month view");
		var sController = this.controller.stageController;
		sController.popScene();
		sController.pushScene({name: 'month', transition: Mojo.Transition.crossFade, disableSceneScroller: true});
  },
  
	// ** CACHE **
	
	getFromCache: function(dayDate) {
		//Mojo.Log.info("getFromCache key: %s ",this.keyFromDate(dayDate));
		return DayAssistant.dayCache.get(this.keyFromDate(dayDate));
	},	
	
 	addToCache: function(dayDate, events) {
		var key = this.keyFromDate(dayDate);
		DayAssistant.dayCache.set(key, events);
		DayAssistant.cacheSize = DayAssistant.dayCache.keys().length;
		while (DayAssistant.cacheSize > DayAssistant.MAX_CACHE_SIZE) {
			// Remove the day that is farthest away from the current day
			var farthestIndex = -1;
			var farthestDiff = 0;
			var keys = DayAssistant.dayCache.keys();
			var currentKey = this.keyFromDate(getCurrentDateTime());
			for (var i = 0; i < keys.length; i++) {
				var diff = Math.abs(currentKey - keys[i]);
				if (diff > farthestDiff) {
					farthestDiff = diff
					farthestIndex = i;
				}
			}
			if (farthestIndex >= 0) {
				DayAssistant.dayCache.unset(keys[farthestIndex]);
				DayAssistant.cacheSize--;
			}
		}
		
		/* Enable this code to debug what's in the cache */
		/*
		var dkeys = DayAssistant.dayCache.keys();
		Mojo.Log.info("CACHE START");
		for (var i = 0; i < dkeys.length; i++) {
			Mojo.Log.info("key:%s ", dkeys[i]);
		}
		
		*/
	},
	
	keyFromDate: function(dayDate) {
		var date = new Date(dayDate);
		return date.clearTime().getTime();
	},
	
	clearCache: function() {
		//Mojo.Log.info("day-assistant: clearCache");
		DayAssistant.dayCache = new Hash();
		DayAssistant.cacheSize = 0;
		
		// Cancel the getEvents subscriptions since we don't need them
		// any longer for the cache
		if (this.currentQuery) {
			this.currentQuery.cancel();
			this.currentQuery=null;
			this.pendingQuery=null;
		}
	},
 
	
	getEventFromCache: function(eventId, isAllDay, dayDate) {
		var cachedEvent = null;
		var dayData = this.getFromCache(dayDate).days[0];
		
		if (dayData) {
			if (isAllDay) {
				cachedEvent = dayData.allDayEvents.find(function(evt){
					return evt.id == eventId;
				});
			} else {
				cachedEvent = dayData.events.find(function(evt){
					return evt.id == eventId;
				});
			}
		}
		
		return cachedEvent;
	},
	
	
	// *** COMPRESSION ***
	
	getCompressionCenterScrollOffset: function(day, centerY) {
		var scrollOffset = 0;
		
		for (i = 0; i < day.freeTimes.length; i++) {
			var free = day.freeTimes[i];
			if (free.duration <= 150)
				continue;
				
			// Note the values passed to compressPos below are intentionally reversed.
			// This is done because we have already toggled the this.compressed value before calling this routine
			var top = this.compressPos(free.topCompress, free.top);
			var height = this.compressPos(this.hourHeight, free.height);
			
			if (centerY > (top + height)) {
				scrollOffset += free.height - this.hourHeight;
			} else if (centerY > top) {
				scrollOffset += (free.height - this.hourHeight) * 0.5;
			} else {
				break;
			}
		}
		
		return scrollOffset;
	},
	
	calculatePosToDrawAmPm:function(currentTop)
	{	
		if(this.animating)
			return;

		//find the nearest drawn timeline after this
		var data = this.getFromCache(this.days[this.THIS_DAY].defaultTime);
		var day;
		if(data)
			day= data.days[0];
		
		for(i=0;i<24;i++)
		{
			currTimeline =(this.compressed && day)?day.timelines[i].topCompress:(this.hourHeight*i);
		    yPixel = currTimeline+11;
			if(currentTop<=yPixel)
			{
				yPixel+=7;
				if(i>11)
					text = $L('pm');
				else
					text = $L('am');
				var indicator =	this.controller.get('ampm_indicator');
				indicator.update(text);	
				if (i >= this.startHour && i < this.endHour) 
						color = "rgba(85, 85, 85,1)";
				else 
						color = "rgba(119, 119, 119, 1)";

				
				indicator.setStyle({top: yPixel+'px', color:color}); //show();
				break;
			}
		}
	},
	

	toggleCompression: function (animate, centerY) {
		var day = this.getFromCache(getCurrentDateTime().set({hour: 0, minute: 0, second: 0, millisecond: 0}).getTime());
		//Mojo.Log.info("day-assistant: toggleCompression");
				
		if (!this.compressed) { // uncompressed to compressed
			this.tapHandler.deactivate();
			this.tapHandlerCompressed.activate();
		} else { // compressed to uncompressed
			this.tapHandlerCompressed.deactivate();
			this.tapHandler.activate();
			this.divs[this.THIS_DAY].eventsCanvas.setAttribute("height", "1200px");
			
			// Growing the canvas height seems to erase the whole thing, so we need to re-render the day
			// if we're animating, in order to ensure that the piece before the first free-time is drawn properly.
			// This piece is NOT redrawn during the animation.
			if(animate) {
				this.renderDay(this.THIS_DAY, day, false, true);
			}
		}
		
		this.compressed = !this.compressed;
		
		if (animate) {
			
			var dayData = day.days[0];
			var currentTop = this.tds[this.THIS_DAY].scroller.mojo.getScrollPosition().top;
			var scrollOffset = this.getCompressionCenterScrollOffset(dayData, centerY);
			var animationDuration = 0.4;
			if (dayData.maxFreeDuration < 300)
				animationDuration = 0.3
			this.animating = true;
			this.controller.get('ampm_indicator').hide();
			this.controller.get('current_time').hide();
			Mojo.Animation.animateValue(Mojo.Animation.queueForElement(this.scrollDataDiv), 'bezier', function(value) {
					this.animationValue = value;
					this.renderDay(this.THIS_DAY, day, false);
					if (this.compressed) {
						this.tds[this.THIS_DAY].scroller.mojo.scrollTo(undefined, Math.round(currentTop + scrollOffset * value));
					} else {
						this.tds[this.THIS_DAY].scroller.mojo.scrollTo(undefined, Math.round(currentTop - scrollOffset * value));
					}
				}.bind(this), 
				{
				from: 0.0,
				to: 1.0,
				duration: animationDuration,
				curve: 'ease-in-out',
				onComplete: function(){
					this.animating = false;
					this.updateFromCache(false);
				}.bind(this)
			});
		} else {
			this.updateFromCache(false);
		}
	},
	
	// *** ALERTS ***
	remindersUpdated: function() {
		if (this.reminders.getNumReminders() == 0) {
			this.appMenuModel.items = this.appMenuModel.items.findAll(function(menuItem){
				return (menuItem.id != this.reminderMenuItemId);
			}.bind(this));
			this.controller.modelChanged(this.appMenuModel);
		} else {
			var menuItemVisible = false;
			this.appMenuModel.items.each(function(menuItem){
				if (menuItem.id == this.reminderMenuItemId) {
					menuItemVisible = true;
				}
			}, this);
			if (!menuItemVisible) {
				this.appMenuModel.items.splice(5,0,{label:$L('Missed reminders...'), command:'reminders', id: this.reminderMenuItemId});
				this.controller.modelChanged(this.appMenuModel);
			}
		}
	},
	
	setEventUpdated:function(eventId){
		//get the event from cache
		//add the styling to it
		
		if (eventId) {
			this.eventUpdatingStateOn = true;
			this.eventUpdatingId =eventId;
			var thisDay =getCurrentDateTime();
			dayData = this.getFromCache(getCurrentDateTime().set({hour: 0, minute: 0, second: 0, millisecond: 0}));
			
			
			this.renderDay(this.THIS_DAY, dayData, false);
			
			
		}else{
			//need to look into the case of new events separately.
			//it does not work right now with edit details scene not popping
			
			/*this.scratchEventDiv =this.controller.get('scratch_event');
			if (this.scratchEventDiv != undefined) {
				if(this.scratchEvent)
					this.scratchEvent.saved =true;
				this.eventUpdatingStateOn =true;
				//set the style on this div
				
				this.scratchEventDiv.addClassName('event-updated');

				//remove the tap and key handlers on this div as now it should be
				//frozen due to update happening in the background
				
				if (this.scratchEventTapHandler) {
					Mojo.Event.stopListening(this.controller.sceneElement, Mojo.Event.tap, this.scratchEventTapHandler);
					this.scratchEventTapHandler = null;
				}
				if (this.scratchEventHoldHandler) {
					Mojo.Event.stopListening(this.controller.sceneElement, Mojo.Event.hold, this.scratchEventHoldHandler);
					this.scratchEventHoldHandler =null;
				}
				if (this.scratchEventKeyEventHandler) {
					Mojo.Event.stopListening(this.scratchEventDiv, "keyup", this.scratchEventKeyEventHandler);
					this.scratchEventKeyEventHandler =null;
				}				
				
			}*/
		}
		
	},
	
});

DayAssistant.MIN_EVENT_HEIGHT = 24;
DayAssistant.MAX_CACHE_SIZE = 15;		// in days
DayAssistant.cacheSize = 0;
DayAssistant.dayCache = new Hash();
