/**
 * <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
 */

package com.tootsville;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.Vector;

import org.json.JSONException;
import org.json.JSONObject;
import org.starhope.appius.except.NotFoundException;
import org.starhope.appius.game.AppiusClaudiusCaecus;
import org.starhope.appius.mb.Messages;
import org.starhope.appius.messaging.AbstractCensor;
import org.starhope.appius.sql.SQLPeerDatum;
import org.starhope.appius.sys.op.FilterResult;
import org.starhope.appius.sys.op.FilterStatus;
import org.starhope.appius.types.GameWorldMessage;
import org.starhope.appius.user.AbstractUser;
import org.starhope.appius.user.User;
import org.starhope.appius.util.AppiusConfig;

import com.tootsville.hangman.Censor;

/**
 * @author brpocock
 */
public class MailMessage extends SQLPeerDatum implements
		GameWorldMessage {

	/**
	 * The censoring object used to filter in-game mail messages
	 */
	private final static AbstractCensor censor = new Censor ();
	/**
	 * Java serialization version ID
	 */
	private static final long serialVersionUID = -1350270979016126771L;

	/**
	 * TODO: document this field (brpocock, Nov 19, 2009) body
	 * (MailMessage)
	 */
	private String body;

	/**
	 * TODO: document this field (brpocock, Nov 19, 2009) fromID
	 * (MailMessage)
	 */
	private int fromID;

	/**
	 * TODO: document this field (brpocock, Nov 19, 2009) fromName
	 * (MailMessage)
	 */
	private String fromName;

	/**
	 * TODO: document this field (brpocock, Nov 19, 2009) id
	 * (MailMessage)
	 */
	private int id;
	/**
	 * If this message is in reply to another, then this is the message
	 * ID of the parent message.
	 */
	private int inReplyTo = -1;
	/**
	 * If true, the user (or a moderator) has deleted this message
	 */
	private boolean isDeleted;

	/**
	 * If true, this message is a Wall Post on TootsBook.
	 */
	private boolean isWallPost = false;

	/**
	 * TODO: document this field (brpocock, Nov 19, 2009) read
	 * (MailMessage)
	 */
	private Timestamp read;
	/**
	 * TODO: document this field (brpocock, Nov 19, 2009) sent
	 * (MailMessage)
	 */
	private Timestamp sent;
	/**
	 * TODO: document this field (brpocock, Nov 19, 2009) subject
	 * (MailMessage)
	 */
	private String subject;
	/**
	 * TODO: document this field (brpocock, Nov 19, 2009) toID
	 * (MailMessage)
	 */
	private int toID;
	/**
	 * The name of the message's recipient.
	 */
	private String toName;

	/**
	 * 
	 */
	public MailMessage () {
		subject = "eMail";
		fromID = -1;
		toID = -1;
		body = "";
		read = null;
		sent = null;
		toName = "";
		fromName = "";
		isDeleted = false;
		isWallPost = false;
		inReplyTo = -1;
	}

	/**
	 * Instantiate a mail message from a database row
	 * 
	 * @param row the database row to be interpreted
	 * @throws SQLException if the dataset can't be interpreted
	 */
	public MailMessage (final ResultSet row) throws SQLException {
		set (row);
	}

	/**
	 * @see GameWorldMessage#delete()
	 */
	public void delete () {
		isDeleted = true;
		changed ();
	}

	/**
	 * @see GameWorldMessage#flush()
	 */
	@Override
	public void flush () {
		if (id == -1) {
			insert ();
			return;
		}
		Connection con = null;
		PreparedStatement st = null;
		try {
			con = AppiusConfig.getDatabaseConnection ();
			st = con
					.prepareStatement ("UPDATE messages SET fromUserID = ?, toUserID = ?, sentTime = ?, readTime = ?, subject = ?, body = ?, isDeleted = ?, inReplyTo = ? WHERE ID=?");
			st.setInt (1, fromID);
			st.setInt (2, toID);
			st.setTimestamp (3, sent);
			if (null == read) {
				st.setNull (4, Types.TIMESTAMP);
			} else {
				st.setTimestamp (4, read);
			}
			st.setString (5, subject);
			st.setString (6, body);
			st.setString (7, isDeleted ? "D" : isWallPost ? "W" : " ");
			if (inReplyTo > 0) {
				st.setInt (8, inReplyTo);
			} else {
				st.setNull (8, Types.INTEGER);
			}
			st.setInt (9, id);
			st.execute ();
		} 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 WRITEME
	 * @see GameWorldMessage#getBody()
	 */
	public String getBody () {
		return body;
	}

	/**
	 * @param idToGet WRITEME
	 * @return WRITEME
	 * @throws NotFoundException WRITEME
	 */
	public GameWorldMessage getByID (final int idToGet)
			throws NotFoundException {
		Connection con = null;
		PreparedStatement st = null;
		ResultSet rs = null;
		try {
			con = AppiusConfig.getDatabaseConnection ();
			st = con
					.prepareStatement ("SELECT * FROM messages WHERE ID=?");
			st.setInt (1, idToGet);
			rs = st.executeQuery ();
			rs.next ();
			return new MailMessage (rs);
		} catch (final SQLException e) {
			throw new NotFoundException (String.valueOf (idToGet));
		} finally {
			if (null != rs) {
				try {
					rs.close ();
				} catch (final SQLException e) { /* No Op */
				}
			}
			if (null != st) {
				try {
					st.close ();
				} catch (final SQLException e) { /* No Op */
				}
			}
			if (null != con) {
				try {
					con.close ();
				} catch (final SQLException e) { /* No Op */
				}
			}

		}
	}

	/**
	 * @see org.starhope.appius.sql.SQLPeerDatum#getCacheUniqueID()
	 */
	@Override
	protected String getCacheUniqueID () {
		return String.valueOf (id);
	}

	/**
	 * @return WRITEME
	 * @see GameWorldMessage#getFromID()
	 */
	public int getFromID () {
		return fromID;
	}

	/**
	 * @return WRITEME
	 * @see GameWorldMessage#getFromName()
	 */
	public String getFromName () {
		return fromName;
	}

	/**
	 * @return WRITEME
	 * @see GameWorldMessage#getID()
	 */
	public int getID () {
		return id;
	}

	/**
	 * @return WRITEME
	 * @see GameWorldMessage#getInReplyTo()
	 */
	public int getInReplyTo () {
		// default getter (brpocock, Dec 1, 2009)
		return inReplyTo;
	}

	/**
	 * Fetch an ordered set of messages based upon a prepared statement
	 * 
	 * @param st A prepared statement ready to be executed
	 * @return the set of messages returned by the given prepared
	 *         statement
	 * @throws SQLException if there's a problem getting results
	 */
	public Vector <GameWorldMessage> getMessagesFrom (
			final PreparedStatement st) throws SQLException {
		final Vector <GameWorldMessage> messages = new Vector <GameWorldMessage> ();

		if (st.execute ()) {
			final ResultSet rs = st.getResultSet ();
			while (rs.next ()) {
				AppiusClaudiusCaecus.blather ("Mail subject: "
						+ rs.getString ("subject"));
				try {
					messages.add (new MailMessage (rs));
				} catch (final RuntimeException e) {
					AppiusClaudiusCaecus
							.blather ("Skipping a bad message :-(");
				}
			}
		} else {
			AppiusClaudiusCaecus.blather ("User has no mail");
		}
		return messages;
	}

	/**
	 * @return WRITEME
	 * @see GameWorldMessage#getRead()
	 */
	public Timestamp getRead () {
		return read;
	}

	/**
	 * @return WRITEME
	 * @see GameWorldMessage#getSent()
	 */
	public Timestamp getSent () {
		return sent;
	}

	/**
	 * @return WRITEME
	 * @see GameWorldMessage#getSubject()
	 */
	public String getSubject () {
		return subject;
	}

	/**
	 * @return WRITEME
	 * @see GameWorldMessage#getToID()
	 */
	public int getToID () {
		return toID;
	}

	/**
	 * @return WRITEME
	 * @see GameWorldMessage#getToName()
	 */
	public String getToName () {
		return toName;
	}

	/**
	 * 
	 */
	private void insert () {
		Connection con = null;
		PreparedStatement st = null;
		ResultSet rs = null;
		try {
			con = AppiusConfig.getDatabaseConnection ();
			st = con
					.prepareStatement (
							"INSERT INTO messages (fromUserID, toUserID, sentTime, readTime, subject, body, isDeleted, inReplyTo) VALUES (?,?,?,?,?,?,?,?)",
							Statement.RETURN_GENERATED_KEYS);

			st.setInt (1, fromID);
			st.setInt (2, toID);
			st.setTimestamp (3, sent);
			if (null == read) {
				st.setNull (4, Types.TIMESTAMP);
			} else {
				st.setTimestamp (4, read);
			}
			st.setString (5, subject);
			st.setString (6, body);
			st.setString (7, isDeleted ? "D" : isWallPost ? "W" : "M");
			if (inReplyTo > 0) {
				st.setInt (8, inReplyTo);
			} else {
				st.setNull (8, Types.INTEGER);
			}
			st.execute ();
			rs = st.getGeneratedKeys ();
			rs.next ();
			id = rs.getInt (1);

		} catch (final SQLException e) {
			AppiusClaudiusCaecus
					.reportBug ("Couldn't write message", e);
		} finally {
			if (null != rs) {
				try {
					rs.close ();
				} catch (final SQLException e) { /* No Op */
				}
			}
			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 WRITEME
	 * @see GameWorldMessage#isDeleted()
	 */
	public boolean isDeleted () {
		return isDeleted;
	}

	/**
	 * @return WRITEME
	 * @see GameWorldMessage#isWallPost()
	 */
	public boolean isWallPost () {
		// default getter (brpocock, Dec 1, 2009)
		return isWallPost;
	}

	/**
	 * @see GameWorldMessage#markAsRead()
	 */
	public void markAsRead () {
		if (null != read)
			return;
		read = new Timestamp (System.currentTimeMillis ());
		changed ();
	}

	/**
	 * @return WRITEME
	 * @see GameWorldMessage#send()
	 */
	public boolean send () {
		insert ();
		return true;
	}

	/**
	 * @see org.starhope.appius.sql.SQLPeerDatum#set(java.sql.ResultSet)
	 */
	@Override
	protected void set (final ResultSet rs) throws SQLException {
		id = rs.getInt ("id");
		setBody (rs.getString ("body"));
		setSubject (rs.getString ("subject"));
		fromID = rs.getInt ("fromUserID");
		fromName = User.getUserNameForID (fromID);
		toID = rs.getInt ("toUserID");
		toName = User.getUserNameForID (toID);
		sent = rs.getTimestamp ("sentTime");
		try {
			read = rs.getTimestamp ("readTime");
			if (rs.wasNull ()) {
				read = null;
			}
		} catch (final SQLException e) {
			read = null;
		}
		try {
			inReplyTo = rs.getInt ("inReplyTo");
			if (rs.wasNull ()) {
				inReplyTo = -1;
			}
		} catch (final SQLException e) {
			inReplyTo = -1;
		}
		final String place = rs.getString ("isDeleted");
		isDeleted = "D".equals (place);
		isWallPost = "W".equals (place);
	}

	/**
	 * @param body1 The new body content for the message
	 * @see GameWorldMessage#setBody(java.lang.String)
	 */
	public boolean setBody (final String body1) {
		try {
			Censor.prime (AppiusConfig.getDatabaseConnection ());
		} catch (final SQLException e) {
			throw AppiusClaudiusCaecus.fatalBug (e);
		}
		final FilterResult carlSays = MailMessage.censor
				.filterMessage (body1);
		if (FilterStatus.Ok == carlSays.status) {
			body = body1;
			return true;
		}
		body = "";
		final AbstractUser sender = User.getByID (getFromID ());
		if (null != sender && sender.isOnline ()) {
			sender
					.acceptMessage (
							// FIXME Get text
							"That's not nice",
							"POST",
							"I removed the contents of your mail message, because it was not appropriate for Tootsville");
		}
		return false;
	}

	/**
	 * @param sender the sender (FROM: field) for the message
	 * @see GameWorldMessage#setFrom(org.starhope.appius.user.User)
	 */
	public void setFrom (final User sender) {
		fromID = sender.getUserID ();
		fromName = sender.getUserName ();
	}

	/**
	 * @param fromID1 the sender's user ID
	 * @see GameWorldMessage#setFromID(int)
	 */
	public void setFromID (final int fromID1) {
		fromID = fromID1;
		fromName = User.getUserNameForID (fromID1);
	}

	/**
	 * @param parentMessageID WRITEME
	 * @see GameWorldMessage#setInReplyTo(int)
	 */
	public void setInReplyTo (final int parentMessageID) {
		// default setter (brpocock, Dec 1, 2009)
		inReplyTo = parentMessageID;
	}

	/**
	 * @param read1 WRITEME
	 * @see GameWorldMessage#setRead(java.sql.Timestamp)
	 */
	public void setRead (final Timestamp read1) {
		read = read1;
	}

	/**
	 * @param sent1 WRITEME
	 * @see GameWorldMessage#setSent(java.sql.Timestamp)
	 */
	public void setSent (final Timestamp sent1) {
		sent = sent1;
	}

	/**
	 * @param subject1 The Subject of the message
	 * @see GameWorldMessage#setSubject(java.lang.String)
	 */
	public void setSubject (final String subject1) {
		try {
			Censor.prime (AppiusConfig.getDatabaseConnection ());
		} catch (final SQLException e) {
			throw AppiusClaudiusCaecus.fatalBug (e);
		}
		final FilterResult carlSays = MailMessage.censor
				.filterMessage (subject1);
		if (FilterStatus.Ok == carlSays.status) {
			subject = subject1;
		} else {
			subject = "";
			final AbstractUser sender = User.getByID (getFromID ());
			if (null != sender && sender.isOnline ()) {
				sender
						.acceptMessage (
								// FIXME get text
								"That's not nice",
								"POST",
								"I removed the contents of your mail message, because it was not appropriate for Tootsville");
			}
		}
	}

	/**
	 * @param recipient WRITEME
	 * @see GameWorldMessage#setTo(org.starhope.appius.user.User)
	 */
	public void setTo (final User recipient) {
		toID = recipient.getUserID ();
		toName = recipient.getUserName ();
	}

	/**
	 * @param toID1 WRITEME
	 * @see GameWorldMessage#setToID(int)
	 */
	public void setToID (final int toID1) {
		toID = toID1;
		toName = User.getUserNameForID (toID1);
	}

	/**
	 * // * @param wallPostQ WRITEME
	 * 
	 * @see GameWorldMessage#setWallPost(boolean)
	 */
	public void setWallPost (final boolean wallPostQ) {
		isWallPost = wallPostQ;
	}

	/**
	 * @see GameWorldMessage#toJSON()
	 */
	@Override
	public JSONObject toJSON () {
		return toJSON (false);
	}

	/**
	 * @param inMailbox WRITEME
	 * @return WRITEME
	 * @see GameWorldMessage#toJSON(boolean)
	 */
	public JSONObject toJSON (final boolean inMailbox) {
		final JSONObject self = new JSONObject ();
		try {
			self.put ("from", fromName);
			self.put ("to", toName);
			self.put ("subject", subject);
			if (!inMailbox) {
				self.put ("body", body);
			}
			self.put ("sent", Messages.prettyDate (sent));
			self.put ("id", id);
			if (null != read) {
				self.put ("read", Messages.prettyDate (read));
			}
			if (inReplyTo > 0) {
				self.put ("reply-to", inReplyTo);
			}
			if (isWallPost) {
				self.put ("wall", true);
			}
			if (isDeleted) {
				self.put ("deleted", true);
			}
		} catch (final JSONException e) {
			AppiusClaudiusCaecus.reportBug (e);
		}
		return self;
	}

	/**
	 * @see GameWorldMessage#undelete()
	 */
	public void undelete () {
		isDeleted = false;
		changed ();
	}
}
