/**
 * <p>
 * Copyright © 2009-2010, Bruce-Robert Pocock
 * </p>
 * <p>
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or (at
 * your option) any later version.
 * </p>
 * <p>
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 * </p>
 * <p>
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 * </p>
 * 
 * @author brpocock,theys
 */

package org.starhope.appius.user;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.Date;
import java.sql.Timestamp;
import java.util.HashMap;

import javax.naming.NamingException;

import org.starhope.appius.except.DataException;
import org.starhope.appius.except.GameLogicException;
import org.starhope.appius.except.NotFoundException;
import org.starhope.appius.except.NotReadyException;
import org.starhope.appius.game.AppiusClaudiusCaecus;
import org.starhope.appius.messaging.Mail;
import org.starhope.appius.sql.SQLPeerDatum;
import org.starhope.appius.types.AgeBracket;
import org.starhope.appius.util.AppiusConfig;

/**
 * This is an abstract superclass for both Parents and Users.
 * 
 * @author brpocock, theys
 */
public abstract class Person extends SQLPeerDatum {

	/**
	 * Java serialization unique ID
	 */
	private static final long serialVersionUID = 5635026067346684335L;

	/**
	 * <p>
	 * Get reference to User or Parent for purchasing a membership
	 * subscription
	 * </p>
	 * <p>
	 * Currently, kids under 17 return their parent.
	 * </p>
	 * 
	 * @param p The person, for whom someone else may be responsible for
	 *        payments
	 * @return the Person object that able to use a credit card.
	 */
	public static Person getResponsiblePerson (final Person p) {
		if (p instanceof User
				&& ((User) p).getAgeGroup () != AgeBracket.Adult)
			return ((User) p).getParent ();
		return p;
	}

	/**
	 * If true, we can contact this user. Note that we usually mail
	 * parents of kids, not the kids themselves.
	 */
	protected boolean canContact = false;

	/**
	 * TODO: document this field (theys, Nov 5, 2009) couponCode
	 * (Person)
	 */
	private transient String couponCode = "";

	/**
	 * The national dialect of the language {@link #language} which the
	 * user speaks.
	 */
	protected String dialect = "US";
	/**
	 * Given answer to forgotten password question.
	 */
	protected String forgotPasswordAnswer = "";
	/**
	 * The user's question to answer if they forget their password.
	 */
	protected String forgotPasswordQuestion = "What is your favorite food?";

	/**
	 * The user's given name. Optional. Just for use in membership
	 * screens to identify the user (especially to his/her parent)
	 */
	protected String givenName = "";
	/**
	 * The user's preferred human language for communications. Currently
	 * must be "en" (English). Use ISO codes.
	 */
	protected String language = "en";
	/**
	 * The user's eMail address. Optional for kids (need their parent's,
	 * though); mandatory for teens or adults.
	 */
	protected String mail = "";
	/**
	 * The timestamp of the confirmation of the user's (or parent's)
	 * eMail.
	 */
	protected Date mailConfirmed;
	/**
	 * TODO: document this field (brpocock, Nov 5, 2009) mailConfirmSent
	 * (Person)
	 */
	protected Date mailConfirmSent = null;
	/**
	 * The user (or parent)'s unencrypted (plain text) password. Alice
	 * and Bill would be so, so sad.
	 */
	protected String password;

	/**
	 * 
	 */
	public Person () {
		super ();
	}

	/**
	 * Can this person be contacted for marketing and other purposes?
	 * 
	 * @return true, if this person permits communications of that sort
	 */
	public boolean canContact () {
		return canContact;
	}

	/**
	 * Returns true if the password is correct. Returns false if
	 * password is not set or the guess was blank.
	 * 
	 * @param passwordGuess The password which is to be checked
	 * @return true, if the password is correct and not null
	 */
	public boolean checkPassword (final String passwordGuess) {
		if (null == passwordGuess || null == password)
			return false;
		return password.equals (passwordGuess);
	}

	/**
	 * @see org.starhope.appius.sql.SQLPeerDatum#flush()
	 */
	@Override
	public abstract void flush ();

	/**
	 * Send the user their forgotten password if they know the answer to
	 * their secret question. If {@link #remindPassword()} throws a
	 * {@link NotFoundException}, this will fail and return false as
	 * well.
	 * 
	 * @param forgottenPasswordQ The question being answered
	 * @param forgottenPasswordA The answer provided
	 * @return true if answer is correct (also calls
	 *         {@link #remindPassword()}); and false if it is not
	 */
	public boolean forgotPassword (final String forgottenPasswordQ,
			final String forgottenPasswordA) {

		if (null == forgottenPasswordA
				|| null == forgottenPasswordQ
				|| "".equals (forgottenPasswordA)
				|| "".equals (forgottenPasswordQ)
				|| !forgotPasswordQuestion.equals (forgottenPasswordQ)
				|| !forgotPasswordAnswer
				.equalsIgnoreCase (forgottenPasswordA))
			return false;

		try {
			remindPassword ();
		} catch (final NotReadyException e) {
			return false;
		}
		return true;
	}

	/**
	 * Generate a new, random password using only ASCII-7 printable
	 * characters ($20 to $7e). Password's length will range from 10-20
	 * characters in total.
	 */
	protected void generateNewPassword () {
		final StringBuilder newPass = new StringBuilder ();
		for (int i = 0; i < AppiusConfig.getRandomInt (10, 20); ++i) {
			newPass.append ( ((char) AppiusConfig
					.getRandomInt (32, 126)));
		}
		setPassword (newPass.toString ());
	}

	/**
	 * Get a cookie object for sending mail. Cookies are generated
	 * differently between User and Parent.
	 * 
	 * @see Parent
	 * @see User
	 * @return an opaque string that identifies the user uniquely
	 */
	public abstract String getApprovalCookie ();

	/**
	 * Get the filename of the eMail template file to be used to confirm
	 * this person's account
	 * 
	 * @return the template filename to be used for confirming this
	 *         account
	 */
	public abstract String getConfirmationTemplate ();

	/**
	 * Get the {@link #couponCode}
	 * 
	 * @return The {@link #couponCode}
	 */
	public String getCouponCode () {
		return couponCode;
	}

	/**
	 * Get this person's preferred language-dialect.
	 * 
	 * @return the dialect
	 */
	public String getDialect () {
		return "US"; // dialect;
	}

	/**
	 * Get the name to be displayed in user interface for this person.
	 * This should give the person's given name, but if that information
	 * is unavailable, fall back upon other unique identifier such as
	 * their avatar label
	 * 
	 * @return the display name
	 */
	public abstract String getDisplayName ();

	/**
	 * Get the forgotten password question
	 * 
	 * @return the question to ask
	 */
	public String getForgotPasswordAnswer () {
		return forgotPasswordAnswer;
	}

	/**
	 * Get the forgotten password question
	 * 
	 * @return the question to ask
	 */
	public String getForgotPasswordQuestion () {
		return forgotPasswordQuestion;
	}

	/**
	 * @return the givenName
	 */
	public String getGivenName () {
		return givenName;
	}

	/**
	 * Returns the historical contents of this user's record.
	 * 
	 * @param after If non-null, specifies the date after which we want
	 *        to view records. To see all records, back to the creation
	 *        of the user record, supply a null.
	 * @param limit If this is a positive number, it limits the results
	 *        to this number of records.
	 * @return A map of timestamps to key/value pairs. All values are
	 *         expressed as strings (even though they may have numeric,
	 *         enumerative, or date / date-time types in the database),
	 *         since this is primarily (only?) for human-viewable
	 *         auditing.
	 */
	public abstract HashMap <Timestamp, HashMap <String, String>> getHistory (
			final Date after, final int limit);

	/**
	 * @return the language
	 */
	public String getLanguage () {
		return "en"; // language;
	}

	/**
	 * @return the mail
	 */
	public String getMail () {
		return mail;
	}

	/**
	 * @return the mailConfirmed
	 */
	public Date getMailConfirmed () {
		return mailConfirmed;
	}

	/**
	 * @return the password
	 */
	public String getPassword () {
		if (null == password || password.length () == 0) {
			password = "\012";
		}
		return password;
	}

	/**
	 * <p>
	 * Get an user name suggestion for this person
	 * </p>
	 * XXX This belongs in User, only.
	 * 
	 * @return a user name that could be used
	 */
	public abstract String getPotentialUserName ();

	/**
	 * Get the eMail address of a responsible person: either the player,
	 * or the parent. Currently, kids 13-17 return their own mail.
	 * 
	 * @return the eMail address
	 */
	public abstract String getResponsibleMail ();

	/**
	 * Can this person be contacted for marketing and other purposes?
	 * 
	 * @return true, if this person permits communications of that sort
	 * @deprecated in favour of just canContact.
	 */
	@Deprecated
	public boolean isCanContact () {
		return canContact ();
	}

	/**
	 * Send a reminder eMail for a forgotten password. This presumes
	 * that the eMail address on file has been validated, and that the
	 * person has already authenticated in some way.
	 * 
	 * @throws NotReadyException if the user does not have an eMail
	 *         address, or it has not been confirmed
	 */
	protected void remindPassword () throws NotReadyException {
		if ("".equals (getMail ()) || null == getMail ())
			throw new NotReadyException ("noReminder:noMailAddress");
		if (null == getMailConfirmed ())
			throw new NotReadyException ("noReminder:notConfirmed");
		try {
			Mail.sendPasswordRecoveryMail (this);
		} catch (final FileNotFoundException e) {
			AppiusClaudiusCaecus.reportBug (e);
		} catch (final IOException e) {
			AppiusClaudiusCaecus.reportBug (e);
		} catch (final NotFoundException e) {
			AppiusClaudiusCaecus.reportBug (e);
		}
	}

	/**
	 * <p>
	 * Rename the user account, updating all necessary related records.
	 * Note, in particular, that Smartfox is wholly dependant upon user
	 * names, so all records related to Smartfox must be updated!
	 * </p>
	 * <p>
	 * If the user is currently online, this will fuck up hilariously, I
	 * think.
	 * </p>
	 * 
	 * @param newName The new user name
	 */
	public abstract void rename (final String newName);

	/**
	 * Sends confirmation mail to whomever should receive it
	 */
	public void sendConfirmationMail () {
		try {
			Mail.sendSignupMail (this);
			mailConfirmSent = new Date (System.currentTimeMillis ());
		} catch (final FileNotFoundException e) {
			AppiusClaudiusCaecus.reportBug (e);
		} catch (final IOException e) {
			AppiusClaudiusCaecus.reportBug (e);
		} catch (final NotFoundException e) {
			AppiusClaudiusCaecus.reportBug (e);
		} catch (final DataException e) {
			AppiusClaudiusCaecus.reportBug (e);
		} catch (final NamingException e) {
			AppiusClaudiusCaecus.reportBug (e);
		}
	}

	/**
	 * Sends mail to user or parent when a staff member resets their
	 * password.
	 */
	public void sendStaffPasswordReset () {
		try {
			Mail.sendStaffPaswordResetMail (this);
		} catch (final FileNotFoundException e) {
			AppiusClaudiusCaecus.reportBug (e);
		} catch (final IOException e) {
			AppiusClaudiusCaecus.reportBug (e);
		} catch (final NotFoundException e) {
			AppiusClaudiusCaecus.reportBug (e);
		}
	}

	/**
	 * TODO: document this method (brpocock, Sep 25, 2009)
	 */
	public void sentConfirmationMail () {
		mailConfirmSent = new Date (System.currentTimeMillis ());

	}

	/**
	 * @param canContact1 if true, the user has explicitly given us
	 *        their legal consent to be contacted for marketing and
	 *        other options.
	 */
	public void setCanContact (final boolean canContact1) {
		canContact = canContact1;
		flush ();
	}

	/**
	 * TODO: document this method (theys, Nov 5, 2009)
	 * 
	 * @param newCouponCode WRITEME
	 */
	public void setCouponCode (final String newCouponCode) {
		// default setter (theys, Oct 28, 2009)
		couponCode = newCouponCode;
	}

	/**
	 * TODO: document this method (brpocock, Sep 25, 2009)
	 * 
	 * @param answer WRITEME
	 */
	public void setForgotPasswordAnswer (final String answer) {
		forgotPasswordAnswer = answer;
		flush ();
	}

	/**
	 * TODO: document this method (brpocock, Sep 25, 2009)
	 * 
	 * @param question WRITEME
	 */
	public void setForgotPasswordQuestion (final String question) {
		forgotPasswordQuestion = question;
		flush ();
	}

	/**
	 * @param givenName1 the givenName to set
	 */
	public void setGivenName (final String givenName1) {
		givenName = givenName1;
		flush ();
	}

	/**
	 * @param mail1 the mail to set
	 * @throws GameLogicException WRITEME
	 */
	public void setMail (final String mail1) throws GameLogicException {
		mail = mail1;
		mailConfirmed = null;
		changed ();
	}

	/**
	 * @param mailConfirmed1 the mailConfirmed to set
	 */
	public void setMailConfirmed (final Date mailConfirmed1) {
		if (null != mailConfirmed)
			return;
		mailConfirmed = mailConfirmed1;
		flush ();
	}

	/**
	 * Changes the person's password
	 * 
	 * @param password1 the password to set
	 */
	public void setPassword (final String password1) {
		if (null == password1 || password1.length () == 0) {
			password = "\n";
		}
		password = password1;
		flush ();
	}

	/**
	 * @param question WRITEME
	 * @param answer WRITEME
	 * @param newPassword WRITEME
	 * @throws GameLogicException WRITEME
	 */
	public void setPasswordAndPasswordRecovery (final String question,
			final String answer, final String newPassword)
	throws GameLogicException {
		forgotPasswordQuestion = question;
		forgotPasswordAnswer = answer;
		if (null == newPassword || newPassword.length () == 0) {
			password = "\n";
		}
		setPassword (newPassword);
		flush ();

	}

	/**
	 * Set the password-recovery question and answer pair
	 * 
	 * @param forgottenPasswordQuestion the question
	 * @param forgottenPasswordAnswer the correct answer
	 */
	public void setPasswordRecovery (
			final String forgottenPasswordQuestion,
			final String forgottenPasswordAnswer) {
		forgotPasswordQuestion = forgottenPasswordQuestion;
		forgotPasswordAnswer = forgottenPasswordAnswer;
		flush ();
	}

	/**
	 * TODO: document this method (brpocock, Sep 25, 2009)
	 * 
	 * @return WRITEME
	 */
	public String setRandomPassword () {
		// FIXME
		setPassword ("changeme");
		return "changeme";
	}

}
