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

import org.json.JSONException;
import org.json.JSONObject;
import org.starhope.appius.game.AbstractRoom;
import org.starhope.appius.game.AppiusClaudiusCaecus;
import org.starhope.appius.sql.SQLPeerDatum;
import org.starhope.appius.types.AbstractZone;
import org.starhope.appius.user.AbstractUser;
import org.starhope.appius.user.User;
import org.starhope.appius.util.AppiusConfig;

/**
 * 
 * Generic class that handles both buddy lists and ignore lists,
 * depending upon its mood. Unlike most of my stuff, you can actually
 * use the constructor safely, here.
 * 
 * @author brpocock
 * 
 */
public class UserList extends SQLPeerDatum implements
org.starhope.appius.types.UserList {

	/**
	 * Java serializations unique ID
	 */
	private static final long serialVersionUID = 3162343971089479060L;

	/**
	 * WRITEME
	 */
	transient AbstractZone activeZone = null;
	/**
	 * WRITEME
	 */
	private int myUserID;
	/**
	 * WRITEME
	 */
	private final String tableName;

	/**
	 * WRITEME
	 */
	HashMap <Integer, String> theRealList = new HashMap <Integer, String> ();

	/**
	 * 
	 * @param myGuy The user whose buddy list or ignore list is being
	 *            instantiated
	 * @param buddyTrueBlockFalse true, for buddy list; false, for
	 *            ignore list.
	 */
	public UserList (final AbstractUser myGuy,
			final boolean buddyTrueBlockFalse) {
		myUserID = myGuy.getUserID ();
		if (buddyTrueBlockFalse) {
			tableName = "buddyList";
		} else {
			tableName = "ignoreList";
		}
		try {
			readList ();
		} catch (final SQLException e) {
			AppiusClaudiusCaecus.reportBug (e);
		}
	}

	/**
	 * Add a user to this list
	 * 
	 * @param other the user to be added
	 * @return true if the user was null (no-op) or was added
	 *         successfully; false if the user was already on the list
	 * @see org.starhope.appius.types.UserList#addUser(AbstractUser)
	 */
	public boolean addUser (final AbstractUser other) {
		if (null == other) return true;
		theRealList.put (other.getUserID (), other.getAvatarLabel ());

		Connection con = null;
		PreparedStatement doActualAdd = null;
		try {
			con = AppiusConfig.getDatabaseConnection ();
			if ("buddyList".equals (tableName)) {
				doActualAdd = con
				.prepareStatement ("INSERT INTO buddyList (userID, buddyID) VALUES (?, ?)");
			} else if ("ignoreList".equals (tableName)) {
				doActualAdd = con
				.prepareStatement ("INSERT INTO ignoreList (userID, buddyID) VALUES (?, ?)");
			} else
				throw AppiusClaudiusCaecus
				.fatalBug ("tableName invalid");

			doActualAdd.setInt (1, myUserID);
			doActualAdd.setInt (2, other.getUserID ());
			if (1 != doActualAdd.executeUpdate ()) {
				AppiusClaudiusCaecus
				.reportBug ("Adding a buddy did not update 1 row in the database. User "
						+ myUserID
						+ " tried to add "
						+ other.getUserID ()
						+ " ("
						+ other.getAvatarLabel () + ")");
			}
			doActualAdd.close ();
		} catch (final SQLException e) {
			if (e.getMessage ().contains ("Duplicate entry ")) {
				// No op
			} else {
				AppiusClaudiusCaecus.reportBug (
						"adding a buddy failed on SQL", e);
			}
		} finally {
			if (null != doActualAdd) {
				try {
					doActualAdd.close ();
				} catch (final SQLException e) { /* No Op */
				}
			}
			if (null != con) {
				try {
					con.close ();
				} catch (final SQLException e) { /* No Op */
				}
			}
		}
		flush ();
		return true;
	}

	/**
	 * Returns an extremely expensive list of users.
	 * 
	 * @return all users on the given list as User objects. Very
	 *         expensive inquiry.
	 */
	@Deprecated
	public User [] asArrayOfUsers () {
		final LinkedList <AbstractUser> users = new LinkedList <AbstractUser> ();
		for (final int i : theRealList.keySet ()) {
			users.add (User.getByID (i));
		}
		return users.toArray (new User [users.size ()]);
	}

	/**
	 * @return WRITEME
	 * @see org.starhope.appius.types.UserList#asMap()
	 */
	public Map <Integer, String> asMap () {
		final HashMap <Integer, String> ret = new HashMap <Integer, String> ();
		ret.putAll (theRealList);
		return ret;
	}

	/**
	 * @return WRITEME
	 * @see org.starhope.appius.types.UserList#asNames()
	 */
	public Set <String> asNames () {
		return new HashSet <String> (theRealList.values ());
	}

	/**
	 * @see org.starhope.appius.types.UserList#changed()
	 */
	@Override
	public void changed () {
		flush ();
	}

	/**
	 * @see org.starhope.appius.types.UserList#flush()
	 */
	@Override
	public void flush () {
		Connection con = null;
		PreparedStatement st = null;
		try {
			con = AppiusConfig.getDatabaseConnection ();
			if ("buddyList".equals (tableName)) {
				st = con
				.prepareStatement ("INSERT IGNORE INTO buddyList SET userID=?, buddyID=?");
			} else if ("ignoreList".equals (tableName)) {
				st = con
				.prepareStatement ("INSERT IGNORE INTO ignoreList SET userID=?, buddyID=?");
			} else
				throw AppiusClaudiusCaecus.fatalBug ("tableName invalid");
			st.setInt (1, myUserID);
			for (final int user : theRealList.keySet ()) {
				st.setInt (2, user);
			}
		} 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 */
				}
			}

		}
	}

	/**
	 * This is an overriding method.
	 * 
	 * @see org.starhope.appius.sql.SQLPeerDatum#getCacheUniqueID()
	 */
	@Override
	protected String getCacheUniqueID () {
		return String.valueOf (myUserID) + tableName.charAt (0);
	}

	/**
	 * WRITEME: document this method (brpocock, Aug 31, 2009)
	 * 
	 * @param zone WRITEME
	 * 
	 */
	private void init (final AbstractZone zone) {
		synchronized (this) {
			if (activeZone != zone) {
				activeZone = zone;
			}
		}
	}

	/**
	 * @param u WRITEME
	 * @return WRITEME
	 * @see org.starhope.appius.types.UserList#isOnList(AbstractUser)
	 */
	public boolean isOnList (final AbstractUser u) {
		final java.util.Iterator <Integer> who = theRealList.keySet ()
		.iterator ();
		while (who.hasNext ()) {
			if (theRealList.get (who.next ()).equals (
					u.getAvatarLabel ())) return true;
		}
		return false;
	}

	/**
	 * 
	 * TODO: document this method (brpocock, Sep 25, 2009)
	 * 
	 * @throws SQLException WRITEME
	 */
	private void readList () throws SQLException {
		System.err.println (" Loading the " + tableName + " for user "
				+ myUserID);

		Connection con = null;
		PreparedStatement st = null;
		try {
			con = AppiusConfig.getDatabaseConnection ();
			if ("buddyList".equals (tableName)) {
				st = con
						.prepareStatement ("SELECT userID, buddyID, users.userName AS buddyName FROM buddyList LEFT JOIN users ON buddyID=users.ID WHERE userID=? AND users.isActive='OK' LIMIT 351");
				// FIXME: Remove limit
			} else if ("ignoreList".equals (tableName)) {
				st = con
						.prepareStatement ("SELECT userID, buddyID, users.userName AS buddyName FROM ignoreList LEFT JOIN users ON buddyID=users.ID WHERE userID=? AND users.isActive='OK' LIMIT 351");
				// FIXME: Remove limit
			} else
				throw AppiusClaudiusCaecus
						.fatalBug ("tableName invalid");
			st.setInt (1, myUserID);
			if (st.execute ()) {
				set (st.getResultSet ());
			}
		} 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 */
				}
			}

		}
	}

	/**
	 * Remove a user from this list
	 * 
	 * @param other The other user to remove from this list
	 * @return true if the user has been removed from the list, or was
	 *         null; false, if the user was non-null, but not on the
	 *         list to begin with
	 * @see org.starhope.appius.types.UserList#removeUser(AbstractUser)
	 */
	public boolean removeUser (final AbstractUser other) {
		if (null == other) return true;
		if (theRealList.remove (other.getUserID ()) != null) {
			Connection con = null;
			PreparedStatement st = null;
			try {
				con = AppiusConfig.getDatabaseConnection ();
				if ("buddyList".equals (tableName)) {
					st = con
					.prepareStatement ("DELETE FROM buddyList WHERE userID=?, buddyID=?");
				} else if ("ignoreList".equals (tableName)) {
					st = con
					.prepareStatement ("DELETE FROM ignoreList WHERE userID=?, buddyID=?");
				} else
					throw AppiusClaudiusCaecus.fatalBug ("tableName invalid");
				st.setInt (1, myUserID);
				st.setInt (2, other.getUserID ());
				st.executeUpdate ();
			} catch (final SQLException e) {
				AppiusClaudiusCaecus
				.reportBug (
						"removing a buddy on the in-RAM cache failed on SQL",
						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 true;
		}
		return false;
	}

	/**
	 * This is an overriding method.
	 * 
	 * @see org.starhope.appius.sql.SQLPeerDatum#set(java.sql.ResultSet)
	 */
	@Override
	protected void set (final ResultSet rs) throws SQLException {
		theRealList.clear ();
		while (rs.next ()) {
			// System.err.printf (" for %10d buddy %10d\n", myUserID, rs
			// .getInt ("buddyID"));
			myUserID = rs.getInt ("userID");
			theRealList.put (rs.getInt ("buddyID"), rs
					.getString ("buddyName"));
		}
	}

	/**
	 * @return the size of this list
	 * @see org.starhope.appius.types.UserList#size()
	 */
	public int size () {
		return theRealList.size ();
	}

	/**
	 * Convert this list into a JSON form. This takes a Zone reference
	 * so that the status of users (as to where they are located and
	 * whether they are online) can be reported in the list.
	 * 
	 * @param zone The zone for which the list's online status should be
	 *            checked.
	 * @return the list in JSON form.
	 * @throws JSONException WRITEME
	 * @see org.starhope.appius.types.UserList#toJSON(org.starhope.appius.types.AbstractZone)
	 */
	public JSONObject toJSON (final AbstractZone zone)
	throws JSONException {
		if ("ignoreList".equals (tableName)) {
			final JSONObject self = new JSONObject ();
			final Iterator <Integer> ignorees = theRealList.keySet ()
			.iterator ();
			int i = 0;
			while (ignorees.hasNext ()) {
				self.put (String.valueOf (i++ ), theRealList
						.get (ignorees.next ()));
			}
			return self;
		}

		init (zone);
		final JSONObject self = new JSONObject ();
		final Iterator <AbstractRoom> roomer = zone.getRoomList ()
		.iterator ();
		final HashMap <Integer, String> roomMoniker = new HashMap <Integer, String> ();
		while (roomer.hasNext ()) {
			final AbstractRoom room = roomer.next ();
			roomMoniker.put (room.getID (), room.getName ());
		}
		int i = 0;
		for (final int who : theRealList.keySet ()) {
			final JSONObject urec = new JSONObject ();
			final AbstractUser listedUser = User.getByID (who);
			urec.put ("buddy", listedUser.getAvatarLabel ());
			final AbstractRoom buddyRoom = listedUser.getRoom ();
			final AbstractZone buddyZone = null != buddyRoom ? buddyRoom
					.getZone ()
					: null;
					synchronized (this) {
						if (null == buddyZone) {
							urec.put ("online", "false");
						} else {
							urec.put ("online", "true");
							if (null != buddyRoom) {
								urec.put ("room", buddyRoom.getMoniker ());
								urec.put ("roomName", buddyRoom.getTitle ());
							}
							if ( !buddyZone.equals (activeZone)) {
								urec.put ("zone", buddyZone.getName ());
							}
						}
					}
					self.put (String.valueOf (i++ ), urec);
		}
		return self;
	}

}
