dojo.provide("dojox.off._common");

dojo.require("dojox.storage");
dojo.require("dojox.sql");
dojo.require("dojox.off.sync");

// Author: Brad Neuberg, bkn3@columbia.edu, http://codinginparadise.org

// summary:
//		dojox.off is the main object for offline applications.
dojo.mixin(dojox.off, {
	// isOnline: boolean
	//	true if we are online, false if not
	isOnline: false,
	
	// NET_CHECK: int
	//		For advanced usage; most developers can ignore this.
	//		Time in seconds on how often we should check the status of the
	//		network with an automatic background timer. Defaults to 5.
	NET_CHECK: 5,
	
	// STORAGE_NAMESPACE: String
	//		For advanced usage; most developers can ignore this.
	//		The namespace we use to save core data into Dojo Storage.
	//		We namespace based on the page's URL so that multiple
	//		apps served from this domain won't have their data clash
	STORAGE_NAMESPACE: "dojo_offline_" 
						+ window.location.href.replace(/[:\/\\ \.]/g, "_"),
	
	// enabled: boolean
	//		For advanced usage; most developers can ignore this.
	//		Whether offline ability is enabled or not. Defaults to true.
	enabled: true,
	
	// availabilityURL: String
	//		For advanced usage; most developers can ignore this.
	//		The URL to check for site availability.  We do a GET request on
	//		this URL to check for site availability.  By default we check for a
	//		simple text file in src/off/network_check.txt that has one value
	//		it, the value '1'.
	availabilityURL: dojo.moduleUrl("dojox", "off/network_check.txt"),
	
	// goingOnline: boolean
	//		For advanced usage; most developers can ignore this.
	//		True if we are attempting to go online, false otherwise
	goingOnline: false,
	
	// coreOpFailed: boolean
	//		For advanced usage; most developers can ignore this.
	//		A flag set by the Dojo Offline framework that indicates that the
	//		user denied some operation that required the offline cache or an
	//		operation failed in some critical way that was unrecoverable. For
	//		example, if the offline cache is Google Gears and we try to get a
	//		Gears database, a popup window appears asking the user whether they
	//		will approve or deny this request. If the user denies the request,
	//		and we are doing some operation that is core to Dojo Offline, then
	//		we set this flag to 'true'.  This flag causes a 'fail fast'
	//		condition, turning off offline ability.
	coreOpFailed: false,
	
	// doNetChecking: boolean
	//		For advanced usage; most developers can ignore this.
	//		Whether to have a timing interval in the background doing automatic
	//		network checks at regular intervals; the length of time between
	//		checks is controlled by dojox.off.NET_CHECK. Defaults to true.
	doNetChecking: true,
	
	// hasOfflineCache: boolean
	//		For advanced usage; most developers can ignore this.
	//  	Determines if an offline cache is available or installed; an
	//  	offline cache is a facility that can truely cache offline
	//  	resources, such as JavaScript, HTML, etc. in such a way that they
	//  	won't be removed from the cache inappropriately like a browser
	//  	cache would. If this is false then an offline cache will be
	//  	installed. Only Google Gears is currently supported as an offline
	//  	cache. Future possible offline caches include Firefox 3.
	hasOfflineCache: null,
	
	// browserRestart: boolean
	//		For advanced usage; most developers can ignore this.
	//		If true, the browser must be restarted to register the existence of
	//		a new host added offline (from a call to addHostOffline); if false,
	//		then nothing is needed.
	browserRestart: false,
	
	_initializeCalled: false,
	_storageLoaded: false,
	_pageLoaded: false,
	
	onLoad: function(){
		// summary:
		//	Called when Dojo Offline can be used.
		// description:
		//	Do a dojo.connect to this to know when you can
		//	start using Dojo Offline:
		//		dojo.connect(dojox.off, "onLoad", myFunc);
	},
	
	onNetwork: function(type){
		// summary:
		//	Called when our on- or offline- status changes.
		// description:
		//	If we move online, then this method is called with the
		//	value "online". If we move offline, then this method is
		//	called with the value "offline". You can connect to this
		//	method to do add your own behavior:
		//
		//		dojo.connect(dojox.off, "onNetwork", someFunc)
		//
		//	Note that if you are using the default Dojo Offline UI
		//	widget that most of the on- and off-line notification
		//	and syncing is automatically handled and provided to the
		//	user.
		// type: String
		//	Either "online" or "offline".
	},
	
	initialize: function(){ /* void */
		// summary:
		//		Called when a Dojo Offline-enabled application is finished
		//		configuring Dojo Offline, and is ready for Dojo Offline to
		//		initialize itself.
		// description:
		//		When an application has finished filling out the variables Dojo
		//		Offline needs to work, such as dojox.off.ui.appName, it must
		//		this method to tell Dojo Offline to initialize itself.
		
		//		Note:
		//		This method is needed for a rare edge case. In some conditions,
		//		especially if we are dealing with a compressed Dojo build, the
		//		entire Dojo Offline subsystem might initialize itself and be
		//		running even before the JavaScript for an application has had a
		//		chance to run and configure Dojo Offline, causing Dojo Offline
		//		to have incorrect initialization parameters for a given app,
		//		such as no value for dojox.off.ui.appName. This method is
		//		provided to prevent this scenario, to slightly 'slow down' Dojo
		//		Offline so it can be configured before running off and doing
		//		its thing.	

		//console.debug("dojox.off.initialize");
		this._initializeCalled = true;
		
		if(this._storageLoaded && this._pageLoaded){
			this._onLoad();
		}
	},
	
	goOffline: function(){ /* void */
		// summary:
		//		For advanced usage; most developers can ignore this.
		//		Manually goes offline, away from the network.
		if((dojox.off.sync.isSyncing)||(this.goingOnline)){ return; }
		
		this.goingOnline = false;
		this.isOnline = false;
	},
	
	goOnline: function(callback){ /* void */
		// summary: 
		//		For advanced usage; most developers can ignore this.
		//		Attempts to go online.
		// description:
		//		Attempts to go online, making sure this web application's web
		//		site is available. 'callback' is called asychronously with the
		//		result of whether we were able to go online or not.
		// callback: Function
		//		An optional callback function that will receive one argument:
		//		whether the site is available or not and is boolean. If this
		//		function is not present we call dojo.xoff.onOnline instead if
		//		we are able to go online.
		
		//console.debug("goOnline");
		if(dojox.off.sync.isSyncing || dojox.off.goingOnline){
			return;
		}
		
		this.goingOnline = true;
		this.isOnline = false;
		
		// see if can reach our web application's web site
		this._isSiteAvailable(callback);
	},
	
	onFrameworkEvent: function(type /* String */, saveData /* Object? */){
		//	summary:
		//		For advanced usage; most developers can ignore this.
		//		A standard event handler that can be attached to to find out
		//		about low-level framework events. Most developers will not need to
		//		attach to this method; it is meant for low-level information
		//		that can be useful for updating offline user-interfaces in
		//		exceptional circumstances. The default Dojo Offline UI
		//		widget takes care of most of these situations.
		//	type: String
		//		The type of the event:
		//
		//		* "offlineCacheInstalled"
		//			An event that is fired when a user
		//			has installed an offline cache after the page has been loaded.
		//			If a user didn't have an offline cache when the page loaded, a
		//			UI of some kind might have prompted them to download one. This
		//			method is called if they have downloaded and installed an
		//			offline cache so a UI can reinitialize itself to begin using
		//			this offline cache.
		//		* "coreOperationFailed"
		//			Fired when a core operation during interaction with the
		//			offline cache is denied by the user. Some offline caches, such
		//			as Google Gears, prompts the user to approve or deny caching
		//			files, using the database, and more. If the user denies a
		//			request that is core to Dojo Offline's operation, we set
		//			dojox.off.coreOpFailed to true and call this method for
		//			listeners that would like to respond some how to Dojo Offline
		//			'failing fast'.
		//		* "save"
		//			Called whenever the framework saves data into persistent
		//			storage. This could be useful for providing save feedback
		//			or providing appropriate error feedback if saving fails 
		//			due to a user not allowing the save to occur
		//	saveData: Object?
		//		If the type was 'save', then a saveData object is provided with
		//		further save information. This object has the following properties:	
		//
		//		* status - dojox.storage.SUCCESS, dojox.storage.PENDING, dojox.storage.FAILED
		//		Whether the save succeeded, whether it is pending based on a UI
		//		dialog asking the user for permission, or whether it failed. 	
		//
		//		* isCoreSave - boolean
		//		If true, then this save was for a core piece of data necessary
		//		for the functioning of Dojo Offline. If false, then it is a
		//		piece of normal data being saved for offline access. Dojo
		//		Offline will 'fail fast' if some core piece of data could not
		//		be saved, automatically setting dojox.off.coreOpFailed to
		//		'true' and dojox.off.enabled to 'false'.
		//
		// 		* key - String
		//		The key that we are attempting to persist
		//
		// 		* value - Object
		//		The object we are trying to persist
		//
		// 		* namespace - String
		//		The Dojo Storage namespace we are saving this key/value pair
		//		into, such as "default", "Documents", "Contacts", etc.
		//		Optional.
		if(type == "save"){
			if(saveData.isCoreSave && (saveData.status == dojox.storage.FAILED)){
				dojox.off.coreOpFailed = true;
				dojox.off.enabledabled = false;
			
				// FIXME: Stop the background network thread
				dojox.off.onFrameworkEvent("coreOperationFailed");
			}
		}
	},
	
	_checkOfflineCacheAvailable: function(callback){
		// is a true, offline cache running on this machine?
		this.hasOfflineCache = dojo.isGears;
		
		callback();
	},
	
	_onLoad: function(){
		//console.debug("dojox.off._onLoad");
		// both local storage and the page are finished loading
		
		// cache the Dojo JavaScript -- just use the default dojo.js
		// name for the most common scenario
		// FIXME: TEST: Make sure syncing doesn't break if dojo.js
		// can't be found, or report an error to developer
		dojox.off.files.cache(dojo.moduleUrl("dojo", "dojo.js"));
		
		// pull in the files needed by Dojo
		this._cacheDojoResources();
		
		// FIXME: need to pull in the firebug lite files here!
		// workaround or else we will get an error on page load
		// from Dojo that it can't find 'console.debug' for optimized builds
		// dojox.off.files.cache(djConfig.baseRelativePath + "src/debug.js");
		
		// make sure that resources needed by all of our underlying
		// Dojo Storage storage providers will be available
		// offline
		dojox.off.files.cache(dojox.storage.manager.getResourceList());
		
		// see if we have an offline cache; when done, move
		// on to the rest of our startup tasks
		this._checkOfflineCacheAvailable(dojo.hitch(this, "_onOfflineCacheChecked"));
	},
	
	_onOfflineCacheChecked: function(){
		// this method is part of our _onLoad series of startup tasks
		
		// if we have an offline cache, see if we have been added to the 
		// list of available offline web apps yet
		if(this.hasOfflineCache && this.enabled){
			// load framework data; when we are finished, continue
			// initializing ourselves
			this._load(dojo.hitch(this, "_finishStartingUp"));
		}else if(this.hasOfflineCache && !this.enabled){
			// we have an offline cache, but it is disabled for some reason
			// perhaps due to the user denying a core operation
			this._finishStartingUp();
		}else{
			this._keepCheckingUntilInstalled();
		}
	},
	
	_keepCheckingUntilInstalled: function(){
		// this method is part of our _onLoad series of startup tasks
		
		// kick off a background interval that keeps
		// checking to see if an offline cache has been
		// installed since this page loaded
			
		// FIXME: Gears: See if we are installed somehow after the
		// page has been loaded
		
		// now continue starting up
		this._finishStartingUp();
	},
	
	_finishStartingUp: function(){
		//console.debug("dojox.off._finishStartingUp");
		// this method is part of our _onLoad series of startup tasks
		
		if(this.enabled){
			// kick off a thread to check network status on
			// a regular basis
			this._startNetworkThread();

			// try to go online
			this.goOnline(dojo.hitch(this, function(){
				
				// indicate we are ready to be used
				dojox.off.onLoad();
			}));
		}else{ // we are disabled or a core operation failed
			if(this.coreOpFailed){
				this.onFrameworkEvent("coreOperationFailed");
			}else{
				this.onLoad();
			}
		}
	},
	
	_onPageLoad: function(){
		//console.debug("dojox.off._onPageLoad");
		this._pageLoaded = true;
		
		if(this._storageLoaded && this._initializeCalled){
			this._onLoad();
		}
	},
	
	_onStorageLoad: function(){
		//console.debug("dojox.off._onStorageLoad");
		this._storageLoaded = true;
		
		// were we able to initialize storage? if
		// not, then this is a core operation, and
		// let's indicate we will need to fail fast
		if(!dojox.storage.manager._initialized){
			this.coreOpFailed = true;
			this.enabled = false;
		}
		
		if(this._pageLoaded && this._initializeCalled){
			this._onLoad();		
		}
	},
	
	_isSiteAvailable: function(callback){
		// summary:
		//		Determines if our web application's website is available.
		// description:
		//		This method will asychronously determine if our web
		//		application's web site is available, which is a good proxy for
		//		network availability. The URL dojox.off.availabilityURL is
		//		used, which defaults to this site's domain name (ex:
		//		foobar.com). We check for dojox.off.AVAILABILITY_TIMEOUT (in
		//		seconds) and abort after that
		// callback: Function
		//		An optional callback function that will receive one argument:
		//		whether the site is available or not and is boolean. If this
		//		function is not present we call dojox.off.onNetwork instead if we
		//		are able to go online.
		dojo.xhrGet({
			url:		this._getAvailabilityURL(),
			handleAs:	"text",
			error:		dojo.hitch(this, function(err){
				//console.debug("dojox.off._isSiteAvailable.error: " + err);
				this.goingOnline = false;
				this.isOnline = false;
				if(callback){ callback(false); }
			}),
			load:		dojo.hitch(this, function(data){
				//console.debug("dojox.off._isSiteAvailable.load, data="+data);
				this.goingOnline = false;
				this.isOnline = true;
				
				if(callback){ callback(true);
				}else{ this.onNetwork("online"); }
			})
		});
	},
	
	_startNetworkThread: function(){
		//console.debug("startNetworkThread");
		
		// kick off a thread that does periodic
		// checks on the status of the network
		if(!this.doNetChecking){
			return;
		}
		
		var errFunc = dojo.hitch(this, 
			function(err){
				if(this.isOnline){
					this.isOnline = false;
					this.onNetwork("offline");
				}
			}
		);

		var successFunc = dojo.hitch(this, 
			function(data){
				if(!this.isOnline){
					this.isOnline = true;
					this.onNetwork("online");
				}
			}
		);

		window.setInterval(dojo.hitch(this, function(){
			dojo.xhrGet({
				url:	 	this._getAvailabilityURL(),
				handleAs:	"text",
				error:		errFunc,
				load:		successFunc
			});

		}), this.NET_CHECK * 1000);
	},
	
	_getAvailabilityURL: function(){
		var url = this.availabilityURL.toString();
		
		// bust the browser's cache to make sure we are really talking to
		// the server
		if(url.indexOf("?") == -1){
			url += "?";
		}else{
			url += "&";
		}
		url += "browserbust=" + new Date().getTime();
		
		return url;
	},
	
	_onOfflineCacheInstalled: function(){
		this.onFrameworkEvent("offlineCacheInstalled");
	},
	
	_cacheDojoResources: function(){
		// if we are a non-optimized build, then the core Dojo bootstrap
		// system was loaded as separate JavaScript files;
		// add these to our offline cache list. these are
		// loaded before the dojo.require() system exists
		
		// FIXME: create a better mechanism in the Dojo core to
		// expose whether you are dealing with an optimized build;
		// right now we just scan the SCRIPT tags attached to this
		// page and see if there is one for _base/_loader/bootstrap.js
		var isOptimizedBuild = true;
		dojo.forEach(dojo.query("script"), function(i){
			var src = i.getAttribute("src");
			if(!src){ return; }
			
			if(src.indexOf("_base/_loader/bootstrap.js") != -1){
				isOptimizedBuild = false;
			}
		});
		
		if(!isOptimizedBuild){
			dojox.off.files.cache(dojo.moduleUrl("dojo", "_base.js").uri);
			dojox.off.files.cache(dojo.moduleUrl("dojo", "_base/_loader/loader.js").uri);
			dojox.off.files.cache(dojo.moduleUrl("dojo", "_base/_loader/bootstrap.js").uri);
			
			// FIXME: pull in the host environment file in a more generic way
			// for other host environments
			dojox.off.files.cache(dojo.moduleUrl("dojo", "_base/_loader/hostenv_browser.js").uri);
		}
		
		// add anything that was brought in with a 
		// dojo.require() that resulted in a JavaScript
		// URL being fetched
		
		// FIXME: modify dojo/_base/_loader/loader.js to
		// expose a public API to get this information
	
		for(var i = 0; i < dojo._loadedUrls.length; i++){
			dojox.off.files.cache(dojo._loadedUrls[i]);
		}
		
		// FIXME: add the standard Dojo CSS file
	},
	
	_save: function(){
		// summary:
		//		Causes the Dojo Offline framework to save its configuration
		//		data into local storage.	
	},
	
	_load: function(callback){
		// summary:
		//		Causes the Dojo Offline framework to load its configuration
		//		data from local storage
		dojox.off.sync._load(callback);
	}
});


// wait until the storage system is finished loading
dojox.storage.manager.addOnLoad(dojo.hitch(dojox.off, "_onStorageLoad"));

// wait until the page is finished loading
dojo.addOnLoad(dojox.off, "_onPageLoad");
