/*
 This file is part of the Greenfoot program. 
 Copyright (C) 2011, 2012  Poul Henriksen and Michael Kolling 
 
 This program is free software; you can redistribute it and/or 
 modify it under the terms of the GNU General Public License 
 as published by the Free Software Foundation; either version 2 
 of the License, or (at your option) any later version. 
 
 This program is distributed in the hope that it will be useful, 
 but WITHOUT ANY WARRANTY; without even the implied warranty of 
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 GNU General Public License for more details. 
 
 You should have received a copy of the GNU General Public License 
 along with this program; if not, write to the Free Software 
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA. 
 
 This file is subject to the Classpath exception as provided in the  
 LICENSE.txt file that accompanied this code.
 */
package greenfoot;

import java.util.List;

import org.droidfoot.util.GreenfootUtil;

/**
 * <p>
 * The UserInfo class can be used to store data permanently on a server, and to
 * share this data between different users, when the scenario runs on the
 * Greenfoot web site. This can be used to implement shared high score tables or
 * other examples of shared data.
 * </p>
 * 
 * <p>
 * Storage is only available when the current user is logged in on the Greenfoot
 * site, so for some users storage will not be available. Always use
 * UserInfo.isStorageAvailable() to check before accessing the user data.
 * </p>
 * 
 * <p>
 * A typical code snippet for storing a high score is as follows:
 * </p>
 * 
 * <pre>
 * if (UserInfo.isStorageAvailable()) {
 * 	UserInfo myInfo = UserInfo.getMyInfo();
 * 	if (newScore &gt; myInfo.getScore()) {
 * 		myInfo.setScore(newScore);
 * 		myInfo.store(); // write back to server
 * 	}
 * }
 * </pre>
 * <p>
 * Methods to retrieve user data include getting data for the current user
 * (getMyInfo()), the top scorers (e.g. getTop(10) for the top 10), and data for
 * users with scores near my own score (e.g. getNearby(10)).
 * </p>
 * 
 * <p>
 * The data that can be stored for each user consists of a score, 10 additional
 * general purpose integers, and 5 strings (limited to 50 characters in length).
 * In addition, the user name and user's image can be retrieved from the user
 * data.
 * </p>
 * 
 * <p>
 * For testing purposes, while running within Greenfoot (not on the web site),
 * the user name can be set in the preferences (CTRL-SHIFT-P / CMD-SHIFT-P).
 * This allows to simulate different users during development. When running on
 * the web site, the user name is the name used to log in to the site.
 * </p>
 * 
 * <h2>Droidfoot</h2> On an android device the data are stored locally, not on a
 * server. You can change the user name via the menu item "Settings". The user
 * name may not be empty. In this case method isStorageAvailable will return
 * false.
 * 
 * @author Neil Brown
 * @version 2.4
 * 
 * @author Dietrich Boles (Modifications for Android)
 * @version 2.0
 */
public class UserInfo {

	/** The number of integers that can be stored */
	public static final int NUM_INTS = 10;
	/** The number of Strings that can be stored */
	public static final int NUM_STRINGS = 5;
	/** The maximum number of characters that can be stored in each String */
	public static final int STRING_LENGTH_LIMIT = 50;

	private int[] ints;
	private String[] strings;
	private String userName;
	private int score;
	private int rank;

	// package-visible:
	UserInfo(String userName, int rank) {
		this.userName = userName;
		this.rank = rank;
		score = 0;
		ints = new int[NUM_INTS];
		strings = new String[NUM_STRINGS];
	}

	// package-visible:
	void setRank(int n) {
		rank = n;
	}

	/**
	 * Get the username of the user that this storage belongs to.
	 */
	public String getUserName() {
		return userName;
	}

	/**
	 * Get the value of the int at the given index (0 to NUM_INTS-1, inclusive).
	 * <p>
	 * The default value is zero.
	 */
	public int getInt(int index) {
		return ints[index];
	}

	/**
	 * Get the value of the String at the given index (0 to NUM_STRINGS-1,
	 * inclusive).
	 * <p>
	 * The default value is the empty String.
	 */
	public String getString(int index) {
		return strings[index] == null ? "" : strings[index];
	}

	/**
	 * Set the value of the int at the given index (0 to NUM_INTS-1, inclusive).
	 * 
	 * <p>
	 * Note that to store this value permanently, you must later call store().
	 */
	public void setInt(int index, int value) {
		ints[index] = value;
	}

	/**
	 * Get the value of the String at the given index (0 to NUM_STRINGS-1,
	 * inclusive). Passing null is treated as a blank string. The given String
	 * must be of STRING_LENGTH_LIMIT characters or less (or else the method
	 * will fail).
	 * 
	 * <p>
	 * Note that to store this value permanently, you must later call store().
	 */
	public void setString(int index, String value) {
		if (value != null && value.length() > STRING_LENGTH_LIMIT) {
			System.err
					.println("Error: tried to store a String of length "
							+ value.length()
							+ " in UserInfo, which is longer than UserInfo.STRING_LENGTH_LIMIT ("
							+ STRING_LENGTH_LIMIT + ")");
		} else {
			strings[index] = value;
		}
	}

	/**
	 * Get the user's score. By default, this is zero.
	 */
	public int getScore() {
		return score;
	}

	/**
	 * Set the user's score.
	 * <p>
	 * Note that this really does set the user's score. If you want to record
	 * only the user's highest score, you must code that yourself, using
	 * something like:
	 * 
	 * <pre>
	 * if (latestScore &gt; userData.getScore()) {
	 * 	userData.setScore(latestScore);
	 * }
	 * </pre>
	 * 
	 * Without some code like this, you'll always overwrite the user's previous
	 * score.
	 * 
	 * <p>
	 * Note that to store this value permanently, you must later call store().
	 */
	public void setScore(int score) {
		this.score = score;
	}

	/**
	 * Get the users overall rank for this scenario.
	 * <p>
	 * The user with the highest score will return 1, the user with the second
	 * highest score will return 2, and so on. Players with equal scores will
	 * get equal ranks, so rank will not necessarily be unique. To find the
	 * rank, scores are sorted in descending order (highest score first). If
	 * your scores need to be lowest-first, one trick is to store them as
	 * negative numbers.
	 * <p>
	 * If the rank is unavailable (e.g. because the data hasn't been stored
	 * yet), this function will return -1.
	 */
	public int getRank() {
		return rank;
	}

	/**
	 * Indicate whether storage is available.
	 * <p>
	 * Storage is unavailable if the scenario is run as an applet outside the
	 * Greenfoot website, or as a stand-alone application, or if the user is not
	 * logged in to the Greenfoot website. This last case is very common, so you
	 * should check this function before attempting to use the other static
	 * storage functions. If this function returns false, your scenario should
	 * proceed without using storage.
	 */
	public static boolean isStorageAvailable() {
		// Returns false for applets when not on the Gallery, and stand-alone
		// applications
		// Returns true for inside Greenfoot, and applets on the gallery
		return GreenfootUtil.isStorageSupported();
	}

	/**
	 * Get the data stored for the current user.
	 * 
	 * This method returns null if:
	 * <ul>
	 * <li>there is a problem reading the local storage.csv file (for local
	 * scenarios), or</li>
	 * <li>the scenario is running as a stand-alone application, or applet on
	 * your own website, or</li>
	 * <li>there is a problem connecting to the server (for scenarios on the
	 * greenfoot.org site), or</li>
	 * <li>the user is not logged in (for scenarios on the greenfoot.org site).</li>
	 * </ul>
	 * 
	 * The last case is very common, so you should always be ready to handle a
	 * null return from this function.
	 * 
	 * @return the user's data, or null if there was a problem.
	 */
	public static UserInfo getMyInfo() {
		return GreenfootUtil.getCurrentUserInfo();
	}

	/**
	 * Store the data to the server.
	 * <p>
	 * You can only store data for the current user (that is, data retrieved
	 * using getMyData). If you try to store data for any user other than the
	 * current user, it is guaranteed to fail.
	 * 
	 * @return true if stored successfully, false if there was a problem.
	 */
	public boolean store() {
		boolean success = GreenfootUtil.storeCurrentUserInfo(this);

		if (success) {
			// Update the rank (not very efficient, but simple):
			rank = getMyInfo().rank;
		}

		return success;
	}

	/**
	 * Get a sorted list of the UserInfo items for this scenario, starting at
	 * the top.
	 * 
	 * <p>
	 * This will return one UserInfo item per user, and it will be sorted in
	 * descending order by the score (i.e. the return of getScore()). The
	 * parameter allows you to specify a limit on the amount of users' data to
	 * retrieve. If there is lots of data stored for users in your app, it may
	 * take some time (and bandwidth) to retrieve all users' data, and often you
	 * do not need all the users' data.
	 * </p>
	 * 
	 * <p>
	 * For example, if you want to show the high-scores, store the score with
	 * setScore(score) and store(), and then use getTop(10) to get the users
	 * with the top ten scores.
	 * </p>
	 * 
	 * <p>
	 * Returns null if:
	 * <ul>
	 * <li>there is a problem reading the local file (for local scenarios), or</li>
	 * <li>the scenario is running as a stand-alone application, or applet on
	 * your own website, or</li>
	 * <li>there is a problem connecting to the server (for scenarios on the
	 * greenfoot.org site).</li>
	 * </ul>
	 * You should always be ready to handle a null return from this function.
	 * </p>
	 * 
	 * @param maxAmount
	 *            The maximum number of data items to retrieve. Passing zero or
	 *            a negative number will get all the data, but see the note
	 *            above.
	 * @return A list where each item is a UserInfo, or null if there was a
	 *         problem
	 */
	@SuppressWarnings("rawtypes")
	public static List getTop(int maxAmount) {
		// Will return an empty list if there is no previously stored data
		// Each item is a UserInfo
		return GreenfootUtil.getTopUserInfo(maxAmount);
	}

	/**
	 * Get a sorted list of the UserInfo items for this scenario surrounding the
	 * current user.
	 * 
	 * <p>
	 * This will be one item per user, and it will be sorted in descending order
	 * by the score (i.e. the return of getScore()). The parameter allows you to
	 * specify a limit on the amount of users' data to retrieve. If there is
	 * lots of data stored for users in your app, this may take some time (and
	 * bandwidth) to retrieve all users' data, and often you do not need all the
	 * users' data.
	 * </p>
	 * 
	 * <p>
	 * The items will be those surrounding the current user. So for example,
	 * imagine that the user is 50th of 100 total users (when sorted by
	 * getScore()). Calling getNearby(5) will get the 48th, 49th, 50th, 51st and
	 * 52nd users in that order. Do not rely on the user being at a fixed
	 * location in the middle of the list: calling getNearby(5) when the user is
	 * 2nd overall will get the 1st, 2nd, 3rd, 4th and 5th users, so the user
	 * will be 2nd in the list, and a similar thing will happen if the user is
	 * near the end of the list.
	 * </p>
	 * 
	 * <p>
	 * For example, if you want to show the high-scores surrounding the user,
	 * store the score with setScore(score) and store(), and then use
	 * getNearby(10) to get the ten users with scores close to the current user.
	 * </p>
	 * 
	 * <p>
	 * Returns null if:
	 * <ul>
	 * <li>there is a problem reading the local file (for local scenarios), or</li>
	 * <li>the scenario is running as a stand-alone application, or applet on
	 * your own website, or</li>
	 * <li>there is a problem connecting to the server (for scenarios on the
	 * greenfoot.org site), or</li>
	 * <li>the user is not logged in (for scenarios on the greenfoot.org site).</li>
	 * </ul>
	 * The last case is very common, so you should always be ready to handle a
	 * null return from this function.
	 * </p>
	 * 
	 * 
	 * @param maxAmount
	 *            The maximum number of data items to retrieve. Passing zero or
	 *            a negative number will get all the data, but see the note
	 *            above.
	 * @return A list where each item is a UserInfo, or null if there was a
	 *         problem
	 */
	@SuppressWarnings("rawtypes")
	public static List getNearby(int maxAmount) {
		return GreenfootUtil.getNearbyUserData(maxAmount);
	}

	/**
	 * Return an image of the user. The image size is 50x50 pixels.
	 * <p>
	 * On the Greenfoot website, this is their profile picture. If running
	 * locally (or a profile picture is unavailable), this method returns a
	 * dummy image with the username drawn on the image.
	 * 
	 * @return A 50x50 pixel GreenfootImage
	 */
	public GreenfootImage getUserImage() {
		return GreenfootUtil.getUserImage(userName);
	}
}
