package com.tootsville.tootsbook.server;

import java.math.BigInteger;
import java.util.Vector;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.http.HttpSession;

import org.starhope.appius.except.DataException;
import org.starhope.appius.except.GameLogicException;
import org.starhope.appius.game.AppiusClaudiusCaecus;
import org.starhope.appius.game.inventory.AbstractItem;
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.messaging.MailMessage;
import org.starhope.appius.test.ConnectionDebug;
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.util.AppiusConfig;
import org.starhope.util.LibMisc;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import com.tootsville.Store;
import com.tootsville.WebUtil;
import com.tootsville.tootsbook.client.BookService;
import com.tootsville.tootsbook.client.TootsBook;
import com.tootsville.tootsbook.client.exception.AlreadyExistsException;
import com.tootsville.tootsbook.client.exception.InsufficientFundsException;
import com.tootsville.tootsbook.client.exception.InvalidCredentialsException;
import com.tootsville.tootsbook.client.exception.NotFoundException;
import com.tootsville.tootsbook.client.exception.PrivilegeRequiredException;
import com.tootsville.tootsbook.client.exception.SessionTimedOutException;
import com.tootsville.tootsbook.client.util.AvailableItems;
import com.tootsville.tootsbook.client.util.Post;
import com.tootsville.tootsbook.client.util.StoreInventory;
import com.tootsville.tootsbook.client.util.UserProfile;
import com.tootsville.tootsbook.server.util.Translator;
import com.tootsville.user.Toot;

/**
 * @author twheys@gmail.com The server side implementation of the RPC
 *         service.
 */
public class BookServiceImpl extends RemoteServiceServlet implements
BookService {
	
	/**
	 * WRITEME: Document this type. twheys@gmail.com Feb 15, 2010
	 * 
	 * @author <a href="mailto:twheys@gmail.com@resinteractive.com">Tim
	 *         Heys</a>
	 */
	public enum Page {
		/**
		 * WRITEME
		 */
		HOME,
		/**
		 * WRITEME
		 */
		PROFILE
	}

	/**
	 * WRITEME
	 */
	private static final Logger logger = Logger
	.getLogger (BookServiceImpl.class.getCanonicalName ());

	/**
	 *
	 */
	private static final long serialVersionUID = -2031268038545476519L;
	
	/**
	 * WRITEME: Document this field. twheys@gmail.com Dec 17, 2009
	 * 
	 * @param clientPosts WRITEME twheys@gmail.com
	 * @param serverPosts WRITEME twheys@gmail.com
	 * @return a LinkedQueue of all posts for a user for the profile
	 *         page.
	 */
	static ConcurrentLinkedQueue <Post> getClientPostsForUser (
			final Vector <MailMessage> serverPosts,
			final ConcurrentLinkedQueue <Post> clientPosts) {
		for (final MailMessage serverPost : serverPosts) {
			if ( !"".equals (serverPost.getBody ())) {
				BookServiceImpl.logger.info ("Loading post ("
						+ serverPost.getID () + "):\nSubject: "
						+ serverPost.getSubject () + "\nBody:"
						+ serverPost.getBody ());
				clientPosts.add (Translator
						.getPostFromPost (serverPost));
				serverPost.markAsRead ();
			}
		}
		return clientPosts;
	}
	
	/**
	 * <pre>
	 * twheys@gmail.com Jan 14, 2010
	 * </pre>
	 * 
	 * TO getCommentsForPosts WRITEME...
	 * 
	 * @param clientPosts WRITEME twheys@gmail.com
	 * @return a LinkedQueue with all comments for all posts
	 */
	static ConcurrentLinkedQueue <Post> getCommentsForPosts (
			final ConcurrentLinkedQueue <Post> clientPosts) {
		for (final Post post : clientPosts) {
			try {
				for (final MailMessage serverComment : Nomenclator
						.getDataRecord (MailMessage.class,
								post.getId ()).getRepliesOnWall ()) {
					if ( !"".equals (serverComment.getBody ())) {
						clientPosts.add (Translator
								.getCommentFromReply (post,
										serverComment));
						serverComment.markAsRead ();
					}
				}
			} catch (org.starhope.appius.except.NotFoundException e) {
				AppiusClaudiusCaecus
						.reportBug (
								"Caught a org.starhope.appius.except.NotFoundException in BookServiceImpl.getCommentsForPosts ",
								e);
			}
		}
		return clientPosts;
	}

	/**
	 * The pattern that is used to validate post messages and replies.
	 */
	private final Pattern postRequirements = Pattern
	.compile ("^[a-zA-Z\\.,\\!\\?\\\"\\\'\\s-]*$");

	/**
	 * Test constructor
	 */
	public BookServiceImpl () {
		BookServiceImpl.logger
				.log (Level.ALL,
						"\n"
								+ "****************************************************\n"
								+ "************                              **********\n"
								+ "************           GOOD DAY           **********\n"
								+ "************      THOR HAS COMPLETED      *********\n"
								+ "************     LOADING SUCCESSFULLY     **********\n"
								+ "************                              **********\n"
								+ "****************************************************");
	}

	/**
	 * @see com.tootsville.tootsbook.client.BookService#addComment(com.tootsville.tootsbook.client.util.Post)
	 */

	@Override
	public Post addComment (final Post post)
	throws InvalidCredentialsException, SessionTimedOutException,
	PrivilegeRequiredException {
		BookServiceImpl.logger
		.info ("Posting a new comment onto post ID#:"
				+ post.getParentID ());
		BookServiceImpl.logger.info ("pre validated post: "
				+ post.getMessage ());
		final String message = LibMisc.trimWhiteSpace (post
				.getMessage ());
		validatePostMessage (message);
		try {
			final AbstractUser sessionUser = getSessionUser ();
			if ( !sessionUser.canTalk ()) {
				throw new PrivilegeRequiredException (
						LibMisc.getText ("com.tootsville.post.parentapproval"));
			}
			if (sessionUser instanceof Toot) {
				post.setId ( ((Toot) sessionUser)
						.postReplyOnTootBookWall (message,
								post.getParentID ()));
			}
			return post;
		} catch (final DataException e) {
			throw new InvalidCredentialsException (
					LibMisc.getText ("com.tootsville.post.unexpected"));
		} catch (final GameLogicException e) {
			throw new InvalidCredentialsException (
					LibMisc.getText ("com.tootsville.post.foul"));
		}
	}

	/**
	 * @see com.tootsville.tootsbook.client.BookService#addPost(int,
	 *      com.tootsville.tootsbook.client.util.Post)
	 */

	@Override
	public Post addPost (final int toUserID, final Post post)
	throws InvalidCredentialsException, SessionTimedOutException,
	PrivilegeRequiredException {
		BookServiceImpl.logger.info ("Posting a new message.");
		final String message = LibMisc.trimWhiteSpace (post
				.getMessage ());
		validatePostMessage (message);
		try {
			final AbstractUser sessionUser = getSessionUser ();
			if ( !sessionUser.canTalk ()) {
				throw new PrivilegeRequiredException (
						LibMisc.getText ("com.tootsville.post.parentapproval"));
			}
			if (sessionUser instanceof Toot) {
				post.setId ( ((Toot) sessionUser)
						.postNewOnTootBookWall (toUserID, message));
			}
			return post;
		} catch (final DataException e) {
			throw new InvalidCredentialsException (
					LibMisc.getText ("com.tootsville.post.unexpected"));
		} catch (final GameLogicException e) {
			throw new InvalidCredentialsException (
					LibMisc.getText ("com.tootsville.post.foul"));
		}
	}

	/**
	 * @throws SessionTimedOutException WRITEME twheys@gmail.com
	 * @see com.tootsville.tootsbook.client.BookService#applyItems(int,
	 *      int, int, int, int)
	 */
	@Override
	public void applyItems (final int pageBGSlotNum,
			final int boxStylesSlotNum, final int iconItemSlotNum,
			final int titleBGSlotNum, final int avatarBGSlotNum)
	throws SessionTimedOutException {
		final AbstractUser serverUser = getSessionUser ();
		unequipOldItems (serverUser);
		equipNewItems (pageBGSlotNum, boxStylesSlotNum,
				iconItemSlotNum, titleBGSlotNum, avatarBGSlotNum,
				serverUser);
	}

	/**
	 * @throws SessionTimedOutException WRITEME twheys@gmail.com
	 * @see com.tootsville.tootsbook.client.BookService#checkSession()
	 */
	@Override
	public UserProfile checkSession () throws SessionTimedOutException {
		return Translator.getUserFromUser (getSessionUser ());

	}
	
	/**
	 * WRITEME: Document this field. twheys@gmail.com Dec 14, 2009
	 * 
	 * @param serverUser WRITEME twheys@gmail.com
	 */
	private void createSession (final AbstractUser serverUser) {
		getSession ().setAttribute ("sessionUserID",
				Integer.valueOf (serverUser.getUserID ()));
		getSession ().setAttribute ("ipAddress",
				getThreadLocalRequest ().getRemoteHost ());
	}

	/**
	 * @param id WRITEME
	 * @param post WRITEME
	 * @return WRITEME
	 * @throws SessionTimedOutException WRITEME twheys@gmail.com
	 */
	@Override
	public Post deletePost (final int id, final Post post)
	throws SessionTimedOutException {
		final int loggedInUserID = getSessionUser ().getUserID ();
		if (id == loggedInUserID) {
			try {
				BookServiceImpl.logger.info ("ShadowUser ID#"
						+ loggedInUserID + " is deleting post: "
						+ post.getId ());
				final MailMessage serverPost = Nomenclator
						.getDataRecord (MailMessage.class, post
								.getId ());
				if (loggedInUserID == serverPost.getFromID ()
						|| 0 < getSessionUser ().getStaffLevel ()) {
					serverPost.delete ();
				}
			} catch (final org.starhope.appius.except.NotFoundException e) {
				AppiusClaudiusCaecus.reportBug (
						"Unable to find post to delete: "
						+ post.getId (), e);
			}
		}
		return post;
	}
	
	/**
	 * <pre>
	 * twheys@gmail.com Jan 4, 2010
	 * </pre>
	 * 
	 * TO equipNewItems WRITEME...
	 * 
	 * @param pageBGSlotNum WRITEME twheys@gmail.com
	 * @param boxStylesSlotNum WRITEME twheys@gmail.com
	 * @param iconItemSlotNum WRITEME twheys@gmail.com
	 * @param titleBGSlotNum WRITEME twheys@gmail.com
	 * @param avatarBGSlotNum WRITEME twheys@gmail.com
	 * @param serverUser WRITEME twheys@gmail.com
	 */
	private void equipNewItems (final int pageBGSlotNum,
			final int boxStylesSlotNum, final int iconItemSlotNum,
			final int titleBGSlotNum, final int avatarBGSlotNum,
			final AbstractUser serverUser) {
		final Inventory inv = serverUser.getInventory ();
		try {
			inv.don (inv.getFurnitureBySlot (pageBGSlotNum), null);
			inv.don (inv.getFurnitureBySlot (boxStylesSlotNum), null);
			inv.don (inv.getFurnitureBySlot (iconItemSlotNum), null);
			inv.don (inv.getFurnitureBySlot (titleBGSlotNum), null);
			inv.don (inv.getFurnitureBySlot (avatarBGSlotNum), null);
		} catch (final Exception e) {
			AppiusClaudiusCaecus
			.reportBug ("Unable to equip items in inventory: \n"
					+ " PageBG ID: "
					+ pageBGSlotNum
					+ " boxStyles ID: "
					+ boxStylesSlotNum
					+ " icon ID: "
					+ iconItemSlotNum
					+ " titleBG ID: "
					+ titleBGSlotNum
					+ " avatarBG ID: " + avatarBGSlotNum);
		}
	}

	/**
	 * @see com.tootsville.tootsbook.client.BookService#getAdminConsolePosts(int)
	 */
	@Override
	public Post [] getAdminConsolePosts (final int mostRecentPostID) {
		final ConcurrentLinkedQueue <Post> clientPosts = new ConcurrentLinkedQueue <Post> ();
		final Vector <MailMessage> recentTootbookPosts = WebUtil
		.getRecentTootbookPosts (mostRecentPostID, 100);
		BookServiceImpl.getClientPostsForUser (recentTootbookPosts,
				clientPosts);
		BookServiceImpl.getCommentsForPosts (clientPosts);
		BookServiceImpl.logger.info ("Returning " + clientPosts.size ()
				+ " posts.");
		ConnectionDebug.dumpOpenConnections ();
		return clientPosts.toArray (new Post [clientPosts.size ()]);
	}

	/**
	 * @see com.tootsville.tootsbook.client.BookService#getAvailableThemeItems()
	 */
	@Override
	public AvailableItems getAvailableThemeItems ()
	throws SessionTimedOutException, PrivilegeRequiredException {
		final AbstractUser sessionUser = getSessionUser ();
		if (sessionUser.isPaidMember ()) {
			return Translator.getAvailableItemsFromUser (sessionUser);
		}
		throw new PrivilegeRequiredException ();
	}

	/**
	 * @param userID WRITEME
	 * @return WRITEME
	 * @see com.tootsville.tootsbook.client.BookService#getPosts(int)
	 */
	@Override
	public Post [] getBuddyPosts (final int userID) {
		return getMoreBuddyPosts (userID, 0);
	}
	
	/**
	 * <pre>
	 * twheys@gmail.com Jan 15, 2010
	 * </pre>
	 * 
	 * TO getBuddyPostsForUser WRITEME...
	 * 
	 * @param start WRITEME twheys@gmail.com
	 * @param serverUser WRITEME twheys@gmail.com
	 * @param clientPosts WRITEME twheys@gmail.com
	 * @return a LinkedQueue of posts for the home page
	 */
	private ConcurrentLinkedQueue <Post> getBuddyPostsForUser (
			final int start, final AbstractUser serverUser,
			final ConcurrentLinkedQueue <Post> clientPosts) {
		if (serverUser instanceof Toot) {
			for (final MailMessage serverPost : ((Toot) serverUser)
					.getMailOnWallAndBuddyWall (start,
							TootsBook.NUMBER_OF_POSTS)) {
				if ( !"".equals (serverPost.getBody ())) {
					BookServiceImpl.logger.fine ("Loading post ("
							+ serverPost.getID () + "):\nSubject: "
							+ serverPost.getSubject () + "\nBody:"
							+ serverPost.getBody ());
					clientPosts.add (Translator
							.getPostFromPost (serverPost));
					serverPost.markAsRead ();
				}
			}
		}
		return clientPosts;
	}
	
	/**
	 * <pre>
	 * twheys@gmail.com Feb 24, 2010
	 * </pre>
	 * 
	 * TO getInventoryForStore WRITEME...
	 * 
	 * @param storeID WRITEME twheys@gmail.com
	 * @return WRITEME twheys@gmail.com
	 */
	@Override
	public StoreInventory getInventoryForStore (final int storeID) {
		BookServiceImpl.logger.info ("Getting inventory for store ID#"
				+ storeID);
		final StoreInventory storeInventory = new StoreInventory ();
		final Store tootsBookStore = Store.getByID (storeID);
		for (final AbstractItem item : tootsBookStore.getItems ()) {
			storeInventory.addItem (item.getItemID (),
					item.getTitle (), item.getPrice ().intValue ());
		}
		return storeInventory;
	}

	/**
	 * @param userID WRITEME
	 * @param start WRITEME
	 * @return WRITEME
	 * @see com.tootsville.tootsbook.client.BookService#getMorePosts(int,
	 *      int)
	 */
	@Override
	public Post [] getMoreBuddyPosts (final int userID, final int start) {
		final AbstractUser serverUser = Nomenclator
		.getUserByID (userID);
		try {
			isUserRequestValid (userID, serverUser);
		} catch (final PrivilegeRequiredException e) {
			return new Post [] {};
		}
		final ConcurrentLinkedQueue <Post> clientPosts = new ConcurrentLinkedQueue <Post> ();
		getBuddyPostsForUser (start, serverUser, clientPosts);
		BookServiceImpl.getCommentsForPosts (clientPosts);
		BookServiceImpl.logger.info ("Returning " + clientPosts.size ()
				+ " posts.");
		ConnectionDebug.dumpOpenConnections ();
		return clientPosts.toArray (new Post [clientPosts.size ()]);
	}

	/**
	 * @see com.tootsville.tootsbook.client.BookService#getMorePosts(int,
	 *      int)
	 */
	@Override
	public Post [] getMorePosts (final int userID, final int start) {
		final AbstractUser serverUser = Nomenclator
		.getUserByID (userID);
		try {
			isUserRequestValid (userID, serverUser);
		} catch (final PrivilegeRequiredException e) {
			return new Post [] {};
		}
		final ConcurrentLinkedQueue <Post> clientPosts = new ConcurrentLinkedQueue <Post> ();
		getWallPostsForUser (start, serverUser, clientPosts);
		BookServiceImpl.getCommentsForPosts (clientPosts);
		BookServiceImpl.logger.info ("Returning " + clientPosts.size ()
				+ " posts.");
		ConnectionDebug.dumpOpenConnections ();
		return clientPosts.toArray (new Post [clientPosts.size ()]);
	}
	
	/**
	 * <pre>
	 * twheys@gmail.com Feb 15, 2010
	 * </pre>
	 * 
	 * TO getNewHomePosts WRITEME...
	 * 
	 * @param mostRecentPostID WRITEME twheys@gmail.com
	 * @return WRITEME twheys@gmail.com
	 * @throws SessionTimedOutException WRITEME twheys@gmail.com
	 */
	@Override
	public Post [] getNewHomePosts (final int mostRecentPostID)
	throws SessionTimedOutException {
		return getNewPostsForPage (Page.HOME, getSessionUser (),
				mostRecentPostID);
	}
	
	/**
	 * <pre>
	 * twheys@gmail.com Feb 15, 2010
	 * </pre>
	 * 
	 * TO getNewPostsForPage WRITEME...
	 * 
	 * @param page WRITEME twheys@gmail.com
	 * @param profileOwner WRITEME twheys@gmail.com
	 * @param mostRecentPostID WRITEME twheys@gmail.com
	 * @return WRITEME twheys@gmail.com
	 * @throws SessionTimedOutException WRITEME twheys@gmail.com
	 */
	private Post [] getNewPostsForPage (final Page page,
			final AbstractUser profileOwner, final int mostRecentPostID)
	throws SessionTimedOutException {
		BookServiceImpl.logger
		.info ("Checking for posts newer than ID "
				+ mostRecentPostID);
		Vector <MailMessage> serverPosts = null;
		final ConcurrentLinkedQueue <Post> clientPosts = new ConcurrentLinkedQueue <Post> ();
		switch (page) {
			case HOME:
				if (profileOwner instanceof Toot) {
					serverPosts = ((Toot) profileOwner)
					.getNewMailOnWallAndBuddyWall (mostRecentPostID);
				}
				break;
			case PROFILE:
				if (profileOwner instanceof Toot) {
					serverPosts = ((Toot) profileOwner)
					.getNewMailOnWall (mostRecentPostID);
				}
				break;
			default:
			serverPosts = new Vector <MailMessage> ();
				break;
		}
		if (null == serverPosts) {
			serverPosts = new Vector <MailMessage> ();
		}
		BookServiceImpl
		.getClientPostsForUser (serverPosts, clientPosts);
		BookServiceImpl.getCommentsForPosts (clientPosts);
		BookServiceImpl.logger.info ("Returning " + clientPosts.size ()
				+ " new posts to " + profileOwner.getDisplayName ());
		ConnectionDebug.dumpOpenConnections ();
		return clientPosts.toArray (new Post [clientPosts.size ()]);
	}
	
	/**
	 * <pre>
	 * twheys@gmail.com Feb 15, 2010
	 * </pre>
	 * 
	 * TO getNewProfilePosts WRITEME...
	 * 
	 * @param profileID WRITEME twheys@gmail.com
	 * @param mostRecentPostID WRITEME twheys@gmail.com
	 * @return WRITEME twheys@gmail.com
	 * @throws SessionTimedOutException WRITEME twheys@gmail.com
	 */
	@Override
	public Post [] getNewProfilePosts (final int profileID,
			final int mostRecentPostID) throws SessionTimedOutException {
		// Check session
		getSessionUser ();

		final AbstractUser profileOwner = Nomenclator
		.getUserByID (profileID);
		if (null != profileOwner) {
			return getNewPostsForPage (Page.PROFILE, profileOwner,
					mostRecentPostID);
		}
		return new Post [] {};
	}

	/**
	 * @see com.tootsville.tootsbook.client.BookService#getPeanutsForUser(int)
	 */
	@Override
	public int getPeanutsForUser (final int userID) {

		final AbstractUser userByID = Nomenclator.getUserByID (userID);
		if (null == userByID) {
			return 0;
		}
		if (userByID instanceof Toot) {
            return userByID.getWallet ().get (
                    org.starhope.appius.mb.Currency.getPeanuts ())
                    .intValue ();
		}
		return 0;
	}

	/**
	 * @see com.tootsville.tootsbook.client.BookService#getPosts(int)
	 */
	@Override
	public Post [] getPosts (final int userID) {
		return getMorePosts (userID, 0);
	}
	
	/**
	 * /** Returns the current session
	 * 
	 * @return The current session
	 */
	public HttpSession getSession () {
		return getThreadLocalRequest ().getSession ();
	}
	
	/**
	 * Get the session user or check if the user is still logged in.
	 * 
	 * @return The session ShadowUser
	 * @throws SessionTimedOutException If the session has expired or
	 *             another error occured retrieving it.
	 */
	private AbstractUser getSessionUser ()
	throws SessionTimedOutException {
		try {
			if (null == getSession ().getAttribute ("sessionUserID")) {
				throw new SessionTimedOutException ();
			}
			final int userID = ((Integer) getSession ().getAttribute (
			"sessionUserID")).intValue ();
			final AbstractUser user = Nomenclator.getUserByID (userID);
			validateSession ();
			if (null == user) {
				throw new SessionTimedOutException ();
			}
			if (user.isKicked () || user.isBanned ()) {
				throw new SessionTimedOutException (
						user.getKickedMessage ());
			}
			BookServiceImpl.logger.info ("Found ShadowUser ID#"
					+ user.getUserID () + " in a session.");
			return user;
		} catch (final Exception e) {
			if ( ! (e instanceof SessionTimedOutException)) {
				AppiusClaudiusCaecus
				.reportBug (
						"Unexpected exception retrieving session user.",
						e);
			}
			throw new SessionTimedOutException ();
		}
	}

	/**
	 * @see com.tootsville.tootsbook.client.BookService#getUser(int)
	 */
	@Override
	public UserProfile getUser (final int userID)
	throws NotFoundException {
		BookServiceImpl.logger
		.info ("Client is requesting user information for user ID#"
				+ userID);
		final AbstractUser serverUser = Nomenclator
		.getUserByID (userID);
		if (null == serverUser) {
			throw new NotFoundException ("ShadowUser with ID " + userID
					+ " was not found.");
		}
		if ('$' == serverUser.getAvatarLabel ().charAt (0)) {
			throw new NotFoundException (
			"You are not allowed to view that profile.");
		}
		if ("Shade".equalsIgnoreCase (serverUser.getAvatarLabel ())) {
			throw new NotFoundException (
			"You are not allowed to view that profile.");
		}
		if (serverUser.isCanceled () || serverUser.isBanned ()
				|| serverUser.isKicked ()) {
			throw new NotFoundException (
			"Sorry!  That account is not currently active.");
		}

		final UserProfile clientUser = Translator
		.getUserFromUser (serverUser);
		BookServiceImpl.logger.info ("Returning ShadowUser ID#"
				+ clientUser.getUserID ());
		ConnectionDebug.dumpOpenConnections ();
		return clientUser;
	}
	
	/**
	 * <pre>
	 * twheys@gmail.com Jan 15, 2010
	 * </pre>
	 * 
	 * TO getBuddyPostsForUser WRITEME...
	 * 
	 * @param start WRITEME twheys@gmail.com
	 * @param serverUser WRITEME twheys@gmail.com
	 * @param clientPosts WRITEME twheys@gmail.com
	 * @return a LinkedQueue of posts for the home page
	 */
	private ConcurrentLinkedQueue <Post> getWallPostsForUser (
			final int start, final AbstractUser serverUser,
			final ConcurrentLinkedQueue <Post> clientPosts) {
		if (serverUser instanceof Toot) {
			for (final MailMessage serverPost : ((Toot) serverUser)
					.getMailOnWall (TootsBook.NUMBER_OF_POSTS, start)) {
				if ( !"".equals (serverPost.getBody ())) {
					BookServiceImpl.logger.fine ("Loading post ("
							+ serverPost.getID () + "):\nSubject: "
							+ serverPost.getSubject () + "\nBody:"
							+ serverPost.getBody ());
					clientPosts.add (Translator
							.getPostFromPost (serverPost));
					serverPost.markAsRead ();
				}
			}
		}
		return clientPosts;
	}
	
	/**
	 * WRITEME: Document this field. twheys@gmail.com Dec 17, 2009
	 * 
	 * @param userID WRITEME twheys@gmail.com
	 * @param serverUser WRITEME twheys@gmail.com
	 * @throws PrivilegeRequiredException WRITEME twheys@gmail.com
	 */
	private void isUserRequestValid (final int userID,
			final AbstractUser serverUser)
	throws PrivilegeRequiredException {
		if (null == serverUser) {
			AppiusClaudiusCaecus.reportBug ("Unable to find userID ("
					+ userID + ") when calling for posts.");
			throw new PrivilegeRequiredException (
			"That user does not exist!  Please try refreshing your browser.");
		}
	}

	/**
	 * @see com.tootsville.tootsbook.client.BookService#login(java.lang.String,
	 *      java.lang.String)
	 */
	@Override
	public UserProfile login (final String userName,
			final String password)
	throws NotFoundException, PrivilegeRequiredException,
	InvalidCredentialsException {
		BookServiceImpl.logger
		.info ("Trying to log in from server with u: "
				+ userName + " and p:" + password);
		if (null == userName || null == password) {
			throw new InvalidCredentialsException (
					LibMisc.getText ("com.tootsville.tootsbook.badlogin"));
		}
		UserProfile clientUser = null;

		try {
			clientUser = Translator.getUserFromUser (getSessionUser ());
		} catch (final SessionTimedOutException e) {
			final AbstractUser serverUser = Nomenclator
			.getUserByLogin (userName);
			verifyLogin (userName, password, serverUser);
			clientUser = Translator.getUserFromUser (serverUser);
			createSession (serverUser);
		}

		if (null == clientUser) {
			throw new PrivilegeRequiredException (
					"You must be logged in to do that!");
		}
		return clientUser;
	}

	/**
	 * @see com.tootsville.tootsbook.client.BookService#logout()
	 */
	@Override
	public void logout () {
		getSession ().removeAttribute ("sessionUserID");
	}
	
	/**
	 * <pre>
	 * twheys@gmail.com Feb 26, 2010
	 * </pre>
	 * 
	 * TO purchaseItem WRITEME...
	 * 
	 * @param itemID WRITEME
	 * @throws SessionTimedOutException WRITEME
	 * @throws AlreadyExistsException WRITEME
	 * @throws InsufficientFundsException WRITEME
	 */
	@Override
	public void purchaseItem (final int itemID)
	throws SessionTimedOutException, AlreadyExistsException,
	InsufficientFundsException {
		final AbstractUser sessionUser = getSessionUser ();
		try {
			BookServiceImpl.logger.info ("ShadowUser ID#"
					+ sessionUser.getUserID ()
					+ " is purchasing item #" + itemID);
			sessionUser.purchase (Nomenclator.getDataRecord (
									org.starhope.appius.game.inventory.GenericItemReference.class,
					itemID));
		} catch (final org.starhope.appius.except.NotFoundException e) {
			BookServiceImpl.logger.info ("ShadowUser ID#"
					+ sessionUser.getUserID ()
					+ " is cannot purchase item #" + itemID
					+ " because it doesn't exist!");
			AppiusClaudiusCaecus
			.reportBug (
					"Item not found when purchasing from TootsBook Store",
					e);
		} catch (final org.starhope.appius.except.AlreadyExistsException e) {
			BookServiceImpl.logger.info ("ShadowUser ID#"
					+ sessionUser.getUserID ()
					+ " is cannot purchase item #" + itemID
					+ " because they already have one!");
			throw new AlreadyExistsException (
					LibMisc.getText ("com.tootsbook.store.exception.already-exists"));
		} catch (final org.starhope.appius.except.NonSufficientFundsException e) {
			BookServiceImpl.logger.info ("ShadowUser ID#"
					+ sessionUser.getUserID ()
					+ " is cannot purchase item #" + itemID
					+ " because they can't afford it!");
			throw new InsufficientFundsException (
					LibMisc.getText ("com.tootsbook.store.exception.already-exists"));
		}
	}

	/**
	 * @see com.tootsville.tootsbook.client.BookService#reportPost(com.tootsville.tootsbook.client.util.Post)
	 */
	@Override
	public Post reportPost (final Post post)
	throws SessionTimedOutException {
		final String reportPostMessage = "TootsBook Post:"
			+ post.getAuthor ().getPublicProfileLink ()
			+ "\nPost ID#: " + post.getId () + "\nMessage:\n"
			+ post.getMessage ();
		BookServiceImpl.logger.info (reportPostMessage);
		final AbstractUser reportedUser = Nomenclator.getUserByID (post
				.getAuthor ().getUserID ());
		final AbstractUser sessionUser = getSessionUser ();

		// You can't report the man.
		if (reportedUser.hasStaffLevel (8)) {
			getSession ().invalidate ();
			if (sessionUser instanceof Toot) {
				((Toot) sessionUser).addPeanuts (BigInteger
						.valueOf ( -500), "givenuts");
			}
			throw new SessionTimedOutException ();
		}
		reportedUser.reportedToModeratorBy (sessionUser,
				reportPostMessage);
		return post;
	}
	
    final static String [] oldEquipString = { "tb-avatar-bg",
            "tb-box-styles", "tb-icon", "tb-page-bg", "tb-title-bg" };

	/**
	 * <pre>
	 * twheys@gmail.com Jan 4, 2010
	 * </pre>
	 * 
	 * TO unequipOldItems WRITEME...
	 * 
	 * @param serverUser WRITEME
	 */
	private void unequipOldItems (final AbstractUser serverUser) {
		final Inventory inv = serverUser.getInventory ();
        InventoryItem item;
        for (String typeString : oldEquipString) {
            try {
                item = inv.getActiveItemByType (Nomenclator
                        .getDataRecord (InventoryItemType.class,
                                typeString));
                if (null != item)
                    item.unequip ();
            } catch (final org.starhope.appius.except.NotFoundException e) {
                throw AppiusClaudiusCaecus
                        .fatalBug (
                                "Unhandled org.starhope.appius.except.NotFoundException in unequipOldItems",
                                e);
            }
        }
	}
	
	/**
	 * Validate a post message using String comparison and pattern
	 * matching.
	 * 
	 * @param message The message to be validated.
	 * @throws InvalidCredentialsException WRITEME
	 */
	private void validatePostMessage (final String message)
	throws InvalidCredentialsException {
		BookServiceImpl.logger.info ("validating post: " + message);
		final Matcher postValidator = postRequirements
		.matcher (message);
		if (message.length () > TootsBook.MAX_CHAR_LENGTH) {
			throw new InvalidCredentialsException (
					LibMisc.getText ("com.tootsville.post.toolong"));
		}
		if ( !postValidator.matches ()) {
			throw new InvalidCredentialsException (
					LibMisc.getText ("com.tootsville.post.invalid"));
		}
	}
	
	/**
	 * <pre>
	 * twheys@gmail.com Jan 13, 2010
	 * </pre>
	 * 
	 * TO validateSession WRITEME...
	 * 
	 * @throws SessionTimedOutException WRITEME
	 */
	private void validateSession () throws SessionTimedOutException {
		final String currentIP = getThreadLocalRequest ()
		.getRemoteHost ();
		final String cachedIP = (String) getThreadLocalRequest ()
		.getSession ().getAttribute ("ipAddress");
		System.out.println ("Current IP: " + currentIP
				+ "\nCached IP: " + cachedIP);
		if ( !currentIP.equalsIgnoreCase (cachedIP)) {
			getThreadLocalRequest ().getSession ().invalidate ();
			throw new SessionTimedOutException ();
		}
	}
	
	/**
	 * <pre>
	 * twheys@gmail.com Dec 17, 2009
	 * </pre>
	 * 
	 * TO verifyLogin WRITEME...
	 * 
	 * @param userName WRITEME twheys@gmail.com
	 * @param password WRITEME twheys@gmail.com
	 * @param serverUser WRITEME twheys@gmail.com
	 * @throws NotFoundException WRITEME twheys@gmail.com
	 * @throws PrivilegeRequiredException WRITEME twheys@gmail.com
	 * @throws InvalidCredentialsException WRITEME twheys@gmail.com
	 */
	private void verifyLogin (final String userName,
			final String password, final AbstractUser serverUser)
	throws NotFoundException, PrivilegeRequiredException,
	InvalidCredentialsException {
		if (null == serverUser) {
			throw new NotFoundException (
					"No user exists with the user name " + userName
					+ "!");
		}
		if (serverUser.isNPC ()) {
			throw new NotFoundException ("You're not " + userName);
		}
		if ( ! (serverUser instanceof User)) {
			throw new NotFoundException ("You're not " + userName);
		}
		if ( ! ((User) serverUser).checkPassword (password)) {
			throw new InvalidCredentialsException (
					LibMisc.getText ("com.tootsville.tootsbook.badlogin"));
		}
		if (serverUser.isKicked () || serverUser.isBanned ()) {
			throw new PrivilegeRequiredException (
					serverUser.getKickedMessage ());
		}
		if (serverUser.isCanceled ()) {
			throw new PrivilegeRequiredException (
					LibMisc.getText ("canceled"));
		}
		if (AppiusConfig
				.getConfigBoolOrFalse ("org.starhope.appius.requireBeta")) {
			log ("Beta Testing enabled");
			if ( ! ((GeneralUser) serverUser).canBetaTest ()) {
				throw new PrivilegeRequiredException (
						LibMisc.getText ("beta-test"));
			}
		}
	}


}
