/**
 * <p>
 * Copyright © 2009-2010, Bruce-Robert Pocock & Res Interactive, LLC.
 * All Rights Reserved. Licensed for perpetual use, modification, and/or
 * distribution by either party.
 * </p>
 * 
 * @author brpocock@star-hope.org
 */
package com.tootsville.user;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.HashMap;
import java.util.Locale;
import java.util.Random;
import java.util.Vector;

import org.json.JSONException;
import org.json.JSONObject;
import org.starhope.appius.except.AlreadyExistsException;
import org.starhope.appius.except.AlreadyUsedException;
import org.starhope.appius.except.DataException;
import org.starhope.appius.except.ForbiddenUserException;
import org.starhope.appius.except.GameLogicException;
import org.starhope.appius.except.NotFoundException;
import org.starhope.appius.except.UserDeadException;
import org.starhope.appius.game.AppiusClaudiusCaecus;
import org.starhope.appius.game.Zone;
import org.starhope.appius.game.inventory.GenericItemReference;
import org.starhope.appius.game.inventory.Inventory;
import org.starhope.appius.game.inventory.InventoryItem;
import org.starhope.appius.game.inventory.InventoryItemType;
import org.starhope.appius.mb.Currency;
import org.starhope.appius.mb.UserAddress;
import org.starhope.appius.mb.UserEnrolment;
import org.starhope.appius.net.ServerThread;
import org.starhope.appius.room.Room;
import org.starhope.appius.types.AgeBracket;
import org.starhope.appius.types.GameWorldMessage;
import org.starhope.appius.user.AvatarClass;
import org.starhope.appius.user.Nomenclator;
import org.starhope.appius.user.User;
import org.starhope.appius.user.UserHouse;
import org.starhope.appius.user.UserRecord;
import org.starhope.appius.user.Wallet;
import org.starhope.appius.user.events.Action;
import org.starhope.appius.user.events.EventRecord;
import org.starhope.appius.user.events.Quaestor;
import org.starhope.appius.util.AppiusConfig;
import org.starhope.util.LibMisc;

import biz.source_code.base64Coder.Base64Coder;

import com.tootsville.promo.PeanutCode;
import com.tootsville.promo.Promotion;

/**
 * A Toot™ character as a specific user within Tootsville™. Contains
 * Tootsville-specific methods that don't belong in the GPL core of
 * Appius. Some of these may be changed to deprecated replacements for
 * the GPL functionality when new ideas, such as currency possessions,
 * are incorporated into the base class.
 * 
 * @author brpocock@star-hope.org
 */
public class Toot extends User {

	/**
	 * Avatar for free members
	 */
	private static final int FREE_AVATAR = 20;

	/**
	 * which avatar ID's are for the basic 8? 1..8 (this is the 8)
	 */
	private static final int HOW_MANY_ARE_BASIC_8 = 8;

	/**
	 * Safety limiter on peanuts
	 */
	// XXX: remove to config value?
	private static final BigInteger peanutLimiter = BigInteger
	.valueOf (
			99999L);

	/**
	 * Avatar for premium members
	 */
	private static final int PREMIUM_AVATAR = 9;

	/**
	 * Java® serialisation unique ID
	 */
	private static final long serialVersionUID = 5113003589233884081L;

	/**
	 * Find the user who is uniquely identified by the given approval
	 * cookie
	 * 
	 * @param cookie The approval cookie used in the eMail
	 * @return the user identified by the cookie
	 * @throws IOException if the cookie can't be decoded
	 * @throws NotFoundException if the user can't be identified
	 */
	public static Toot getByApprovalCookie (final String cookie)
	throws IOException, NotFoundException {
		final String info = Base64Coder.decodeString (cookie);
		final String [] infos = info.split ("/");
		if (infos.length != 3) throw new NotFoundException (cookie);
		AppiusClaudiusCaecus.blather ("DECODE approval cookie ID: "
				+ infos [0] + " Basic 8: " + infos [1] + " User Name: "
				+ infos [2]);
		final Toot tryThis = (Toot) Nomenclator.getUserByID (Integer
				.parseInt (infos [0]));
		if (null == tryThis
				|| tryThis.getBasic8Choice () != Integer
				.parseInt (infos [1])
				|| !tryThis.getUserName ().toLowerCase (Locale.ENGLISH)
				.equals (infos [2])) throw new NotFoundException (cookie);
		return tryThis;
	}

	/**
	 * @param id the message ID to which replies are wanted
	 * @return mail messages posted on the user's wall in reply to the
	 *         given message
	 */
	public static Vector <GameWorldMessage> getMailOnWallInReplyTo (
			final int id) {
		Vector <GameWorldMessage> messages = new Vector <GameWorldMessage> ();
		Connection con = null;
		PreparedStatement st = null;
		try {
			con = AppiusConfig.getDatabaseConnection ();
			st = con
			.prepareStatement ("SELECT * FROM messages WHERE isDeleted='W' AND inReplyTo=? AND body<>'' ORDER BY messages.sentTime ASC");

			st.setInt (1, id);
			messages = AppiusConfig.newGameWorldMessage ()
			.getMessagesFrom (st);
		} catch (final SQLException e) {
			AppiusClaudiusCaecus.reportBug (e);
		} finally {
			LibMisc.closeAll (st, con);
		}
		return messages;
	}

	/**
	 * Z$Z
	 * 
	 * @return Z$Z
	 */
	public static String [] getZ$Z () {
		return new String [] {
				"Fcrpvny Gunaxf Gb\nFntr Fgnexvr, Evpuneq Uneaqra, Zntv Zp Pbexyr, Znqvfba Crppv, Puevf Oheyvatunz, Wbpryla Jvafybj, Tnoevry Jvafybj, Pbql Gnsg, Znel Oynwvna naq rirelbar ryfr!",
				"Gbbgfivyyr Sebag-raq “Crefrcubar” ol\n\tEboreg Qnjfba\nOnfrq hcba 2008 Gbbgfivyyr™ Sebag-raq “Nzcuvovbhf” ol:\n\tPuevf Naqrefba\n\tOenaqba Obbxre\n\tWba Onegyrg\n\tOelna Pbql\n\tOreenr Zrvkfryy\nNaq freire “Avtugzner” ol:\n\tZnex Zp Pbexyr\n\tPney Avpby\n\tOehpr-Eboreg Cbpbpx\n",
				"Gbbgfivyyr Sebag-raq “Crefrcubar VV” ol Fnzrre Wbfuv naq Revp Srvyvat\n\n“Nccvhf Pynhqvhf Pnrphf” tnzr freire ol\n\tOehpr-Eboreg Cbpbpx\n\tCbegvbaf © 2008-2010, Oehpr-Eboreg Cbpbpx\n\t\tyvprafrq haqre gur TAH TCY\n\nZrzorefuvc+Ovyyvat naq GbbgfObbx ol\n\tGvz Urlf naq Rqjneq Jvaxryzna\n",
				"Flfgrzf Nqzvavfgengvba\tTrar Pebax\nBcrengvbaf Znantre\tRzvyl Gnsg\nOvyyvat Znantre\t\t\tRzvyl Qnjfba\nUrnq Yvsrthneq\t\tEvpuneq Uneaqra\nNeg Qverpgbe\t\t\tWbua Zp Xvayrl\nYrnq Navzngbe\t\t\tFrna Xvat\n",
		"\t»\tGbbgfivyyr™\t«\n\nPbclevtug © 2007-2010, Erf Vagrenpgvir, YYP\nNyy Evtugf Erfreirq\n\nQverpgbe\tYbhvf Crppv\nGrpuavpny Qverpgbe\tPuevf Oehaare\nYvar Cebqhpre\tYneel Oretre\nZnexrgvat\tWblpr Crppv, Wraavsre Crgref, Xevfgv Qhaa, Nyrk Crppv, naq Obo Onlqnyr\n" };
	}

	/**
	 * Trailer record for a Toot
	 */
	final TootUserTrailer trailer;

	/**
	 * Instantiate a new Toot, given the user's birthdate, chosen Basic
	 * 8 character, and the requested user name.
	 * 
	 * @param playerDateOfBirth the player's birthdate
	 * @param avatarTitle the player's chosen Basic 8 character
	 * @param userNameRequested the player's requested login / avatar /
	 *            user name
	 * @throws AlreadyUsedException if the user name given is already
	 *             used
	 * @throws ForbiddenUserException if the user account is not
	 *             permitted to be created — e.g. the user name given
	 *             might have been deemed obscene
	 * @throws NumberFormatException if the supplied date of birth is
	 *             “impossible”
	 * @see User#User(Date, String, String, String, String, String)
	 */
	public Toot (final Date playerDateOfBirth,
			final String avatarTitle, final String userNameRequested)
	throws NumberFormatException, AlreadyUsedException,
	ForbiddenUserException {
		this (playerDateOfBirth, avatarTitle, userNameRequested, "\n",
				"(No question given)", "諃諃諄諃誯");
	}

	/**
	 * Instantiate a new Toot, given the user's birthdate, chosen Basic
	 * 8 character, and the requested user name.
	 * 
	 * @param playerDateOfBirth the player's birthdate
	 * @param avatarTitle the player's chosen Basic 8 character
	 * @param userNameRequested the player's requested login / avatar /
	 *            user name
	 * @throws AlreadyUsedException if the user name given is already
	 *             used
	 * @throws ForbiddenUserException if the user account is not
	 *             permitted to be created — e.g. the user name given
	 *             might have been deemed obscene
	 * @throws NumberFormatException if the supplied date of birth is
	 *             “impossible”
	 * @see User#User(Date, String, String, String, String, String)
	 */
	public Toot (final Date playerDateOfBirth,
			final String avatarTitle, final String userNameRequested,
			final String passwordQuestion, final String passwordAnswer,
			final String password)
	throws AlreadyUsedException, ForbiddenUserException,
	NumberFormatException {
		super (playerDateOfBirth, avatarTitle, userNameRequested,
				passwordAnswer, passwordQuestion, password);
		trailer = new TootUserTrailer ();
		trailer.setID (getUserID ());
		trailer.setBasic8Choice (getAvatarClass ().getID ());
		setWallet ();
	}

	/**
	 * Recall a user by ID
	 * 
	 * @param id the user ID
	 * @throws NotFoundException if the user can't be found
	 * @see User#User(int)
	 * @see Nomenclator#getUserByID(int)
	 */
	public Toot (final int id) throws NotFoundException {
		super (id);
		trailer = Nomenclator.getDataRecord (TootUserTrailer.class, id);
		addDefaultFreeItems ();
		setWallet ();
	}

	/**
	 * Simple constructor from login (recalls existing user)
	 * 
	 * @param newUserLogin Requested user login
	 * @throws NotFoundException If the login does not exist already
	 * @see User#User(String)
	 */
	public Toot (final String newUserLogin) throws NotFoundException {
		super (newUserLogin);
		trailer = Nomenclator.getDataRecord (TootUserTrailer.class,
				newUserLogin);
		setWallet ();
	}

	/**
	 * instantiate a Toot from a data record WRITEME Loads the trailer
	 * record, too WRITEME
	 * 
	 * @param rec the user record for the Toot
	 * @throws NotFoundException if the trailer record can't be found
	 * @see User#User(UserRecord)
	 */
	public Toot (final UserRecord rec) throws NotFoundException {
		super (rec);
		trailer = Nomenclator.getDataRecord (TootUserTrailer.class, rec
				.getUserID ());
		setWallet ();
	}

	/**
	 * Take a peanut code and apply the benefits thereof to this user
	 * 
	 * @param peanutSerial the serial number / code sequence in the
	 *            database
	 * @return a user-visible message explaining the benefits that were
	 *         applied
	 * @throws NotFoundException if the peanut code serial number is not
	 *             found
	 * @throws AlreadyUsedException if that peanut code has already been
	 *             used
	 */
	public final String acceptPeanutCode (final String peanutSerial)
	throws NotFoundException, AlreadyUsedException {
		final String peanutCode = peanutSerial
		.toLowerCase (Locale.ENGLISH);
		final BigInteger nuts = PeanutCode.redeemPeanutCode (this,
				peanutCode);
		/* XXX different event type? */
		addPeanuts (nuts, "registration");
		final String prefix = peanutSerial.substring (0, 3);
		try {
			final Promotion promo = Promotion.getByLink (prefix);
			setReferer (promo.getCode ());
		} catch (final NotFoundException e) {
			// ignore, not a promo peanut code.
		}
		return "You got " + nuts.intValue () + " peanuts";
	}

	/**
	 * Add the default free items to the user's inventory
	 */
	private void addDefaultFreeItems () {
		final Inventory inv = getInventory ();
		for (final String code : AppiusConfig
				.getList ("com.tootsville.Toot.freeStuff")) {
			inv.addDefaultFreeItem (Integer.parseInt (code));
		}
	}

	/**
	 * Add a number of peanuts as a gift or other event. (Gifts, use
	 * moniker “gift.” Gifts are refused if the user has already reached
	 * {@link Toot#peanutLimiter})
	 * 
	 * @param add additional peanuts given
	 * @param moniker reason for peanuts
	 */
	public final void addPeanuts (final BigInteger add,
			final String moniker) {
		if ("gift".equals (moniker)) {
			if (getPeanuts ().compareTo (Toot.peanutLimiter) > 0) return;
		}
		EventRecord event;
		try {
			event = Quaestor.startEvent (this, moniker);
		} catch (final AlreadyExistsException e1) {
			AppiusClaudiusCaecus
			.reportBug (
					"Caught a AlreadyExistsException in addPeanuts",
					e1);
			return;
		} catch (final NotFoundException e) {
			AppiusClaudiusCaecus
			.reportBug (
					"Caught a NotFoundException in Toot.addPeanuts ",
					e);
			return;
		}
		event.end (add);
	}

	/**
	 * This is an overriding method.
	 * 
	 * @see org.starhope.appius.user.User#affirmFreeMember()
	 */
	@Override
	public final void affirmFreeMember () {
		if (getAvatarClass ().getID () == Toot.FREE_AVATAR
				|| getAgeGroup () == AgeBracket.System) return;
		blog ("free member; setting to free biped");
		AvatarClass basic8class;
		try {
			basic8class = Nomenclator.getDataRecord (AvatarClass.class,
					getBasic8Choice ());
		} catch (final NotFoundException e) {
			throw AppiusClaudiusCaecus
			.fatalBug (
					"Caught a NotFoundException in Toot.affirmFreeMember ",
					e);
		}
		addDefaultFreeItems ();
		getInventory ().addDefaultFreeItem (
				basic8class.getDefaultPattern ().getItemID (), true)
				.setColour (0, basic8class.getDefaultPatternColor ());
		try {
			setAvatarClass (Nomenclator.getDataRecord (
					AvatarClass.class, Toot.FREE_AVATAR));
		} catch (final NotFoundException e) {
			throw AppiusClaudiusCaecus
			.fatalBug (
					"Caught a NotFoundException in Toot.affirmFreeMember ",
					e);
		}
		super.affirmFreeMember ();
	}

	/**
	 * This is an overriding method.
	 * 
	 * @see org.starhope.appius.user.User#affirmPaidMember()
	 */
	@Override
	public final void affirmPaidMember () {
		super.affirmPaidMember ();
		final int avatarClassID = getAvatarClass ().getID ();
		if (avatarClassID >= 1
				&& avatarClassID <= Toot.HOW_MANY_ARE_BASIC_8
				|| avatarClassID == Toot.FREE_AVATAR) {
			addDefaultFreeItems ();
			final AvatarClass oldAvatar = getAvatarClass ();
			AvatarClass baseAvatar;
			try {
				baseAvatar = Nomenclator.getDataRecord (
						AvatarClass.class, getBasic8Choice ());
				setAvatarClass (Nomenclator.getDataRecord (
						AvatarClass.class, Toot.PREMIUM_AVATAR));
			} catch (final NotFoundException e) {
				throw AppiusClaudiusCaecus
				.fatalBug (
						"Caught a NotFoundException in Toot.affirmPaidMember ",
						e);
			}
			setBaseColor (oldAvatar.getDefaultBaseColor ());
			setExtraColor (oldAvatar.getDefaultExtraColor ());
			try {
				getActiveItemsByType (Nomenclator.getDataRecord (
						InventoryItemType.class, "Pattern"));
			} catch (final NotFoundException e) {
				AppiusClaudiusCaecus
				.reportBug (
						"Caught a NotFoundException in Toot.affirmPaidMember ",
						e);
			}
			final GenericItemReference pattern = baseAvatar.getDefaultPattern ();
			blog ("basic 8:" + getBasic8Choice () + " avatar: "
					+ baseAvatar.toString () + " pattern: " + pattern);
			final InventoryItem gotPattern = getInventory ()
			.addDefaultFreeItem (pattern.getItemID ());
			try {
				getInventory ().doff (
						Nomenclator.getDataRecord (
								InventoryItemType.class, "pattern"));
			} catch (final NotFoundException e) {
				AppiusClaudiusCaecus.reportBug (
						"I forget what patterns are?", e);
			}
			getInventory ().don (gotPattern,
					baseAvatar.getDefaultPatternColor ());
		}
	}

	/**
	 * Send a biff message to announce the number of message in a user's
	 * Inbox.
	 * 
	 * @param abstractZone The zone in which the user is standing
	 * @param room The room in which the user is standing
	 * @throws JSONException If the biff can't be sent
	 */
	public void biff (final Zone abstractZone, final Room room)
	throws JSONException {
		final JSONObject results = new JSONObject ();
		results.put ("newMail", getInboxCount ());
		acceptSuccessReply ("postman", results, room);
	}

	/**
	 * Get bare
	 */
	public void doffPatterns () {
		for (final InventoryItem item : this
				.getItemsByType ("patterns")) {
			getInventory ().doff (item);
		}
	}

	/**
	 * Remove Pivitz
	 */
	public void doffPivitz () {
		for (final InventoryItem item : this.getItemsByType ("pivitz")) {
			getInventory ().doff (item);
		}
	}

	/**
	 * This is an overriding method.
	 * 
	 * @see org.starhope.appius.user.User#doTransport()
	 */
	@Override
	public void doTransport () {
		if (AppiusConfig
				.getConfigBoolOrFalse ("com.tootsville.bugs.noEmotes")) {
			speak (getRoom (), "Fwoosh!");
		} else {
			speak (getRoom (), "/zapattack");
		}
	}

	/**
	 * Retrieve a number of of MailMessages starting with the most
	 * recent.
	 * 
	 * @param numberOfMessages The number of posts needed.
	 * @return mail messages posted on the user's wall
	 */
	public Vector <GameWorldMessage> getAllMailOnWall (
			final int numberOfMessages) {
		return getMailOnWallAndBuddyWall (0, numberOfMessages);
	}

	/**
	 * @return Get a cookie to be used in creating the user's
	 *         parent/self-approval eMails.
	 */
	@Override
	public String getApprovalCookie () {
		return Base64Coder
		.encodeString ( (getUserID () + "/"
				+ trailer.getBasic8Choice () + "/" + getRequestedName ()
				.toLowerCase (Locale.ENGLISH)));
	}

	/**
	 * @return the basic8Choice
	 */
	public int getBasic8Choice () {
		return trailer.getBasic8Choice ();
	}

	/**
	 * Get the type of house (exterior frame)
	 * 
	 * @return the ID of the house exterior frame chosen by the user, or
	 *         -1 if the user hasn't chosen one yet.
	 * @deprecated use {@link UserHouse#getHouseTypeID()}
	 */
	@Deprecated
	public int getHouseTypeID () {
		return getHouse ().getHouseTypeID ();
	}

	/**
	 * <p>
	 * XXX: contains SQL
	 * </p>
	 * 
	 * @return Get the number of message in the Toot's Inbox
	 */
	public int getInboxCount () {
		Connection con = null;
		PreparedStatement st = null;
		ResultSet inboxCount = null;
		int count = 0;
		try {
			con = AppiusConfig.getDatabaseConnection ();
			st = con
			.prepareStatement ("SELECT COUNT(*) FROM messages WHERE readTime IS NULL AND isDeleted=' ' AND toUserID=?");

			st.setInt (1, getUserID ());
			inboxCount = st.executeQuery ();
			inboxCount.next ();
			count = inboxCount.getInt (1);
		} catch (final SQLException e) {
			AppiusClaudiusCaecus
			.reportBug ("Can't get unread message count for "
					+ getLogin (), e);
		} finally {
			if (null != inboxCount) {
				try {
					inboxCount.close ();
				} catch (final SQLException e) { /* No Op */
				}
			}
			if (null != st) {
				try {
					st.close ();
				} catch (final SQLException e) { /* No Op */
				}
			}
			if (null != con) {
				try {
					con.close ();
				} catch (final SQLException e) { /* No Op */
				}
			}
		}
		return count;
	}

	/**
	 * Get the ID of the type of lot (neighbourhood) for the user's
	 * house. (Usually not set)
	 * 
	 * @return The lot ID
	 * @deprecated use {@link UserHouse#getLotID()}
	 */
	@Deprecated
	public int getLotID () {
		return getHouse ().getLotID ();
	}

	/**
	 * @return all messages in the user's mailbox
	 */
	public Vector <GameWorldMessage> getMailInBox () {
		return getMailInBox (0, Integer.MAX_VALUE);
	}

	/**
	 * <p>
	 * XXX: Contains SQL
	 * </p>
	 * 
	 * @param limit the maximum number of messages to be returned
	 * @param offset the first message index to return
	 * @return mail messages in the user's inbox
	 */
	public Vector <GameWorldMessage> getMailInBox (final int offset,
			final int limit) {
		Vector <GameWorldMessage> messages = new Vector <GameWorldMessage> ();
		Connection con = null;
		PreparedStatement st = null;
		try {
			con = AppiusConfig.getDatabaseConnection ();
			st = con
			.prepareStatement ("SELECT * FROM messages WHERE toUserID=? AND isDeleted=' ' ORDER BY messages.sentTime DESC LIMIT ? OFFSET ?");
			st.setInt (1, getUserID ());
			st.setInt (2, limit);
			st.setInt (3, offset);
			messages = AppiusConfig.newGameWorldMessage ()
			.getMessagesFrom (st);
		} catch (final SQLException e) {
			AppiusClaudiusCaecus.reportBug (e);
		} finally {
			if (null != st) {
				try {
					st.close ();
				} catch (final SQLException e) { /* No Op */
				}
			}
			if (null != con) {
				try {
					con.close ();
				} catch (final SQLException e) { /* No Op */
				}
			}

		}
		return messages;
	}

	/**
	 * Retrieve a number of of MailMessages starting with the most
	 * recent.
	 * 
	 * @param numberOfMessages The number of posts needed.
	 * @return mail messages posted on the user's wall
	 * @deprecated use {@link #getMailOnWall(int, int)}
	 */
	@Deprecated
	public Vector <GameWorldMessage> getMailOnMyWall (
			final int numberOfMessages) {
		return getMailOnMyWall (0, numberOfMessages);
	}

	/**
	 * Retrieve a number of of MailMessages starting with the most
	 * recent.
	 * 
	 * @param numberOfMessages The number of posts needed.
	 * @param start the first message (counting backwards in time from
	 *            the latest)
	 * @return mail messages posted on the user's wall
	 * @deprecated use {@link #getMailOnWall(int, int)}
	 */
	@Deprecated
	public Vector <GameWorldMessage> getMailOnMyWall (
			final int numberOfMessages, final int start) {
		return getMailOnWall (numberOfMessages, start);
	}

	/**
	 * <p>
	 * XXX:contains SQL
	 * </p>
	 * 
	 * @param offset the first message to fetch (counting from 0 being
	 *            the user's first wall message ever)
	 * @param limit the last message to fetch. If there are fewer
	 *            messages than this index, you may receive less
	 *            results.
	 * @return mail messages posted on the user's wall
	 */
	public Vector <GameWorldMessage> getMailOnWall (final int limit,
			final int offset) {
		Vector <GameWorldMessage> messages = new Vector <GameWorldMessage> ();
		Connection con = null;
		PreparedStatement st = null;
		ResultSet rs = null;
		try {
			con = AppiusConfig.getDatabaseConnection ();
			st = con
			.prepareStatement ("SELECT * FROM messages WHERE (toUserID=? OR (toUserID=1 AND fromUserID=?)) AND isDeleted='W' AND inReplyTo IS NULL AND body<>'' ORDER BY messages.sentTime DESC LIMIT ? OFFSET ?");
			st.setInt (1, getUserID ());
			st.setInt (2, getUserID ());
			st.setInt (3, limit);
			st.setInt (4, offset);
			rs = st.executeQuery ();
			messages = AppiusConfig.newGameWorldMessage ()
			.getMessagesFrom (st);
		} catch (final SQLException e) {
			AppiusClaudiusCaecus.reportBug (e);
		} finally {
			if (null != rs) {
				try {
					rs.close ();
				} catch (final SQLException e) { /* No Op */
				}
			}
			if (null != st) {
				try {
					st.close ();
				} catch (final SQLException e) { /* No Op */
				}
			}
			if (null != con) {
				try {
					con.close ();
				} catch (final SQLException e) { /* No Op */
				}
			}
		}
		return messages;
	}

	/**
	 * <pre>
	 * twheys@gmail.com Jan 15, 2010
	 * </pre>
	 * 
	 * TO getMailOnWallAndBuddyWall WRITEME...
	 * 
	 * @param offset WRITEME twheys@gmail.com
	 * @param limit WRITEME twheys@gmail.com
	 * @return WRITEME twheys@gmail.com
	 */
	public Vector <GameWorldMessage> getMailOnWallAndBuddyWall (
			final int offset, final int limit) {
		Vector <GameWorldMessage> messages = new Vector <GameWorldMessage> ();
		Connection con = null;
		PreparedStatement st = null;
		ResultSet rs = null;
		try {
			con = AppiusConfig.getDatabaseConnection ();
			st = con
			.prepareStatement ("SELECT * FROM messages WHERE ((fromUserID IN (SELECT buddyID FROM buddyList WHERE userID=?) OR fromUserID=? OR fromUserID IN (SELECT ID FROM users WHERE notable='Y')) AND (fromUserID=toUserID OR toUserID=1) AND isDeleted='W' AND inReplyTo IS NULL AND body<>'') ORDER BY messages.sentTime DESC LIMIT ? OFFSET ?");
			st.setInt (1, getUserID ());
			st.setInt (2, getUserID ());
			st.setInt (3, limit);
			st.setInt (4, offset);
			rs = st.executeQuery ();
			messages = AppiusConfig.newGameWorldMessage ()
			.getMessagesFrom (st);
		} catch (final SQLException e) {
			AppiusClaudiusCaecus.reportBug (e);
		} finally {
			if (null != rs) {
				try {
					rs.close ();
				} catch (final SQLException e) { /* No Op */
				}
			}
			if (null != st) {
				try {
					st.close ();
				} catch (final SQLException e) { /* No Op */
				}
			}
			if (null != con) {
				try {
					con.close ();
				} catch (final SQLException e) { /* No Op */
				}
			}
		}
		return messages;
	}

	/**
	 * @param slotNumber WRITEME
	 * @return WRITEME
	 * @throws NotFoundException WRITEME
	 */
	// public TootBookTheme getTootBookThemeItemBySlot (
	// final int slotNumber) throws NotFoundException {
	// final Iterator <InventoryItem> inv = getInventory ()
	// .iterator ();
	// while (inv.hasNext ()) {
	// final InventoryItem item = inv.next ();
	// if (item instanceof TootBookTheme) {
	// final TootBookTheme tootBookTheme = (TootBookTheme) item;
	// if (slotNumber == tootBookTheme.getSlotNumber ()) {
	// return tootBookTheme;
	// }
	// }
	// }
	// blog (" No furniture in slot " + slotNumber
	// + " is found in inventory");
	// throw new NotFoundException (String.valueOf (slotNumber));
	// }

	/**
	 * @see org.starhope.appius.user.AbstractUser#getMoney(org.starhope.appius.mb.Currency)
	 */
	@Override
	@Deprecated
	public BigInteger getMoney (final Currency currency) {
		// if (Currency.getPeanuts ().equals (currency)) {
		return getPeanuts ();
		// }
		// return BigDecimal.ZERO;
	}

	/**
	 * <pre>
	 * twheys@gmail.com Feb 12, 2010
	 * </pre>
	 * 
	 * TO getNewMailOnWall get mail on Wall that has an ID greater than
	 * newerThanID WRITEME
	 * 
	 * @param newerThanID WRITEME
	 * @return new posts or an empty vector
	 */
	public Vector <GameWorldMessage> getNewMailOnWall (
			final int newerThanID) {
		Vector <GameWorldMessage> messages = new Vector <GameWorldMessage> ();
		Connection con = null;
		PreparedStatement st = null;
		ResultSet rs = null;
		try {
			con = AppiusConfig.getDatabaseConnection ();
			st = con
			.prepareStatement ("SELECT * FROM messages WHERE ((toUserID=? AND fromUserID<>toUserID) OR (toUserID=1 AND fromUserID=?)) AND isDeleted='W' AND inReplyTo IS NULL AND body<>'' AND ID>? ORDER BY messages.sentTime DESC");
			st.setInt (1, getUserID ());
			st.setInt (2, getUserID ());
			st.setInt (3, newerThanID);
			rs = st.executeQuery ();
			messages = AppiusConfig.newGameWorldMessage ()
			.getMessagesFrom (st);
		} catch (final SQLException e) {
			AppiusClaudiusCaecus.reportBug (e);
		} finally {
			LibMisc.closeAll (rs, st, con);
		}
		return messages;
	}

	/**
	 * <pre>
	 * twheys@gmail.com Feb 12, 2010
	 * </pre>
	 * 
	 * TO getNewMailOnWallAndBuddyWall get mail on Wall or Buddy Walls
	 * that has an ID greater than newerThanID WRITEME
	 * 
	 * @param newerThanID WRITEME
	 * @return new posts or an empty vector
	 */
	public Vector <GameWorldMessage> getNewMailOnWallAndBuddyWall (
			final int newerThanID) {
		Vector <GameWorldMessage> messages = new Vector <GameWorldMessage> ();
		Connection con = null;
		PreparedStatement st = null;
		ResultSet rs = null;
		try {
			con = AppiusConfig.getDatabaseConnection ();
			st = con
			.prepareStatement ("SELECT * FROM messages WHERE ((fromUserID IN (SELECT buddyID FROM buddyList WHERE userID=?) OR fromUserID IN (SELECT ID FROM users WHERE notable='Y')) AND fromUserID<>? AND (fromUserID=toUserID OR toUserID=1) AND isDeleted='W' AND inReplyTo IS NULL AND body<>'') AND ID>? ORDER BY messages.sentTime DESC");
			st.setInt (1, getUserID ());
			st.setInt (2, getUserID ());
			st.setInt (3, newerThanID);
			rs = st.executeQuery ();
			messages = AppiusConfig.newGameWorldMessage ()
			.getMessagesFrom (st);
		} catch (final SQLException e) {
			AppiusClaudiusCaecus.reportBug (e);
		} finally {
			LibMisc.closeAll (rs, st, con);
		}
		return messages;
	}

	/**
	 * @return the amount of peanuts (currency) that this user has
	 */
	public BigInteger getPeanuts () {
		return getWallet ().get (Currency.getPeanuts ())
		.toBigInteger ();
	}

	/**
	 * @see org.starhope.appius.user.User#getSubversionRevision()
	 */
	@Override
	public String getSubversionRevision () {
		return "$Rev: 4224 $";
	}

	/**
	 * @return The last post this user wrote on their own wall.
	 */
	public String getTootBookStatus () {
		Connection con = null;
		PreparedStatement st = null;
		ResultSet rs = null;
		try {
			con = AppiusConfig.getDatabaseConnection ();
			st = con
			.prepareStatement ("SELECT body FROM messages WHERE toUserID=? AND isDeleted='W' AND fromUserID=toUserID AND inReplyTo IS NULL AND body<>'' ORDER BY sentTime DESC LIMIT 1");

			st.setInt (1, getUserID ());
			if (st.execute ()) {
				rs = st.getResultSet ();
				if (rs.next ()) return rs.getString ("body");
			}
		} catch (final SQLException e) {
			AppiusClaudiusCaecus.reportBug (e);
		} finally {
			LibMisc.closeAll (rs, st, con);
		}
		return LibMisc.getText ("com.tootsville.tootsbook.status");
	}

	/**
	 * @return hash map of display names and URLs of available themes
	 *         that this user owns
	 */
	@Deprecated
	public HashMap <String, String> getTootsBookThemes () {
		return new HashMap <String, String> ();
	}

	/**
	 * Get the URL for the active TootBook theme folder for this user.
	 * 
	 * @return relative URL for the theme folder
	 */
	public String getTootsBookThemeURL () {
		return "default";
	}

	/**
	 * @return the toot time left in hours (without minutes)
	 */
	public int getTootTimeLeft () {
		return trailer.getTootTimeLeft ();
	}

	/**
	 * @return the toot time remaining as hours & minutes delimited by
	 *         "hr " and "min"
	 */
	public String getTootTimeLeft$ () {
		return trailer.getTootTimeLeft () + "hr "
		+ trailer.getTootTimeLeftMinutes () + "min";
	}

	/**
	 * @return the toot time left minutes (after removing hours)
	 */
	public int getTootTimeLeftMinutes () {
		return trailer.getTootTimeLeftMinutes ();
	}

	// /**
	// * WRITEME: document this method (brpocock@star-hope.org, Aug 28,
	// * 2009)
	// *
	// * @param item WRITEME
	// * @throws AlreadyExistsException WRITEME
	// * @throws NotFoundException WRITEME
	// * @throws NonSufficientFundsException WRITEME
	// */
	// public void purchase (final AbstractItem item)
	// throws AlreadyExistsException, NotFoundException,
	// NonSufficientFundsException {
	// endEventPurchaseRaw (startEventRaw ("purchase"), item);
	// }

	// /**
	// * WRITEME: document this method (brpocock@star-hope.org, Dec 1,
	// 2009)
	// *
	// * @param getHighScores WRITEME
	// * @param wrapper WRITEME
	// * @param eventID WRITEME
	// * @throws SQLException WRITEME
	// * @throws JSONException WRITEME
	// */
	// private void pushHighScoresIntoJSON (
	// final PreparedStatement getHighScores,
	// final JSONObject wrapper, final int eventID)
	// throws SQLException, JSONException {
	// final JSONObject highScoreList = new JSONObject ();
	//
	// final ResultSet scoreFetcher = getHighScores.getResultSet ();
	// int i = 1;
	// while (scoreFetcher.next ()) {
	// final JSONObject scoreInfo = new JSONObject ();
	// final int thatID = scoreFetcher.getInt ("ID");
	// BigDecimal scorePoints = scoreFetcher
	// .getBigDecimal ("points");
	// if (scoreFetcher.wasNull ()) {
	// scorePoints = BigDecimal.ZERO;
	// }
	// if (scorePoints.compareTo (BigDecimal.ZERO) > 0) {
	// if (eventID == thatID) {
	// wrapper.put ("gotHighScore", i);
	// if (AppiusConfig
	// .getConfigBoolOrFalse ("com.tootsbook.system.event.highscore")) {
	// final String gameName = wrapper
	// .getString ("gameName");
	// sendSystemWallMail (
	// LibMisc
	// .getText ("com.tootsbook.system.event.highscore"),
	// AppiusConfig
	// .getConfigOrDefault (
	// "com.tootsbook.system.event.images.highscore",
	// ""), gameName);
	// }
	// }
	// scoreInfo.put ("points", scorePoints.doubleValue ());
	// scoreInfo.put ("ID", thatID);
	// scoreInfo.put ("userName", scoreFetcher
	// .getString ("userName"));
	// highScoreList.put (String.valueOf (i++ ), scoreInfo);
	// }
	// }
	//
	// wrapper.put ("highScores", highScoreList);
	// }

	// /**
	// * Get high score information from the database and return it as a
	// * JSON object
	// *
	// * @param eventID an event of the type for which we want the top
	// 24
	// * scores
	// * @param wrapper the JSON container to absorb the high score list
	// */
	// protected void putEventHighScoresIntoJSON (final int eventID,
	// final JSONObject wrapper) {
	// PreparedStatement getHighScores = null;
	// Connection con = null;
	// try {
	// con = AppiusConfig.getDatabaseConnection ();
	// getHighScores = con
	// .prepareStatement
	// ("SELECT events.ID AS ID, points, userName FROM events LEFT JOIN users ON users.ID = events.creatorID WHERE eventTypeID = ( SELECT eventTypeID FROM events AS e2 WHERE e2.ID = ? ) AND completionTimestamp > DATE(NOW() - INTERVAL 1 MONTH) ORDER BY points DESC LIMIT 24");
	// getHighScores.setInt (1, eventID);
	// if (getHighScores.execute ()) {
	// pushHighScoresIntoJSON (getHighScores, wrapper, eventID);
	// }
	// } catch (final JSONException e) {
	// AppiusClaudiusCaecus.reportBug (
	// "Caught a Exception in getHighScores", e);
	// } catch (final SQLException e) {
	// AppiusClaudiusCaecus.reportBug (
	// "Caught a Exception in getHighScores", e);
	// } finally {
	// LibMisc.closeAll (getHighScores, con);
	// }
	// }

	/**
	 * @return the tootTimeRefill
	 */
	public int getTootTimeRefill () {
		return trailer.getTootTimeRefill ();
	}

	/**
	 * @return the Toot time left in minutes, including hours
	 */
	public int getTotalTootTimeLeftMinuts () {
		return trailer.getTootTimeLeft () * 60
		+ trailer.getTootTimeLeftMinutes ();
	}

	/**
	 * Give a gift of peanuts. Or, take away peanuts. This sends the
	 * Earnings balloon message to the user.
	 * 
	 * @param numNuts WRITEME
	 * @param event event string
	 * @throws AlreadyExistsException WRITEME
	 * @throws JSONException WRITEME
	 */
	public void giftPeanuts (final BigInteger numNuts,
			final String event) throws AlreadyExistsException,
			JSONException {
		addPeanuts (numNuts, event);
		if (isOnline ()) {
			sendEarnings (getRoom (), Currency.getPeanuts (),
					new BigDecimal (numNuts));
		}
	}

	/**
	 * @return true, if the Toot Timer resets every day for this user.
	 */
	public boolean isTootTimerDay () {
		return trailer.isTootTimerDay ();
	}

	/**
	 * @return true, if the Toot Timer resets every week for this user.
	 */
	public boolean isTootTimerMonth () {
		return trailer.isTootTimerMonth ();
	}

	/**
	 * This is an overriding method.
	 * 
	 * @see org.starhope.appius.user.User#local_create()
	 */
	@Override
	public void local_create () {
		addPeanuts (BigInteger.valueOf (AppiusConfig.getIntOrDefault (
				"com.tootsville.registration.freePeanuts", 500)),
		"registration");

		addDefaultFreeItems ();
	}

	/**
	 * This is an overriding method.
	 * 
	 * @see org.starhope.appius.user.User#postLoginGlobal()
	 */
	@Override
	public void postLoginGlobal () {
		super.postLoginGlobal ();
		addDefaultFreeItems ();
	}

	/**
	 * Post a new message on this users TootBook wall.
	 * 
	 * @param toID The ID of the User who's being posted to
	 * @param body The body of the message being sent.
	 * @return The ID of the new post.
	 * @throws DataException If the replyToID does not resolve a
	 *             message, or if the message fails to send.
	 * @throws GameLogicException if the mail fails the filters. XXX
	 *             Filter exceptions
	 */
	public int postNewOnTootBookWall (final int toID, final String body)
	throws DataException, GameLogicException {
		return sendWallMail (toID, body, -1);
	}

	/**
	 * Post a reply to a post on a TootBook wall.
	 * 
	 * @param body The message to post.
	 * @param replyToID the ID of the post being replied to.
	 * @return The ID of the new post.
	 * @throws DataException If the replyToID does not resolve a
	 *             message, or if the message fails to send.
	 * @throws GameLogicException If the mail fails the filters. XXX
	 *             filter exceptions
	 */
	public int postReplyOnTootBookWall (final String body,
			final int replyToID) throws DataException,
			GameLogicException {
		try {
			return sendWallMail (AppiusConfig.newGameWorldMessage ()
					.getByID (replyToID).getToID (), body, replyToID);
		} catch (final NotFoundException e) {
			AppiusClaudiusCaecus
			.reportBug ("Unable to post a reply to Mail ID#"
					+ replyToID, e);
			throw new DataException ("No post exists with ID: "
					+ replyToID);
		}
	}

	/**
	 * Send an earnings notice to the client. This pops up the blue
	 * “thought bubble” just like a fountain event.
	 * 
	 * @param room WRITEME
	 * @param msg WRITEME
	 */
	@Override
	public void sendEarnings (final Room room, final String msg) {
		final JSONObject result = new JSONObject ();
		try {
			result.put ("msg", msg);
			result.put ("totalPeanuts", getPeanuts ().toString ());
			result.put ("status", true);
			result.put ("from", "earning");
			if (AppiusConfig
					.getConfigBoolOrTrue ("com.tootsville.bugs.noEarnings")) {
				result.put ("from", "pub");
				result.put ("u", getAvatarLabel ());
				result.put ("id", getUserID ());
				result.put ("t", msg);
			}
		} catch (final JSONException e) {
			AppiusClaudiusCaecus.reportBug (e);
		}
		final ServerThread serverThread = getServerThread ();
		if (null != serverThread) {
			try {
				serverThread.sendResponse (result, room.getID (), true);
			} catch (final UserDeadException e) {
				// don't care
			}
		}
	}

	/**
	 * Send an in-game eMail message
	 * 
	 * @param to The user to receive the message
	 * @param subject The subject of the message
	 * @param body The body/contents of the message
	 * @return true, if the message was sent successfully.
	 */
	private boolean sendMail (final int to, final String subject,
			final String body) {
		final GameWorldMessage msg = AppiusConfig
		.newGameWorldMessage ();
		msg.setFrom (this);
		msg.setSubject (subject);
		msg.setSent (new Timestamp (System.currentTimeMillis ()));
		msg.setBody (body);
		msg.setToID (to);
		Quaestor.getDefault ().action (
				new Action (this, "mail.sent", Nomenclator
						.getUserByID (to), subject, msg));
		return msg.send ();
	}

	/**
	 * @param to WRITEME
	 * @param subject WRITEME
	 * @param body WRITEME
	 * @return WRITEME
	 */
	public boolean sendMail (final String to, final String subject,
			final String body) {
		final int toID = Nomenclator.getUserIDForLogin (to);
		return this.sendMail (toID, subject, body);
	}

	/**
	 * Send a promotional plush Toot to this user, at the specified
	 * address.
	 * 
	 * @param shippingAddress the address to which to ship the plush
	 *            Toot
	 * @param tootSelect the Toot selected
	 * @throws SQLException if the order can't be inserted into shipping
	 * @throws GameLogicException if the user attempts to send the plush
	 *             Toot to someone else
	 */
	public void sendPlushTootPromo (final UserAddress shippingAddress,
			final int tootSelect) throws SQLException,
			GameLogicException {
		if ( !shippingAddress.getUser ().equals (this)) throw new GameLogicException ("refuse to send promo Toot ",
				shippingAddress.getUser (), this);
		PreparedStatement ship = null;

		Connection con = null;
		try {
			con = AppiusConfig.getDatabaseConnection ();
			ship = con
			.prepareStatement ("INSERT INTO shipping (name, address, city, state, zip_code, toot, userID, ordered) VALUES (?,?,?,?,?,?,?,NOW())");
			ship.setString (1, getDisplayName ());
			ship.setString (2, shippingAddress.getAddressPair ());
			ship.setString (3, shippingAddress.getCity ());
			ship.setString (4, shippingAddress.getProvince ());
			ship.setString (5, shippingAddress.getPostalCode ());
			try {
				ship.setString (6, Nomenclator.getDataRecord (
						AvatarClass.class, tootSelect).getName ());
			} catch (final NotFoundException e) {
				AppiusClaudiusCaecus
				.reportBug (
						"Caught a NotFoundException in Toot.sendPlushTootPromo ",
						e);
				ship.setString (6, "Doodle");
			}
			ship.setInt (7, getUserID ());
			ship.execute ();
		} catch (final SQLException e) {
			AppiusClaudiusCaecus.reportBug (e);
			throw e;
		} finally {
			LibMisc.closeAll (ship, con);
		}

		Quaestor.getDefault ()
		.action (
				new Action (this, "TOOT/free.toot",
						"A Toot plush is on its way!",
						shippingAddress));
	}

	/**
	 * <pre>
	 * twheys@gmail.com Feb 18, 2010
	 * </pre>
	 * 
	 * TO sendSystemWallMail send a mail from THIS user to $Quicksilver
	 * WRITEME
	 * 
	 * @param message WRITEME
	 * @param attachment WRITEME
	 * @param game WRITEME
	 * @return true if message sent successfully.
	 */
	public boolean sendSystemWallMail (final String message,
			final String attachment, final String game) {
		final GameWorldMessage msg = AppiusConfig
		.newGameWorldMessage ();
		final String systemMessage = message.replace ("$username",
				getUserName ()).replace ("$game", game);
		blog ("Sending system message to TootsBook:\n" + systemMessage);
		msg.setFrom (this);
		msg.setAttachmentURL (attachment);
		msg.setSubject ("");
		msg.setWallPost (true);
		msg.setSent (new Timestamp (System.currentTimeMillis ()));
		msg.setBody (systemMessage);
		msg.setToID (1);
		return msg.send ();
	}

	/**
	 * Creates a new post on a TootBook Profile.
	 * 
	 * @param toID The ID of the User who is receiving this post.
	 * @param body The body/contents of the message
	 * @param inReplyTo The ID of the post that is being replied to. -1
	 *            if this is not a reply.
	 * @return the ID of the new post.
	 * @throws DataException If the post fails to send.
	 * @throws GameLogicException If the post fails the filters.
	 */
	private int sendWallMail (final int toID, final String body,
			final int inReplyTo) throws DataException,
			GameLogicException {
		final GameWorldMessage msg = AppiusConfig
		.newGameWorldMessage ();
		msg.setFrom (this);
		msg.setSent (new Timestamp (System.currentTimeMillis ()));
		msg.setBody (body);
		msg.setWallPost (true);
		msg.setToID (toID);
		msg.setInReplyTo (inReplyTo);
		if ( !msg.send ()) throw new DataException ("Failed to post new wall message.");
		return msg.getID ();
	}

	/**
	 * @param newToot the basic8Choice to set
	 */
	public void setBasic8Choice (final int newToot) {
		trailer.setBasic8Choice (newToot);
	}

	/**
	 * This is an overriding method.
	 * 
	 * @see org.starhope.appius.user.User#setReferer(java.lang.String)
	 */
	@Override
	public void setReferer (final String theReferer) {
		if (null == getReferer ()) {
			if ("mimo".equals (theReferer)) {
				final Random rnd = new Random ();
				final int itemNumber = 1108 + rnd.nextInt (3);
				switch (itemNumber) {
					case 1108:
						blog ("A new Mimo Toot gets a Mimo Pivitz.");
						break;
					case 1109:
						blog ("A new Mimo Toot gets a disco Pivitz.");
						break;
					case 1110:
						blog ("A new Mimo Toot gets a popcorn Pivitz.");
						break;
					default:
						blog ("A new Mimo Toot fails to get an expected Pivitz");
				}
				getInventory ().addDefaultFreeItem (itemNumber);
				final InventoryItem mimoPivitz = InventoryItem
				.getByID (itemNumber);
				getInventory ().don (mimoPivitz, null);
			}
		}
		super.setReferer (theReferer);
	}

	/**
	 * @param useDailyTimer the tootTimerDay to set
	 */
	public void setTootTimerDay (final boolean useDailyTimer) {
		trailer.setTootTimerDay (useDailyTimer);
	}

	/**
	 * @param refillAmount the tootTimeRefill to set
	 */
	public void setTootTimeRefill (final int refillAmount) {
		trailer.setTootTimeRefill (refillAmount);
	}

	/**
	 * @param useMonthlyTimer the tootTimerMonth to set
	 */
	public void setTootTimerMonth (final boolean useMonthlyTimer) {
		trailer.setTootTimerMonth (useMonthlyTimer);
	}

	/**
	 * WRITEME brpocock@star-hope.org Aug 3, 2010
	 */
	private void setWallet () {
		final Wallet wallet = Wallet.forUser (userRecord);
		wallet.put (Currency.getPeanuts (), new BigDecimal (
				getPeanuts ()));
	}

	/**
	 * WRITEME: document this method (brpocock@star-hope.org, Sep 8,
	 * 2009)
	 * 
	 * @param moniker WRITEME
	 */
	public void stampPassport (final String moniker) {
		Connection con = null;
		PreparedStatement st = null;
		try {
			con = AppiusConfig.getDatabaseConnection ();
			st = con
			.prepareStatement ("INSERT IGNORE INTO userWorlds (userID, worldID) SELECT ?, roomList.ID FROM roomList WHERE moniker=?");
			st.setInt (1, getUserID ());
			st.setString (2, moniker);
			st.executeUpdate ();
		} catch (final SQLException e) {
			AppiusClaudiusCaecus.reportBug (
					"trouble stamping passport for " + moniker, e);
		} finally {
			LibMisc.closeAll (st, con);
		}
	}

	/**
	 * This is an overriding method.
	 * 
	 * @see org.starhope.appius.user.User#startEnrolment(org.starhope.appius.mb.UserEnrolment)
	 */
	@Override
	public void startEnrolment (final UserEnrolment userEnrolment) {
		super.startEnrolment (userEnrolment);
		final int productID = userEnrolment.getProductID ();
		final String orderSource = userEnrolment.getOrderSource ();
		if (10 == productID && "auth".equals (orderSource)) {
			addPeanuts (BigInteger.valueOf (7000), "subscription");
		} else if (2 == productID && "auth".equals (orderSource)) {
			addPeanuts (BigInteger.valueOf (5000), "subscription");
		} else if (3 == productID && "auth".equals (orderSource)) {
			addPeanuts (BigInteger.valueOf (7000), "subscription");
		}
	}

	/**
	 * <p>
	 * Sends a limited subset of data for the client to know. This is
	 * the information which the client is allowed to know about their
	 * own user account; this is a superset of the information returned
	 * by {@link #getPublicInfo()}, which is the information which any
	 * other user can obtain about it. The information from
	 * {@link #getPublicInfo()} is contained in the “avatar” key of this
	 * method's return values.
	 * </p>
	 * 
	 * @return A subset of the user record as a JSON object:
	 *         <dl>
	 *         <dt>avatar</dt>
	 *         <dd>See {@link #getPublicInfo()}</dd>
	 *         <dt>isBirthday</dt>
	 *         <dd>True or false: is today this user's birthday</dd>
	 *         <dt>isMember</dt>
	 *         <dd>True, if the user is a paid member</dd>
	 *         <dt>canEnterChatZone</dt>
	 *         <dd>True, if the user can enter a Zone marked for public
	 *         (free-form) chat</dd>
	 *         <dt>canEnterMenuZone</dt>
	 *         <dd>True, if the user can enter a Zone marked for chat
	 *         only by selections from menus or icons (no typing
	 *         free-form chat)</dd>
	 *         <dt>canTalk</dt>
	 *         <dd>True, if the user is permitted to type (talk)</dd>
	 *         <dt>language_dialect</dt>
	 *         <dd>The ISO language code and dialect code. For example,
	 *         “en_US”</dd>
	 *         <dt>needsParent</dt>
	 *         <dd>True, if the user needs a parent to give approval
	 *         under COPPA rules</dd>
	 *         <dt>tootTimeLeft</dt>
	 *         <dd>The amount of Toot Time left on this user's account,
	 *         in the form hours “:” minutes.</dd>
	 *         <dt>tootTimer</dt>
	 *         <dd>The interval between increments of Toot Time; either
	 *         “day” or “week”</dd>
	 *         <dt>userID</dt>
	 *         <dd>The user's ID number</dd>
	 *         <dt>userName</dt>
	 *         <dd>The user's login name</dd>
	 *         <dt>peanuts</dt>
	 *         <dd>The count of the user's peanuts (currency). From
	 *         00,000 to 99,999</dd>
	 *         <dt>staffLevel</dt>
	 *         <dd>If the user is a staff member, their base staff
	 *         level. For most users, returns 0</dd>
	 *         <dt>ipAddress</dt>
	 *         <dd>If the user is online, this key will have their IP
	 *         address (or, potentially, hostname)</dd>
	 *         </dl>
	 */
	@Override
	public JSONObject toJSON () {
		final JSONObject self = new JSONObject ();
		try {
			self.put ("avatar", getPublicInfo ());
			self.put ("isBirthday", isBirthday ());
			self.put ("isMember", isPaidMember ());
			self.put ("canEnterChatZone", canEnterChatZone ());
			self.put ("canEnterMenuZone", canEnterMenuZone ());
			self.put ("canTalk", isCanTalk ());
			self.put ("language_dialect", getLanguage () + "_"
					+ getDialect ());
			self.put ("needsParent", needsParent ()
					&& getParentID () == -1);
			self.put ("tootTimeLeft", getTootTimeLeft$ ());
			self.put ("tootTimer", isTootTimerDay () ? "day"
					: isTootTimerMonth () ? "month" : "?");
			self.put ("userID", getUserID ());
			self.put ("userName", getLogin ());
			self.put ("peanuts", getPeanuts ().toString ());
			self.put ("staffLevel", getStaffLevel ());
			if (null != getServerThread ()) {
				self.put ("ipAddress", getIPAddress ());
			}
		} catch (final JSONException e) {
			AppiusClaudiusCaecus.reportBug (e);
		}
		return self;
	}

	/**
	 * Transfer peanuts from one user to another
	 * 
	 * @param numNuts number of peanuts
	 * @param fromUser user giving up the peanuts
	 * @param event event for which the
	 * @throws AlreadyExistsException WRITEME
	 * @throws JSONException WRITEME
	 */
	public void transferPeanuts (final BigInteger numNuts,
			final Toot fromUser, final String event)
	throws AlreadyExistsException, JSONException {
		giftPeanuts (numNuts, event);
		fromUser.giftPeanuts (numNuts.negate (), event);
	}

	/**
	 * @see org.starhope.appius.user.GeneralUser#updateWallet()
	 */
	@Override
	public void updateWallet () {
		final Wallet wallet = getWallet ();
		wallet.getRecordLoader ().refresh (wallet);
		super.updateWallet ();
		// final JSONObject fakeWallet = new JSONObject ();
		// final Currency peanuts = Currency.getPeanuts ();
		// final JSONObject peanutsJSON = peanuts.toJSON ();
		// try {
		// peanutsJSON.put ("amt", getPeanuts ().toPlainString ());
		//
		// fakeWallet.put (peanuts.getCode (), peanutsJSON);
		// } catch (final JSONException e) {
		// AppiusClaudiusCaecus.reportBug (
		// "Caught a JSONException in Toot.updateWallet ", e);
		// }
		// acceptSuccessReply ("wallet", fakeWallet, currentRoom);
	}

}
