// Copyright 2001-2004, 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;

import java.util.Comparator;

import danger.app.DataStore;
import danger.util.DEBUG;

/**
 * The Scores object is the main client of the DataStore itself, and provides
 * access to the saved HighScores. It handles sorting and storage.
 * 
 * The Scores object is created as a singleton. It is referenced by getting the
 * single instance using Scores.getInstance(). In addition, all of the methods
 * are defined as static, so that a Scores object reference is unnecessary.
 * 
 * The Scores object is also implementing Comparator through compare(), so that
 * it can sort the objects in the DataStore.
 */

public class Scores implements Comparator, Resources {
	/**
	 * Convenience constants to make access more clear.
	 */
	private static final boolean REQUEST_AUTO_SYNC = true;

	private static final boolean MAKE_DIRTY = true;

	/**
	 * The MAX_SCORE_COUNT is set to 50, which is the present limit specified in
	 * the DataStore guidelines, which can be found on developer.danger.com.
	 * This record limit may be relaxed in the future, so be sure to check the
	 * FAQ if this limit is too restrictive. Each HighScore is a single record.
	 * After the limit has been reached, further additions will require
	 * replacement of lower scores.
	 */
	private static final int MAX_SCORE_COUNT = 50;

	/**
	 * Two HighScore objects to be used, and reused during sort operations. They
	 * are static so that there will only be one instance of each.
	 */
	private static HighScore sScore0 = new HighScore(null, 0, 0, null);

	private static HighScore sScore1 = new HighScore(null, 0, 0, null);

	/**
	 * The sStore is the single reference to the single instance of this
	 * singleton object. It is private and static to prevent any other code from
	 * accessing it accidentally. It is initialized upon the first call to
	 * getInstance().
	 */
	private static Scores sScores = null;

	/**
	 * Always non-null; datastore for all the scores.
	 */
	private static DataStore sDataStore;

	// ------------------------------------------------------------------------
	/**
	 * Constructor for Scores.
	 * 
	 * This constructor is private, so that it cannot be instantiated by any
	 * other code. This makes it a singleton.
	 */
	private Scores() {
		// Create the object in any case. If nothing is saved, the DB will still
		// not exist, and there will be no network traffic. The referenced
		// object is always needed.
		sDataStore = DataStore
				.createDataStore("high-scores", REQUEST_AUTO_SYNC);

		// For debugging purposes, dump storage info to the console. This shows
		// all DataStores on the device, and only shows record counts and byte
		// counts, so it is of limited value.
		DataStore.dumpList();
	}

	/**
	 * getInstance() The routine returns the singleton reference to 'this'.
	 * Because of the private constructor, and getInstance(), there will only be
	 * one instance of this object.
	 */
	public static Scores getInstance() {
		if (sScores == null) {
			sScores = new Scores();
		}
		return (sScores);
	}

	/**
	 * Sort the data based on the current user choice. This is necessary after
	 * the data has been restored after a hard-reset. This Scores object is
	 * passed as the comparator parameter.
	 */
	public static void sort() {
		sDataStore.sort(sScores);
	}

	/**
	 * Compare two HighScore objects to determine how to sort them. For score
	 * itself, it is clearly the larger number. For the name string, it will use
	 * the L10N friendly byte array collator function, in the 2.0 version. For
	 * the 1.1 version, the Collator is unavailable, so the unlocalizable
	 * StringUtils will be used. To handle these versions, the utility routine
	 * in the Version object is used, which will pick up the correct version at
	 * compile time.
	 * 
	 * It is notable that the Objects arg0, arg1 that are passed into this
	 * routine arrive as byte[], the same as were passed to the DataStore object
	 * to be stored. This makes the comparison of two HighScore objects more
	 * convoluted than simply comparing fields. The byte[] are converted back to
	 * HighScore objects in order to access their fields for comparison. This
	 * conversion back to HighScore objects would be expensive, for large
	 * numbers of objects. Since the DataStore guidelines currently specify 50
	 * records max, this is not presently a problem.
	 * 
	 * DataStores use an insertion sort. This is highly inefficient for large
	 * numbers of objects, but it is expected that a maximum of 50 records will
	 * be saved to the service, also making this a non-issue.
	 */
	public int compare(Object arg0, Object arg1) {

		// Convert byte arrays back into HighScore objects.
		sScore0.fromByteArray((byte[]) arg0);
		sScore1.fromByteArray((byte[]) arg1);

		switch (Settings.getSortBy()) {	// Get SettingsDB user choice.
		case ID_MENU_SORT_NAME:
			return (Version
					.compareStrings(sScore0.getName(), sScore1.getName()));

		case ID_MENU_SORT_SCORE:
			// Highest score at top of list, reverses subtraction.
			return (sScore1.getScore() - sScore0.getScore());

		case ID_MENU_SORT_LEVEL:
			return (sScore1.getLevel() - sScore0.getLevel());

		case ID_MENU_SORT_DATE:
			return (sScore0.getDate().compareTo(sScore1.getDate()));
		}
		return 0;
	}

	/**
	 * Clear all the settings from the DB saved on the network. This is
	 * something that should be done rarely, because it causes a lot of work for
	 * the service. This routine is primarily here to allow for special cases,
	 * like resetting the settings back to their programmatic defaults, with no
	 * settings saved. Or for handling something terrible like a corrupted DB.
	 * 
	 * In this program, it is called from a shortcut operation to reset the
	 * program back to first states, in order to demonstrate how the program and
	 * resource defaults are used before the DB is created.
	 * 
	 * There has been some confusion as to whether removeAllRecords() actually
	 * works or not. It does work as expected, but requires the usual 5 minute
	 * timeout for DB changes to take effect. Rebooting the device is not
	 * necessary, but the discardOnReboot() does work also.
	 */
	public void clearAllDataStore() {
		sDataStore.removeAllRecords();
	}

	/**
	 * Add a record to the DataStore. Insert the record, sorted. This will keep
	 * the Highest score at the top of the list. If sorting is unnecessary, use
	 * insertRecord() or addRecord() instead.
	 * 
	 * Insertion sorts are not very efficient. In this case, there are a small
	 * number of records expected (MAX_SCORE_COUNT). If the number were much
	 * larger, this sort would not be a good approach.
	 * 
	 * If the maximum number of records is already stored, replace the lowest
	 * score. Replacing the lower score is superior to deleting and adding a new
	 * one, because it generates less back-end service work.
	 */
	public static void addScore(HighScore newScore) {
		if (getRecordCount() < MAX_SCORE_COUNT) {
			int index = sDataStore.insertRecordSorted(sScores, newScore
					.toByteArray());
			DEBUG.p("add score UID: " + sDataStore.getRecordUID(index));
		} else {
			// Find the lowest score by iterating over them all, as they might
			// not be sorted by Score.
			int lowestScore = Integer.MAX_VALUE;
			int lowestIndex = 0;
			for (int i = 0; i < getRecordCount(); i++) {
				sScore0.fromByteArray(sDataStore.getRecordData(i));
				if (sScore0.getScore() < lowestScore) {
					lowestIndex = i;
					lowestScore = sScore0.getScore();
				}
			}
			// Replace the record at lowestIndex with newScore, if it's better.
			// It's marked as dirty, so that it will be sent to the service.
			if (newScore.getScore() > lowestScore) {
				sDataStore.setRecordData(lowestIndex, newScore.toByteArray(),
						MAKE_DIRTY);
				sort(); // potentially out of order now, resort.
				DEBUG.p("replace score UID: "
						+ sDataStore.getRecordUID(lowestIndex));
			}
		}
	}

	/**
	 * Read a record from the DataStore at index.
	 * 
	 * Using this DEBUG string demonstrates how the UIDs are managed by the
	 * service. Upon first creation, the UID is a negative number, unrelated to
	 * the index for the record. While stored solely upon the device itself, the
	 * UID remains a negative number. After the service has synchronized with
	 * the device and backed up the data, the UIDs are made positive. Same
	 * number, but a positive instead of a negative.
	 */
	public static HighScore getScoreAt(int index) {
		DEBUG.p("i:" + index + " UID: " + sDataStore.getRecordUID(index));

		byte[] scoreData = sDataStore.getRecordData(index);
		return (new HighScore(scoreData));
	}

	/**
	 * Return the number of records in the private sDataStore.
	 */
	public static int getRecordCount() {
		return sDataStore.getRecordCount();
	}

}
