/**
 * 
 */
package com.tootsville.rahab;

import it.gotoandplay.smartfoxclient.ISFSEventListener;
import it.gotoandplay.smartfoxclient.SFSEvent;
import it.gotoandplay.smartfoxclient.SmartFoxClient;

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Vector;

import org.json.JSONException;
import org.json.JSONObject;

/**
 * TODO: Document this type. Nov 16, 2009 : theys
 * 
 * @author <a href="mailto:theys@resinteractive.com">Tim Heys</a>
 * 
 */
public class Kaleb {

	/**
	 * TODO: document this field (theys)
	 */
	class Zone {

		/**
		 * TODO: document this field (theys)
		 */
		private int numberOfUsers = 0;
		/**
		 * TODO: document this field (theys)
		 */
		private String zoneHost = "";

		/**
		 * TODO: document this field (theys)
		 */
		private String zoneName = "";

		/**
		 * TODO: document this field (theys)
		 * 
		 * @param newZone WRITEME
		 * @param newZoneHost WRITEME
		 * @param newZoneUsers WRITEME
		 */
		public Zone (final String newZone, final String newZoneHost,
				final int newZoneUsers) {
			zoneName = newZone;
			zoneHost = newZoneHost;
			numberOfUsers = newZoneUsers;
		}

		/**
		 * @see java.lang.Object#equals(java.lang.Object)
		 */
		@Override
		public boolean equals (final Object obj) {
			return obj instanceof Zone
					&& ((Zone) obj).getZoneName ().equals (
							getZoneName ())
					&& ((Zone) obj).getNumberOfUsers () == getNumberOfUsers ();
		}

		/**
		 * TODO: document this field (theys)
		 * 
		 * @return WRITEME
		 */
		public int getNumberOfUsers () {
			// default getter (theys, Aug 19, 2009)
			return numberOfUsers;
		}

		/**
		 * TODO: document this field (theys)
		 * 
		 * @return WRITEME
		 */
		public String getZoneHost () {
			// default getter (theys, Aug 19, 2009)
			return zoneHost;
		}

		/**
		 * TODO: document this field (theys)
		 * 
		 * @return WRITEME
		 */
		public String getZoneName () {
			// default getter (theys, Aug 19, 2009)
			return zoneName;
		}

		/**
		 * TODO: document this field (theys)
		 * 
		 * @param numberOfUsers1 WRITEME
		 */
		public void setNumberOfUsers (final int numberOfUsers1) {
			// default setter (theys, Aug 19, 2009)
			numberOfUsers = numberOfUsers1;
		}

		/**
		 * TODO: document this field (theys)
		 * 
		 * @param zoneHost1 WRITEME
		 */
		public void setZoneHost (final String zoneHost1) {
			// default setter (theys, Aug 19, 2009)
			zoneHost = zoneHost1;
		}

		/**
		 * TODO: document this field (theys)
		 * 
		 * @param zoneName1 WRITEME
		 */
		public void setZoneName (final String zoneName1) {
			// default setter (theys, Aug 19, 2009)
			zoneName = zoneName1;
		}
	}

	/**
	 * WRITEME: Document this field. theys Dec 14, 2009
	 */
	private static final String LOAD_CLIENT_USERNAME_PREFIX = "$loadclient.";

	/**
	 * 
	 */
	private static final String EDEN = "$Eden";

	/**
	 * TODO: document this field (theys)
	 */
	private static final String roomPassword = "Lasciate ogne speranza, voi ch-intrate";

	/**
	 * 
	 * TODO: Document this method. Nov 16, 2009 : theys
	 * 
	 * @param args
	 */
	public static void main (final String args[]) {
		int connections = 0;
		do {
			try {
				System.out
						.print ("Enter the number of connections:   ");
				connections = Integer.parseInt (Kaleb.scan.nextLine ());
				break;
			} catch (final NumberFormatException e) {
				System.err.println ("Please enter an INTEGER\n");
			}
		} while (true);

		for (int number = 0; number < connections; number++ ) {
			final int x = number;
			final Thread t = new Thread () {
				@Override
				public void run () {
					System.err.println (LOAD_CLIENT_USERNAME_PREFIX + x
							+ ": running");
					new Kaleb (x);
				}
			};
			System.err.println (LOAD_CLIENT_USERNAME_PREFIX + number
					+ ": starting");
			t.start ();
			System.err.println (LOAD_CLIENT_USERNAME_PREFIX + number
					+ ": started");
		}
	}

	/**
	 * TODO: document this field (theys)
	 */
	protected transient Timer antiTimeOut;

	/**
	 * 
	 */
	boolean isConnected = false;

	/**
	 * Key used for encoding password
	 */
	protected String key;

	/**
	 * 
	 */
	private final ISFSEventListener messageEvent = new ISFSEventListener () {
		public void handleEvent (final SFSEvent event) {
			System.out.println ("Public message params: "
					+ event.getParams ().toString ());

			final String publicMessage = event.getParams ().getString (
					"message");

			if (publicMessage.contains (Kaleb.messageSplit)) {
				System.out.println ("Message pre parse: "
						+ publicMessage);

				// Message arrives in $Eaves room as
				// <username>0x1f<roomname>0x1f<message><0x1f>. Split on
				// the 0x1f char.
				final String [] splits = publicMessage
						.split (Kaleb.messageSplit);
				final String userName = splits [0];
				final String room = splits [1];
				final String msg = splits [2];

				System.out.println (userName + " (" + room + "): "
						+ msg);
			}
		}
	};

	/**
	 * 
	 */
	public static final String messageSplit = "\u001f";

	/**
	 * 
	 */
	public final ISFSEventListener onConnection = new ISFSEventListener () {
		public void handleEvent (final SFSEvent event) {
			if (event.getParams ().getBool ("success")) {
				System.out
						.println ("Successfully connected to server.");
				isConnected = true;
			} else {
				System.err
						.println ("Cannot connect to server.  Please retry.");
			}
		}
	};

	/**
	 * 
	 */
	private final ISFSEventListener onExtensionRequest = new ISFSEventListener () {
		public void handleEvent (final SFSEvent event) {
			try {
				// params and dataObj are used to read parameters with
				// onExtensionRespons
				System.out.println ("Extension request.");
				final JSONObject dataObj = new JSONObject (event
						.getParams ().toString ())
						.getJSONObject ("dataObj");
				// System.out.println ("dataObj as string: "
				// + dataObj.toString ());
				if (dataObj.has ("_cmd"))
					// logOK param for successful log in attempt
					if (dataObj.getString ("_cmd").equals ("logOK")
							|| dataObj.getString ("_cmd").equals (

							"loginComplete")) {
						System.out.println ("Have log OK.");
						if (dataObj.has ("zoneList")) {
							/**
							 * Only $Eden zone provides a zone list. We
							 * connect to $Eden first to get a zone
							 * list, but then connect to another zone
							 * from there. If dataObj doesn't have a
							 * zoneList property we assume we've
							 * connected to a different zone.
							 * 
							 */
							final JSONObject zoneList = dataObj
									.getJSONObject ("zoneList");
							System.out
									.println ("Logged in successfully!");

							Kaleb.this.promptForZone (Kaleb.this
									.initZoneList (zoneList));

							return;
						}
						// Check staff level before allowing user to log
						// in. If
						// user is not a staff member exit the program.
						if (dataObj.has ("user")) {
							final JSONObject userObj = dataObj
									.getJSONObject ("user");
							if (! (0 < userObj.getInt ("staffLevel"))) {
								System.exit (-1);
							}
						}
						System.out
								.println ("Requesting list of rooms.");
						smartFox.getRoomList ();

						return;

						// logKO for unsuccessful log attempt
					} else if (dataObj.getString ("_cmd").equals (
							"logKO")) {
						System.err
								.println ("Login failed.  Invalid username or password");
						return;
					} else
						return;
			} catch (final JSONException e) {
				// TODO Auto-generated catch block
				e.printStackTrace ();
			}
		}
	};

	/**
	 * 
	 */
	private final ISFSEventListener onJoinRoom = new ISFSEventListener () {
		/**
		 * @see it.gotoandplay.smartfoxclient.ISFSEventListener#handleEvent(it.gotoandplay.smartfoxclient.SFSEvent)
		 */
		public void handleEvent (final SFSEvent event) {
			antiTimeOut = new Timer ();
			antiTimeOut.scheduleAtFixedRate (task, 0, 1 * 1000 * 60);
			Kaleb.this.mainLoop ();
		}
	};

	/**
	 * 
	 */
	private final ISFSEventListener onJoinRoomError = new ISFSEventListener () {
		/**
		 * 
		 */
		public void handleEvent (final SFSEvent event) {
			System.err.println ("Failed to join room!");
			System.exit (-1);
		}
	};

	/**
	 * SF Listener for receiving random key
	 */
	private final ISFSEventListener onRandomKey = new ISFSEventListener () {
		public void handleEvent (
				final it.gotoandplay.smartfoxclient.SFSEvent event) {
			key = event.getParams ().getString ("key");
			System.out.println ("Magic key receieved.");
			Kaleb.this.login (Kaleb.EDEN);
		}
	};

	/**
	 * 
	 */
	private final ISFSEventListener onRoomListUpdate = new ISFSEventListener () {
		public void handleEvent (final SFSEvent event) {
			smartFox.joinRoom ("$Eaves", Kaleb.roomPassword, false);
		}
	};

	/**
	 * 
	 */
	protected String password;

	/**
	 * 
	 */
	protected static final Scanner scan = new Scanner (System.in);

	/**
	 * WRITEME: Document this field. theys Dec 14, 2009
	 */
	private final ISFSEventListener onConnectionLost = new ISFSEventListener () {
		/**
		 * WRITEME: Document this field. theys Dec 14, 2009
		 * 
		 * @param event
		 */
		public void handleEvent (final SFSEvent event) {
			System.err.println (userName + " Disconnected");
			System.exit (-2);
		}
	};

	/**
	 * 
	 */
	protected String server;

	/**
	 * Smart Fox Server
	 */
	public transient final SmartFoxClient smartFox;

	/**
	 * TODO: document this field (theys)
	 */
	transient TimerTask task = new TimerTask () {
		@Override
		public void run () {
			smartFox.sendXtMessage ("Tootsville", "ping",
					new JSONObject ());
		}
	};

	/**
	 * 
	 */
	protected String userName;

	/**
	 * @param x
	 * 
	 */
	public Kaleb (final int x) {
		smartFox = new SmartFoxClient (false);
		userName = LOAD_CLIENT_USERNAME_PREFIX + x;
		password = "p4$$w0rd";
		server = "whitney-dev.tootsville.com";
		initSmartFox ();

	}

	/**
	 * TODO: document this method (brpocock, Aug 18, 2009)
	 * 
	 * @param pass WRITEME
	 * 
	 * @return WRITEME
	 */
	protected String getApple (final String pass) {
		/* Check the CHAP key hex code sequence */

		byte [] sha1digest = {};
		try {
			final MessageDigest stomach = MessageDigest
					.getInstance ("SHA1");
			stomach.reset ();
			try {
				stomach.update ( (key + pass).getBytes ("US-ASCII"));
			} catch (final UnsupportedEncodingException e) {
				e.printStackTrace ();
			}
			sha1digest = stomach.digest ();
		} catch (final NoSuchAlgorithmException e) {
			e.printStackTrace ();
		} catch (final NumberFormatException e) {
			e.printStackTrace ();
		}
		final StringBuilder sha1hex = new StringBuilder (
				sha1digest.length * 2);
		for (final byte element : sha1digest) {
			sha1hex.append (Integer.toString (
					(element & 0xff) + 0x100, 16).substring (1));
		}
		return sha1hex.toString ();
	}

	/**
	 * TODO: Document this method. Nov 16, 2009 : theys
	 * 
	 */
	protected void initSmartFox () {
		smartFox.setDebug (false);
		smartFox.smartConnect = false; // Don't use BlueBox proxy
		// smartFox.addEventListener (SFSEvent.onBuddyList, this);
		// smartFox.addEventListener (SFSEvent.onBuddyListError, this);
		// smartFox.addEventListener (SFSEvent.onBuddyListUpdate, this);
		// smartFox.addEventListener (SFSEvent.onBuddyPermissionRequest,
		// this);
		// smartFox.addEventListener (SFSEvent.onBuddyRoom, this);
		// smartFox.addEventListener (SFSEvent.onConfigLoadFailure,
		// this);
		// smartFox.addEventListener (SFSEvent.onConfigLoadSuccess,
		// this);
		// smartFox.addEventListener (SFSEvent.onCreateRoomError, this);
		// smartFox.addEventListener (SFSEvent.onDebugMessage, this);
		// smartFox.addEventListener (SFSEvent.onLogin, this);
		// smartFox.addEventListener (SFSEvent.onLogout, this);
		// smartFox.addEventListener (SFSEvent.onObjectReceived, this);
		// smartFox.addEventListener (SFSEvent.onPlayerSwitched, this);

		// smartFox.addEventListener (SFSEvent.onUserCountChange, this);
		// smartFox.addEventListener (SFSEvent.onUserEnterRoom, this);
		// smartFox.addEventListener (SFSEvent.onUserLeaveRoom, this);
		// smartFox.addEventListener (SFSEvent.onUserVariablesUpdate,
		// this);

		// smartFox.addEventListener (SFSEvent.onPublicMessage,
		// messageEvent);
		// smartFox.addEventListener (SFSEvent.onModeratorMessage,
		// messageEvent);
		// smartFox.addEventListener (SFSEvent.onAdminMessage,
		// messageEvent);
		// smartFox.addEventListener (SFSEvent.onPrivateMessage,
		// messageEvent);

		smartFox.addEventListener (SFSEvent.onConnection, onConnection);
		smartFox.addEventListener (SFSEvent.onConnectionLost,
				onConnectionLost);
		smartFox.addEventListener (SFSEvent.onRandomKey, onRandomKey);
		smartFox.addEventListener (SFSEvent.onRoomListUpdate,
				onRoomListUpdate);
		smartFox.addEventListener (SFSEvent.onExtensionResponse,
				onExtensionRequest);
		smartFox.addEventListener (SFSEvent.onJoinRoom, onJoinRoom);
		smartFox.addEventListener (SFSEvent.onJoinRoomError,
				onJoinRoomError);

		try {
			smartFox.connect (server, 2770);
			smartFox.getRandomKey ();
		} catch (final Exception e) {
			System.err.println ("Failed to initialize connection.");
		}

	}

	/**
	 * TODO: document this field (theys)
	 * 
	 * @param zoneList WRITEME
	 * @return
	 * @throws JSONException WRITEME
	 */
	@SuppressWarnings ("unchecked")
	protected Vector <Zone> initZoneList (final JSONObject zoneList)
			throws JSONException {
		System.out.println ("Number of zones: " + zoneList.length ());
		final Iterator <String> keys = zoneList.keys ();
		String keyName = "";
		String zoneName = "", zoneHost = "";
		int usersOn;

		final Vector <Zone> zones = new Vector <Zone> ();
		while (keys.hasNext ()) {
			keyName = keys.next ();
			System.out.println ("New zone #" + keyName);
			final JSONObject zoneObj = zoneList.getJSONObject (keyName);
			zoneName = zoneObj.getString ("name");
			zoneHost = zoneObj.getString ("host");
			usersOn = Integer.parseInt (zoneObj.getString ("usersOn"));
			zones.add (new Zone (zoneName, zoneHost, usersOn));
		}
		return zones;
	}

	/**
	 * TODO: Document this method. Nov 16, 2009 : theys
	 * 
	 * @param zone WRITEME
	 * 
	 */
	protected void login (final String zone) {
		if (null == zone) {
			System.err.println ("Sorry, that zone is invalid.");
			return;
		} else if (Kaleb.EDEN.equals (zone)) {
			smartFox.login (zone, userName, getApple (password));
			return;
		}

		smartFox.logout ();
		smartFox.login (zone, userName, getApple (password));

	}

	/**
	 * TODO: Document this method. Nov 17, 2009 : theys
	 * 
	 */
	protected void mainLoop () {
		System.out.println (userName + " is now connected.");
		do {
			try {
				Thread.sleep (1000);
			} catch (final InterruptedException e) {
				// do nothing
			}
		} while (true);
	}

	/**
	 * TODO: Document this method. Nov 16, 2009 : theys
	 * 
	 * @param zones WRITEME
	 * 
	 */
	protected void promptForZone (final Vector <Zone> zones) {
		System.out.println ("Joining zone: "
				+ zones.lastElement ().getZoneName ());
		login (zones.lastElement ().getZoneName ());
	}

	/**
	 * TODO: Document this method. Nov 17, 2009 : theys
	 * 
	 * @param input WRITEME
	 * 
	 * @param nextLine WRITEME
	 */
	private void sendMessage (final String input) {
		smartFox.sendPublicMessage (input);
	}

}
