/**
 * 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.BigInteger;
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.Iterator;

import org.json.JSONException;
import org.json.JSONObject;
import org.starhope.appius.except.AlreadyExistsException;
import org.starhope.appius.except.NotFoundException;
import org.starhope.appius.except.PrivilegeRequiredException;
import org.starhope.appius.game.AppiusClaudiusCaecus;
import org.starhope.appius.game.Commands;
import org.starhope.appius.game.inventory.Inventory;
import org.starhope.appius.game.inventory.InventoryItem;
import org.starhope.appius.mb.Currency;
import org.starhope.appius.room.Room;
import org.starhope.appius.sql.SQLPeerEnum;
import org.starhope.appius.sys.admin.Security;
import org.starhope.appius.sys.admin.SecurityCapability;
import org.starhope.appius.sys.op.OpCommands;
import org.starhope.appius.types.GameWorldMessage;
import org.starhope.appius.user.AbstractUser;
import org.starhope.appius.user.GeneralUser;
import org.starhope.appius.user.Nomenclator;
import org.starhope.appius.user.User;
import org.starhope.appius.user.UserHouse;
import org.starhope.appius.user.events.EventRecord;
import org.starhope.appius.user.events.Quaestor;
import org.starhope.appius.util.AppiusConfig;

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

/**
 * Extension commands for JSON commands and operator commands specific
 * to Tootsville™
 *
 * @author brpocock@star-hope.org
 */
public class ExtensionCommands {

	/**
	 * <p>
	 * This command has never been implemented and simply returns an
	 * error of "not-implemented"
	 * </p>
	 * <p>
	 * Add a staff journal entry.
	 * </p>
	 * <p>
	 * Staff members can create a journal entry which is stored for
	 * review in a customer service application such as Joshua. Creating
	 * a ModeratorJournal object will parse for certain values such as
	 * [@username].
	 * </p>
	 *
	 * @param jso { "entry": TEXT }
	 * @param u Operator making journal entry
	 * @param room unused
	 * @throws JSONException WRITEME twheys@gmail.com
	 * @throws PrivilegeRequiredException if the user attempting to add
	 *             a journal entry isn't a staff member
	 */
	@Deprecated
	public static void do_addJournalEntry (final JSONObject jso,
			final AbstractUser u, final Room room)
			throws JSONException, PrivilegeRequiredException {
		// u.assertStaffLevel (User.STAFF_LEVEL_STAFF_MEMBER);
		// final String entry = jso.getString ("entry");
		// new ModeratorJournal (u, entry);
		u.acceptErrorReply ("addJournalEntry", "not-implemented", null,
				room);
	}
	
	/**
	 * <p>
	 * Response from the first run screen for the user's house
	 * </p>
	 * <p>
	 * Either create the user's house and lot, or add a room to their
	 * house.
	 * </p>
	 * 
	 * @param jso Data describing the user's lot { lot: lot-ID, house:
	 *            house-ID }, or adding a room, { index: roomIndex }
	 * @param u The user buying the lot
	 * @param room The room in which the user is found — for
	 *            communications purposes, at least.
	 * @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_createUserHouse (final JSONObject jso,
			final AbstractUser u, final Room room)
			throws JSONException {
		if ( ! (u instanceof Toot)) {
			return;
		}
		final Toot user = (Toot) u;
		if (jso.has ("index")) {
			user.addRoom (jso.getInt ("index"));
		} else {
			final UserHouse house = new UserHouse (user);
			house.setLotID (Integer.parseInt (jso.getString ("lot")));
			house.setHouseTypeID (Integer.parseInt (jso
					.getString ("house")));
			user.addRoom (0);
			final Inventory inventory = user.getInventory ();
			inventory.addDefaultFreeItem (36);
			inventory.addDefaultFreeItem (37);
			inventory.addDefaultFreeItem (38);
		}
		u.acceptSuccessReply ("createUserHouse", jso, room);
	}
	
	/**
	 * Delete a message from the user's mailbox
	 * 
	 * @param jso { "id": MESSAGE-ID }
	 * @param u user deleting the message
	 * @param room room in which the user is standing
	 * @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_deleteMailMessage (final JSONObject jso,
			final AbstractUser u, final Room room)
			throws JSONException {
		if ( ! (u instanceof Toot)) {
			return;
		}
		((Toot) u).deleteMail (jso.getInt ("id"));
		u.acceptSuccessReply ("deleteMailMessage", jso, room);
	}

	/**
	 * <p>
	 * JSON object contains "type" = either "clothes" or "pivitz". Does
	 * not affect patterns.
	 * </p>
	 * <p>
	 * Response with total avatar info from "wardrobe"
	 * </p>
	 *
	 * @param jso { "type": "pivitz" } or { "type": "clothes" } or {
	 *            "slot": SLOT-NUMBER }
	 * @param u The user calling this method
	 * @param room The room in which this user is standing
	 * @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_doff (final JSONObject jso,
			final AbstractUser u, final Room room)
			throws JSONException {
		final JSONObject reply = new JSONObject ();
		if (jso.has ("slot")) {
			InventoryItem i = null;
			final int userSlot = jso.getInt ("slot");
			final Inventory inventory = u.getInventory ();
			for (final InventoryItem n : inventory) {
				if (n.getSlotNumber () == userSlot) {
					i = n;
				}
			}
			if (null == i) {
				u.acceptErrorReply ("doff", "not.found", jso, room);
				return;
			}
			if (i.getOwnerID () != u.getUserID ()) {
				u.acceptErrorReply ("doff", "not.owner", jso, room);
				return;
			}
			inventory.doff (i);
			u.sendWardrobe ();
			return;
		}
		if (jso.getString ("type").equals ("pivitz")) {
			if (u instanceof Toot) {
				((Toot) u).doffPivitz ();
				reply.put ("type", "pivitz");
				u.acceptSuccessReply ("doff", reply, room);
			}
		} else {
			u.doffClothes ();
			reply.put ("type", "clothes");
			u.acceptSuccessReply ("doff", reply, room);
		}
		u.sendWardrobe ();
		reply.put ("isActive", false);
		Commands.do_getInventoryByType (reply, u, room);
	}
	
	/**
	 * <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 Room 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 ();

		EventRecord eventByID;
		try {
			eventByID = Quaestor.getEventByID (eventID);
		} catch (final NotFoundException e) {
			AppiusClaudiusCaecus
					.reportBug (
							"Caught a NotFoundException in ExtensionCommands.do_endEvent ",
							e);
			u.acceptErrorReply ("endEvent", "event.notFound", null,
					room);
			return;
		}

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

		BigInteger score = BigInteger.ZERO;
		if (jso.has ("score")) {
			score = BigInteger.valueOf ((long) jso.getDouble ("score"));
		}

		String medal = "";
		if (jso.has ("medal")) {
			medal = jso.getString ("medal");
		}

		try {
			eventByID.addMedalEarned (medal);
		} catch (final NotFoundException e) {
			AppiusClaudiusCaecus
					.reportBug (
							"Caught a medal("
									+ medal
									+ ") NotFoundException in ExtensionCommands.do_endEvent ",
							e);
		}
		eventByID.end (score);
		u.acceptSuccessReply ("endEvent", eventByID.toJSON (), 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>
	 * “limit” defaults to 100 messages
	 * </p>
	 *
	 * @param jso { } for all messages; { from: X limit: Y } for a
	 *            subset
	 * @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 Room 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;
		final int offset = jso.has ("from") ? jso.getInt ("from") : 0;
		final int limit = jso.has ("limit") ? jso.getInt ("limit")
				: 100;

		for (final GameWorldMessage m : user.getMailInBox (offset,
				limit)) {
			mail.put (String.valueOf (i++ ), m.toJSON (true));
		}
		reply.put ("mail", mail);
		u.acceptSuccessReply ("getMailInBox", reply, room);
	}

	/**
	 * <p>
	 * Get a message out of the user's mailbox
	 * </p>
	 * Response: <tt> message: { id: </tt>number<tt>, from: </tt>
	 * userName <tt>, to: </tt>userName<tt>,
	 * subject: "", sentTime: </tt>date & time string
	 * <tt>, readTime: </tt>date & time string</tt>, body: "" } </tt>
	 *
	 * @param jso { id: mailboxID }
	 * @param u user invoking the command (whose mailbox to check)
	 * @param room room in which the user is standing at the time
	 * @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 Room 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@star-hope.org, Sep 1,
	 * 2009)
	 * <p>
	 * This is a deprecated method that used to be needed for
	 * Tootlympics. It should no longer be called and will be removed.
	 * </p>
	 * <p>
	 * returns ranks {1: {name:"name", gold:int, silver:int, bronze:int}
	 * ...}
	 *</p>
	 * 
	 * @param jso { count=10, type=tootlympics }
	 * @param u the user requesting the results
	 * @param room the room in which the user is standing
	 * @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
	 */
	@Deprecated
	public static void do_getMedalRankings (final JSONObject jso,
			final AbstractUser u, final Room 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)
								.intValue ();
					}
					weightedScore.put (userName, Double
							.valueOf (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. Replies
	 * with <code>{ "passport": ... }</code> from
	 * {@link Toot#getPassport_JSON()}
	 * 
	 * @param jso unused
	 * @param u user asking for their passport
	 * @param room room in which the user is standing
	 * @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 Room room)
			throws JSONException, SQLException {
		if ( ! (u instanceof Toot)) {
			return;
		}
		u.acceptSuccessReply ("passport", ((Toot) u)
				.getPassport_JSON (), room);
	}
	
	/**
	 * replies as per
	 * {@link #do_getStoreItems(JSONObject, AbstractUser, Room)} , but
	 * gives also "order" with the order of the
	 * categories/shelves/stores in the shop
	 * 
	 * @param jso { shop: MONIKER }
	 * @param u the user asking
	 * @param room user's room
	 * @throws JSONException if bad things happen
	 * @throws NumberFormatException if bad things happen
	 */

	public static void do_getShopItems (final JSONObject jso,
			final AbstractUser u, final Room room)
			throws NumberFormatException, JSONException {
		final JSONObject stores = new JSONObject ();
		int i = 0;
		for (final String storeNumber : AppiusConfig
				.getList ("org.starhope.appius.shop."
						+ jso.getString ("shop"))) {
			stores.put (String.valueOf (i++ ), storeNumber);
		}
		ExtensionCommands.do_getStoreItems (stores, u, room);
		final JSONObject order = new JSONObject ();
		order.put ("order", stores);
		u.acceptSuccessReply ("getShopItems", stores, room);
	}
	
	/**
	 * Get the information about items found in a logical store (or
	 * store-category / shelf)
	 * <p>
	 * Results: totalPeanuts (XXX Tootsville®-specific), stores...
	 * </p>
	 * <ul>
	 * <li>"stores"
	 * <ul>
	 * <li>sequence index
	 * <ul>
	 * <li>"storeNumber": ID</li>
	 * <li>"items"
	 * <ul>
	 * <li>sequence index
	 * <ul>
	 * <li>"id": id number</li>
	 * <li>"title": title</li>
	 * <li>"price": price to purchase the item</li>
	 * </ul>
	 * </li>
	 * </ul>
	 * </li>
	 * </ul>
	 * </li>
	 * </ul>
	 * </li>
	 * </ul>
	 * 
	 * @param jso An array of store ID's for which you want to get the
	 *            items. The keys are ignored: typically, they'll be
	 *            ascending numbers, but that can be any unique values,
	 *            which will be discarded. The value of each key must be
	 *            the integer store (category) ID number.
	 * @param u The user requesting the information. XXX Special case:
	 *            if the user is a Toot, gives back the totalPeanuts of
	 *            the user
	 * @param room The room in which the user is standing. Ignored.
	 * @throws NumberFormatException If a store ID is not a valid
	 *             integer
	 * @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
	 */
	@SuppressWarnings ( { "cast", "unchecked" })
	public static void do_getStoreItems (final JSONObject jso,
			final AbstractUser u, final Room room)
			throws NumberFormatException, JSONException {
		final JSONObject result = new JSONObject ();
		final JSONObject stores = new JSONObject ();
		final Iterator <String> keys = (Iterator <String>) jso.keys ();
		while (keys.hasNext ()) {
			final String key = keys.next ();
			final int storeID = jso.getInt (key);
			stores.put (String.valueOf (storeID), Store.getByID (
					storeID).toJSON ());
		}
		if (u instanceof Toot) {
			result.put ("totalPeanuts", ((Toot) u).getWallet ().get (
					Currency.getPeanuts ()));
		}
		result.put ("stores", stores);
		u.acceptSuccessReply ("getStoreItems", result, room);
	}

	/**
	 * WRITEME: document this method (brpocock@star-hope.org, 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 Room 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);
		}
	}



	/**
	 * Send an in-game eMail message
	 *
	 * @param jso { to: USERNAME, subject: "...", body: "..." }
	 * @param u user sending mail message
	 * @param room room user is in
	 * @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
	 */
	@SuppressWarnings ("unchecked")
	public static void do_sendMailMessage (final JSONObject jso,
			final AbstractUser u, final Room room)
			throws JSONException {
		final String subject = jso.getString ("subject");
		final String body = jso.getString ("body");
		if ( ! (u instanceof Toot)) {
			return;
		}
		final Toot user = (Toot) u;

		if (jso.has ("toList")) {
			final JSONObject toList = jso.getJSONObject ("toList");
			final Iterator <String> keys = toList.keys ();
			boolean allsWell = true;
			while (keys.hasNext ()) {
				if ( !user.sendMail (toList.getString (keys.next ()),
						subject, body)) {
					allsWell = false;
				}
			}
			if (allsWell) {
				u.acceptSuccessReply ("sendMailMessage", null, room);
			} else {
				u.acceptErrorReply ("sendMailMessage", "mail.fail",
						null, room);
			}
			return;
		}
		final String to = jso.getString ("to");
		if (user.sendMail (to, subject, body)) {
			u.acceptSuccessReply ("sendMailMessage", null, room);
		} else {
			u.acceptErrorReply ("sendMailMessage", "mail.fail", null,
					room);
		}
	}

	/**
	 * stamp the user's passport ...
	 *
	 * @param jso { room: (MONIKER) }
	 * @param u user having passport stamped
	 * @param room room they're in now
	 * @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 if it can't be saved
	 */
	@Deprecated
	public static void do_stampPassport (final JSONObject jso,
			final AbstractUser u, final Room 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);
	}

	/**
	 * Get the revision level of this file
	 *
	 * @return the Subversion revision level of this file
	 */
	public static String getRev () {
		return "$Rev: 4151 $";
	}
	
	/**
	 * 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 Room 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 Room 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 Room 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 Room room) {
		u.acceptMessage ("#checklists", "Carl", "Warn list: "
				+ Censor.getBlackListLength () + "\n" + "Kick list: "
				+ Censor.getWorseListLength () + "\n" + "White list: "
				+ room.getZone ().getCensor ().getWhiteListLength ());
		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 Room room) {
		BigInteger maxPeanuts = BigInteger
				.valueOf (u.getStaffLevel () * 768L);
		if (Security.hasCapability (u, SQLPeerEnum.get (
				SecurityCapability.class, "tootsville.maxNuts"))) {
			maxPeanuts = BigInteger.valueOf (99999L);
		}
		final String numNutsString = words [0];
		final BigInteger nuts = BigInteger.valueOf (Long
				.parseLong (numNutsString));
		if (nuts.compareTo (maxPeanuts) <= 0) {
			String recipient = words [1];

			if ("#here".equals (recipient)) {
				recipient = "@" + room.getMoniker ();
			}
			if (recipient.charAt (0) == '@') {
				Room r;
				try {
					r = room.getZone ().getRoom (
							recipient.substring (1));
				} catch (NotFoundException e1) {
					u.acceptMessage ("Gift", "Quaestor",
							"Can't find room "
									+ recipient.substring (1));
					return;
				}
				BigInteger gifted = BigInteger.ZERO;
				int receivers = 0;
				for (final AbstractUser pasivo : r.getAllUsers ()) {
					if (pasivo instanceof GeneralUser) {
						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.acceptMessage ("Gift", "Quæstor", "Gave "
						+ numNutsString + " peanuts each to "
						+ receivers + " users, for a total of "
						+ gifted.toString () + " peanuts gifted.");
				return;
			}

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

				}
			} catch (final AlreadyExistsException e) {
				AppiusClaudiusCaecus.reportBug (e);
			} catch (final JSONException e) {
				AppiusClaudiusCaecus.reportBug (e);
			}
			return;
		}
		u.acceptMessage ("#givenuts", "Catullus",
				"You can only give away " + maxPeanuts.toString ()
						+ " peanuts. That's a lot!");
	}
	
	/**
	 * 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 Room 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.acceptMessage ("#killegg", "Catullus",
						"Couldn't kill the egg");
				return;
			}
		} catch (final SQLException e) {
			AppiusClaudiusCaecus.reportBug (e);
			u.acceptMessage ("#killegg", "Catullus",
					"Couldn't kill the egg: " + e);
			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;
	}

	/**
	 * temporarily set the Super Toot Bot “egg” at the player's current
	 * coördinates. This will be broken by virtual of Super Toot Bot
	 * removal in an upcoming release.
	 *
	 * @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.
	 */
	@Deprecated
	public static void op_layegg (final String [] words,
			final AbstractUser u, final Room 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.acceptMessage ("#layegg", "Catullus",
						"Couldn't lay an egg...");
				return;
			}
		} catch (final SQLException e) {
			AppiusClaudiusCaecus.reportBug (e);
			u.acceptMessage ("#layegg", "Catullus",
					"Couldn't lay an egg: " + e);
			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.acceptMessage ("#layegg", "Catullus",
				"Laid an egg. (Re-enter room to see it.)");
		return;
	}

	/**
	 * get scores for a game for Ricky's High Score Contest.
	 *
	 * @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 Room 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.acceptMessage ("Ricky's High Score Contest",
						"DJ Blakkat", "Bad event ID value");
				return;
			}
			st.setString (2, words [1]);
			try {
				st.setInt (3, words.length > 2 ? Integer
						.parseInt (words [2]) : 10);
			} catch (final NumberFormatException e) {
				u.acceptMessage ("Ricky's High Score Contest",
						"DJ Blakkat", "Bad score count");
				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.acceptMessage ("Ricky's High Score Contest", "DJ Blakkat",
				scores.toString ());
	}

	/**
	 * Set the movie in the Theatre to the correct (latest) movie, immediately.
	 * @param words ignored
	 * @param u operator account
	 * @param room any room in the zone in which the theatre should be updated
	 */
	public static void op_setmovie (final String [] words,
			final AbstractUser u, final Room room) {
		TootsvilleRC.setMovieTheater (room.getZone ());
	}

	/**
	 * <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>
	 * Special-cased for Zap Attack
	 * </p>
	 * See {@link OpCommands#op_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_zagame (final String [] words,
			final AbstractUser u, final Room 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);
			Room arena;
			try {
				arena = room.getZone ().getRoom ("zapArena1");
			} catch (NotFoundException e) {
				throw AppiusClaudiusCaecus
						.fatalBug (
								"Caught a NotFoundException in ExtensionCommands.op_zagame ",
								e);
			}
			arena.getGameEvent ("com.tootsville.game.ZapAttack")
					.acceptCommand (u, arena, command);
			return;
		}
	}

}
