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

// ** APP ASSISTANT
var AppAssistant = Class.create({
	
	initialize: function(appController) {
		Mojo.Log.info("AppAssistant initialize");
		this.appController = appController;
		this.openReminderAlert = null;
		
		// CURRENT DATE & TIME - Keeps track of the current day/month/time in view
		this.currentDateTime = new Date();
		this.currentDateTimeObservers = new Hash();
		
		// DAY CHANGE - Observers interested in the system time changing from one
		// day to another
		this.dayChangeObservers = new Hash();
		
		// TIME FORMAT
		// We keep track of the views that want to be notified when the system time format
		// changes so that we don't have to make a system service call in each view that is 
		// interested in the time format.
		this.timeFormat == "HH12";
		this.timeFormatObservers = new Hash();
		this.timeChangeObservers = new Hash();
		
		// MANAGERS - Keeps track of information used across views
		this.prefsManager = new CalendarPrefsManager();
		this.calendarsManager = new CalendarsManager();
		this.reminderManager = new ReminderManager();
		
		
		// SERVICES
		this.calendarService = new CalendarMojoService;
		this.contactsService= new ContactsMojoService;
		this.pimSyncService= new PIMSyncMojoService;
		this.accountsService = new AccountsMojoService;
		this.systemService = new SystemMojoService;
		this.appManagerService = new AppManagerMojoService;
		this.formatterService = new Formatter;
		
		//get recurrense rule parser
		this.recurrenceRule = new RecurrenceRules();
		
		this.handlelaunchCount = 0;
		
		// COOKIES
		this.cookie = new Mojo.Model.Cookie("com.palm.app.calendar");
		
		if(Mojo.Host.current === Mojo.Host.mojoHost) {
			// Skip firstUse in mojohost
			this.cookie.put({firstUse: true});
		}
		
		if (this.cookie.get()){
			// the cookie already exists so we don't change the value of firstUse
		} else {
			// the cookie doesn't exist, default firstUse to true
			this.cookie.put({firstUse: true});
		}
	},
	
	setup: function() {
		Mojo.Log.info("AppAssistant setup");
		this.calendarsManager.setup();
		
		// Retrieve and subscribe to the timeFormat as saved in the system prefs
		this.timeFormatRequest = this.systemService.getTimeFormat(this.getTimeFormatCallback.bind(this));
		//retrieve the time change if it  happens
		this.timeChangeRequest = this.systemService.getSystemTime(this.getSystemTimeCallback.bind(this));
		
		this.updateCalendarAppIcon();
		this.setupDayChangeTimer();
	},
	
	handleLaunch: function(param) {
		// TEMP Ignore the first time we get called which is at boot time. We need this until framework
		// gives us a flag to let us now if this being called at boot.
		this.handlelaunchCount++;
		if (this.handlelaunchCount <= 1)
	  	 	return;	
			
		//if this failed during initialization, try again	
		if (!this.calendarsManager.accounts) {			
			this.calendarsManager.getCalendarsByAccount();
		}	
		
		//if this failed during initialization, try again
		if(!this.prefsManager.prefs){
			this.prefsManager.getCalendarPrefs();
		}
			
	  	var launchParams = {};
	  	if (PalmSystem.launchParams) {
			launchParams = PalmSystem.launchParams.evalJSON(true);
	 	 } else {
	    	Mojo.Log.info("Calendar App: No Launch Params");
	 	 }        
		
		if (launchParams.alarm) {
			Mojo.Log.info("$$$$$$$$ AppAssistant handleLaunch : alarm");
			this.doAlarm(false,false,	launchParams.alarm , 
										launchParams.attendees, 
										launchParams.isAllDay, 
										launchParams.subject, 
										launchParams.location, 
										launchParams.emailAccountId);
										
			this.scheduleNextReminder(launchParams.alarm);
		}
		else if (launchParams.alarmsnooze){
			Mojo.Log.info("$$$$$$$$ AppAssistant handleLaunch : alarmsnooze");
			this.doAlarm(true,launchParams.dontAlert,launchParams.alarmsnooze);
			
		} 
		else if (launchParams.alarmclose){
			Mojo.Log.info("$$$$$$$$ AppAssistant handleLaunch : alarmclose");
			this.closeReminder(launchParams.alarmclose);
		} 
		else if (launchParams.dayChange){
			this.dayChange();
		}
		else if (launchParams.launchType == "passwordInvalid") {
			this.showPasswordNotification(launchParams);
		}
		else {
			//we always want to launch today rather than the last viewed day
			//per bug NOV-29174
			goToToday();
			var calendarStageController = Mojo.Controller.getAppController().getStageController("calendar");
			if (!calendarStageController) {
				var stageCreateCallback = function(stageController){
					this.handleLaunchTypes(stageController, launchParams, false);
					// during fresh app launch no need to call stageController.activate() - fix NOV-877334 
					//this.focusCalendarStage();
				}.bind(this);
				var imageName;
				if (launchParams) {
				    if (launchParams.details) {
				        imageName = 'images/splash-calendar-edit.png';
				    }
				}
				this.openCalendarStage(stageCreateCallback, imageName);
			} else {
				this.handleLaunchTypes(calendarStageController, launchParams, true);
				this.focusCalendarStage();
			}
		}
	},
	
	launchDayView: function(stageController, date, appExists) {
		var sceneExists = false;
		if (appExists) {
			// If the top scene is not the Day View, try to pop to it.
			// If the top scene is already the Day View, there's no point
			// in popping the scene
			if (stageController.topScene().sceneName != "day") {
				stageController.popScenesTo("day");
			}
			
			// Check to see if the day scene is already up
			if (stageController.topScene().sceneName == "day") {
				sceneExists = true;
			}
			
			if (date) {
				// Check to see if we need to update the date that's being shown.
				// If it's the same no need to set it again which causes a redraw
				var curDateTime = getCurrentDateTime().clearTime();
				var dateWithNoTime = new Date(date).clearTime();
				if (!curDateTime.equals(dateWithNoTime)) {
					// The current date being shown is not the same as the one
					// we want so we set it.
					setCurrentDateTime(date);
				}
			}
		} else if (date) {
				setCurrentDateTime(date);
		}
		
		// Only push the Day View if it's not already visible
		if (!sceneExists) {
			stageController.pushScene({name: "day", transition: Mojo.Transition.crossFade, disableSceneScroller: true});
		}
	},
	
	handleLaunchTypes: function(stageController, launchParams, appExists){
		if (launchParams && launchParams.date) {
			this.launchDayView(stageController, new Date(parseInt(launchParams.date)), appExists);
			return;
		}
		if (launchParams && launchParams.reminders) {
			if (!appExists) {
				stageController.pushScene({name: "day", disableSceneScroller: true});
			}
			stageController.pushScene("reminder-list");
			return;
		}
		if (launchParams && launchParams.details) {
			if (!appExists) {
				stageController.pushScene({name: "day", disableSceneScroller: true});
			}
			// TODO need to pass event occurence date instead of today's date
			stageController.pushScene('edit', launchParams.details, new Date(), false);
			return;
		}
		if (launchParams && launchParams.newEvent) {
			if (!appExists) {
				stageController.pushScene({name: "day", disableSceneScroller: true});
			}
			
			this.createNewEventAndShowDetails(stageController, launchParams.newEvent);
		}
		
		if (appExists) {
			// If this.isFirstUse, do not launch into Day View.
			if (!this.isFirstUse)
				this.launchDayView(stageController, new Date(), appExists);
		} else {
			this.launchCalendarScene(stageController, this.isFirstUse());
		}
		if(launchParams && launchParams.launchType == "editPassword"){
			this.controller.closeStage("PasswordChangedNotification");
			stageController.pushScene("prefs");
			stageController.pushScene("accountlogin", launchParams.accountId, launchParams.login, null, null, "", null)
		}
	},
	
	showPasswordNotification:function(launchParams){
            var messageTemplate = new Template($L("Incorrect password for #{login}"));
            var bannerParams = {
                bannerText: messageTemplate.evaluate(launchParams)
            }
			var newLaunchParams = Object.extend({}, launchParams);
			newLaunchParams.launchType = "editPassword";
            this.controller.showBanner(messageTemplate.evaluate(launchParams), newLaunchParams, '');
            var passwordStageController = this.controller.getStageController("PasswordChangedNotification");
		    var f = function(iconClass, accountId, login, domain,stageController){
                stageController.pushScene('passwordchanged', {
					iconClass: iconClass,
					accountId: accountId,
					login:login,
					domain:domain
				});
            }.bind(this, launchParams.iconClass, launchParams.accountId, launchParams.login, launchParams.domain);
			if (passwordStageController) {
				f(passwordStageController);
			}
			else {
				var stageparams = {
					name: "PasswordChangedNotification",
					lightweight: true
				};
				this.controller.createStageWithCallback(stageparams, f, 'dashboard');
			}
            return;		
	},
	
	closeReminder: function(eventId) {
		Mojo.Log.info("$$$$$$$$ AppAssistant closeReminder:start");
		
		var rem =getReminderManager().getReminder(eventId);
		if (rem) {
			Mojo.Log.info("$$$$$$$$ AppAssistant closeReminder: removeReminder:"+eventId);
			getReminderManager().removeReminder(eventId);
		}
		Mojo.Log.info("$$$$$$$$ AppAssistant closeReminder:end");
	},
	
	scheduleNextReminder: function(eventId) {
		Mojo.Log.info("$$$$$$$$ AppAssistant scheduleNextReminder: for event:"+eventId);
		new Mojo.Service.Request('palm://com.palm.calendar', {
			method: 'scheduleNextReminder',
			parameters:  {id: eventId}
		});
	},
	
	startPowerActivity: function(activityName, serialNo, duration) {
		new Mojo.Service.Request('palm://com.palm.power/com/palm/power', {
					                    method: 'activityStart',
					                    parameters: {id: 'com.palm.app.calendar.' + activityName + '-' + serialNo, duration_ms: duration}
					                	});
	},
	
	savedNewEvent: function(response) {
		//Mojo.Log.info("savedNewEvent " + Object.toJSON(response));
		// The save succeeded so let's just launch the Event Details
		var calendarStageController = Mojo.Controller.getAppController().getStageController("calendar");
		calendarStageController.pushScene('edit', response.id.toString(), new Date(), false);
	},
	
	saveFailedNewEvent: function(response) {
		// If the new event failed, then put an empty new event scene
		//Mojo.Log.info("saveFailedNewEvent " + Object.toJSON(response));
		var calendarStageController = Mojo.Controller.getAppController().getStageController("calendar");
		calendarStageController.pushScene('edit', 0, 0, false);
	},
	
	createNewEventAndShowDetails: function(stageController, eventParams) {
		//Mojo.Log.info("createNewEventAndShowDetails");
		// Create the event, and only launch the Edit scene if the event was successfully created
		var newEvent = new CalendarEvent(eventParams);
		newEvent.useDefaultsOnParseFailures = true;
		//Mojo.Log.info("createNewEventAndShowDetails " + Object.toJSON(newEvent));
		getCalendarService().setEvent(newEvent, this.savedNewEvent.bind(this), undefined /*sceneController*/, this.saveFailedNewEvent.bind(this));
	},
	
	processCloseAlarmStage:function(newStageName)
	{
		Mojo.Log.info("$$$$$$$$ AppAssistant processCloseAlarmStage :start");
		var hasNewContent =false;
		
		var reminders =getReminderManager().getAllReminders();
		for(i=0;i<reminders.size();i++)
		{
			var stageName =this.createAlarmStageName(reminders[i].id);
		
			//If a reminder is displayed, and it is now the reminder's start time, we're trying to 
			//sound the reminder again, so it's possible this is the same reminder. If this is the case,
			//don't close it.
			if (stageName == newStageName) 
				continue;
				
			Mojo.Log.info("$$$$$$$$ AppAssistant processCloseAlarmStage:getStageController for stage:" + stageName);
			var alarmStageController = Mojo.Controller.getAppController().getStageController(stageName);
			if (!alarmStageController) {
				Mojo.Log.info("$$$$$$$$ AppAssistant processCloseAlarmStage:gettingStageProxy for stage:" + stageName);
				alarmStageController = Mojo.Controller.getAppController().getStageProxy(stageName);
				
			}
			else if (alarmStageController.hasNewContent()) {
				hasNewContent = true;	
			}
			
			if (alarmStageController) {
				Mojo.Log.info("$$$$$$$$ AppAssistant processCloseAlarmStage:delegateToSceneAssistant specialCleanup ");
				alarmStageController.delegateToSceneAssistant("specialCleanup");
			}
		}
		Mojo.Log.info("$$$$$$$$ AppAssistant processCloseAlarmStage :end");
		return hasNewContent;
	},
	
	createAlarmStageName:function(eventId)
	{
		return "calendar-alarm-"+eventId;
	},
	
	// *** ALARMS ***
	doAlarm: function(isSnoozedReminder,dontAlert,eventId, attendees, isAllDay, subject, location, emailAccountId) {	
		Mojo.Log.info("$$$$$$$$ AppAssistant doAlarm :start,"+dontAlert);
	
		//first check if is a snoozed reminder and if it exists in reminder manager
		//and if it does not then do nothing
		var rem =getReminderManager().getReminder(eventId);
		if (!rem && isSnoozedReminder)
			return; 
				
		this.startPowerActivity('reminderAlert', eventId, 2000);
		var stageName = this.createAlarmStageName(eventId);
		var hasNewContent =this.processCloseAlarmStage(stageName);
		
		Mojo.Log.info("$$$$$$$$ AppAssistant doAlarm:calling updateReminder:"+eventId);
		getReminderManager().updateReminder(eventId, subject, location, attendees, isAllDay, emailAccountId);
		
		var sound='calendar';
		var useSound=false;
		var prefs = this.prefsManager.getPrefs();
		if(dontAlert)
		{
			sound='none'
		}
		else if (prefs) {
			switch (prefs.alarmSoundOn) {
				case 0:
				   	sound='none';
				   	break;
				  
				case 2:
					sound = prefs.ringtonePath;
					useSound = true;
					break;
					
			   case 3:
				    sound = 'vibrate'; //do not localize
				    break;
						
			   case 1:
			   default:
					sound = 'calendar';
					break;	
			}
		}			
			
		var pushReminderScene = function(eventId,needThrob,stageController) {
			stageController.pushScene('reminder',eventId,needThrob);
 		}
		var height;
		var reminder = getReminderManager().getReminder(eventId);
		reminder.userClosed = false;
		if (reminder.attendees.length > 1 /*now we include the organizer in attendee list*/)
			height = 203;
		else
		  	height = 149;
		
		var needThrobbing =(hasNewContent || !isSnoozedReminder) ? true:false;
		Mojo.Log.info("$$$$$$$$ AppAssistant doAlarm:createStageWithCallback for stage: "+this.createAlarmStageName(eventId)+", stage height is: "+height);
				
		//See if the stage already exists, if so, just sound again. Otherwise, push the scene					
		var reminderStageController = Mojo.Controller.getAppController().getStageController(stageName);
		var soundfile = (useSound ? sound: null);
		var soundclass = (useSound ? null : sound);
		if (reminderStageController) {
			Mojo.Log.info("$$$$$$$$ AppAssistant doAlarm: stage " + stageName + " already exists: class:" + soundclass + " file: "+soundfile);
			//TODO: MAKE SURE BOTH VERSIONS WORK
			Mojo.Controller.getAppController().playSoundNotification(soundclass, soundfile, null, true);
		}
		else {
			if (useSound) {
				Mojo.Controller.getAppController().createStageWithCallback({
					name: this.createAlarmStageName(eventId),
					lightweight: true,
					applicationStylesheets: ["stylesheets/notification.css"],
					height: height,
					sound: sound,
				}, pushReminderScene.bind(this, eventId, needThrobbing), 'popupalert');
			}
			else {
				Mojo.Controller.getAppController().createStageWithCallback({
					name: this.createAlarmStageName(eventId),
					lightweight: true,
					applicationStylesheets: ["stylesheets/notification.css"],
					height: height,
					soundclass: sound,
				}, pushReminderScene.bind(this, eventId, needThrobbing), 'popupalert');
			}
		}
		
		Mojo.Log.info("$$$$$$$$ AppAssistant doAlarm:calling getStageController for calendar-dashboard");
		var dashboardStageController = Mojo.Controller.getAppController().getStageController("calendar-dashboard");
		if (!dashboardStageController) {
			Mojo.Log.info("$$$$$$$$ AppAssistant doAlarm:calling getStageProxy for calendar-dashboard");
			dashboardStageController = Mojo.Controller.getAppController().getStageProxy("calendar-dashboard");
		}
		if (!dashboardStageController) {
			var pushDashboardScene = function(stageController){
				stageController.pushScene('dashboard');
			}
			Mojo.Log.info("$$$$$$$$ AppAssistant doAlarm:calling createStageWithCallback for calendar-dashboard");
			Mojo.Controller.getAppController().createStageWithCallback({
				name: "calendar-dashboard",
				lightweight: true,
				applicationStylesheets: ["stylesheets/notification.css"],
			}, pushDashboardScene, "dashboard");
		}
		Mojo.Log.info("$$$$$$$$ AppAssistant doAlarm :end");
	},
	
	openCalendarStage: function (f, imageName) {
		Mojo.Controller.getAppController().createStageWithCallback({name: "calendar", lightweight: Mojo.Controller.appInfo.lwStages, splashBackgroundName: imageName}, f);
	},
	
	focusCalendarStage: function() {
		var stageController = Mojo.Controller.appController.getStageController("calendar");
		if (stageController) {
			stageController.activate();
		}
	},
	
	 // *** FIRST USE ***
	isFirstUse: function() {
		if (this.cookie) {
			var cookie = this.cookie.get();
			if (!cookie || cookie.firstUse) {
				
				return true;
			} else {
				
				return false;
			}
		}
	},
	
	launchCalendarScene: function(stageController, showFirstUse) {
		// Clear the view stack (just in case the starting scene is up)
		stageController.popScenesTo();
		if (showFirstUse && Mojo.Host.current !== Mojo.Host.mojoHost) {
			// If this is the first use of Calendar, show the first use scene
			this.firstUseManager = new FirstUseManager(stageController);
		} else {
			// Otherwise show the Day View
			stageController.pushScene({name: "day", transition: Mojo.Transition.crossFade, disableSceneScroller: true});
		}
	},
	
	// *** TIME FORMAT ***
	getTimeFormatCallback: function(response) {
		//Mojo.Log.info("app-assistant: getTimeFormatCallback");
		if (response && response.timeFormat) {
			this.timeFormat = response.timeFormat;
			notifyTimeFormatObservers()
		}
	},
	
	getSystemTimeCallback:function(response){
		notifyTimeChangeObservers();
	},	
	
	// *** DAY CHANGE ***
	
	iconSuccessCallback: function(response){
		Mojo.Log.info("icon change succeeded: "+Object.toJSON(response));
	},
	
	iconFailureCallback: function(response){
		console.error("icon change failed: "+Object.toJSON(response));
	},
	
	updateCalendarAppIcon: function() {
		//Mojo.Log.info("******** app-assistant: updateCalendarAppIcon")
		// Get today's date
		var today = new Date();
		var iconUrl = Mojo.appPath + 'images/launcher/icon-' + today.getDate() + '.png';
		//Mojo.Log.info("TRYING TO SET CALENDAR ICON TO " + iconUrl);
		Mojo.Log.info("updating icon to "+iconUrl);
		
		if(Mojo.Host.current === Mojo.Host.mojoHost) {
			return;
		}
		
		this.updateIconRequest = new Mojo.Service.Request('palm://com.palm.applicationManager', {
    		method: 'updateLaunchPointIcon',
    		parameters: { launchPointId: 'com.palm.app.calendar_default', icon: iconUrl},
			onSuccess: this.iconSuccessCallback,
			onFailure: this.iconFailureCallback,
			
		});
	},
	
	dayChangeSuccessCallback: function(response){
		Mojo.Log.info("dayChangeRequest succeeded: "+Object.toJSON(response));
	},
	
	dayChangeFailureCallback: function(response){
		console.error("dayChangeRequest failed: "+Object.toJSON(response));
	},
	
	setupDayChangeTimer: function() {
		// When day changes at midnight make sure to update all views interested
		// in updating the day
		var tomorrow = new Date().addDays(1).clearTime();
		
		if(Mojo.Host.current === Mojo.Host.mojoHost) {
			return;
		}
		Mojo.Log.info("setting up DayChange Timer: "+tomorrow.toUTCString());
		
		this.dayChangeRequest = new Mojo.Service.Request('palm://com.palm.taskScheduler', {
					                    method: 'updateTask',
					                    parameters: {uri: 'palm://com.palm.applicationManager/open',
													 arguments: {'id': 'com.palm.app.calendar',
													 			 'params': {'dayChange': true}},
													 key: 'calendar-dayChange',
													 start: {date: tomorrow.toUTCString()},
													 repeatInterval: 86400000,	// 1 day
													 prereq: ['MSM_OFF'],
													 misfirePolicy: {executeNow: true}},
										onSuccess: this.dayChangeSuccessCallback,
										onFailure: this.dayChangeFailureCallback			 
					                	});
	},
	
	dayChange: function() {
		// Update the calendar launch icon
		this.updateCalendarAppIcon();
		
		// Notify any observers
		notifyDayChangeObservers();
	},
	
	cleanup: function() {
		//Mojo.Log.info("AppAssistant cleanup");
		this.calendarsManager.cleanup();
		this.prefsManager.cleanup();
		if (this.firstUseManager)
			this.firstUseManager.cleanup();
		this.reminderManager.cleanup();
		
		if (this.timeFormatRequest)
			this.timeFormatRequest.cancel();
		if(this.timeChangeRequest)
			this.timeChangeRequest.cancel();
	}
	
});

// ** STAGE ASSISTANT
function StageAssistant(stageController) {
}


AppAssistant.long_max_value ="Long.MAX_VALUE"; //this is the end validity for forever recurring events

// ** CURRENT DATE & TIME
function observeCurrentDateTime(sceneName, controller) {
	getAppAssistant().currentDateTimeObservers.set(sceneName, controller);
}

function stopObservingCurrentDateTime(sceneName) {
	getAppAssistant().currentDateTimeObservers.unset(sceneName);
}

function notifyCurrentDateTimeObservers(scrollToDefault) {
	getAppAssistant().currentDateTimeObservers.each(function(scenePair) {
		var controller = scenePair.value;
		
		controller.currentDateTimeUpdated(scrollToDefault);
	});
}

function getCurrentDateTime() {
	var dateTimeStr = getAppAssistant().currentDateTime;
	//var date = Mojo.Format.formatDate(dateTimeStr, "MMMM dd, yyyy hh:mm");
	//Mojo.Log.info("getCurrentDateTime: %s" , date);
	return new Date(dateTimeStr);		
}

// Sets the current date to the specified newDate... leaves the current time along
function setCurrentDate(newDate) {
	
	//Mojo.Log.info("setCurrentDate: %s", Mojo.Format.formatDate(newDate, "MMMM dd, yyyy hh:mm"));
	if (newDate instanceof Date) {
		// Only update the date if its different... that way we don't notify observers when the date
		// really hasn't changed
		var appAssistant = getAppAssistant();
		var currentDate = appAssistant.currentDateTime;
		if (currentDate.getFullYear() != newDate.getFullYear() ||
				currentDate.getMonth() != newDate.getMonth() ||
				currentDate.getDate() != newDate.getDate()) 
		{
			// Fix for JIRA NOV-81364 & NOV-81065
			// old code set the new date components separately via setFullYear + setDate + setMonth	(see bug for more details)
			appAssistant.currentDateTime = new Date(newDate.getFullYear(), newDate.getMonth(), newDate.getDate(), currentDate.getHours(), currentDate.getMinutes(), currentDate.getSeconds());			
			notifyCurrentDateTimeObservers();
		}
	}
}

function setCurrentDateTime(dateTime, dontNotify, scrollToDefault) {
	//Mojo.Log.info("setCurrentDateTime: %s", Mojo.Format.formatDate(dateTime, "MMMM dd, yyyy hh:mm"));
	
	// Only set the current date and time if we know that the dateTime parameter
	// is of the Date class
	if (dateTime instanceof Date) {
		var appAssistant = getAppAssistant();
		if (dateTime.getTime() != appAssistant.currentDateTime.getTime()) {
	  		appAssistant.currentDateTime = new Date(dateTime);
			// Notify all subscribers of the current date and time that the date/time has changed
			if (!dontNotify)
				notifyCurrentDateTimeObservers(scrollToDefault);
		}
  }
}

function goToToday() {
	//Mojo.Log.info("goToToday");
	setCurrentDate(new Date());
}

function observeDayChange(sceneName, controller) {
	getAppAssistant().dayChangeObservers.set(sceneName, controller);
}

function stopObservingDayChange(sceneName) {
	getAppAssistant().dayChangeObservers.unset(sceneName);
}

function notifyDayChangeObservers() {
	getAppAssistant().dayChangeObservers.each(function(scenePair) {
		var controller = scenePair.value;
		
		controller.dayChanged();
	});
}

// ** DATE/TIME UTILITY FUNCTIONS
var UNITS_YEARS = "0";
var UNITS_MONTHS = "1";
var UNITS_WEEKS = "2";
var UNITS_DAYS = "3";
var UNITS_HOURS = "4";
var UNITS_MINUTES = "5";
var UNITS_SECONDS = "6";

function getPeriodValue(text) {
	if (text == "none") 
		return undefined;

	for (i = 0; i < text.length; i++) {
		ch = text.charAt(i);
		if (ch >= '0' && ch <= '9')
			break;
	}
	startIndex = i;
	
	for (i = startIndex; i < text.length; i++) {
		ch = text.charAt(i);
		if (ch < '0' || ch > '9')
			break;
	}
	endIndex = i;
	
	return text.substring(startIndex, endIndex);
}

function getPeriodUnits(text) {
	isTime = false;
	text = text.toUpperCase();
	if (text.substring(0, 2) == "PT")
		isTime = true;
	else if (text.substring(0, 1) == "P")
		isTime = false;
	else
		return undefined;
			
	switch (text.charAt(text.length - 1)) {
		case 'Y':	units = isTime ? undefined : UNITS_YEARS;		break;
		case 'M':	units = isTime ? UNITS_MINUTES : UNITS_MONTHS;	break;
		case 'D':	units = isTime ? undefined : UNITS_DAYS;		break;
		case 'H':	units = isTime ? UNITS_HOURS : undefined;		break;
		case 'S':	units = isTime ? UNITS_SECONDS : undefined;		break;
	}
	
	return units;
}

function numToSuffix(num) {
	var resultStr = num;
	if (num == "11" || num == "12" || num == "13") {
		resultStr += $L("th");
	}
	else {
		ch = num.charAt(num.length - 1);
		
		if (ch == '1') 
			resultStr += $L("st");
		else if (ch == '2') 
			resultStr += $L("nd");
		else if (ch == '3')
			resultStr += $L("rd");
		else 
			resultStr += $L("th");
	}
	
	return resultStr;
}

function getDOWCount(value) {
	var date = new Date(value);
	var day;
	/*if it is the very first day, subtracting one day will cause it to go to previous month*/
	day =(date.getDate() ==1)? 1:date.addDays(-1).getDate();		
	var num = parseInt(day / 7) + 1;
	return num;
}

function getAppAssistant() {
	return Mojo.Controller.getAppController().assistant;
}

// ** SERVICES
function getCalendarService() {
	return getAppAssistant().calendarService;
}
function getContactsService() {
	return getAppAssistant().contactsService;
}
function getPimSyncService() {
	return getAppAssistant().pimSyncService;
}
function getAccountsService() {
	return getAppAssistant().accountsService;
}
function getSystemService() {
	return getAppAssistant().systemService;
}
function getAppManagerService() {
	return getAppAssistant().appManagerService;
}

function getFormatterService(){
	Mojo.Log.info("***********************in getFormatterService");
	return getAppAssistant().formatterService;
}

function getRecurrenceRule(){
	return getAppAssistant().recurrenceRule;
}

// ** REMINDER MANAGER
function getReminderManager() {
	return getAppAssistant().reminderManager;
}

// ** MULTIPLE CALENDAR SUPPORT
function getCalendarsManager() {
	return getAppAssistant().calendarsManager;
}

function getCurrentCal() {
	return getCalendarsManager().getCurrentCal();
}

function setCurrentCal(newCal) {
	getCalendarsManager().setCurrentCal(newCal);
}

function buildCalendarsMenu(includeAll, includeReadOnly, includeExcludedFromAll) {
	return getCalendarsManager().buildCalendarsMenu(includeAll, includeReadOnly, includeExcludedFromAll);
}

// ** CALENDAR PREFS
function getPrefsManager() {
	return getAppAssistant().prefsManager;
}

function getPrefs() {
	return getPrefsManager().getPrefs();
}


// ** FIRST USE
function getFirstUseManager() {
	return getAppAssistant().firstUseManager;
}

function setFirstUseToFalse() {
	getAppAssistant().cookie = this.cookie || new Mojo.Model.Cookie("com.palm.app.calendar");
	getAppAssistant().cookie.put({firstUse: false});
}

function finishedFirstUse() {
	getAppAssistant().firstUseManager.cleanup();
	delete getAppAssistant().firstUseManager;
	getAppAssistant().firstUseManager = undefined;
}

// ** TIME FORMAT
function observeTimeFormat(sceneName, controller) {
	getAppAssistant().timeFormatObservers.set(sceneName, controller);
}

function stopObservingTimeFormat(sceneName) {
	getAppAssistant().timeFormatObservers.unset(sceneName);
}

function notifyTimeFormatObservers() {
	getAppAssistant().timeFormatObservers.each(function(scenePair) {
		var controller = scenePair.value;
		
		controller.timeFormatUpdated();
	});
}

function getTimeFormat() {
	return getAppAssistant().timeFormat;
}
// **System  Time Change
function observeTimeChange(sceneName, controller){
	getAppAssistant().timeChangeObservers.set(sceneName,controller);
}
function stopObservingTimeChange(sceneName) {
	getAppAssistant().timeChangeObservers.unset(sceneName);
}

function notifyTimeChangeObservers(){
		//Mojo.Log.info("notify time change observers");
		
		// Update the calendar icon and day change timer since the system time has changed
		getAppAssistant().updateCalendarAppIcon();
		getAppAssistant().setupDayChangeTimer();
		
		// Notify observers that system has changed
		getAppAssistant().timeChangeObservers.each(function(scenePair) {
		var controller = scenePair.value;
		controller.timeChangeUpdated();
	});
}

function parseFromId(eventid){
	var targetModel = {
		id: 0,
		start: 0,
		end: 0
	}
	
	//eventid string should be formatted 'id-start-end'	
	//if '-' is not in the eventid string, assume we only got an id# and no times
	var extract = eventid.indexOf("-");
	if (extract == -1) {
		targetModel.id = eventid;
		return Object.clone(targetModel);
	}
		
	//potential formats for id string:
	// xxxx-yyyy-zzzz   -> start > 0 and end > 0 (starts and ends after midnight 1/1/1970 UTC)
	// xxxx--yyyy-zzzz  -> start < 0 and end > 0 (starts before midnight 1/1/1970 and ends after midnight 1/1/1970 UTC)
	// xxxx--yyyy--zzzz -> start < 0 and end < 0 (starts and ends before midnight 1/1/1970 UTC)
	var regexp = new RegExp(/^(\d+)-(-?\d+)-(-?\d+)$/);  
	var tokens = regexp.exec(eventid);
		
	//We should have four tokens: whole string match, id, start, and end
	//if we don't have four tokens, the string is somehow malformed. 
	//Use the whole string and hope it works out okay.	
	if (tokens && tokens.length !== 4) {
		targetModel.id = eventid;
		return Object.clone(targetModel);
	}
		
	targetModel.id = tokens[1];
	targetModel.start = parseInt(tokens[2]);
	targetModel.end = parseInt(tokens[3]);
	
	return Object.clone(targetModel);
}
