// Copyright 2001-2005, Danger, Inc.  All Rights Reserved.
// This file is subject to the Danger, Inc. Sample Code License,
// which is provided in the file SAMPLE_CODE_LICENSE.
// Copies are also available from http://developer.danger.com/

package com.example.DataStoreDemo;

/**
 This sample program has been developed specifically to demonstrate the use of 
 the DataStore class.  It will perform other operations related to that goal, 
 like having a Table UI, but will not use or demonstrate full Application 
 functionality.  This will keep the sample shorter and simpler, and more focused
 upon the interesting piece, the Scores object.

 For a more complete Application example, please refer to the 
 HelloWorld/Template app.

 The code base is specifically designed to start with the 1.1 source as the 
 default, in the 'common' directory.  The only 2.0 APIs used are found in the 
 Version class, which includes utility routines to improve the application under
 later OSes.  The 2.0 directory includes the most recent version.
 Newer versions are compiled using build variables (e.g. -DLIB_OS_VERSION="2.3" 
 -DBUILD_NUMBER="90526")


 This sample demonstrates the following DataStore techniques:
 1) Serializing data as an OutputStream, to convert to byte array.
 2) Saving and restoring byte arrays into DataStore.
 3) Using the DataStore to keep records sorted, via Comparator.
 4) Finding specific records.
 5) Deleting a DataStore completely.
 6) Using 2.0 specific APIs for Strings & Dates to improve the user experience.
 7) Proper DATASTORE_RESTORED event handling, for hard-resets.
 8) How to save and display high scores.


 There are a couple of scenarios that are helpful to demonstrate how the 
 sample and DataStores work.

 Normal case:
 1) Run DataStoreDemo, on a device, or the simulator.
 2) At the MainWindow, choose the menu operation for New Score, several times.
 3) Change sorting to Name or Level.
 4) Note that the settings have changed.
 5) Quit simulator or reboot device.
 6) Run DataStoreDemo.
 7) Note that the data and sort preference were retained over the reboot.

 Hard-Reset case (simulator):
 0) Allow the simulator to sit long enough (>5 minutes) to allow network save.
 1) For the simulator, find the datastores folder in the working directory.
 2) Delete the datastores folder.  
 3) Run DataStoreDemo.
 4) Allow the network to connect. 
 5) Note that the score list reflects the previously saved scores and sort.

 Hard-Reset case (device):
 1) Hard reset with paperclip and power, or use poweroff in htconsole.
 2) Start device, then run DataStoreDemo.
 3) After network connects, note that the scores list is restored.


 For testing purposes, there are 9 different situations that need to work
 correctly in order to pass.  These are different code paths that can produce
 subtle bugs like missed sortings.  The main reason to do these tests is 
 because the DataStoreRestored events are handled by the same routine for
 both the SettingsDB and DataStore.  But, the DataStore will not send an
 event if the data is restored before the app is launched.
 
*0) First run, no high-score, no settings. Default sort to Score.

 With no settings stored (default sort by score):
*1) Soft reboot, normal operation.  high-score display.
    No events.
*2) Hard reset restore, auto-launch application. App before data.
    Receive DataStoreRestored for high-score.
*3) Hard reset restore, late launch.  App after data restored.
    No events. 
*4) Hard reset. Add high-score before connect. Merge data.
    Receive DataStoreRestored for high-score.
 
 Same with settings set to non-default, sort by Level, Name, or Date.
*5) Soft reboot, normal operation.  settings and high-score display.
    No events.
*6) Hard reset restore, auto-launch application. App before data.
    Receive DataStoreRestored for high-score and settings both.
*7) Hard reset restore, late launch.  App after data restored.
    Receive DataStoreRestored for settings only. 
*8) Hard reset. Add high-score before connect. Merge data.
    Receive DataStoreRestored for high-score and settings both.
 
 
 Please note that at some point, the device or the simulator will need to idle
 for 5 minutes, to allow the save to network operation to run.  Hard-resets
 before the 5 minute timeout will not be restored, as the data never makes it
 to the service.  The network has saved the data when the message:
 [java] Srvc: send 'danger.io.ServiceObjects.Pong'
 appears in the console.

 The timeout is necessary for any clearAll() or removeAllRecords() operation as
 well.  Both work as expected, but it is imperative to allow the Pong to return,
 as the marker that the operation has finished on the service.  It is then 
 necessary to restart the simulator, or reboot the device, to finalize the 
 delete.  If those steps are followed, it *is* possible to fully delete a 
 DataStore or SettingsDB. This technique can also be used if a SettingsDB were 
 initially created no-sync, but later it is desired to become sync.  There is no
 way to change the sync behavior without deleting.  
 
 In general, there is no way to determine the difference between a first-run
 of the application, and a hard-reset case.  Generally speaking, this should
 never be a concern.  If the app does not find any settings upon startup, it
 should assume it is first-run and use programmatic defaults, but switch back 
 to normal operation upon receiving DATASTORE_RESTORED.  For something like High
 Score data, even if the user creates new records before the restore, the new 
 records will be merged into the restored ones.

 There is a notable bug in the 2.3 version of the simulator.  When Save 
 Datastores is enabled, the settings will be saved as expected, simulating the 
 battery backed up RAM of a normal SettingsDB.  (If Save Datastores is 
 unchecked, no settings will be saved over reboots.)  When Save Datastores is 
 enabled, the datastores will be saved in the working directory (usually the sdk
 root), and the 2.3 version will cause the 2.0 or 1.1 simulator to crash with an
 out of memory error.  Delete the datastores folder to fix the problem.
 */

import danger.app.Application;
import danger.app.DataStore;
import danger.app.Event;
import danger.app.EventType;
import danger.util.DEBUG;

// ------------------------------------------------------------------------

/**
 * Implements the main application class.
 * 
 * Resources and Commands are created by drc from the DataStoreDemo.rsrc file.
 */
public class DataStoreDemo extends Application implements Resources, Commands {

	/**
	 * All instance variables are private, unless there is a good reason not to
	 * be. Good programming habits help to avoid bugs. For reference: "Writing
	 * Solid Code" by Steve Maguire. Also highly recommended: "Code Complete" by
	 * Steve McConnell.
	 */
	private MainWindow mWindow;

	// ------------------------------------------------------------------------
	/**
	 * Creates the main application class.
	 */
	public DataStoreDemo() {
		// All DEBUG.p are simply debugging comments sent to the console.
		DEBUG.p("............................DataStoreDemo startup");

		// First reference to getInstance, will create the Scores object
		// itself, for storing HighScores. Same with Settings object.
		Scores.getInstance();
		Settings.getInstance();
		Settings.setSortBy(ID_MENU_SORT_SCORE); // set programmatic default.

		// Register for AutoSync of the DataStore. However, this is not
		// necessary in general. Useful if the DataStore is being created
		// shared.
		DataStore.registerListenerForAutoSyncDone(this);

		// Create the application's main and only window from resources.
		mWindow = (MainWindow) getResources().getScreen(ID_MAIN_SCREEN);
		mWindow.show();
	}

	/**
	 * Rebuild the screen list from the DataStore and redraw.
	 */
	public void refreshWindow() {
		mWindow.createScoreTable();
		mWindow.invalidate();
	}

	/**
	 * When the hidden menu option is chosen, come here to put up an Alert, to
	 * decide if the settings are to be deleted. 'this' is the Application
	 * Listener receiveEvent, which will receive the EVENT_DELETE_ALL_OK. Or, it
	 * might receive the Back/Cancel event, in which case nothing is done.
	 */
	public void deleteAllRequest() {
		showAlert(ID_DELETE_ALL_ALERT, this);
	}

	/**
	 * The Delete All dialog will send the Delete_All_OK event here, when the
	 * user makes that choice. The Back/Done is ignored as it dismisses the
	 * dialog and acts as a Cancel button.
	 * 
	 * The App also handles the Datastore_Restored event. It is sent when a
	 * device hard reset has caused the battery backed datastores to be lost,
	 * after they have been reloaded from the network. There will be two events
	 * received, one for the Settings:sort-by, and one for the
	 * DataStore:high-score. For sort-by, the data is resorted and displayed, as
	 * if the user chose a different sort-by from the menu. For the high-score
	 * data, it is sorted and redisplayed, because the data comes back from the
	 * service unsorted.
	 * 
	 * The event for the DataStore will not be sent if the app is launched after
	 * the restore has occurred. The data is ready to be read. However, for the
	 * SettingsDB, the event will *always* be sent, even when the app is
	 * launched late.
	 * 
	 * Lastly the Auto_Sync_Done event is received here as well. This event is
	 * of limited value, and is usually seen at the same time as the
	 * Datastore_Restored. Unless the application has cross-datastore
	 * requirements, this event can be safely ignored. For a DataStore it is
	 * necessary to call registerAutoSyncBundleListener(). For a SettingsDB, it
	 * is automatically registered.
	 */
	public boolean receiveEvent(Event e) {
		switch (e.type) {

		// This event does not occur in the normal case of launching or
		// rebooting or even powering off the device. This only occurs when the
		// device is hard-reset, and data is restored from the network.
		// It is notable that after the DataStore is restored, that it comes
		// back unsorted.
		case EventType.EVENT_DATASTORE_RESTORED:
			// If it's for "settings", rebuild the MainWindow to set up the UI
			// to match. The endsWith is used, because the string sometimes
			// specifies the full path, including the "com.example...".
			if (e.argument.toString().endsWith("settings")) {
				mWindow = (MainWindow) getResources().getScreen(ID_MAIN_SCREEN);
				mWindow.show();
			} else {
				// Event is for DataStore "high-score" data instead. Just
				// resort and refresh the list.
				Scores.sort();
				refreshWindow();

				// This is also the spot to resolve DataStore conflicts. This is
				// for the case of a hard-reset, and the user creates new data
				// off network, *before* the DATASTORE_RESTORED.
				// If nothing is done here, the DataStore may have duplicate
				// records with different UIDs. For HighScore data there are no
				// conflicts, as even duplicate scores are OK. New off-network
				// scores will be merged into the restored data.
			}

			DEBUG.p("DataStoreDemo: EVENT_DATASTORE_RESTORED " + e.argument);
			return true;

		// This notifies the app that the *device* as a whole has been
		// fully synchronized with the service, not just the app.
		// In this app, this event is unused, because there are no cross-app
		// needs. Most apps can safely ignore this event.
		// An example of potential cross-application communication might be
		// the gallery, if the application needed access to the photos.
		// Cross-app datastores are created using createSharedDataStore().
		case EventType.EVENT_AUTO_SYNC_DONE:
			DEBUG.p("DataStoreDemo: EVENT_AUTO_SYNC_DONE");
			return true;

		// Called in response to the Delete All click in the Confirmation
		// Dialog. The App object was specified as the Listener when the
		// dialog was created.
		case EVENT_DELETE_ALL_OK:
			Scores.getInstance().clearAllDataStore();
			Settings.setSortBy(ID_MENU_SORT_SCORE); // set programmatic default.
			Settings.getInstance().clearAllSettingsDB();

			// Now the window display doesn't match zero DB values, so refresh
			// the window to display an empty pane.
			refreshWindow();
			DEBUG.p("DataStoreDemo: EVENT_DELETE_ALL_OK");
			return true;
		}

		// If the event wasn't handled above, send it back to the superclass.
		return super.receiveEvent(e);
	}

}
