/**
 * Copyright © 2009-2010, Bruce-Robert Pocock & Res Interactive, LLC.
 * All Rights Reserved. Licensed for perpetual use, modification, and/or
 * distribution by either party.
 */

package com.tootsville;

import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Locale;

import org.json.JSONException;
import org.json.JSONObject;
import org.starhope.appius.except.*;
import org.starhope.appius.game.AbstractRoom;
import org.starhope.appius.game.AppiusClaudiusCaecus;
import org.starhope.appius.game.Room;
import org.starhope.appius.game.Zone;
import org.starhope.appius.game.inventory.HomeDecorItem;
import org.starhope.appius.types.GameWorldMessage;
import org.starhope.appius.types.ItemCreationTemplate;
import org.starhope.appius.user.AbstractUser;
import org.starhope.appius.user.User;
import org.starhope.appius.util.AppiusConfig;

import com.tootsville.hangman.Censor;
import com.tootsville.user.Toot;

/**
 * Extension commands for JSON commands and operator commands specific
 * to Tootsville™
 * 
 * @author brpocock
 */
public class ExtensionCommands {
	/**
	 * <p>
	 * This method terminates an event (probably a minigame, but
	 * possibly a fountain) which was initiated by startEvent.
	 * </p>
	 * <p>
	 * For fountains, the score is ignored, and a random number from
	 * 1..100 is used as the effective score. Since fountains (should)
	 * have a 1:1 points:peanuts ratio, this will earn the player 1..100
	 * peanuts randomly per fountain visit.
	 * </p>
	 * <p>
	 * Response: JSON sent to user: { ended: event ID; peanuts: number
	 * of peanuts earned; highScores: array of scores, indexed by
	 * position on the high score list (1..24), each of which contains:
	 * { points: number of points scored by the high-scoring user;
	 * userName: the name of the user }, totalPeanuts: user's new total
	 * peanut balance }
	 * </p>
	 * 
	 * <p>
	 * Additionally, if this user earned a high score on this event,
	 * s/he will get the attribute in the top level of the response as
	 * "gotHighScore": with the value being the position number which
	 * was earned. For example, earning no high score omits the
	 * "gotHighScore" attribute altogether; earning the third highest
	 * score will return instead "gotHighScore" == 3.
	 * </p>
	 * 
	 * 
	 * 
	 * @param jso JSON parameters. { moniker = the event's moniker; id =
	 *            the event ID to be ended; score = the earned score, in
	 *            points (not peanuts); status = one of "cxl" to cancel
	 *            an event (in which case, score should be 0), or "cmp"
	 *            to complete an event (score may be zero or more). }
	 * @param u The calling user
	 * @param room The room in which the user is found (for replies)
	 * @throws JSONException if something nasty happens
	 */
	public static void do_endEvent (final JSONObject jso,
			final AbstractUser u, final AbstractRoom room)
	throws JSONException {
		final String moniker = jso.getString ("moniker");
		final int eventID;
		try {
			eventID = jso.getInt ("eventID");
		} catch (final JSONException e) {
			AppiusClaudiusCaecus.reportBug ("event ID is invalid as “"
					+ jso.getString ("eventID") + "” in "
					+ jso.toString (), e);
			return;
		}
		final JSONObject result = new JSONObject ();

		if (jso.getString ("status").equals ("cxl")) {
			// Delete existing event
			u.cancelEvent (eventID);
			result.put ("canceled", eventID);
			u.acceptSuccessReply ("endEvent", result, room);
			//return;
		}

		BigDecimal score = new BigDecimal (0);
		if (jso.has ("score")) {
			score = new BigDecimal (jso.getDouble ("score"));
		}

		String medal = "";
		if (jso.has ("medal")) {
			medal = jso.getString ("medal");
		}
		u.acceptSuccessReply ("endEvent", u.endEvent (eventID,
				moniker, score, medal), room);

	}

	/**
	 * <p>
	 * Get the contents of the user's inbox as mail envelopes. Message
	 * bodies are <em>not</em> returned.
	 * </p>
	 * <p>
	 * Response: mail: { 0: { id: number, from: userName, to: userName,
	 * subject: "", sentTime: date & time string, readTime: date & time
	 * string, body: "" }, ... }
	 * </p>
	 * <p>
	 * XXX allow selecting a subset of messages
	 * </p>
	 * 
	 * @param jso { }
	 * @param u the user requesting the inbox contents
	 * @param room the room in which the user is standing. Not
	 *        particularly important.
	 * @throws JSONException Thrown if the data cannot be interpreted
	 *         from the JSON objects passed in, or conversely, if we
	 *         can't encode a response into a JSON form
	 */
	public static void do_getMailInBox (final JSONObject jso,
			final AbstractUser u, final AbstractRoom room)
	throws JSONException {
		assert u instanceof Toot;
		final Toot user = (Toot) u;
		final JSONObject reply = new JSONObject ();
		final JSONObject mail = new JSONObject ();
		int i = 0;
		for (final GameWorldMessage m : user.getMailInBox ()) {
			mail.put (String.valueOf (i++ ), m.toJSON (true));
		}
		reply.put ("mail", mail);
		u.acceptSuccessReply ("getMailInBox", reply, room);
	}

	/**
	 * 
	 * WRITEME: document this method (brpocock, Aug 28, 2009)
	 * 
	 * Response: message: { id: number, from: userName, to: userName,
	 * subject: "", sentTime: date & time string, readTime: date & time
	 * string, body: "" }
	 * 
	 * @param jso { id: mailboxID }
	 * @param u WRITEME
	 * @param room WRITEME
	 * @throws JSONException Thrown if the data cannot be interpreted
	 *             from the JSON objects passed in, or conversely, if we
	 *             can't encode a response into a JSON form
	 * @throws NotFoundException WRITEME
	 */
	public static void do_getMailMessage (final JSONObject jso,
			final AbstractUser u, final AbstractRoom room)
	throws NotFoundException, JSONException {
		final GameWorldMessage m = AppiusConfig.newGameWorldMessage ()
		.getByID (jso.getInt ("id"));
		final JSONObject message = new JSONObject ();
		message.put ("message", m.toJSON ());
		u.acceptSuccessReply ("getMailMessage", message, room);
	}

	/**
	 * WRITEME: document this method (brpocock, Sep 1, 2009)
	 * <p>
	 * returns ranks {1: {name:"name", gold:int, silver:int, bronze:int}
	 * ...}
	 *</p>
	 * 
	 * @param jso { count=10, type=tootlympics }
	 * @param u WRITEME
	 * @param room WRITEME
	 * @throws JSONException Thrown if the data cannot be interpreted
	 *             from the JSON objects passed in, or conversely, if we
	 *             can't encode a response into a JSON form
	 * @throws NotFoundException WRITEME
	 * @throws SQLException WRITEME
	 */
	public static void do_getMedalRankings (final JSONObject jso,
			final AbstractUser u, final AbstractRoom room)
	throws JSONException, NotFoundException, SQLException {
		if (jso.getString ("type").equals ("tootlympics")) {
			// final int count = jso.getInt ("count");
			final Connection conn = AppiusConfig
			.getDatabaseConnection ();
			PreparedStatement getEm = null;
			ResultSet gotEm = null;
			try {
				getEm = conn
				.prepareStatement ("SELECT users.userName AS userName, COUNT(events.medalType) AS medalCount FROM events,users WHERE medalType = 0 AND events.creatorID = users.ID AND events.completionTimestamp < '$date' GROUP BY users.userName ORDER BY COUNT(events.medalType) DESC LIMIT 10");
				gotEm = getEm.executeQuery ();
				final HashMap <String, Double> weightedScore = new HashMap <String, Double> ();
				while (gotEm.next ()) {
					double weighted = gotEm.getInt ("medalCount");
					final String userName = gotEm
					.getString ("userName");
					if (weightedScore.containsKey (userName)) {
						weighted += weightedScore.get (userName);
					}
					weightedScore.put (userName, weighted);
				}
			} catch (final SQLException e) {
				throw e;
			} finally {
				if (null != getEm) {
					try {
						getEm.close ();
					} catch (final SQLException e1) {
						AppiusClaudiusCaecus.reportBug ("In finally",
								e1);
					}
					getEm = null;
				}
				if (null != gotEm) {
					try {
						gotEm.close ();
					} catch (final SQLException e1) {
						AppiusClaudiusCaecus.reportBug ("In finally",
								e1);
					}
					gotEm = null;
				}
			}
		}
	}

	/**
	 * Get the list of places that the user has already been to.
	 * 
	 * @param jso WRITEME
	 * @param u WRITEME
	 * @param room WRITEME
	 * @throws JSONException Thrown if the data cannot be interpreted
	 *             from the JSON objects passed in, or conversely, if we
	 *             can't encode a response into a JSON form
	 * @throws SQLException WRITEME
	 */
	public static void do_getPassport (
			final JSONObject jso, final AbstractUser u, final AbstractRoom room)
	throws JSONException, SQLException {
		if (! (u instanceof Toot))
			return;
		u.acceptSuccessReply ("passport", ((Toot)u).getPassport_JSON (),
				room);
	}

	/**
	 * WRITEME: document this method (brpocock, Aug 28, 2009)
	 * 
	 * @param jso { id: message ID # }
	 * @param u WRITEME
	 * @param room WRITEME
	 * @throws JSONException Thrown if the data cannot be interpreted
	 *             from the JSON objects passed in, or conversely, if we
	 *             can't encode a response into a JSON form
	 * @throws NumberFormatException WRITEME
	 * @throws NotFoundException WRITEME
	 */
	public static void do_markMailMessageAsRead (final JSONObject jso,
			final AbstractUser u, final AbstractRoom room)
	throws NumberFormatException, JSONException, NotFoundException {
		final GameWorldMessage m = AppiusConfig.newGameWorldMessage ()
		.getByID (Integer.parseInt (jso.getString ("id")));
		m.markAsRead ();
		if (u instanceof Toot) {
			((Toot) u).biff (room.getZone (), room);
		}
	}

	/**
	 * <p>
	 * Execute a purchase of an item by an user. Deducts peanuts,
	 * registers (and ends) an event, and adds the appropriate item to
	 * the user's inventory.
	 * </p>
	 * <p>
	 * Returns a success reply to the caller with "bought": (itemID) ,
	 * "title": (item title), and "totalPeanuts": (the user's new
	 * personal balance of peanuts to spend, with this transaction
	 * already deducted) (status:true, from:purchase)
	 * </p>
	 * <p>
	 * Error reply of err="nsf" = not-sufficient-funds, or err="dupe" =
	 * duplicate item
	 * </p>
	 * 
	 * @param jso <code>{ "itemID": itemID }</code>
	 * @param u The buyer
	 * @param room The buyer's room
	 * @throws JSONException if the data can't be put in or out of
	 *             JSON-land.
	 * @throws NotFoundException if the item was bought and didn't exist
	 */
	public static void do_purchase (final JSONObject jso,
			final AbstractUser u, final AbstractRoom room)
	throws JSONException, NotFoundException {
		final int itemID = jso.getInt ("itemID");
		if ( ! (u instanceof Toot)) {
			AppiusClaudiusCaecus.reportBug ("User is not a Toot™: #"
					+ u.getUserID ());
			return;
		}
		final Toot user = (Toot) u;
		final JSONObject result = new JSONObject ();
		result.put ("bought", itemID);
		final ItemCreationTemplate storeItem = AppiusConfig
		.getItemCreationTemplate (itemID);
		result.put ("title", storeItem.getTitle ());
		result.put ("desc", storeItem.getDescription ());
		try {
			final int eventID = user.startEventRaw ("purchase");
			user.endEventPurchaseRaw (eventID, itemID);
		} catch (final AlreadyExistsException e) {
			u.acceptErrorReply ("purchase", "dupe", result, room);
			return;
		} catch (final NonSufficientFundsException e) {
			result.put ("totalPeanuts", user.getPeanuts ()
					.toPlainString ());
			u.acceptErrorReply ("purchase", "nsf", result, room);
			return;
		}
		result
		.put ("totalPeanuts", user.getPeanuts ()
				.toPlainString ());
		u.acceptSuccessReply ("purchase", result, room);
	}

	/**
	 * WRITEME: document this method (brpocock, Sep 8, 2009)
	 * 
	 * @param jso { to: USERNAME, subject: "...", body: "..." }
	 * @param u WRITEME
	 * @param room WRITEME
	 * @throws JSONException Thrown if the data cannot be interpreted
	 *             from the JSON objects passed in, or conversely, if we
	 *             can't encode a response into a JSON form
	 */
	public static void do_sendMailMessage (final JSONObject jso,
			final AbstractUser u, final AbstractRoom room)
	throws JSONException {
		final String to = jso.getString ("to");
		final String subject = jso.getString ("subject");
		final String body = jso.getString ("body");
		if ( ! (u instanceof Toot)) return;
		final Toot user = (Toot) u;
		if (user.sendMail (to, subject, body)) {
			u.acceptSuccessReply ("sendMailMessage", null, room);
		} else {
			u.acceptErrorReply ("sendMailMessage", "mail.fail", null,
					room);
		}
	}

	/**
	 * WRITEME: document this method (brpocock, Sep 8, 2009)
	 * 
	 * @param jso { room: (MONIKER) }
	 * @param u WRITEME
	 * @param room WRITEME
	 * @throws JSONException Thrown if the data cannot be interpreted
	 *         from the JSON objects passed in, or conversely, if we
	 *         can't encode a response into a JSON form
	 * @throws SQLException WRITEME
	 */
	public static void do_stampPassport (final JSONObject jso,
			final AbstractUser u, final AbstractRoom room)
			throws JSONException, SQLException {
		if (! (u instanceof Toot)) return;
		final Toot user = (Toot) u;
		user.stampPassport (jso.getString ("room"));
		u.acceptSuccessReply ("passport", user.getPassport_JSON (),
				room);
	}

	/**
	 * <p>
	 * Attempt to begin an event. Might return an error. Uses
	 * {@link User#startEvent(String)} for the heavy lifting.
	 * </p>
	 * 
	 * <p>
	 * Note that for all fountains, use the magic moniker “fountain”
	 * </p>
	 * <p>
	 * Calls back the user with either of:
	 * </p>
	 * <dl>
	 * <dt>alreadyDone: true; status: false; err: "event.alreadyDone"</dt>
	 * <dd>This returns for fountains that have already given peanuts
	 * today (where today started at midnight, database local time)</dd>
	 * <dt>eventID: (NUM), filename: "blah.swf", asVersion: { 2, 3, or
	 * not }, status: true</dt>
	 * <dd>For successfully registered events. Must be completed or
	 * canceled using
	 * {@link #do_endEvent( JSONObject ,AbstractUser ,  AbstractRoom )}</dd>
	 * </dl>
	 * 
	 * 
	 * @param jso JSON payload from the caller. Data: moniker = event
	 *            moniker.
	 * @param u The caller = the user performing the event
	 * @param room The caller's room. For fountains, we'll use this
	 *            room's moniker to figure out which fountain is which
	 * @throws JSONException if JSON data can't be put into a response,
	 *             or gotten out of a command.
	 * @throws SQLException probably means that the moniker is bad, but
	 *             I'm not really doing much to validate it here
	 */
	public static void do_startEvent (final JSONObject jso,
			final AbstractUser u, final AbstractRoom room)
	throws JSONException, SQLException {
		String moniker = jso.getString ("moniker");

		if (moniker.equals ("fountain")) {
			moniker = "fountain/" + room.getName ();
		}
		final JSONObject result = u.startEvent (moniker);
		try {
			if (result.getBoolean ("alreadyDone")) {
				u.acceptErrorReply ("startEvent", "event.alreadyDone",
						result, room);
				return;
			}
		} catch (final JSONException e) {
			// not an error
		}
		u.acceptSuccessReply ("startEvent", result, room);

	}

	/**
	 * Get the revision level of this file
	 * 
	 * @return the Subversion revision level of this file
	 */
	public static String getRev () {
		return "$Rev: $";
	}

	/**
	 * Add a regular expression pattern to the list of patterns which
	 * will cause a user to be immediately kicked out of the game for a
	 * period of time. This is known as the “red list.” Requires
	 * Designer level of staff privileges.
	 * 
	 * @param words The command parameters (whitespace-delimited list)
	 *            provided after the # command name
	 * @param u The user invoking the operator command
	 * @param room The room in which the user is standing (as a room
	 *            number). This can be -1 under certain circumstances.
	 */
	public static void op_addtokicklist (final String [] words,
			final AbstractUser u, final AbstractRoom room) {
		try {
			u.assertStaffLevel (User.STAFF_LEVEL_DESIGNER);
		} catch (final PrivilegeRequiredException e) {
			u.sendOops ();
			return;
		}
		Censor.addToWorseList (words [0]);
		return;
	}

	/**
	 * Add a regular expression pattern to the list of patterns which
	 * cannot be spoken in the game world. These messages will send the
	 * “oops” emote to the user speaking them. This is known as the
	 * “blacklist.” Requires Designer level of staff privileges.
	 * 
	 * @param words The command parameters (whitespace-delimited list)
	 *            provided after the # command name
	 * @param u The user invoking the operator command
	 * @param room The room in which the user is standing (as a room
	 *            number). This can be -1 under certain circumstances.
	 */
	public static void op_addtowarnlist (final String [] words,
			final AbstractUser u, final AbstractRoom room) {
		try {
			u.assertStaffLevel (User.STAFF_LEVEL_DESIGNER);
		} catch (final PrivilegeRequiredException e) {
			u.sendOops ();
			return;
		}
		Censor.addToBlackList (words [0]);
		return;
	}

	/**
	 * Add a regular expression pattern to the set of regular
	 * expressions which are whitelisted against being blocked in
	 * speech. These patterns are explicitly permitted and are not
	 * affected by the warnings list (blacklist) or kick list (red
	 * list). Requires Designer level of staff privileges.
	 * 
	 * @param words The command parameters (whitespace-delimited list)
	 *            provided after the # command name
	 * @param u The user invoking the operator command
	 * @param room The room in which the user is standing (as a room
	 *            number). This can be -1 under certain circumstances.
	 */
	public static void op_addtowhitelist (final String [] words,
			final AbstractUser u, final AbstractRoom room) {
		try {
			u.assertStaffLevel (User.STAFF_LEVEL_DESIGNER);
		} catch (final PrivilegeRequiredException e) {
			u.sendOops ();
			return;
		}
		Censor.addToWhiteList (words [0]);
		return;
	}

	/**
	 * Return an administrative message with the number of regular
	 * expression patterns found on the warnings list, kick list, and
	 * whitelist.
	 * 
	 * @param words The command parameters (whitespace-delimited list)
	 *            provided after the # command name
	 * @param u The user invoking the operator command
	 * @param room The room in which the user is standing (as a room
	 *            number). This can be -1 under certain circumstances.
	 */
	public static void op_checklists (final String [] words,
			final AbstractUser u, final AbstractRoom room) {
		u.acceptAdminMessage ("Warn list: "
				+ Censor.getBlackListLength () + "\n" + "Kick list: "
				+ Censor.getWorseListLength () + "\n" + "White list: "
				+ Zone.censor.getWhiteListLength (), "#checklists",
		"Carl");
		return;
	}

	/**
	 * <p>
	 * Send a command into the operator command interpreter for a
	 * running game (if that game provides one)
	 * </p>
	 * <p>
	 * Usage: <tt>#game</tt> <i>gameIdentifier</i> <i>(strings...)</i>
	 * </p>
	 * <p>
	 * FIXME: currently special-cased for Zap Attack
	 * </p>
	 * 
	 * @param words The command parameters (whitespace-delimited list)
	 *            provided after the # command name
	 * @param u The user invoking the operator command
	 * @param room The room in which the user is standing (as a room
	 *            number). This can be -1 under certain circumstances.
	 */
	public static void op_game (final String [] words,
			final AbstractUser u, final AbstractRoom room) {
		try {
			u.assertStaffLevel (User.STAFF_LEVEL_DEVELOPER);
		} catch (final PrivilegeRequiredException e) {
			u.sendOops ();
			return;
		}
		if (words.length < 2) {
			u.sendOops ();
			return;
		}
		if ("zapAttack".equals (words [0])) {
			final String [] command = Arrays.copyOfRange (words, 2,
					words.length);
			final AbstractRoom arena = room.getZone ().getRoomByName (
			"zapArena1");
			arena.getGameEvent ().acceptCommand (u, arena, command);
			return;
		}
	}

	/**
	 * Give (or take) peanuts to (from) a named user
	 * 
	 * @param words The command parameters (whitespace-delimited list)
	 *            provided after the # command name
	 * @param u The user invoking the operator command
	 * @param room The room in which the user is standing (as a room
	 *            number). This can be -1 under certain circumstances.
	 */
	public static void op_givenuts (final String [] words,
			final AbstractUser u, final AbstractRoom room) {
		BigDecimal maxPeanuts = BigDecimal
		.valueOf (u.getStaffLevel () * 768L);
		if (u.hasStaffLevel (User.STAFF_LEVEL_DEVELOPER)) {
			maxPeanuts = BigDecimal.valueOf (99999999999L);
		}
		final String numNutsString = words [0];
		final BigDecimal nuts = BigDecimal.valueOf (Long
				.parseLong (numNutsString));
		if (nuts.compareTo (maxPeanuts) <= 0) {
			String recipient = words [1];

			if ("#here".equals (recipient)) {
				recipient = "@" + room.getMoniker ();
			}
			if (recipient.charAt (0) == '@') {
				final AbstractRoom r = room.getZone ().getRoomByName (
						recipient.substring (1));
				if (null == r) {
					u.acceptAdminMessage ("Can't find room "
							+ recipient.substring (1), "#givenuts",
					"Catullus");
					return;
				}
				BigDecimal gifted = BigDecimal.ZERO;
				int receivers = 0;
				for (final AbstractUser pasivo : r.getAllUsers ()) {
					if (pasivo instanceof Toot) {
						try {
							((Toot) pasivo).giftPeanuts (nuts,
							"givenuts");
							gifted = gifted.add (nuts);
							++receivers;
						} catch (final AlreadyExistsException e) {
							AppiusClaudiusCaecus.reportBug (e);
						} catch (final JSONException e) {
							AppiusClaudiusCaecus.reportBug (e);
						}
					}
				}
				u.acceptAdminMessage ("Gave " + numNutsString
						+ " peanuts each to " + receivers
						+ " users, for a total of "
						+ gifted.toPlainString () + " peanuts gifted.",
						"Gift", "Quaestor");
				return;
			}

			final AbstractUser pasivo = User.getByLogin (recipient);
			if (null == pasivo) {
				u.acceptAdminMessage ("Can't find user: “" + recipient
						+ "”", "#givenuts", "Catullus");
				return;
			}
			try {
				if (pasivo instanceof Toot) {
					((Toot) pasivo).giftPeanuts (nuts, "givenuts");
					u.acceptAdminMessage ("Gave a gift of "
							+ nuts.toPlainString ()
							+ " peanuts to "
							+ pasivo.getAvatarLabel ()
							+ ", for a total of "
							+ ((Toot) pasivo).getPeanuts ()
							.toPlainString (), "#givenuts",
					"Catullus");

				}
			} catch (final AlreadyExistsException e) {
				AppiusClaudiusCaecus.reportBug (e);
			} catch (final JSONException e) {
				AppiusClaudiusCaecus.reportBug (e);
			}
			return;
		}
		u.acceptAdminMessage ("You can only give away "
				+ maxPeanuts.toPlainString ()
				+ " peanuts. That's a lot!", "#givenuts", "Catullus");
	}

	/**
	 * Remove the KaTootel egg Super Toot Bot from the game
	 * 
	 * @param words The command parameters (whitespace-delimited list)
	 *            provided after the # command name
	 * @param u The user invoking the operator command
	 * @param room The room in which the user is standing (as a room
	 *            number). This can be -1 under certain circumstances.
	 */
	public static void op_killegg (final String [] words,
			final AbstractUser u, final AbstractRoom room) {
		Connection con = null;
		PreparedStatement st = null;
		try {
			con = AppiusConfig.getDatabaseConnection ();
			st = con
			.prepareStatement ("UPDATE tootBotSchedule SET x=1000, y=1000 WHERE botName='KaTootel.Egg'");
			if (st.executeUpdate () == 0) {
				u.acceptAdminMessage ("Couldn't kill the egg",
						"#killegg", "Catullus");
				return;
			}
		} catch (final SQLException e) {
			AppiusClaudiusCaecus.reportBug (e);
			u.acceptAdminMessage ("Couldn't kill the egg: " + e,
					"#killegg", "Catullus");
			return;
		} 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;
	}

	/**
	 * TODO: document this method (brpocock, Nov 20, 2009)
	 * 
	 * @param words The command parameters (whitespace-delimited list)
	 *            provided after the # command name
	 * @param u The user invoking the operator command
	 * @param room The room in which the user is standing (as a room
	 *            number). This can be -1 under certain circumstances.
	 */
	public static void op_layegg (final String [] words,
			final AbstractUser u, final AbstractRoom room) {
		final String userPos = u.getVariable ("d");
		final String [] pos = userPos.split ("~");
		final double posX = Double.parseDouble (pos [0]);
		final double posY = Double.parseDouble (pos [1]);
		Connection con = null;
		PreparedStatement st = null;
		try {
			con = AppiusConfig.getDatabaseConnection ();
			st = con
			.prepareStatement ("UPDATE tootBotSchedule SET x=?, y=?, room=? WHERE botName='KaTootel.Egg'");
			st.setInt (1, ((int) Math.floor (posX)));
			st.setInt (2, ((int) Math.floor (posY)));
			st.setString (3, room.getName ());
			if (st.executeUpdate () == 0) {
				u.acceptAdminMessage ("Couldn't lay an egg...",
						"#layegg", "Catullus");
				return;
			}
		} catch (final SQLException e) {
			AppiusClaudiusCaecus.reportBug (e);
			u.acceptAdminMessage ("Couldn't lay an egg: " + e,
					"#layegg", "Catullus");
			return;
		} 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 */
				}
			}

		}
		u.acceptAdminMessage (
				"Laid an egg. (Re-enter room to see it.)", "#layegg",
		"Catullus");
		return;
	}

	/**
	 * TODO: document this method (brpocock, Nov 20, 2009)
	 * 
	 * @param words The command parameters (whitespace-delimited list)
	 *            provided after the # command name
	 * @param u The user invoking the operator command
	 * @param room The room in which the user is standing (as a room
	 *            number). This can be -1 under certain circumstances.
	 */
	public static void op_scores (final String [] words,
			final AbstractUser u, final AbstractRoom room) {
		try {
			u.assertStaffLevel (User.STAFF_LEVEL_MODERATOR);
		} catch (final PrivilegeRequiredException e) {
			u.sendOops ();
			return;
		}
		if (words.length < 2) {
			u.sendOops ();
			return;
		}
		final StringBuilder scores = new StringBuilder ();
		Connection con = null;
		PreparedStatement st = null;
		ResultSet rs = null;
		try {
			con = AppiusConfig.getDatabaseConnection ();
			st = con
			.prepareStatement ("SELECT userName, points FROM events LEFT JOIN users ON events.creatorID = users.ID WHERE eventTypeID = ? AND creationTimestamp > DATE(?) ORDER BY points DESC LIMIT ?");
			try {
				st.setInt (1, Integer.parseInt (words [0]));
			} catch (final NumberFormatException e) {
				u.acceptAdminMessage ("Bad event ID value", "#scores",
				"DJ Blakkat");
				return;
			}
			st.setString (2, words [1]);
			try {
				st.setInt (3, words.length > 2 ? Integer
						.parseInt (words [2]) : 10);
			} catch (final NumberFormatException e) {
				u.acceptAdminMessage ("Bad score count", "#scores",
				"DJ Blakkat");
				return;
			}
			rs = st.executeQuery ();
			int i = 1;
			while (rs.next ()) {
				scores.append (i);
				scores.append (".  \t");
				scores.append (rs.getString ("userName"));
				scores.append ('\t');
				scores.append (rs.getInt ("points"));
				scores.append ('\n');
				++i;
			}
		} catch (final SQLException e) {
			AppiusClaudiusCaecus.reportBug (
					"Caught a SQLException in op_scores", e);
		} finally {
			if (null != rs) {
				try {
					rs.close ();
				} catch (final SQLException e) {
					AppiusClaudiusCaecus
					.reportBug (
							"Caught a SQLException in op_scores in finally for rs",
							e);
				}
			}
			if (null != st) {
				try {
					st.close ();
				} catch (final SQLException e) {
					AppiusClaudiusCaecus
					.reportBug (
							"Caught a SQLException in op_scores in finally for st",
							e);
				}
			}
			if (null != con) {
				try {
					con.close ();
				} catch (final SQLException e1) {
					AppiusClaudiusCaecus
					.reportBug (
							"Caught a SQLException in op_scores in finally for con",
							e1);
				}
			}
		}
		u.acceptAdminMessage (scores.toString (),
				"Ricky's High Score Contest", "DJ Blakkat");
	}

}
