/**
 * Copyright 2009-2010, Res Interactive, LLC. All Rights Reserved.
 */

package com.tootsville;

import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.net.URLDecoder;
import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Map;
import java.util.Vector;
import java.util.Map.Entry;

import javax.servlet.ServletRequest;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.jsp.PageContext;

import net.authorize.admc.authnet.AimTransaction;
import net.authorize.admc.authnet.AuthNetException;

import org.starhope.appius.except.AlreadyExistsException;
import org.starhope.appius.except.AlreadyUsedException;
import org.starhope.appius.except.DataException;
import org.starhope.appius.except.GameLogicException;
import org.starhope.appius.except.NotFoundException;
import org.starhope.appius.game.AppiusClaudiusCaecus;
import org.starhope.appius.mb.BeforePageHook;
import org.starhope.appius.mb.Enrolment;
import org.starhope.appius.mb.MBErrorReason;
import org.starhope.appius.mb.MBGoal;
import org.starhope.appius.mb.MBParamTranslator;
import org.starhope.appius.mb.MBSession;
import org.starhope.appius.mb.Messages;
import org.starhope.appius.mb.Payment;
import org.starhope.appius.mb.PaymentGateway;
import org.starhope.appius.mb.PostParentRegistrationHook;
import org.starhope.appius.mb.PostPaymentHook;
import org.starhope.appius.mb.PostUserRegistrationHook;
import org.starhope.appius.mb.UserEnrolment;
import org.starhope.appius.mb.fields.MBFieldIdent;
import org.starhope.appius.types.GameWorldMessage;
import org.starhope.appius.user.AbstractPerson;
import org.starhope.appius.user.AbstractUser;
import org.starhope.appius.user.GeneralUser;
import org.starhope.appius.user.Nomenclator;
import org.starhope.appius.user.Parent;
import org.starhope.appius.user.User;
import org.starhope.appius.util.AppiusConfig;
import org.starhope.util.LibMisc;

import com.tootsville.promo.Promotion;
import com.tootsville.user.Toot;

/**
 * <p>
 * WebUtil contains business logic methods called by the Membership And
 * Billing front-end JSP pages. This isolates the more complex logic
 * from the presentation-level code in the JSP files.
 * </p>
 * <p>
 * This, however, turned into a mess of spaghetti code that was mostly
 * split up into more appropriate places, including funnelling most of
 * it into {@link MBSession}.
 * </p>
 *
 * @author twheys@gmail.com
 * @author brpocock@star-hope.org
 */
public class WebUtil implements MBParamTranslator,
		PostUserRegistrationHook, PostParentRegistrationHook,
		PostPaymentHook, BeforePageHook {

	/**
	 * singleton for hook impl
	 */
	private static WebUtil singleton = new WebUtil ();

	/**
	 * Apply a Peanut code to a new user account
	 *
	 * @param session user's session object
	 * @param newToot the user account just registered
	 */
	public static void applyPeanutCodeToNewUser (
			final MBSession session, final Toot newToot) {
		try {
			final String pnt = newToot.acceptPeanutCode (session
					.getExtraParam ("com.tootsville.peanutCode"));
			AppiusClaudiusCaecus
					.blather ("Peanut code accepted with message of "
							+ pnt);
			session.addToast ("code", pnt);
		} catch (final NotFoundException e) {
			AppiusClaudiusCaecus.blather ("Peanut code denied!");
			session.addErrorToast (Messages.code_invalid ()); // TODO
		} catch (final AlreadyUsedException e) {
			AppiusClaudiusCaecus.blather ("Peanut code already used!");
			session
					.addErrorToast (LibMisc
							.getTextOrDefault (
									"mb.tootsville.peanutCode.alreadyUsed",
									"The peanut code that you entered has already been used. Peanut codes can only be used once.")); // TODO
		}
	}

	/**
	 * <pre>
	 * twheys@gmail.com Feb 2, 2010
	 * </pre>
	 * <p>
	 * Check an IBC code / Chase-It / prepaid card token …
	 * </p>
	 *
	 * @param ibcCode prepaid membership card token
	 * @param sessionUser the user claiming the card's benefits
	 * @return next URL in chain
	 * @throws GameLogicException WRITEME
	 */
	private static String checkIBC (final Object ibcCode,
			final User sessionUser) throws GameLogicException {
		if (null == ibcCode) {
			throw new GameLogicException ("No IBC Attriburte", null,
					null);
		}
		final String eCode = ibcCode.toString ();
		try {
			AppiusClaudiusCaecus.blather ("Applying Chase-It eCode ("
					+ eCode + ") to User ID: "
					+ sessionUser.getUserID ());
			IBCUtil.redeemCode (eCode, sessionUser);
			AppiusClaudiusCaecus
					.blather ("Chase-It eCode applied successfully to User ID: "
							+ sessionUser.getUserID ());
		} catch (final DataException e) {
			AppiusClaudiusCaecus.reportBug (e);
		}
		return "/membership/register/thankyou/?who=chase";

	}

	/**
	 * Process cookies and set session values. Currently only handles
	 * “remember my name” buttons.
	 *
	 * @param request the HTTP request passed through the servlet.
	 */
	public static void cookieMonster (final HttpServletRequest request) {
		MBSession session = MBSession.get (request);
		// ----------- Check for cookies and autologin if one exists and
		// is valid.
		// cookie name: username
		for (Cookie cookie : request.getCookies ()) {
			if (cookie.getName ().equals ("TootsvillePlayer")
					&& cookie.getValue ().length () > 2) {
				session.loginAuth.setValueIfNull (cookie.getValue ());
				break;
			} else if (cookie.getName ().equals ("TootsvilleParent")
					&& cookie.getValue ().length () > 2) {
				session.mailProvided
						.setValueIfNull (cookie.getValue ());
			}
		}
	}

	/**
	 * <pre>
	 * twheys@gmail.com Jan 7, 2010
	 * </pre>
	 * <p>
	 * Create a new promotional programme code series. These are the
	 * “peanut codes” that can be printed on promotional materials such
	 * as post-cards or hang-tags and distributed to kids, and then
	 * later redeemed for in-game peanuts. The promotional codes will
	 * consist of a given prefix (which can be used for tracking the
	 * users redeeming them later) and a series of random codes
	 * following it.
	 * </p>
	 * <p>
	 * Prefix codes should all be 4-character alphabetic and avoid the
	 * use of the letters “O” and “I” if their usage might be ambiguous
	 * in-situ.
	 * </p>
	 *
	 * @param prefix The unique 4-character code representing the
	 *            promotional programme; this will be prefaced onto each
	 *            generated code.
	 * @param numberOfCodes The number of promotional codes to be
	 *            generated
	 * @param minValue The minimum number of peanuts to be awarded for
	 *            each code
	 * @param maxValue The maximum number of peanuts to be awarded for
	 *            each code
	 * @return The newly-created Promotion (which can be safely ignored,
	 *         but); or, null on failure (which maybe should be
	 *         checked?)
	 */
	public static Promotion createNewEventPromotion (
			final String prefix, final int numberOfCodes,
			final int minValue, final int maxValue) {
		try {
			final Promotion p = Promotion.createEventPromotion (prefix);
			p.createPeanutCodesForPromotion (numberOfCodes, minValue,
					maxValue);
			return p;
		} catch (final IndexOutOfBoundsException e) {
			AppiusClaudiusCaecus.reportBug (e);
		} catch (final DataException e) {
			AppiusClaudiusCaecus.reportBug (e);
		} catch (final AlreadyExistsException e) {
			AppiusClaudiusCaecus.reportBug (e);
		}
		return null;
	}

	/**
	 * @return the WebUtil singleton
	 */
	public static WebUtil get () {
		return WebUtil.singleton;
	}

	/**
	 * <p>
	 * Get a collection of recent TootsBook postings.
	 * </p>
	 * <p>
	 * This returns a set of TootsBook messages (wall posts). It will
	 * return only “wall postings,” where the user set their own status;
	 * it will not return comments upon this, nor another user's
	 * postings.
	 * </p>
	 * XXX: contains SQL
	 *
	 * @param offset The first message to be returned, in negative
	 *            timewise order (newest-first)
	 * @param limit The number of messages to be returned, starting at
	 *            offset, in negative timewise order (newest-first)
	 * @return a set of GameWorldMessage objects ordered in negative
	 *         timewise order
	 */
	public static Vector <GameWorldMessage> getRecentTootbookPosts (
			final int offset, final int limit) {
		Vector <GameWorldMessage> messages = new Vector <GameWorldMessage> ();
		Connection con = null;
		PreparedStatement st = null;
		try {
			con = AppiusConfig.getDatabaseConnection ();
			st = con
					.prepareStatement ("SELECT * FROM messages WHERE fromUserID=toUserID AND isDeleted='W' AND inReplyTo IS NULL AND body<>'' ORDER BY messages.sentTime DESC LIMIT ? OFFSET ?");
			st.setInt (1, limit);
			st.setInt (2, offset);
			messages = AppiusConfig.newGameWorldMessage ()
					.getMessagesFrom (st);
		} catch (final SQLException e) {
			AppiusClaudiusCaecus.reportBug (e);
		} finally {
			if (null != st) {
				try {
					st.close ();
				} catch (final SQLException e) { /* No Op */
				}
			}
			if (null != con) {
				try {
					con.close ();
				} catch (final SQLException e) { /* No Op */
				}
			}
		}
		return messages;
	}

	/**
	 * Syntactic sugar function for embedding the set-up step in JSP
	 * pages
	 */
	public static void go () {
		WebUtil.get ().configureMembershipAndBilling ();
	}

	/**
	 * Deprecated, use {@link AppiusClaudiusCaecus#blather(String)}
	 *
	 * @param blah string to log
	 * @deprecated use {@link AppiusClaudiusCaecus#blather(String)}
	 * @see AppiusClaudiusCaecus#blather(String)
	 * @see AppiusClaudiusCaecus#blather(String, String, String, String,
	 *      boolean)
	 */
	@Deprecated
	public static void log (final String blah) {
		AppiusClaudiusCaecus.blather (blah);
	}

	/**
	 * <p>
	 * Verifies POST data to ensure ... something? ... WRITEME ... and
	 * then it renews the subscription
	 * </p>
	 * <p>
	 * WRITEME: this must be the Authorize.net Silent-POST handler?
	 * </p>
	 * <p>
	 * FIXME: This stuff belongs in AuthorizeNetGateway
	 * </p>
	 *
	 * @param request servlet request from the user... or from
	 *            Authorize.Net ... ?
	 * @throws DataException if the validation keys aren't present
	 * @throws UnsupportedEncodingException WRITEME
	 */
	@SuppressWarnings ("unchecked")
	public static void renewSubscription (final ServletRequest request)
			throws DataException, UnsupportedEncodingException {
		if (null == request.getParameter ("x_subscription_id")
				|| null == request
						.getParameter ("x_subscription_paynum")) {
			throw new DataException (
					"HTTP Post does not contain required values to be verified as a subscription transaction");
		}

		final BigDecimal paid = new BigDecimal (URLDecoder.decode (
				request.getParameter ("x_amount"), "UTF-8"));
		final BigDecimal transID = new BigDecimal (URLDecoder.decode (
				request.getParameter ("x_trans_id"), "UTF-8"));
		final String md5hash = URLDecoder.decode (request
				.getParameter ("x_MD5_Hash"), "UTF-8");
		final String resultReason = URLDecoder.decode (request
				.getParameter ("x_response_reason_text"), "UTF-8");
		final boolean testMode = "true".equals (URLDecoder.decode (
				request.getParameter ("x_test_request"), "UTF-8"));
		final String invoiceID = URLDecoder.decode (request
				.getParameter ("x_invoice_num"), "UTF-8");
		final String [] invoice = invoiceID.split ("-");
		final String payer = URLDecoder.decode (request
				.getParameter ("x_first_name"), "UTF-8")
				+ " "
				+ URLDecoder.decode (request
						.getParameter ("x_last_name"), "UTF-8");
		String orderSource = "";
		String orderCode = "";
		int sequence = 0;
		try {
			orderSource = invoice [0];
			orderCode = invoice [1];
			sequence = Integer.parseInt (URLDecoder.decode (request
					.getParameter ("x_subscription_paynum"), "UTF-8"));
		} catch (final IndexOutOfBoundsException e) {
			AppiusClaudiusCaecus.reportBug (
					"Invoice ID does not contain order source or order code: "
							+ invoiceID, e);
			throw new DataException (
					"HTTP POST does not contain required values to be verified as a subscription transaction");
		} catch (final NumberFormatException e) {
			AppiusClaudiusCaecus
					.reportBug (
							"Payment Number is not abled to be parsed to int: "
									+ request
											.getParameter ("x_subscription_paynum"),
							e);
			throw new DataException (
					"HTTP POST does not contain required values to be verified as a subscription transaction");
		}

		UserEnrolment subscription = null;
		Payment payment = null;
		try {
			subscription = UserEnrolment.getBySourceAndCode (
					orderSource, orderCode);
			payment = new Payment (subscription);
		} catch (final Exception e) {
			AppiusClaudiusCaecus.fatalBug (e);
		}

		if (null == payment || null == subscription) {
			throw new DataException (
					"HTTP POST does not contain required values to be verified as a subscription transaction");
		}

		try {
			payment.setGatewayTransactionCode (transID);
			payment.setTest (testMode);
			payment.addAnnotation ("net.authorize.arb.subscriptionID",
					request.getParameter ("x_subscription_id"));
			payment.addAnnotation ("net.authorize.arb.reason.code",
					request.getParameter ("x_response_reason_code"));
			payment.addAnnotation ("net.authorize.arb.reason.text",
					request.getParameter ("x_response_reason_text"));
			payment.addAnnotation ("com.tootsville.user.id", request
					.getParameter ("x_cust_id"));
			payment.addAnnotation ("net.authorize.arb.type", request
					.getParameter ("x_type"));
			payment.addAnnotation ("net.authorize.arb.code", request
					.getParameter ("x_code"));
			payment.addAnnotation ("net.authorize.arb.description",
					request.getParameter ("x_description"));
		} catch (final AlreadyUsedException e) {
			AppiusClaudiusCaecus.reportBug ("Payment already made", e);
			throw new DataException (
					"HTTP Post does not contain required values to be verified as a subscription transaction");
		}

		final StringBuilder message = new StringBuilder ();
		message.append ("Servlet Request Parameters");
		final Map <Object, Object> names = request.getParameterMap ();
		for (final Entry <Object, Object> entry : names.entrySet ()) {
			message.append ("\n\tName: ");
			message.append (entry.getKey ());
			message.append ("\n\tValue: ");
			message.append (entry.getValue ());
			message.append ("\n\tClass: ");
			message.append (entry.getValue ().getClass ()
					.getCanonicalName ());
		}
		System.out.println (message.toString ());

		try {
			AimTransaction.checkMd5Hash (md5hash, "", transID
					.toPlainString (), paid.toPlainString ());
			payment.setVerified (true);
		} catch (final AuthNetException e) {
			AppiusClaudiusCaecus
					.reportBug ("Possible attempt to bypass security!"
							+ "\n\n"
							+ "I'm not allowing this transaction "
							+ "to continue because the MD5 "
							+ "checksum returned from the "
							+ "Authorize.Net servers "
							+ "*** DOES NOT VALIDATE. ***" + "\n\n"
							+ "This is likely to indicate an "
							+ "attempt to forge a response, "
							+ "possibly violating the "
							+ "integrity of customer data. " + "\n\n"
							+ "**********************************\n"
							+ "* THIS IS A VERY, VERY BIG DEAL. *\n"
							+ "**********************************\n", e);
			throw new DataException (
					"HTTP POST from Authorize.Net refused due to MD5 Signature failure");
		}

		payment.setResultReason (resultReason);
		payment.setSequence (sequence);
		payment.setPrice (subscription.getEnrolment ().getPrice ());
		payment.setPayer (payer);
		payment.setPaymentFor ("ENROL");
		payment.setPaid (paid);
		final String responseCode = URLDecoder.decode (request
				.getParameter ("x_response_code"), "UTF-8");
		try {
			payment.setSuccess (1 == Integer.parseInt (responseCode));
		} catch (final NumberFormatException e) {
			AppiusClaudiusCaecus.reportBug (
					"ARB Response Code is not being sent as a number.  Response Code: "
							+ responseCode, e);
			payment.setSuccess (false);
		}
		payment.shredCredentials ();

		try {
			payment.close ();
		} catch (final SQLException e) {
			AppiusClaudiusCaecus.fatalBug (e);
		}

		if ( !"auth".equalsIgnoreCase (orderSource)) {
			AppiusClaudiusCaecus
					.reportBug ("LEGACY AUTHORIZE.NET TRANSACTION DETAILS:\n"
							+ LibMisc.stringify (names));
		}
	}

	/**
	 * find users who ... um ... something. WRITEME twheys@gmail.com
	 * <p>
	 * XXX: It looks like this is going to fetch up users and parents
	 * based upon ID's that could potentially be the same ??
	 * </p>
	 * <p>
	 * Note, {@link Nomenclator#getUserByLogin(String)} understands # +
	 * user ID. So user names of the form "#123" are acceptable... I
	 * don't think {@link Nomenclator#getParentByMail(String)} has an
	 * analogous method, though.
	 * </p>
	 *
	 * @param searchParams WRITEME
	 * @return WRITEME
	 */
	public static Vector <AbstractPerson> searchUsers (
			final String searchParams) {
		final Vector <AbstractPerson> results = new Vector <AbstractPerson> ();

		if (null == searchParams || "".equals (searchParams)) {
			return results;
		}
		final int someKindOfID;
		try {
			someKindOfID = Integer.parseInt (searchParams);
		} catch (final NumberFormatException e) {
			return results;
		}
		{
			final AbstractUser userByID = Nomenclator
					.getUserByID (someKindOfID);
			if (userByID instanceof GeneralUser) {
				results.add ((Toot) userByID);
			}
		}
		{
			final Parent p = Nomenclator.getParentByID (someKindOfID);
			if (null != p) {
				results.add (p);
			}
		}
		{
			final Toot u = (Toot) Nomenclator
					.getUserByLogin (searchParams);
			if (null != u) {
				results.add (u);
			}
		}
		{
			final Parent p = Nomenclator.getParentByMail (searchParams);
			if (null != p) {
				results.add (p);
			}
		}
		{
			final Parent parent = Nomenclator
					.getParentByMail (searchParams);
			if (null != parent) {
				results.add (parent);
			}
		}

		return results;
	}

	/**
	 * <p>
	 * This is the really critical stage of the billing process, and one
	 * in which we've historically had a <em>lot</em> of trouble.
	 * </p>
	 * <p>
	 * This method takes the user-provided credit-card/billing
	 * information from the web payment form, and attempts to process
	 * the credit-card transaction through the {@link PaymentGateway}
	 * and add a {@link UserEnrolment} to the user's record.
	 * </p>
	 * <p>
	 * There are a lot of things going on in here, and a lot of values
	 * being passed around. As a result, this is a very long, very
	 * linear patch of code, of the type I (BRP) dislike seeing
	 * immensely; however, I don't see any legitimately effective way to
	 * break this down into subroutines, since most of the values are
	 * being passed around in a huge bulk of separate things.
	 * </p>
	 * <p>
	 * XXX Eventually, this should be using some more high-level objects
	 * that will check their own values in a more reasonable way; e.g.
	 * an Address object smart enough to parse and validate its own
	 * component values and return detailed exceptions that can be
	 * propagated to the end-user.
	 * </p>
	 * <p>
	 * Due to lack of time, I'm afraid I need to just refer the
	 * interested user to the source code comments.
	 * </p>
	 * <p>
	 * WRITEME: There should be more detail in this JavaDoc!
	 * </p>
	 * <p>
	 * <i>above vapidly poor documentation by brpocock@star-hope.org</i>
	 * </p>
	 */

	UserEnrolment subscription = null;

	/**
	 * noop ctor is private for singleton impl
	 */
	private WebUtil () { /* no op */
	}

	/**
	 * @see org.starhope.appius.mb.BeforePageHook#beforePage(org.starhope.appius.mb.MBSession,
	 *      javax.servlet.jsp.PageContext)
	 */
	@Override
	public void beforePage (final MBSession mbSession,
			final PageContext context) {
		WebUtil.cookieMonster ((HttpServletRequest) context
				.getRequest ());
	}

	/**
	 * This performs some start-up configuration of the generic, Romance
	 * M&B system for working with Tootsville, particularly.
	 */
	public void configureMembershipAndBilling () {
		MBSession.mapField (MBFieldIdent.ADDRESS, "address");
		MBSession.mapField (MBFieldIdent.CAN_CONTACT, "canContact");
		MBSession.mapField (MBFieldIdent.MAIL_PROVIDED, "email");
		MBSession.mapConfirmField (MBFieldIdent.MAIL_PROVIDED,
				"emailConfirm");
		MBSession.mapField (MBFieldIdent.CHARACTER_CLASS, "toot");
		MBSession.mapField (MBFieldIdent.LOGIN_AUTH, "uUsername");
		MBSession.mapField (MBFieldIdent.PASSWORD_AUTH, "uPassword");
		MBSession.mapField (MBFieldIdent.PASSWORD_REQUESTED,
				"passwordRequested");
		MBSession.mapConfirmField (MBFieldIdent.PASSWORD_REQUESTED,
				"passwordRequestedConfirm");
		MBSession.mapField (MBFieldIdent.LOGIN_REQUESTED,
				"loginRequested");
		MBSession.mapField (MBFieldIdent.TERMS_CONDITIONS_AGREE,
				"termsConditionsAgree");
		MBSession.mapField (MBFieldIdent.RULES_AGREE, "agree");
		// TODO…
		MBSession.provideURL (new MBFieldIdent [] {
				MBFieldIdent.LOGIN_AUTH, MBFieldIdent.MAIL_AUTH,
				MBFieldIdent.PASSWORD_AUTH }, "/membership");
		MBSession.provideURL (
				new MBFieldIdent [] { MBFieldIdent.CHARACTER_CLASS },
				"/membership/register/pick-a-toot");
		MBSession.provideURL (
				new MBFieldIdent [] { MBFieldIdent.RULES_AGREE },
				"/membership/register/rules");
		MBSession.provideURL (new MBFieldIdent [] {
				MBFieldIdent.LOGIN_REQUESTED,
				MBFieldIdent.MAIL_PROVIDED,
				MBFieldIdent.DATE_OF_BIRTH_GIVEN,
				MBFieldIdent.CAN_CONTACT,
				MBFieldIdent.FORGOT_PASSWORD_QUESTION,
				MBFieldIdent.FORGOT_PASSWORD_ANSWER,
				MBFieldIdent.PASSWORD_REQUESTED,
				MBFieldIdent.TERMS_CONDITIONS_AGREE },
				"/membership/register/user-details");
		// TODO…
		MBSession.addParamTranslator (this);
		MBSession.addPostUserRegistrationHook (this);
		MBSession.addPostParentRegistrationHook (this);
		MBSession.addPostEnrolmentHook (this);

		MBSession.setBeforePageHook (this);
	}

	/**
	 * @see org.starhope.appius.mb.PostPaymentHook#doAfterEnrolmentAfterLapse(org.starhope.appius.mb.MBSession,
	 *      org.starhope.appius.user.User,
	 *      org.starhope.appius.mb.Enrolment,
	 *      org.starhope.appius.mb.UserEnrolment,
	 *      org.starhope.appius.mb.Payment)
	 */
	@Override
	public void doAfterEnrolmentAfterLapse (final MBSession session,
			final User user, final Enrolment enrolment,
			final UserEnrolment userEnrolment, final Payment payment) {
		giveCouponForEnrolment (session, user, payment);
	}

	/**
	 * @see org.starhope.appius.mb.PostPaymentHook#doAfterEnrolmentRenewal(org.starhope.appius.mb.MBSession,
	 *      org.starhope.appius.user.User,
	 *      org.starhope.appius.mb.Enrolment,
	 *      org.starhope.appius.mb.UserEnrolment,
	 *      org.starhope.appius.mb.Payment)
	 */
	@Override
	public void doAfterEnrolmentRenewal (final MBSession session,
			final User user, final Enrolment enrolment,
			final UserEnrolment userEnrolment, final Payment payment) {
		// no-op
	}

	/**
	 * @see org.starhope.appius.mb.PostPaymentHook#doAfterFirstTimeEnrolment(org.starhope.appius.mb.MBSession,
	 *      org.starhope.appius.user.User,
	 *      org.starhope.appius.mb.Enrolment,
	 *      org.starhope.appius.mb.UserEnrolment,
	 *      org.starhope.appius.mb.Payment)
	 */
	@Override
	public void doAfterFirstTimeEnrolment (final MBSession session,
			final User user, final Enrolment enrolment,
			final UserEnrolment userEnrolment, final Payment payment) {
		giveCouponForEnrolment (session, user, payment);
	}

	/**
	 * WRITEME: Document this method brpocock@star-hope.org
	 *
	 * @param session WRITEME
	 * @param user WRITEME
	 * @param payment WRITEME
	 */
	private void giveCouponForEnrolment (final MBSession session,
			final User user, final Payment payment) {
		AppiusClaudiusCaecus.blather ("Checking for promotions...");
		if (AppiusConfig
				.getConfigBoolOrFalse ("com.tootsville.coupons.plushToot")
		/* && "us".equalsIgnoreCase (payment.get) */) {
			AppiusClaudiusCaecus
					.blather ("Sending Code for free plush toot promotion.");

			final Coupon coupon = new Coupon (user.getDebugName ());
			try {
				payment.addAnnotation ("com.tootsville.coupon", coupon
						.toString ());
			} catch (final AlreadyUsedException e) {
				AppiusClaudiusCaecus.reportBug (
						"Coupon Code annotation already used", e);
			}
			AbstractPerson pageUser;
			try {
				pageUser = session.getVisitorAsUser ();
			} catch (NotFoundException e) {
				try {
					pageUser = session.getVisitorAsParent ();
				} catch (NotFoundException e1) {
					AppiusClaudiusCaecus
							.reportBug (
									"Lost track of to whom I'm trying to give this coupon",
									e1);
					return;
				}
			}
			coupon.sendCouponCode (pageUser);
		}
	}

	/**
	 * @see org.starhope.appius.mb.PostParentRegistrationHook#postParentRegistration(org.starhope.appius.mb.MBSession,
	 *      org.starhope.appius.user.Parent)
	 */
	@Override
	public void postParentRegistration (final MBSession session,
			final Parent newUser) {
		// TODO Auto-generated method stub brpocock@star-hope.org
		AppiusClaudiusCaecus
				.reportBug ("unimplemented PostParentRegistrationHook::postParentRegistration (brpocock@star-hope.org, Oct 14, 2010)");

	}

	/**
	 * @see org.starhope.appius.mb.PostUserRegistrationHook#postUserRegistration(org.starhope.appius.mb.MBSession,
	 *      org.starhope.appius.user.User)
	 */
	@Override
	public void postUserRegistration (final MBSession session,
			final User newUser) {
		final String peanutCode = session
				.getExtraParam ("com.tootsville.peanutCode");

		if ( ! (newUser instanceof Toot)) {
			AppiusClaudiusCaecus
					.reportBug ("Hey, you're not an oliphaunt!");
			return;
		}

		if (null != peanutCode && !"".equals (peanutCode)) {
			AppiusClaudiusCaecus
					.blather ("Attempting to redeem peanut code: "
							+ peanutCode);
			WebUtil.applyPeanutCodeToNewUser (session, (Toot) newUser);
		}
	}

	/**
	 * @see org.starhope.appius.mb.MBParamTranslator#translateParameters(MBSession,
	 *      ServletRequest)
	 */
	@SuppressWarnings ( { "cast", "unchecked" })
	@Override
	public void translateParameters (final MBSession session,
			final ServletRequest request) {

		Map <String, String> params = (Map <String, String>) request
				.getParameterMap ();
		for (String param : params.keySet ()) {
			if (param.startsWith ("do:") && param.length () > 3) {
				try {
					session.add (MBGoal.valueOf (param.substring (3)));
				} catch (IllegalArgumentException e) {
					// invalid param; no such enum constant
				}
			}
		}

		final String dobDay = request.getParameter ("dobDay");
		final String dobMonth = request.getParameter ("dobMonth");
		final String dobYear = request.getParameter ("dobYear");
		if (null != dobDay && null != dobMonth && null != dobYear) {
			final String dobString = dobYear + "-"
					+ (dobMonth.length () == 1 ? "0" : "") + dobMonth
					+ "-" + (dobDay.length () == 1 ? "0" : "") + dobDay;
			Calendar c = new GregorianCalendar ();
			try {
				final Date date = java.sql.Date.valueOf (dobString);
				if (null != date) {
					AppiusClaudiusCaecus
							.blather ("Setting date of birth: "
							+ date.toString ());
					c.setTimeInMillis (date.getTime ());
					session
							.clearErrorsFor (MBFieldIdent.DATE_OF_BIRTH_GIVEN);
					session.dateOfBirth.setValue (c);
				} else {
					AppiusClaudiusCaecus
							.blather ("dob given isn't a date: "
									+ dobString);
					session.add (MBFieldIdent.DATE_OF_BIRTH_GIVEN,
							MBErrorReason.INCORRECT);
				}
			} catch (RuntimeException e) {
				AppiusClaudiusCaecus.blather ("No good on dob: "
						+ e.getMessage ());
			}
		}

		final String stateProvinceOrLocality;
		if (null != request.getParameter ("state")
				&& !"".equals (request.getParameter ("state"))) {
			stateProvinceOrLocality = request.getParameter ("state");
			session.province.setValue (stateProvinceOrLocality);
		} else if (null != request.getParameter ("locality")
				&& !"".equals (request.getParameter ("locality"))) {
			stateProvinceOrLocality = request.getParameter ("locality");
			session.province.setValue (stateProvinceOrLocality);
		} else if (null != request.getParameter ("province")
				&& !"".equals (request.getParameter ("province"))) {
			stateProvinceOrLocality = request.getParameter ("province");
			session.province.setValue (stateProvinceOrLocality);
		}
		/*
		 * Special case due to UI layout with European countries in a
		 * sub-menu
		 */
		if ("eu".equals (request.getParameter ("country"))) {
			final String country = request.getParameter ("eu_country")
					.substring (1);
			AppiusClaudiusCaecus.blather ("Eu checkout (country ."
					+ country + ")");
			session.country.setValue (country);
		} else {
			session.country.setValue (request.getParameter ("country"));
		}

	}

}
