//==============================================================================  
// OrbiterMicro_1.0.0.370_Release
// www.unionplatform.com
// Release Date: 26-April-2011
// (c) Copyright USER1 Subsystems Corporation
//==============================================================================

//==============================================================================
// PACKAGE MANAGEMENT
//==============================================================================

// JSDOC helpers

/** @namespace 
    @name net
    @private */
/** @namespace 
    @name net.user1
    @private */
/** @namespace 
    @name net.user1.events
    @private */
/** @namespace 
    @name net.user1.logger
    @private */
/** @namespace 
    @name net.user1.orbiter
 */
/** @namespace 
    @name net.user1.utils
 */

// create utils package
var net = net ? net : {};
net.user1 = net.user1 ? net.user1 : {};
net.user1.utils = net.user1.utils ? net.user1.utils : {};

//  Convenience method to create packages
/** @function */
net.user1.utils.createPackage = function (packageName) {
  var parts = packageName.split(".");
  var part = window;
  
  for (var i = 0; i < parts.length; i++) {
    part = part[parts[i]] === undefined ? (part[parts[i]] = {}) : part[parts[i]];
  }
};
/** @function */
net.user1.utils.extend = function (subclass, superclass) {
  function superclassConstructor () {};
  superclassConstructor.prototype = superclass.prototype;
  subclass.superclass = superclass.prototype;
  subclass.prototype = new superclassConstructor();
  subclass.prototype.constructor = subclass;
};
//==============================================================================
// PACKAGE DECLARATIONS
//==============================================================================
net.user1.utils.createPackage("net.user1.logger");
net.user1.utils.createPackage("net.user1.events");
net.user1.utils.createPackage("net.user1.orbiter");
net.user1.utils.createPackage("net.user1.utils");
//==============================================================================
// A COLLECTION OF ARRAY UTILITIES
//==============================================================================
/** @class */
net.user1.utils.ArrayUtils = new Object();

net.user1.utils.ArrayUtils.indexOf = function (arr, obj) {
  if (arr.indexOf ) {
    return arr.indexOf(obj);
  }

  for (var i = arr.length; --i >= 0; ) {
    if (arr[i] === obj) {
      return i;
    }
  }
  
  return -1;
};
//==============================================================================
// UPC CONSTANTS
//==============================================================================
/** @class */
net.user1.orbiter.UPC = new Object();

// CLIENT TO SERVER
/** @constant */
net.user1.orbiter.UPC.SEND_MESSAGE_TO_ROOMS = "u1";            
/** @constant */
net.user1.orbiter.UPC.SEND_MESSAGE_TO_CLIENTS = "u2";            
/** @constant */
net.user1.orbiter.UPC.SET_CLIENT_ATTR = "u3";       
/** @constant */
net.user1.orbiter.UPC.JOIN_ROOM = "u4";             
/** @constant */
net.user1.orbiter.UPC.SET_ROOM_ATTR = "u5";         
/** @constant */
net.user1.orbiter.UPC.LEAVE_ROOM = "u10";           
/** @constant */
net.user1.orbiter.UPC.CREATE_ACCOUNT = "u11"; 
/** @constant */
net.user1.orbiter.UPC.REMOVE_ACCOUNT = "u12";
/** @constant */
net.user1.orbiter.UPC.CHANGE_ACCOUNT_PASSWORD = "u13";
/** @constant */
net.user1.orbiter.UPC.LOGIN = "u14";            
/** @constant */
net.user1.orbiter.UPC.GET_CLIENTCOUNT_SNAPSHOT = "u18";                
/** @constant */
net.user1.orbiter.UPC.SYNC_TIME = "u19";
/** @constant */
net.user1.orbiter.UPC.GET_ROOMLIST_SNAPSHOT = "u21";
/** @constant */
net.user1.orbiter.UPC.CREATE_ROOM = "u24";                       
/** @constant */
net.user1.orbiter.UPC.REMOVE_ROOM = "u25";                       
/** @constant */
net.user1.orbiter.UPC.WATCH_FOR_ROOMS = "u26";            
/** @constant */
net.user1.orbiter.UPC.STOP_WATCHING_FOR_ROOMS = "u27"; 
/** @constant */
net.user1.orbiter.UPC.GET_ROOM_SNAPSHOT = "u55";
/** @constant */
net.user1.orbiter.UPC.SEND_MESSAGE_TO_SERVER = "u57"; 
/** @constant */
net.user1.orbiter.UPC.OBSERVE_ROOM = "u58"; 
/** @constant */
net.user1.orbiter.UPC.STOP_OBSERVING_ROOM = "u61"; 
/** @constant */
net.user1.orbiter.UPC.SET_ROOM_UPDATE_LEVELS = "u64"; 
/** @constant */
net.user1.orbiter.UPC.CLIENT_HELLO = "u65"; 
/** @constant */
net.user1.orbiter.UPC.REMOVE_ROOM_ATTR = "u67"; 
/** @constant */
net.user1.orbiter.UPC.REMOVE_CLIENT_ATTR = "u69"; 
/** @constant */
net.user1.orbiter.UPC.SEND_ROOMMODULE_MESSAGE = "u70"; 
/** @constant */
net.user1.orbiter.UPC.SEND_SERVERMODULE_MESSAGE = "u71"; 
/** @constant */
net.user1.orbiter.UPC.TERMINATE_SESSION = "u83";
/** @constant */
net.user1.orbiter.UPC.LOGOFF = "u86";  
/** @constant */
net.user1.orbiter.UPC.GET_CLIENTLIST_SNAPSHOT = "u91";  
/** @constant */
net.user1.orbiter.UPC.WATCH_FOR_CLIENTS = "u92";  
/** @constant */
net.user1.orbiter.UPC.STOP_WATCHING_FOR_CLIENTS = "u93";  
/** @constant */
net.user1.orbiter.UPC.GET_CLIENT_SNAPSHOT = "u94";  
/** @constant */
net.user1.orbiter.UPC.OBSERVE_CLIENT = "u95";  
/** @constant */
net.user1.orbiter.UPC.STOP_OBSERVING_CLIENT = "u96";  
/** @constant */
net.user1.orbiter.UPC.GET_ACCOUNTLIST_SNAPSHOT = "u97";  
/** @constant */
net.user1.orbiter.UPC.WATCH_FOR_ACCOUNTS = "u98";  
/** @constant */
net.user1.orbiter.UPC.STOP_WATCHING_FOR_ACCOUNTS = "u99";  
/** @constant */
net.user1.orbiter.UPC.GET_ACCOUNT_SNAPSHOT = "u100";  
/** @constant */
net.user1.orbiter.UPC.OBSERVE_ACCOUNT = "u121";  
/** @constant */
net.user1.orbiter.UPC.STOP_OBSERVING_ACCOUNT = "u122"; 
/** @constant */
net.user1.orbiter.UPC.ADD_ROLE = "u133";  
/** @constant */
net.user1.orbiter.UPC.REMOVE_ROLE = "u135";  
/** @constant */
net.user1.orbiter.UPC.KICK_CLIENT = "u149";  
/** @constant */
net.user1.orbiter.UPC.BAN = "u137";  
/** @constant */
net.user1.orbiter.UPC.UNBAN = "u139";  
/** @constant */
net.user1.orbiter.UPC.GET_BANNED_LIST_SNAPSHOT = "u141";  
/** @constant */
net.user1.orbiter.UPC.WATCH_FOR_BANNED_ADDRESSES = "u143";  
/** @constant */
net.user1.orbiter.UPC.STOP_WATCHING_FOR_BANNED_ADDRESSES = "u145";  

// SERVER TO CLIENT
/** @constant */
net.user1.orbiter.UPC.JOINED_ROOM = "u6";
/** @constant */
net.user1.orbiter.UPC.RECEIVE_MESSAGE = "u7";
/** @constant */
net.user1.orbiter.UPC.CLIENT_ATTR_UPDATE = "u8";
/** @constant */
net.user1.orbiter.UPC.ROOM_ATTR_UPDATE = "u9";
/** @constant */
net.user1.orbiter.UPC.CLIENT_METADATA = "u29";
/** @constant */
net.user1.orbiter.UPC.CREATE_ROOM_RESULT = "u32";
/** @constant */
net.user1.orbiter.UPC.REMOVE_ROOM_RESULT = "u33";
/** @constant */
net.user1.orbiter.UPC.CLIENTCOUNT_SNAPSHOT = "u34";
/** @constant */
net.user1.orbiter.UPC.CLIENT_ADDED_TO_ROOM = "u36";
/** @constant */
net.user1.orbiter.UPC.CLIENT_REMOVED_FROM_ROOM = "u37";
/** @constant */
net.user1.orbiter.UPC.ROOMLIST_SNAPSHOT = "u38";
/** @constant */
net.user1.orbiter.UPC.ROOM_ADDED = "u39";
/** @constant */
net.user1.orbiter.UPC.ROOM_REMOVED = "u40";
/** @constant */
net.user1.orbiter.UPC.WATCH_FOR_ROOMS_RESULT = "u42";
/** @constant */
net.user1.orbiter.UPC.STOP_WATCHING_FOR_ROOMS_RESULT = "u43";
/** @constant */
net.user1.orbiter.UPC.LEFT_ROOM = "u44";
/** @constant */
net.user1.orbiter.UPC.CHANGE_ACCOUNT_PASSWORD_RESULT = "u46";
/** @constant */
net.user1.orbiter.UPC.CREATE_ACCOUNT_RESULT = "u47";
/** @constant */
net.user1.orbiter.UPC.REMOVE_ACCOUNT_RESULT = "u48";
/** @constant */
net.user1.orbiter.UPC.LOGIN_RESULT = "u49";
/** @constant */
net.user1.orbiter.UPC.SERVER_TIME_UPDATE = "u50";
/** @constant */
net.user1.orbiter.UPC.ROOM_SNAPSHOT = "u54";
/** @constant */
net.user1.orbiter.UPC.OBSERVED_ROOM = "u59";
/** @constant */
net.user1.orbiter.UPC.GET_ROOM_SNAPSHOT_RESULT = "u60";
/** @constant */
net.user1.orbiter.UPC.STOPPED_OBSERVING_ROOM = "u62";
/** @constant */
net.user1.orbiter.UPC.CLIENT_READY = "u63";
/** @constant */
net.user1.orbiter.UPC.SERVER_HELLO = "u66";
/** @constant */
net.user1.orbiter.UPC.JOIN_ROOM_RESULT = "u72";
/** @constant */
net.user1.orbiter.UPC.SET_CLIENT_ATTR_RESULT = "u73";
/** @constant */
net.user1.orbiter.UPC.SET_ROOM_ATTR_RESULT = "u74";
/** @constant */
net.user1.orbiter.UPC.GET_CLIENTCOUNT_SNAPSHOT_RESULT = "u75";
/** @constant */
net.user1.orbiter.UPC.LEAVE_ROOM_RESULT = "u76";
/** @constant */
net.user1.orbiter.UPC.OBSERVE_ROOM_RESULT = "u77";
/** @constant */
net.user1.orbiter.UPC.STOP_OBSERVING_ROOM_RESULT = "u78";
/** @constant */
net.user1.orbiter.UPC.ROOM_ATTR_REMOVED = "u79";
/** @constant */
net.user1.orbiter.UPC.REMOVE_ROOM_ATTR_RESULT = "u80";
/** @constant */
net.user1.orbiter.UPC.CLIENT_ATTR_REMOVED = "u81";
/** @constant */
net.user1.orbiter.UPC.REMOVE_CLIENT_ATTR_RESULT = "u82";
/** @constant */
net.user1.orbiter.UPC.SESSION_TERMINATED = "u84";
/** @constant */
net.user1.orbiter.UPC.SESSION_NOT_FOUND = "u85";
/** @constant */
net.user1.orbiter.UPC.LOGOFF_RESULT = "u87";
/** @constant */
net.user1.orbiter.UPC.LOGGED_IN = "u88";
/** @constant */
net.user1.orbiter.UPC.LOGGED_OFF = "u89";
/** @constant */
net.user1.orbiter.UPC.ACCOUNT_PASSWORD_CHANGED = "u90";
/** @constant */
net.user1.orbiter.UPC.CLIENT_LIST_SNAPSHOT = "u101";
/** @constant */
net.user1.orbiter.UPC.CLIENT_ADDED_TO_SERVER = "u102";
/** @constant */
net.user1.orbiter.UPC.CLIENT_REMOVED_FROM_SERVER = "u103";
/** @constant */
net.user1.orbiter.UPC.CLIENT_SNAPSHOT = "u104";
/** @constant */
net.user1.orbiter.UPC.OBSERVE_CLIENT_RESULT = "u105";
/** @constant */
net.user1.orbiter.UPC.STOP_OBSERVING_CLIENT_RESULT = "u106";
/** @constant */
net.user1.orbiter.UPC.WATCH_FOR_CLIENTS_RESULT = "u107";
/** @constant */
net.user1.orbiter.UPC.STOP_WATCHING_FOR_CLIENTS_RESULT = "u108";
/** @constant */
net.user1.orbiter.UPC.WATCH_FOR_ACCOUNTS_RESULT = "u109";
/** @constant */
net.user1.orbiter.UPC.STOP_WATCHING_FOR_ACCOUNTS_RESULT = "u110";
/** @constant */
net.user1.orbiter.UPC.ACCOUNT_ADDED = "u111";
/** @constant */
net.user1.orbiter.UPC.ACCOUNT_REMOVED = "u112";
/** @constant */
net.user1.orbiter.UPC.JOINED_ROOM_ADDED_TO_CLIENT = "u113";
/** @constant */
net.user1.orbiter.UPC.JOINED_ROOM_REMOVED_FROM_CLIENT = "u114";
/** @constant */
net.user1.orbiter.UPC.GET_CLIENT_SNAPSHOT_RESULT = "u115";
/** @constant */
net.user1.orbiter.UPC.GET_ACCOUNT_SNAPSHOT_RESULT = "u116";
/** @constant */
net.user1.orbiter.UPC.OBSERVED_ROOM_ADDED_TO_CLIENT = "u117";
/** @constant */
net.user1.orbiter.UPC.OBSERVED_ROOM_REMOVED_FROM_CLIENT = "u118";
/** @constant */
net.user1.orbiter.UPC.CLIENT_OBSERVED = "u119";
/** @constant */
net.user1.orbiter.UPC.STOPPED_OBSERVING_CLIENT = "u120";
/** @constant */
net.user1.orbiter.UPC.OBSERVE_ACCOUNT_RESULT = "u123";
/** @constant */
net.user1.orbiter.UPC.ACCOUNT_OBSERVED = "u124";
/** @constant */
net.user1.orbiter.UPC.STOP_OBSERVING_ACCOUNT_RESULT = "u125";
/** @constant */
net.user1.orbiter.UPC.STOPPED_OBSERVING_ACCOUNT = "u126";
/** @constant */
net.user1.orbiter.UPC.ACCOUNT_LIST_UPDATE = "u127";
/** @constant */
net.user1.orbiter.UPC.UPDATE_LEVELS_UPDATE = "u128";
/** @constant */
net.user1.orbiter.UPC.CLIENT_OBSERVED_ROOM = "u129";
/** @constant */
net.user1.orbiter.UPC.CLIENT_STOPPED_OBSERVING_ROOM = "u130";
/** @constant */
net.user1.orbiter.UPC.ROOM_OCCUPANTCOUNT_UPDATE = "u131";
/** @constant */
net.user1.orbiter.UPC.ROOM_OBSERVERCOUNT_UPDATE = "u132";
/** @constant */
net.user1.orbiter.UPC.ADD_ROLE_RESULT = "u134";
/** @constant */
net.user1.orbiter.UPC.REMOVE_ROLE_RESULT = "u136";
/** @constant */
net.user1.orbiter.UPC.BAN_RESULT = "u138";
/** @constant */
net.user1.orbiter.UPC.UNBAN_RESULT = "u140";
/** @constant */
net.user1.orbiter.UPC.BANNED_LIST_SNAPSHOT = "u142";
/** @constant */
net.user1.orbiter.UPC.WATCH_FOR_BANNED_ADDRESSES_RESULT = "u144";
/** @constant */
net.user1.orbiter.UPC.STOP_WATCHING_FOR_BANNED_ADDRESSES_RESULT = "u146";
/** @constant */
net.user1.orbiter.UPC.BANNED_ADDRESS_ADDED = "u147";
/** @constant */
net.user1.orbiter.UPC.BANNED_ADDRESS_REMOVED = "u148";
/** @constant */
net.user1.orbiter.UPC.KICK_CLIENT_RESULT = "u150";
//==============================================================================
// MESSAGE CONSTANTS
//==============================================================================
/** @class */
net.user1.orbiter.Messages = new Object();
/** @constant */
net.user1.orbiter.Messages.CLIENT_HEARTBEAT = "CLIENT_HEARTBEAT";
//==============================================================================
// RECEIVE MESSAGE BROADCAST TYPE CONSTANTS
//==============================================================================
/** @class
    @private */
net.user1.orbiter.ReceiveMessageBroadcastType = new Object();
net.user1.orbiter.ReceiveMessageBroadcastType.TO_SERVER  = "0";
net.user1.orbiter.ReceiveMessageBroadcastType.TO_ROOMS   = "1";
net.user1.orbiter.ReceiveMessageBroadcastType.TO_CLIENTS = "2";
//==============================================================================
// ROOM ID PARSING UTILITIES
//==============================================================================
/** @class */
net.user1.orbiter.RoomIDParser = new Object();

net.user1.orbiter.RoomIDParser.getSimpleRoomID = function (fullRoomID) {
  if (fullRoomID.indexOf(".") == -1) {
    return fullRoomID;
  } else {
    return fullRoomID.slice(fullRoomID.lastIndexOf(".")+1);
  }
};

net.user1.orbiter.RoomIDParser.getQualifier = function (fullRoomID) {
  if (fullRoomID.indexOf(".") == -1) {
    return "";
  } else {
    return fullRoomID.slice(0, fullRoomID.lastIndexOf("."));
  }
};

net.user1.orbiter.RoomIDParser.splitID = function (fullRoomID) {
  return [getQualifier(fullRoomID), getSimpleRoomID(fullRoomID)];
};
//==============================================================================
// CLASS DECLARATION
//==============================================================================
/** @class */
net.user1.utils.UDictionary = function () {
};

//==============================================================================
// INSTANCE METHODS
//==============================================================================
net.user1.utils.UDictionary.prototype.length = function () {
  var len = 0;
  for (var p in this) {
    len++;
  } 
  return len;
}
//==============================================================================
// TOKEN CONSTANTS
//==============================================================================
/** @class
    @private */
net.user1.orbiter.Tokens = new Object();

/** @private */
net.user1.orbiter.Tokens.RS = "|";  
/** @private */
net.user1.orbiter.Tokens.GLOBAL_ATTR = "";  
//==============================================================================
// CLASS DECLARATION
//==============================================================================
/** @class */
net.user1.orbiter.System = function () {
  this.clientType     = "OrbiterMicro";
  this.clientVersion  = new net.user1.orbiter.VersionNumber(1,0,0,370);
  this.upcVersion     = new net.user1.orbiter.VersionNumber(1,9,1);
}

//==============================================================================
// INSTANCE METHODS
//==============================================================================  
net.user1.orbiter.System.prototype.getClientType = function () {
  return this.clientType;
}
    
net.user1.orbiter.System.prototype.getClientVersion = function () {
  return this.clientVersion;
}
    
net.user1.orbiter.System.prototype.getUPCVersion = function () {
  return this.upcVersion;
}

net.user1.orbiter.System.prototype.isJavaScriptCompatible = function () {
  if (window != null
      && window.postMessage != null
      && XMLHttpRequest != null) {
    return true;
  }
  return false;
}

net.user1.orbiter.System.prototype.toString = function () {
  return "[object System]";
}

//==============================================================================
// CLASS DECLARATION
//==============================================================================
/** @class */
net.user1.orbiter.VersionNumber = function (major, minor, revision, build) {
  this.major    = major;
  this.minor    = minor;
  this.revision = revision;
  this.build    = build == undefined ? -1 : build;
};

//==============================================================================
// INSTANCE METHODS
//==============================================================================  
net.user1.orbiter.VersionNumber.prototype.fromVersionString = function (value) {
  var upcVersionParts = value.split(".");      
  major    = upcVersionParts[0];
  minor    = upcVersionParts[1];
  revision = upcVersionParts[2];
  build    = upcVersionParts.length == 4 ? upcVersionParts[4] : -1;
}

net.user1.orbiter.VersionNumber.prototype.toStringVerbose = function () {
  var versionString = this.major + "." + this.minor + "." + this.revision
            + ((this.build == -1) ? "" : " (Build " + this.build + ")");
  return versionString;
}
    
net.user1.orbiter.VersionNumber.prototype.toString = function () {
  var versionString = this.major + "." + this.minor + "." + this.revision
            + ((this.build == -1) ? "" : "." + this.build);
  return versionString;
}
//==============================================================================
// A COLLECTION OF NUMERIC FORMATTING FUNCTIONS
//==============================================================================
/** @class */
net.user1.utils.NumericFormatter = new Object();

net.user1.utils.NumericFormatter.dateToLocalHrMinSec = function (date) {
  var timeString = net.user1.utils.NumericFormatter.addLeadingZero(date.getHours()) + ":" 
                 + net.user1.utils.NumericFormatter.addLeadingZero(date.getMinutes()) + ":" 
                 + net.user1.utils.NumericFormatter.addLeadingZero(date.getSeconds());
  return timeString;
}
    
net.user1.utils.NumericFormatter.dateToLocalHrMinSecMs = function (date) {
  return net.user1.utils.NumericFormatter.dateToLocalHrMinSec(date) + "." + net.user1.utils.NumericFormatter.addTrailingZeros(date.getMilliseconds());
}
    
net.user1.utils.NumericFormatter.addLeadingZero = function (n) {
  return ((n>9)?"":"0")+n;
}
    
net.user1.utils.NumericFormatter.addTrailingZeros = function (n) {
  var ns = n.toString();
  
  if (ns.length == 1) {
    return ns + "00";
  } else if (ns.length == 2) {
    return ns + "0";
  } else {
    return ns;
  }
}
//==============================================================================
// CLASS DECLARATION
//==============================================================================
/** @class */
net.user1.events.EventListener = function (listener,
                                           thisArg) {
  this.listener   = listener;
  this.thisArg    = thisArg;
};

//==============================================================================
// INSTANCE METHODS
//==============================================================================
net.user1.events.EventListener.prototype.getListenerFunction = function () {
  return this.listener;
};
    
net.user1.events.EventListener.prototype.getThisArg = function () {
  return this.thisArg;
};

net.user1.events.EventListener.prototype.toString = function () {
  return "[object EventListener]";
};
//==============================================================================
// CLASS DECLARATION
//==============================================================================
/** @class */
net.user1.events.EventDispatcher = function (target) {
  this.listeners = new Object();
  
  if (target !== undefined) {
    this.target = target;
  } else {
    this.target = this;
  }
};

//==============================================================================
// INSTANCE METHODS
//==============================================================================
net.user1.events.EventDispatcher.prototype.addEventListener = function (type, 
                                                                        listener,
                                                                        thisArg) {
  if (this.listeners[type] === undefined) {
    this.listeners[type] = new Array();
  } 
  var listenerArray = this.listeners[type];
  
  if (this.hasListener(type, listener)) {
    return false;
  }
  
  var newListener = new net.user1.events.EventListener(listener,
                                                       thisArg);
  listenerArray.push(newListener);
  return true;      
}

net.user1.events.EventDispatcher.prototype.removeEventListener = function (type,
                                                                           listener) {
  var listenerArray = this.listeners[type];
  if (listenerArray === undefined) {
    return false;
  } 
  
  var foundListener;
  for (var i = 0; i < listenerArray.length; i++) {
    if (listenerArray[i].getListenerFunction() == listener) {
      foundListener = true;
      listenerArray.splice(i, 1);
      break;
    }
  }
  
  if (listenerArray.length == 0) {
    delete this.listeners[message];
  }
  
  return foundListener;      
}
    
net.user1.events.EventDispatcher.prototype.hasListener = function (type, 
                                                                   listener) {
  var listenerArray = this.listeners[type];
  if (listenerArray === undefined) {
    return false;
  } 
      
  for (var i = 0; i < listenerArray.length; i++) {
    if (listenerArray[i].getListenerFunction() == listener) {
      return true;
    }
  }
  return false;
}
    
net.user1.events.EventDispatcher.prototype.getListeners = function (type) {
  return this.listeners[type];
}

net.user1.events.EventDispatcher.prototype.dispatchEvent = function (event) {
  var listenerArray = this.listeners[event.type];
  if (listenerArray === undefined) {
    return;
  }
  if (event.type == undefined) {
    throw new Error("Event dispatch failed. No event name specified by " + event);
  }
  var numListeners = listenerArray.length;
  for (var i = 0; i < numListeners; i++) {
    listenerArray[i].getListenerFunction().apply(listenerArray[i].getThisArg(), [event]);
  }
}

//==============================================================================    
// TOSTRING
//==============================================================================

net.user1.events.EventDispatcher.prototype.toString = function () {
  return "[object EventDispatcher]";
}
//==============================================================================
// CLASS DECLARATION
//==============================================================================
/** @class */
net.user1.events.Event = function (type) {
  if (type !== undefined) {
    this.type = type;
  } else {
    throw new Error("Event creation failed. No type specified. Event: " + this);
  }
};
    
net.user1.events.Event.prototype.toString = function () {
  return "[object Event]";
};
//==============================================================================
// CLASS DECLARATION
//==============================================================================
/** @class
    @extends net.user1.events.Event
*/
net.user1.logger.LogEvent = function (type, message, level, timeStamp) {
  net.user1.events.Event.call(this, type);

  this.message = message;
  this.level = level;
  this.timeStamp = timeStamp;
};

//==============================================================================
// INHERITANCE
//==============================================================================
net.user1.utils.extend(net.user1.logger.LogEvent, net.user1.events.Event);

//==============================================================================
// STATIC VARIABLES
//==============================================================================
/** @constant */
net.user1.logger.LogEvent.UPDATE = "UPDATE";
/** @constant */
net.user1.logger.LogEvent.LEVEL_CHANGE = "LEVEL_CHANGE";
  
//==============================================================================
// INSTANCE METHODS
//==============================================================================
net.user1.logger.LogEvent.prototype.getMessage = function () {
  return this.message;
};
  
net.user1.logger.LogEvent.prototype.getLevel = function () {
  return this.level;
};
  
net.user1.logger.LogEvent.prototype.getTimeStamp = function () {
  return this.timeStamp;
};

net.user1.logger.LogEvent.prototype.toString = function () {
  return "[object LogEvent]";
};

//==============================================================================
// CLASS DECLARATION
//==============================================================================
/** @class

The Logger class dispatches the following events:

<ul class="summary">
<li class="fixedFont">{@link net.user1.logger.LogEvent.LEVEL_CHANGE}</li>
<li class="fixedFont">{@link net.user1.logger.LogEvent.UPDATE}</li>
</ul>

To register for events, use {@link net.user1.events.EventDispatcher#addEventListener}.


    @extends net.user1.events.EventDispatcher
*/
net.user1.logger.Logger = function (historyLength) {
  // Invoke superclass constructor
  net.user1.events.EventDispatcher.call(this);
  
  // Instance variables
  this.suppressionTerms = new Array(); 
  this.timeStampEnabled = false;
  this.logLevel = 0;
  this.messages = new Array();
  this.historyLength = 0;

  // Initialization
  this.setHistoryLength(historyLength == null ? 100 : historyLength);
  this.enableTimeStamp(); 
  this.setLevel(net.user1.logger.Logger.INFO);
};  

//==============================================================================
// INHERITANCE
//==============================================================================
net.user1.utils.extend(net.user1.logger.Logger, net.user1.events.EventDispatcher);
  
//==============================================================================
// STATIC VARIABLES
//==============================================================================
/** @constant */
net.user1.logger.Logger.FATAL = "FATAL"; 
/** @constant */
net.user1.logger.Logger.ERROR = "ERROR"; 
/** @constant */
net.user1.logger.Logger.WARN  = "WARN"; 
/** @constant */
net.user1.logger.Logger.INFO  = "INFO"; 
/** @constant */
net.user1.logger.Logger.DEBUG = "DEBUG";
net.user1.logger.Logger.logLevels = new Array(net.user1.logger.Logger.FATAL,
                                              net.user1.logger.Logger.ERROR, 
                                              net.user1.logger.Logger.WARN, 
                                              net.user1.logger.Logger.INFO, 
                                              net.user1.logger.Logger.DEBUG);

//==============================================================================
// INSTANCE METHODS
//==============================================================================
net.user1.logger.Logger.prototype.setLevel = function (level) {
  if (level !== undefined) {
    for (var i = 0; i < net.user1.logger.Logger.logLevels.length; i++) {
      if (net.user1.logger.Logger.logLevels[i].toLowerCase() == level.toLowerCase()) {
        this.logLevel = i;
        this.dispatchEvent(new net.user1.logger.LogEvent(net.user1.logger.LogEvent.LEVEL_CHANGE, null, level));
        return;
      }
    }
  }

  this.warn("Invalid log level specified: " + level);
};

net.user1.logger.Logger.prototype.getLevel = function () {
  return net.user1.logger.Logger.logLevels[this.logLevel];
};

net.user1.logger.Logger.prototype.fatal = function (msg) {
  this.addEntry(0, net.user1.logger.Logger.FATAL, msg);
};

net.user1.logger.Logger.prototype.error = function (msg) {
  this.addEntry(1, net.user1.logger.Logger.ERROR, msg);
};

net.user1.logger.Logger.prototype.warn = function (msg) {
  this.addEntry(2, net.user1.logger.Logger.WARN, msg);
};

net.user1.logger.Logger.prototype.info = function (msg) {
  this.addEntry(3, net.user1.logger.Logger.INFO, msg);
};

net.user1.logger.Logger.prototype.debug = function (msg) {
  this.addEntry(4, net.user1.logger.Logger.DEBUG, msg);
};

net.user1.logger.Logger.prototype.addSuppressionTerm = function (term) {
  this.debug("Added suppression term. Log messages containing '" 
             + term + "' will now be ignored.");
  this.suppressionTerms.push(term);
};

net.user1.logger.Logger.prototype.removeSuppressionTerm = function (term) {
  var termIndex = net.user1.utils.ArrayUtils.indexOf(this.suppressionTerms, term);
  if (termIndex != -1) {
    this.suppressionTerms.splice(termIndex, 1);
    this.debug("Removed suppression term. Log messages containing '" 
               + term + "' will now be shown.");
    return true;
  }
  return false;
};

/** @private */
net.user1.logger.Logger.prototype.addEntry = function (level, levelName, msg) {
  var timeStamp = "";
  var time;
  
  // Abort if the log's level is lower than the message's level.
  if (this.logLevel < level) {
    return;
  }
  
  // Don't log messages if they contain any of the suppression terms.
  for (var i = this.suppressionTerms.length; --i >= 0;) {
    if (msg.indexOf(this.suppressionTerms[i]) != -1) {
      return;
    }
  }

  if (this.timeStampEnabled) {
    time = new Date();
    timeStamp = time.getMonth()+1 + "/" + String(time.getDate())
              + "/" + String(time.getFullYear()).substr(2)
              + " " + net.user1.utils.NumericFormatter.dateToLocalHrMinSecMs(time) 
              + " UTC" + (time.getTimezoneOffset() >= 0 ? "-" : "+") 
              + Math.abs(time.getTimezoneOffset() / 60);
  }
  
  // Log the message.
  this.addToHistory(levelName, msg, timeStamp);

  var e = new net.user1.logger.LogEvent(net.user1.logger.LogEvent.UPDATE,
                                        msg, levelName, timeStamp);
  this.dispatchEvent(e);
};

net.user1.logger.Logger.prototype.setHistoryLength = function (newHistoryLength) {
  this.historyLength = newHistoryLength;
  
  if (this.messages.length > this.historyLength) {
    this.messages.splice(this.historyLength);
  }
};

net.user1.logger.Logger.prototype.getHistoryLength = function () {
  return this.historyLength;
};

net.user1.logger.Logger.prototype.addToHistory = function (level, msg, timeStamp) {
  this.messages.push(timeStamp + (timeStamp == "" ? "" : " ") + level + ": " + msg);
  if (this.messages.length > this.historyLength) {
    this.messages.shift();
  }
};

net.user1.logger.Logger.prototype.getHistory = function () {
  return this.messages.slice(0);
};

net.user1.logger.Logger.prototype.enableTimeStamp = function () {
  this.timeStampEnabled = true;
};

net.user1.logger.Logger.prototype.disableTimeStamp = function () {
  this.timeStampEnabled = false;
};

net.user1.logger.Logger.prototype.toString = function () {
  return "[object Logger]";
};
//==============================================================================
// CLASS DECLARATION
//==============================================================================
/** @class */
net.user1.orbiter.ConnectionMonitor = function (orbiter) {
  // Instance variables
  this.connectionTimeout = 0;
  this.heartbeatIntervalID = -1;
  this.heartbeatCounter = 0;
  this.heartbeatEnabled = true;
  this.heartbeats = new net.user1.utils.UDictionary();
  
  this.oldestHeartbeat = 0;
  this.heartBeatFrequency = -1;
  
  this.sharedPing = false;
  
  this.autoReconnectFrequency = -1;
  this.autoReconnectTimeoutID = -1;
  this.autoReconnectTimerRunning = false;
  
  this.orbiter = orbiter;
  this.msgManager = orbiter.getMessageManager();
  this.log = orbiter.getLog();
  
  // Initialization
  this.orbiter.addEventListener(net.user1.orbiter.OrbiterEvent.READY, this.connectReadyListener, this);
  this.orbiter.addEventListener(net.user1.orbiter.OrbiterEvent.CLOSE, this.connectCloseListener, this);
  this.disableHeartbeatLogging();
};

//==============================================================================
// STATIC VARIABLES
//==============================================================================
net.user1.orbiter.ConnectionMonitor.DEFAULT_HEARTBEAT_FREQUENCY = 10000;
net.user1.orbiter.ConnectionMonitor.MIN_HEARTBEAT_FREQUENCY = 20;
net.user1.orbiter.ConnectionMonitor.DEFAULT_AUTORECONNECT_FREQUENCY = -1;
net.user1.orbiter.ConnectionMonitor.DEFAULT_CONNECTION_TIMEOUT = 60000;

//==============================================================================
// CONNECTION MONITORING
//==============================================================================
/** @private */
net.user1.orbiter.ConnectionMonitor.prototype.connectReadyListener = function (e) {
  this.msgManager.addMessageListener(net.user1.orbiter.Messages.CLIENT_HEARTBEAT, this.heartbeatMessageListener, this);
  this.startHeartbeat();
  this.stopReconnect();
}

/** @private */
net.user1.orbiter.ConnectionMonitor.prototype.connectCloseListener = function (e) {
  this.stopHeartbeat();
  if (this.autoReconnectFrequency > -1) {
    if (autoReconnectTimerRunning) {
      return;
    } else {
      this.log.warn("[CONNECTION_MONITOR] Disconnection detected."); 
      this.doReconnect();
    }
  }
}
    
//==============================================================================
// HEARTBEAT
//==============================================================================

net.user1.orbiter.ConnectionMonitor.prototype.enableHeartbeat = function () {
  this.log.info("[CONNECTION_MONITOR] Heartbeat enabled.");
  this.heartbeatEnabled = true;
  this.startHeartbeat();
}

net.user1.orbiter.ConnectionMonitor.prototype.disableHeartbeat = function () {
  this.log.info("[CONNECTION_MONITOR] Heartbeat disabled.");
  this.heartbeatEnabled = false;
  this.stopHeartbeat();
}

/** @private */
net.user1.orbiter.ConnectionMonitor.prototype.startHeartbeat = function () {
  if (!this.heartbeatEnabled) {
    this.log.info("[CONNECTION_MONITOR] Heartbeat is currently disabled. Ignoring start request.");
    return;
  }
  
  this.stopHeartbeat();
  
  this.heartbeats = new net.user1.utils.UDictionary();
  
  var currentObj = this;
  var callback   = this.heartbeatTimerListener;
  this.heartbeatIntervalID = setInterval(function () {
    callback.call(currentObj);
  }, this.heartBeatFrequency);
  
}

/** @private */
net.user1.orbiter.ConnectionMonitor.prototype.stopHeartbeat = function () {
  clearInterval(this.heartbeatIntervalID);
  this.heartbeats = null;
}

/** @private */
net.user1.orbiter.ConnectionMonitor.prototype.heartbeatTimerListener = function () {
  if (!this.orbiter.isReady()) {
    this.log.info("[CONNECTION_MONITOR] Orbiter is not connected. Stopping heartbeat.");
    this.stopHeartbeat();
    return;
  }

  var timeSinceOldestHeartbeat;
  var now = new Date().getTime();
  
  this.heartbeats[this.heartbeatCounter] = now;
  this.orbiter.getMessageManager().sendUPC("u2",
                                 net.user1.orbiter.Messages.CLIENT_HEARTBEAT, 
                                 this.orbiter.getClientID(),
                                 "",
                                 this.heartbeatCounter);
  this.heartbeatCounter++;
  
  // Assign the oldest heartbeat
  if (this.heartbeats.length == 1) {
    this.oldestHeartbeat = now;
  } else { 
    this.oldestHeartbeat = Number.MAX_VALUE;
    for (var p in this.heartbeats) {
      if (this.heartbeats[p] < this.oldestHeartbeat) {
        this.oldestHeartbeat = this.heartbeats[p];
      }
    }
  }
  // Close connection if too much time has passed since the last response
  timeSinceOldestHeartbeat = now - this.oldestHeartbeat;
  if (timeSinceOldestHeartbeat > this.connectionTimeout) {
    this.log.warn("[CONNECTION_MONITOR] No response from server in " + 
                  timeSinceOldestHeartbeat + "ms. Starting automatic disconnect.");
    this.orbiter.disconnect();
  }
}

/** @private */
net.user1.orbiter.ConnectionMonitor.prototype.heartbeatMessageListener = function (fromClientID, id) {
  this.orbiter.getMessageManager().sendUPC("u3",
                                           this.orbiter.getClientID(),
                                           "",
                                           "_PING",
                                           (new Date().getTime() - this.heartbeats[parseInt(id)]).toString(),
                                           "",
                                           "0",
                                           this.sharedPing);
  delete this.heartbeats[parseInt(id)];
}

//==============================================================================
// RECONNECTION
//==============================================================================
/** @private */
net.user1.orbiter.ConnectionMonitor.prototype.reconnectTimerListener = function (e) {
  this.stopReconnect();
  if (this.orbiter.getConnection().connectionState == ConnectionState.NOT_CONNECTED) {
    this.doReconnect();
  }
}

/** @private */
net.user1.orbiter.ConnectionMonitor.prototype.stopReconnect = function () {
  clearTimeout(this.autoReconnectTimeoutID);
}

/** @private */
net.user1.orbiter.ConnectionMonitor.prototype.doReconnect = function () {
  // Reset the timer
  this.stopReconnect();

  this.log.warn("[CONNECTION_MONITOR] Attempting automatic reconnect. (Next attempt in "
           + this.autoReconnectFrequency + "ms.)");      
   
  // Schedule the next reconnection attempt
  var currentObj = this;
  var callback   = this.reconnectTimerListener;
  this.autoReconnectTimeoutID = setTimeout(function () {
    callback.call(currentObj);
  }, this.autoReconnectFrequency);
  
  // Attempt to connect
  reactor.connect();
}

//==============================================================================
// CONFIGURATION
//==============================================================================

net.user1.orbiter.ConnectionMonitor.prototype.restoreDefaults = function () {
  this.setAutoReconnectFrequency(net.user1.orbiter.ConnectionMonitor.DEFAULT_AUTORECONNECT_FREQUENCY);
  this.setConnectionTimeout(net.user1.orbiter.ConnectionMonitor.DEFAULT_CONNECTION_TIMEOUT);
  this.setHeartbeatFrequency(net.user1.orbiter.ConnectionMonitor.DEFAULT_HEARTBEAT_FREQUENCY);
}

net.user1.orbiter.ConnectionMonitor.prototype.setHeartbeatFrequency = function (milliseconds) {
  if (milliseconds >= net.user1.orbiter.ConnectionMonitor.MIN_HEARTBEAT_FREQUENCY) {
    this.heartBeatFrequency = milliseconds;
    this.log.info("[CONNECTION_MONITOR] Heartbeat frequency set to " 
                  + milliseconds + " ms.");
    // Log a warning for low heartbeat frequencies...
    if (milliseconds >= net.user1.orbiter.ConnectionMonitor.MIN_HEARTBEAT_FREQUENCY && milliseconds < 1000) {
      this.log.info("[CONNECTION_MONITOR] HEARTBEAT FREQUENCY WARNING: " 
               + milliseconds + " ms. Current frequency will generate "
               + (Math.floor((1000/milliseconds)*10)/10) 
               + " messages per second per connected client.");
    }
    
    // If the connection is ready, then restart
    // the heartbeat when the heartbeat frequency changes.
    if (this.orbiter.isReady()) {
      this.startHeartbeat();
    }
  } else {
    this.log.warn("[CONNECTION_MONITOR] Invalid heartbeat frequency specified: " 
             + milliseconds + ". Frequency must be "
             + net.user1.orbiter.ConnectionMonitor.MIN_HEARTBEAT_FREQUENCY + " or greater.");
  }
}

net.user1.orbiter.ConnectionMonitor.prototype.getHeartbeatFrequency = function () {
  return this.heartBeatFrequency;
}

net.user1.orbiter.ConnectionMonitor.prototype.setAutoReconnectFrequency = function (milliseconds) {
  if (milliseconds > -2) {
    this.autoReconnectFrequency = milliseconds;
    this.log.info("[CONNECTION_MONITOR] Auto-reconnect frequency set to " 
             + milliseconds + " ms.");
    if (milliseconds > -1 && milliseconds < 1000) {
      this.log.info("[CONNECTION_MONITOR] RECONNECT FREQUENCY WARNING: " 
               + milliseconds + " ms. Current frequency will cause "
               + (milliseconds == 0 ? "1000" : (Math.floor((1000/milliseconds)*10)/10)) 
               + " reconnection attempts per second.");
    }
  } else {
    this.log.warn("[CONNECTION_MONITOR] Invalid reconnect frequency specified: " + milliseconds
                             + ". Frequency must -1 or greater.");
  }
}

net.user1.orbiter.ConnectionMonitor.prototype.getAutoReconnectFrequency = function () {
  return this.autoReconnectFrequency;
}

net.user1.orbiter.ConnectionMonitor.prototype.setConnectionTimeout = function (milliseconds) {
  if (milliseconds > 0) {
    this.connectionTimeout = milliseconds;
    this.log.info("[CONNECTION_MONITOR] Connection timeout set to " 
                  + milliseconds + " ms.");
  } else {
    this.log.warn("[CONNECTION_MONITOR] Invalid connection timeout specified: " 
                             + milliseconds + ". Frequency must be greater " 
                             + "than zero.");
  }
}

net.user1.orbiter.ConnectionMonitor.prototype.getConnectionTimeout = function () {
  return this.connectionTimeout;
}

net.user1.orbiter.ConnectionMonitor.prototype.sharePing = function (share) {
  this.sharedPing = share;
}

net.user1.orbiter.ConnectionMonitor.prototype.isPingShared = function () {
  return this.sharedPing;
}

net.user1.orbiter.ConnectionMonitor.prototype.disableHeartbeatLogging = function () {
  this.log.addSuppressionTerm("<A>CLIENT_HEARTBEAT</A>");
  this.log.addSuppressionTerm("<A>_PING</A>");
  this.log.addSuppressionTerm("[_PING]");
  this.log.addSuppressionTerm("<![CDATA[_PING]]>");
}

net.user1.orbiter.ConnectionMonitor.prototype.enableHeartbeatLogging = function () {
  this.log.removeSuppressionTerm("<A>CLIENT_HEARTBEAT</A>");
  this.log.removeSuppressionTerm("<A>_PING</A>");
  this.log.removeSuppressionTerm("[_PING]");
  this.log.removeSuppressionTerm("<![CDATA[_PING]]>");
}

// =============================================================================
// DISPOSAL
// =============================================================================

net.user1.orbiter.ConnectionMonitor.prototype.dispose = function () {
  this.stopHeartbeat();
  this.stopReconnect();

  this.heartbeats = null;
  
  this.orbiter.removeEventListener(net.user1.orbiter.OrbiterEvent.READY, this.connectReadyListener);
  this.orbiter.removeEventListener(net.user1.orbiter.OrbiterEvent.CLOSE, this.connectCloseListener);
  this.orbiter = null;
  this.msgManager.removeMessageListener("u7", this.u7);
  this.msgManager(null);
  this.log = null;
};

















//==============================================================================
// CLASS DECLARATION
//==============================================================================
/** @class
    @private */
net.user1.orbiter.CoreMessageListener = function (orbiter) {
  this.orbiter = orbiter;
  this.log = orbiter.getLog();      
  this.orbiter.getConnection().addEventListener(net.user1.orbiter.ConnectionEvent.BEGIN_CONNECT, 
                                                this.beginConnectListener, this);
  this.registerCoreListeners();
};

net.user1.orbiter.CoreMessageListener.prototype.registerCoreListeners = function () {
  var msgMan = this.orbiter.getMessageManager();
  msgMan.addMessageListener(net.user1.orbiter.UPC.RECEIVE_MESSAGE, this.u7, this);
};

net.user1.orbiter.CoreMessageListener.prototype.beginConnectListener = function (e) {
  var msgMan = this.orbiter.getMessageManager();
  if (msgMan.removeMessageListenersOnDisconnect) {
    this.registerCoreListeners();
  }
};

net.user1.orbiter.CoreMessageListener.prototype.u7 = function (message,
                                                               broadcastType,
                                                               fromClientID,
                                                               toRoomID) {
  var msgMan = this.orbiter.getMessageManager();
  var args;
  var userDefinedArgs = Array.prototype.slice.call(arguments).slice(4);

  // ===== To Clients, or To Server =====
  if (broadcastType != net.user1.orbiter.ReceiveMessageBroadcastType.TO_ROOMS) {
    args = [fromClientID].concat(userDefinedArgs);
    msgMan.notifyMessageListeners(message, args);
    return;
  }
  
  // ===== To Rooms =====
  var listeners = msgMan.getMessageListeners(message);

  // Split the recipient room ID into two parts
  var toRoomSimpleID  = net.user1.orbiter.RoomIDParser.getSimpleRoomID(toRoomID);
  var toRoomQualifier = net.user1.orbiter.RoomIDParser.getQualifier(toRoomID);
  var listenerFound; 
  var listenerIgnoredMessage;
  var messageListener;
                                       
  // ===== Run once for each message listener =====
  for (var i = 0; i < listeners.length; i++) {
    messageListener = listeners[i];
    listenerIgnoredMessage = true;
    
    // --- Has no "forRoomIDs" filter ---
    if (messageListener.getForRoomIDs() == null) {
      args = [fromClientID, toRoomID].concat(userDefinedArgs);
      messageListener.getListenerFunction().apply(messageListener.getThisArg(), args);
      listenerFound = true;
      listenerIgnoredMessage = false;
      continue;  // Done with this listener. On to the next.
    }
    
    // --- Has a "forRoomIDs" filter ---
    var listenerRoomIDs = messageListener.getForRoomIDs();
    var listenerRoomQualifier;
    var listenerRoomSimpleID;
    var listenerRoomIDString;
    // ===== Run once for each room id =====
    for (var i = 0; i < listenerRoomIDs.length; i++) {
      listenerRoomIDString = listenerRoomIDs[i];
      listenerRoomQualifier = net.user1.orbiter.RoomIDParser.getQualifier(listenerRoomIDString);
      listenerRoomSimpleID  = net.user1.orbiter.RoomIDParser.getSimpleRoomID(listenerRoomIDString);

      // Check if the listener is interested in the recipient room...
      if (listenerRoomQualifier == toRoomQualifier
          && 
          (listenerRoomSimpleID == toRoomSimpleID
           || listenerRoomSimpleID == "*")) {
        // Found a match. Notify the listener...
          
        // Prepare args.
        if (listenerRoomIDs.length == 1) {
          // The listener is interested in messages sent to a 
          // specific room only, so omit the "toRoom" arg.
          args = [fromClientID].concat(userDefinedArgs);
        } else {
          // The listener is interested in messages sent to 
          // multiple rooms, so include the "toRoomID" arg so the listener 
          // knows which room received the message.
          args = [fromClientID, toRoomID].concat(userDefinedArgs);
        }
        
        messageListener.getListenerFunction().apply(messageListener.getThisArg(), args);
        listenerFound = true;
        listenerIgnoredMessage = false;
        break; // Stop looking at this listener's room ids
      }
    } // Done looking at this listener's room ids
    if (listenerIgnoredMessage) {
      this.log.debug("Message listener ignored message: " + message + ". "
                     + "Listener registered to receive " 
                     + "messages sent to: " + messageListener.getForRoomIDs() 
                     + ", but message was sent to: " + toRoomID);
    }
  } // Done looking at listeners for the incoming message
  if (!listenerFound) {
    this.log.warn("No message listener handled incoming message: " 
                  + message + ", sent to: " + toRoomID);
  }
};
//==============================================================================
// CLASS DECLARATION
//==============================================================================
/** @class
    @extends net.user1.events.Event
*/
net.user1.orbiter.OrbiterEvent = function (type, 
                                           serverUPCVersion) {
  net.user1.events.Event.call(this, type);

  this.serverUPCVersion = serverUPCVersion;
};

//==============================================================================
// INHERITANCE
//==============================================================================
net.user1.utils.extend(net.user1.orbiter.OrbiterEvent, net.user1.events.Event);
 
//==============================================================================
// STATIC VARIABLES
//==============================================================================
/** @constant */
net.user1.orbiter.OrbiterEvent.READY = "READY";
/** @constant */
net.user1.orbiter.OrbiterEvent.CLOSE = "CLOSE";
/** @constant */
net.user1.orbiter.OrbiterEvent.PROTOCOL_INCOMPATIBLE = "PROTOCOL_INCOMPATIBLE";

//==============================================================================
// INSTANCE METHODS
//==============================================================================  
net.user1.orbiter.OrbiterEvent.prototype.getServerUPCVersion = function () {
  return this.serverUPCVersion;
}

net.user1.orbiter.OrbiterEvent.prototype.toString = function () {
  return "[object OrbiterEvent]";
}  

//==============================================================================
// CLASS DECLARATION
//==============================================================================
/** @class

The Orbiter class dispatches the following events:

<ul class="summary">
<li class="fixedFont">{@link net.user1.orbiter.OrbiterEvent.READY}</li>
<li class="fixedFont">{@link net.user1.orbiter.OrbiterEvent.CLOSE}</li>
<li class="fixedFont">{@link net.user1.orbiter.OrbiterEvent.PROTOCOL_INCOMPATIBLE}</li>
</ul>

To register for events, use {@link net.user1.events.EventDispatcher#addEventListener}.

    @extends net.user1.events.EventDispatcher
*/
net.user1.orbiter.Orbiter = function () {
  // Invoke superclass constructor
  net.user1.events.EventDispatcher.call(this);
  
  // Initialization
  this.system = new net.user1.orbiter.System();
  this.log = new net.user1.logger.Logger();
  this.log.info("User Agent: " + navigator.userAgent + " " + navigator.platform);
  this.log.info("Union Client Version: " + this.system.getClientType() + " " + this.system.getClientVersion().toStringVerbose());
  this.log.info("Client UPC Protocol Version: " + this.system.getUPCVersion().toString());
  
  if (!this.system.isJavaScriptCompatible()) {
    // Missing required JavaScript capabilities, so abort. 
    this.log.fatal("[ORBITERMICRO] JavaScript version incompatibility detected." 
                   + " One of the following was not found: [window.postMessage], "
                   + " [XMLHttpRequest]. Quitting."
                   );
    return;
  }
    
  this.messageManager = new net.user1.orbiter.MessageManager(this.log);
  this.connection = new net.user1.orbiter.HTTPConnection();
  this.connection.setOrbiter(this);
  this.connection.addEventListener(net.user1.orbiter.ConnectionEvent.DISCONNECT, this._disconnectListener, this);
  this.connection.addEventListener(net.user1.orbiter.ConnectionEvent.CONNECT_FAILURE, this._connectFailureListener, this);
  this.connection.addEventListener(net.user1.orbiter.ConnectionEvent.READY, this._readyListener, this);
  this.messageManager.setConnection(this.connection);
  this.connectionMonitor = new net.user1.orbiter.ConnectionMonitor(this);
  this.connectionMonitor.restoreDefaults();
  this.coreMsgListener = new net.user1.orbiter.CoreMessageListener(this);
  this.clientID = "";
  this.sessionID = "";
  this.messageManager.addMessageListener("u29", this.u29, this);
  this.messageManager.addMessageListener("u66", this.u66, this);
  
  this.log.info("[ORBITERMICRO] Initialization complete.");
};

//==============================================================================
// INHERITANCE
//==============================================================================
net.user1.utils.extend(net.user1.orbiter.Orbiter, net.user1.events.EventDispatcher);

//==============================================================================    
// STATIC VARIABLES
//==============================================================================    
net.user1.orbiter.Orbiter.DEFAULT_READY_TIMEOUT = 10000;

//==============================================================================    
// CLIENT ID
//==============================================================================    
net.user1.orbiter.Orbiter.prototype.getClientID = function () {
  return this.clientID;
};
    
//==============================================================================    
// SESSION ID
//==============================================================================     
/** @private */
net.user1.orbiter.Orbiter.prototype.setSessionID = function (sessionID) {
  this.sessionID = sessionID;
};

net.user1.orbiter.Orbiter.prototype.getSessionID = function () {
  return this.sessionID;
};

//==============================================================================    
// UPC LISTENERS
//==============================================================================    
/** @private */
net.user1.orbiter.Orbiter.prototype.u29 = function (clientID) {
  this.clientID = clientID;
};

/** @private */
net.user1.orbiter.Orbiter.prototype.u66 = function (clientID,
                                                    sessionID,
                                                    serverUPCVersionString,
                                                    protocolCompatible) {
  var serverUPCVersion = new net.user1.orbiter.VersionNumber();
  serverUPCVersion.fromVersionString(serverUPCVersionString);
  if (protocolCompatible == "false") {
    this.dispatchProtocolIncompatible(serverUPCVersion);
  }
};

//==============================================================================    
// CONNECTION
//==============================================================================    
net.user1.orbiter.Orbiter.prototype.connect = function (host, port) {
  if (this.connection == null) {
    return;
  }
  this.connection.setServer(host, port);
  this.connection.connect();
};

net.user1.orbiter.Orbiter.prototype.disconnect = function () {
  this.connection.disconnect();
};

net.user1.orbiter.Orbiter.prototype.isReady = function () {
  return this.connection.isReady();
};

//==============================================================================
// CONNECTION EVENT LISTENERS
//==============================================================================
/** @private */
net.user1.orbiter.Orbiter.prototype._disconnectListener = function (e) {
  this.clientID = "";
  this.dispatchClose();
};

/** @private */
net.user1.orbiter.Orbiter.prototype._connectFailureListener = function (e) {
  this.clientID = "";
  this.dispatchClose();
};

/** @private */
net.user1.orbiter.Orbiter.prototype._readyListener = function (e) {
  this.dispatchReady();
};

//==============================================================================    
// INSTANCE RETRIEVAL
//==============================================================================    
net.user1.orbiter.Orbiter.prototype.getLog = function () {
  return this.log;
};

net.user1.orbiter.Orbiter.prototype.getMessageManager = function () {
  return this.messageManager;
};

net.user1.orbiter.Orbiter.prototype.getConnection = function () {
  return this.connection;
};

net.user1.orbiter.Orbiter.prototype.getConnectionMonitor = function () {
  return this.connectionMonitor;
};

net.user1.orbiter.Orbiter.prototype.getSystem = function () {
  return this.system;
};

//==============================================================================    
// EVENT DISPATCH
//==============================================================================
/** @private */
net.user1.orbiter.Orbiter.prototype.dispatchReady = function () {
  this.dispatchEvent(new net.user1.orbiter.OrbiterEvent(net.user1.orbiter.OrbiterEvent.READY));
}

/** @private */
net.user1.orbiter.Orbiter.prototype.dispatchClose = function () {
  this.dispatchEvent(new net.user1.orbiter.OrbiterEvent(net.user1.orbiter.OrbiterEvent.CLOSE));
}

/** @private */
net.user1.orbiter.Orbiter.prototype.dispatchProtocolIncompatible = function () {
  this.dispatchEvent(new net.user1.orbiter.OrbiterEvent(net.user1.orbiter.OrbiterEvent.PROTOCOL_INCOMPATIBLE));
}

//==============================================================================    
// TOSTRING
//==============================================================================
net.user1.orbiter.Orbiter.prototype.toString = function () {
  return "[object Orbiter]";
}
    
//==============================================================================
// CONNECTION STATE CONSTANTS
//==============================================================================
/** @class */
net.user1.orbiter.ConnectionState = new Object();
/** @constant */
net.user1.orbiter.ConnectionState.UNKNOWN                    = -1;
/** @constant */
net.user1.orbiter.ConnectionState.NOT_CONNECTED              = 0;
/** @constant */
net.user1.orbiter.ConnectionState.READY                      = 1;
/** @constant */
net.user1.orbiter.ConnectionState.CONNECTION_IN_PROGRESS     = 2;
/** @constant */
net.user1.orbiter.ConnectionState.DISCONNECTION_IN_PROGRESS  = 3;
/** @constant */
net.user1.orbiter.ConnectionState.LOGGED_IN                  = 4;
//==============================================================================
// CLASS DECLARATION
//==============================================================================
/** @class
    @extends net.user1.events.Event
*/
net.user1.orbiter.ConnectionEvent = function (type, upc, data, connection, status) {
  net.user1.events.Event.call(this, type);
  
  this.upc = upc;
  this.data = data;
  this.connection = connection
  this.status = status;
};

//==============================================================================
// INHERITANCE
//==============================================================================
net.user1.utils.extend(net.user1.orbiter.ConnectionEvent, net.user1.events.Event);

//==============================================================================
// STATIC VARIABLES
//==============================================================================

/** @constant */
net.user1.orbiter.ConnectionEvent.BEGIN_CONNECT = "BEGIN_CONNECT";
/** @constant */
net.user1.orbiter.ConnectionEvent.BEGIN_HANDSHAKE = "BEGIN_HANDSHAKE";
/** @constant */
net.user1.orbiter.ConnectionEvent.READY = "READY";
/** @constant */
net.user1.orbiter.ConnectionEvent.CONNECT_FAILURE = "CONNECT_FAILURE";
/** @constant */
net.user1.orbiter.ConnectionEvent.CLIENT_KILL_CONNECT = "CLIENT_KILL_CONNECT";
/** @constant */
net.user1.orbiter.ConnectionEvent.SERVER_KILL_CONNECT = "SERVER_KILL_CONNECT";
/** @constant */
net.user1.orbiter.ConnectionEvent.DISCONNECT = "DISCONNECT";
/** @constant */
net.user1.orbiter.ConnectionEvent.RECEIVE_UPC = "RECEIVE_UPC";
/** @constant */
net.user1.orbiter.ConnectionEvent.SEND_DATA = "SEND_DATA";
/** @constant */
net.user1.orbiter.ConnectionEvent.RECEIVE_DATA = "RECEIVE_DATA";
/** @constant */
net.user1.orbiter.ConnectionEvent.SESSION_TERMINATED = "SESSION_TERMINATED";
/** @constant */
net.user1.orbiter.ConnectionEvent.SESSION_NOT_FOUND = "SESSION_NOT_FOUND";
  
//==============================================================================
// INSTANCE METHODS
//==============================================================================

net.user1.orbiter.ConnectionEvent.prototype.getUPC = function () {
  return this.upc;
}

net.user1.orbiter.ConnectionEvent.prototype.getData = function () {
  return this.data;
}

net.user1.orbiter.ConnectionEvent.prototype.getStatus = function () {
  return this.status;
}

net.user1.orbiter.ConnectionEvent.prototype.toString = function () {
  return "[object ConnectionEvent]";
}  

//==============================================================================
// HTTP REQUEST MODE CONSTANTS
//==============================================================================
/** @class */
net.user1.orbiter.ConnectionType = new Object();
/** @constant */
net.user1.orbiter.ConnectionType.HTTP =  "HTTP";
//==============================================================================
// CLASS DECLARATION
//==============================================================================
/** @class

The Connection class dispatches the following events:

<ul class="summary">
<li class="fixedFont">{@link net.user1.orbiter.ConnectionEvent.BEGIN_CONNECT}</li>
<li class="fixedFont">{@link net.user1.orbiter.ConnectionEvent.BEGIN_HANDSHAKE}</li>
<li class="fixedFont">{@link net.user1.orbiter.ConnectionEvent.READY}</li>
<li class="fixedFont">{@link net.user1.orbiter.ConnectionEvent.CONNECT_FAILURE}</li>
<li class="fixedFont">{@link net.user1.orbiter.ConnectionEvent.CLIENT_KILL_CONNECT}</li>
<li class="fixedFont">{@link net.user1.orbiter.ConnectionEvent.SERVER_KILL_CONNECT}</li>
<li class="fixedFont">{@link net.user1.orbiter.ConnectionEvent.DISCONNECT}</li>
<li class="fixedFont">{@link net.user1.orbiter.ConnectionEvent.RECEIVE_UPC}</li>
<li class="fixedFont">{@link net.user1.orbiter.ConnectionEvent.SEND_DATA}</li>
<li class="fixedFont">{@link net.user1.orbiter.ConnectionEvent.RECEIVE_DATA}</li>
<li class="fixedFont">{@link net.user1.orbiter.ConnectionEvent.SESSION_TERMINATED}</li>
<li class="fixedFont">{@link net.user1.orbiter.ConnectionEvent.SESSION_NOT_FOUND}</li>
</ul>

To register for events, use {@link net.user1.events.EventDispatcher#addEventListener}.

    @extends net.user1.events.EventDispatcher
*/
net.user1.orbiter.Connection = function (host, port, type) {
  // Call superconstructor
  net.user1.events.EventDispatcher.call(this);

  // Variables
  this.mostRecentConnectAchievedReady = false;
  this.readyCount = 0;
  this.connectAttemptCount = 0;
  this.connectAbortCount = 0;
  this.readyTimeoutID = 0;
  this.readyTimeout = net.user1.orbiter.Orbiter.DEFAULT_READY_TIMEOUT;
  this.orbiter = null;
  this.disposed = false;
  
  // Initialization
  this.setServer(host, port);
  this.connectionType = type;
  this.connectionState = net.user1.orbiter.ConnectionState.NOT_CONNECTED;
};

//==============================================================================    
// INHERITANCE
//============================================================================== 
net.user1.utils.extend(net.user1.orbiter.Connection, net.user1.events.EventDispatcher);

//==============================================================================    
// DEPENDENCIES
//============================================================================== 
/** @private */
net.user1.orbiter.Connection.prototype.setOrbiter = function (orbiter) {
  if (this.orbiter != null) {
    this.orbiter.getMessageManager().removeMessageListener("u63", this.u63);
    this.orbiter.getMessageManager().removeMessageListener("u66", this.u66);
    this.orbiter.getMessageManager().removeMessageListener("u84", this.u84);
    this.orbiter.getMessageManager().removeMessageListener("u85", this.u85);
  }
  this.orbiter = orbiter;
}
  
//==============================================================================    
// CONNECT/DISCONNECT
//============================================================================== 
net.user1.orbiter.Connection.prototype.connect = function () {
  this.disconnect();
  this.connectAttemptCount++;
  this.mostRecentConnectAchievedReady = false;
  this.connectionState = net.user1.orbiter.ConnectionState.CONNECTION_IN_PROGRESS;
  // Start the ready timer. Ready state must be achieved before the timer
  // completes or the connection will auto-disconnect.
  this.startReadyTimer();
  this.dispatchBeginConnect();
}
  
net.user1.orbiter.Connection.prototype.disconnect = function () {
  var state = this.connectionState;
 
  if (state != net.user1.orbiter.ConnectionState.NOT_CONNECTED) {
    this.deactivateConnection();
 
    if (state == net.user1.orbiter.ConnectionState.CONNECTION_IN_PROGRESS) {
      this.connectAbortCount++;
      this.dispatchConnectFailure("Client closed connection before READY state was achieved.");
    } else {
      this.dispatchClientKillConnect();
    }
  }
}
    
/** @private */
net.user1.orbiter.Connection.prototype.deactivateConnection = function () {
  this.connectionState = net.user1.orbiter.ConnectionState.NOT_CONNECTED;
  this.orbiter.setSessionID("");
}
  
//==============================================================================    
// CONNECTION CONFIGURATION
//==============================================================================    
net.user1.orbiter.Connection.prototype.setServer = function (host,
                                                             port) {
  this.host  = host;
  // Check for valid ports
  if (port < 1 || port > 65536) {
    throw new Error("Illegal port specified [" + port + "]. Must be greater than 0 and less than 65537.");
  }
  this.port  = port;
}

net.user1.orbiter.Connection.prototype.getHost = function () {
  return this.host;
}

net.user1.orbiter.Connection.prototype.getPort = function () {
  return this.port;
}

net.user1.orbiter.Connection.prototype.getType = function () {
  return this.connectionType;
}
    
//==============================================================================
// READY HANDSHAKE
//==============================================================================
/** @private */
net.user1.orbiter.Connection.prototype.beginReadyHandshake = function () {
  this.dispatchBeginHandshake();
  
  if (!this.orbiter.getMessageManager().hasMessageListener("u63", this.u63)) {
    this.orbiter.getMessageManager().addMessageListener("u63", this.u63, this);
    this.orbiter.getMessageManager().addMessageListener("u66", this.u66, this);
    this.orbiter.getMessageManager().addMessageListener("u84", this.u84, this);
    this.orbiter.getMessageManager().addMessageListener("u85", this.u85, this);
  }
  
  this.sendHello();
}

/** @private */
net.user1.orbiter.Connection.prototype.sendHello = function() {
  var helloString = this.buildHelloMessage();
  this.orbiter.getLog().debug(this.toString() + " Sending CLIENT_HELLO: " + helloString);
  this.transmitHelloMessage(helloString);
}

/** @private */
net.user1.orbiter.Connection.prototype.buildHelloMessage = function () {
  var helloString = "<U><M>u65</M>"
    + "<L>"
    + "<A>" + this.orbiter.getSystem().getClientType() + "</A>"
    + "<A>" + navigator.userAgent + ";" + this.orbiter.getSystem().getClientVersion().toStringVerbose() + "</A>"
    + "<A>" + this.orbiter.getSystem().getUPCVersion().toString() + "</A></L></U>";
  return helloString;
}

/** @private */
net.user1.orbiter.Connection.prototype.transmitHelloMessage = function (helloString) {
  this.send(helloString);
}
    
//==============================================================================
// READY TIMER
//==============================================================================
/** @private */
net.user1.orbiter.Connection.prototype.readyTimerListener = function () {
  this.stopReadyTimer();
  if (this.connectionState == net.user1.orbiter.ConnectionState.CONNECTION_IN_PROGRESS) {
    this.orbiter.getLog().warn("[CONNECTION] " + this.toString() + ": Failed to achieve" + 
            " ready state after " + this.readyTimeout + "ms. Aborting connection...");
    this.disconnect();
  }
}

/** @private */
net.user1.orbiter.Connection.prototype.stopReadyTimer = function () {
  if (this.readyTimeoutID != -1) {
    clearTimeout(this.readyTimeoutID);
  }
}

/** @private */
net.user1.orbiter.Connection.prototype.startReadyTimer = function () {
  var currentObj = this;
  var callback   = this.readyTimerListener;
  this.stopReadyTimer();
  this.readyTimeoutID = setTimeout (function () {
    callback.call(currentObj);
  }, this.readyTimeout);
}

//==============================================================================
// READY STATE ACCESS
//==============================================================================
/** @private */
net.user1.orbiter.Connection.prototype.getReadyCount = function () {
  return this.readyCount;
}
    
net.user1.orbiter.Connection.prototype.isReady = function () {
  return this.connectionState == net.user1.orbiter.ConnectionState.READY;
}

/** @private */
net.user1.orbiter.Connection.prototype.isValid = function () {
  var valid;
  if (this.connectAttemptCount == 0) {
    valid = true;
  }
  if ((this.connectAttemptCount > 0) && (this.connectAttemptCount == this.connectAbortCount)) {
    valid = true;
  }
  if (this.mostRecentConnectAchievedReady) {
    valid = true;
  }
  return valid;
}

    
//==============================================================================
// UPC LISTENERS
//==============================================================================
/** @private */
net.user1.orbiter.Connection.prototype.u63 = function () {
  this.stopReadyTimer();
  this.connectionState = net.user1.orbiter.ConnectionState.READY;
  this.mostRecentConnectAchievedReady = true;
  this.readyCount++;
  this.connectAttemptCount = 0;
  this.connectAbortCount   = 0;
  this.dispatchReady();
}    

/** @private */
net.user1.orbiter.Connection.prototype.u66 = function (serverVersion, 
                                                       sessionID, 
                                                       upcVersion, 
                                                       protocolCompatible) {
  this.orbiter.setSessionID(sessionID);
};

/** @private */
net.user1.orbiter.Connection.prototype.u84 = function () {
  this.dispatchSessionTerminated();
}    

/** @private */
net.user1.orbiter.Connection.prototype.u85 = function () {
  this.dispatchSessionNotFound();
}    

//==============================================================================    
// TOSTRING
//==============================================================================     
net.user1.orbiter.Connection.prototype.toString = function () {
  return "[" + this.connectionType + ", host: " + this.host + ", port: " + this.port + "]";
}
    
//==============================================================================    
// EVENT DISPATCHING
//==============================================================================  
/** @private */
net.user1.orbiter.Connection.prototype.dispatchSendData = function (data) {
  this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(net.user1.orbiter.ConnectionEvent.SEND_DATA,
                                    null, data, this));
}

/** @private */
net.user1.orbiter.Connection.prototype.dispatchReceiveData = function (data) {
  this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(net.user1.orbiter.ConnectionEvent.RECEIVE_DATA,
                                    null, data, this));
}

/** @private */
net.user1.orbiter.Connection.prototype.dispatchConnectFailure = function (status) {
  this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(net.user1.orbiter.ConnectionEvent.CONNECT_FAILURE,
                                    null, null, this, status));
}

/** @private */
net.user1.orbiter.Connection.prototype.dispatchBeginConnect = function () {
  this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(net.user1.orbiter.ConnectionEvent.BEGIN_CONNECT,
                                    null, null, this));
}

/** @private */
net.user1.orbiter.Connection.prototype.dispatchBeginHandshake = function () {
  this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(net.user1.orbiter.ConnectionEvent.BEGIN_HANDSHAKE,
                                    null, null, this));
}

/** @private */
net.user1.orbiter.Connection.prototype.dispatchReady = function () {
  this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(net.user1.orbiter.ConnectionEvent.READY,
                                    null, null, this));
}

/** @private */
net.user1.orbiter.Connection.prototype.dispatchServerKillConnect  = function () {
  this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(net.user1.orbiter.ConnectionEvent.SERVER_KILL_CONNECT,
                                    null, null, this));
  this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(net.user1.orbiter.ConnectionEvent.DISCONNECT,
                                    null, null, this));
}

/** @private */
net.user1.orbiter.Connection.prototype.dispatchClientKillConnect = function () {
    this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(net.user1.orbiter.ConnectionEvent.CLIENT_KILL_CONNECT,
                                      null, null, this));
    this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(net.user1.orbiter.ConnectionEvent.DISCONNECT,
                                      null, null, this));
}

/** @private */
net.user1.orbiter.Connection.prototype.dispatchSessionTerminated = function () {
  this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(net.user1.orbiter.ConnectionEvent.SESSION_TERMINATED,
                                    null, null, this));
}

/** @private */
net.user1.orbiter.Connection.prototype.dispatchSessionNotFound = function () {
  this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(net.user1.orbiter.ConnectionEvent.SESSION_NOT_FOUND,
                                    null, null, this));
}

//==============================================================================    
// DISPOSAL
//==============================================================================  
net.user1.orbiter.Connection.prototype.dispose = function () {
  this.disposed = true;
  this.messageManager.removeMessageListener("u63", this.u63);
  this.messageManager.removeMessageListener("u66", this.u66);
  this.messageManager.removeMessageListener("u84", this.u84);
  this.messageManager.removeMessageListener("u85", this.u85);
  this.stopReadyTimer();
  this.readyTimer = null;
  this.orbiter = null;
}
//==============================================================================    
// CLASS DECLARATION
//==============================================================================
/** @class

For a list of events dispatched by HTTPConnection, see HTTPConnection's
superclass, {@link net.user1.orbiter.Connection}.

    @extends net.user1.orbiter.Connection
*/
net.user1.orbiter.HTTPConnection = function (host, port) {
  // Invoke superclass constructor
  net.user1.orbiter.Connection.call(this, host, port, net.user1.orbiter.ConnectionType.HTTP);

  // Instance variables
  this.sendDelayTimerEnabled = true;
  this.sendDelayTimeoutID = -1;
  this.sendDelayTimerRunning = false;
  this.sendDelay = net.user1.orbiter.HTTPConnection.DEFAULT_SEND_DELAY;
  
  this.messageQueue = new Array();
  
  this.retryDelay = 500;
  this.retryHelloTimeoutID = -1;
  this.retryIncomingTimeoutID = -1;
  this.retryOutgoingTimeoutID = -1;

  this.helloResponsePending = false;
  this.outgoingResponsePending = false;
  
  // Initialization
  this.addEventListener(net.user1.orbiter.ConnectionEvent.SESSION_TERMINATED, this.sessionTerminatedListener, this);
  this.addEventListener(net.user1.orbiter.ConnectionEvent.SESSION_NOT_FOUND, this.sessionNotFoundListener, this);
  
  // IFRAME POSTMESSAGE HANDLING
  var self = this;
  if (typeof window.addEventListener != "undefined") {
    // ...the standard way 
    window.addEventListener("message", postMessageListener, false);
  } else if (typeof window.attachEvent != 'undefined') { 
    // ...the IE-specific way 
    window.attachEvent('onmessage', postMessageListener); 
  }

  /** @private */
  function postMessageListener (e) {
    if (e.origin != ("http://" + self.host + (self.port == 80 ? "" : (":" + self.port)))) {
      self.orbiter.getLog().error("[CONNECTION] " + self.toString() 
        + " Ignored message from unknown origin: " + e.origin);
      return;
    }
    
    self.processPostMessage(e.data);
  }
}

//==============================================================================
// INHERITANCE
//==============================================================================
net.user1.utils.extend(net.user1.orbiter.HTTPConnection, net.user1.orbiter.Connection);

//==============================================================================    
// STATIC VARIABLES
//==============================================================================    
/** @constant */
net.user1.orbiter.HTTPConnection.DEFAULT_SEND_DELAY = 300;
    
//==============================================================================    
// CONNECTION AND DISCONNECTION
//==============================================================================    
net.user1.orbiter.HTTPConnection.prototype.connect = function () {
  net.user1.orbiter.Connection.prototype.connect.call(this);
  this.makeIFrame();
}
    
/** @private */
net.user1.orbiter.HTTPConnection.prototype.deactivateConnection = function () {
  this.orbiter.getLog().debug("[CONNECTION] " + this.toString() + " Deactivating...");
  this.connectionState = net.user1.orbiter.ConnectionState.DISCONNECTION_IN_PROGRESS;
  this.stopSendDelayTimer();
  if (this.retryHelloTimeoutID != -1) {
    this.clearTimeout(this.retryHelloTimeoutID);
  }
  if (this.retryIncomingTimeoutID != -1) {
    clearTimeout(this.retryIncomingTimeoutID);
  }
  if (this.retryOutgoingTimeoutID != -1) {
    clearTimeout(this.retryOutgoingTimeoutID);
  }
  this.deactivateHTTPRequests();
  net.user1.orbiter.Connection.prototype.deactivateConnection.call(this);
}
    
/** @private */
net.user1.orbiter.HTTPConnection.prototype.deactivateHTTPRequests = function () {
  this.orbiter.getLog().debug("[CONNECTION] " + this.toString() + " Closing all pending HTTP requests.");
  this.postToIFrame("deactivate");
  this.helloResponsePending = false;
  this.outgoingResponsePending = false;
}

//==============================================================================    
// IFRAME MANAGEMENT
//==============================================================================    
/** @private */
net.user1.orbiter.HTTPConnection.prototype.makeIFrame = function () {
  this.iFrameReady = false;
  if (this.iframe != null) {
    this.postToIFrame("dispose");
    document.body.removeChild(this.iframe);
  }
  this.iframe = document.createElement('iframe');
  this.iframe.width = "0px";
  this.iframe.height = "0px";
  this.iframe.border = "0px";
  this.iframe.frameBorder = "0";
  this.iframe.style.visibility = "hidden";
  this.iframe.style.display = "none";
  this.iframe.src = "http://" + this.host + ":" + this.port + "/orbiter";
  document.body.appendChild(this.iframe);
}

/** @private */
net.user1.orbiter.HTTPConnection.prototype.onIFrameReady = function () {
  this.beginReadyHandshake();
}

/** @private */
net.user1.orbiter.HTTPConnection.prototype.postToIFrame = function (cmd, data) {
  if (this.iframe && this.iFrameReady) {
    data = data == undefined ? "" : data;
    this.iframe.contentWindow.postMessage(cmd + "," + data, "http://" + this.host + ":" + this.port);
  }  
}

/** @private */
net.user1.orbiter.HTTPConnection.prototype.processPostMessage = function (postedData) {
  var delimiterIndex = postedData.indexOf(",");
  var cmd  = postedData.substring(0, delimiterIndex);
  var data = postedData.substring(delimiterIndex+1);
  
  switch (cmd) {
    case"ready":
      this.iFrameReady = true;
      this.onIFrameReady();
      break;
      
    case "hellocomplete":
      this.helloCompleteListener(data);
      break;
    
    case "helloerror":
      this.helloErrorListener();
      break;
    
    case "outgoingcomplete":
      this.outgoingCompleteListener();
      break;
    
    case "outgoingerror":
      this.outgoingErrorListener();
      break;
    
    case "incomingcomplete":
      this.incomingCompleteListener(data);
      break;
    
    case "incomingerror":
      this.incomingErrorListener();
      break;
  }
}
    
//==============================================================================    
// UPC LISTENERS (IFRAME-SPECIFIC IMPLEMENTATION)
//==============================================================================

/** @private */
net.user1.orbiter.HTTPConnection.prototype.u66 = function (serverVersion, 
                                                           sessionID, 
                                                           upcVersion, 
                                                           protocolCompatible) {
  net.user1.orbiter.Connection.prototype.u66.call(this,
                                                  serverVersion,
                                                  sessionID,
                                                  upcVersion,
                                                  protocolCompatible);
  if (this.iframe != null) {
    this.postToIFrame("sessionid", sessionID);
  }
}

//==============================================================================    
// SESSION MANAGEMENT
//==============================================================================     

/** @private */
net.user1.orbiter.HTTPConnection.prototype.sessionTerminatedListener = function (e) {
  var state = this.connectionState;
  this.deactivateConnection();
  
  if (state == net.user1.orbiter.ConnectionState.CONNECTION_IN_PROGRESS) {
    this.dispatchConnectFailure("Server terminated session before READY state was achieved.");
  } else {
    this.dispatchServerKillConnect();
  }
}

/** @private */
net.user1.orbiter.HTTPConnection.prototype.sessionNotFoundListener = function (e) {
  var state = this.connectionState;
  
  this.deactivateConnection();
  
  if (state == net.user1.orbiter.ConnectionState.CONNECTION_IN_PROGRESS) {
    this.dispatchConnectFailure("Client attempted to reestablish an expired session" +
  " or establish an unknown session.");
  } else {
    this.dispatchServerKillConnect();
  }
}

//==============================================================================    
// OUTGOING DELAY TIMER
//==============================================================================    
/** @private */
net.user1.orbiter.HTTPConnection.prototype.sendDelayTimerListener = function () {
  this.sendDelayTimerRunning = false;
  if (this.messageQueue.length > 0) {
    this.flushMessageQueue();
  } else {
    // No messages in queue, so take no action.
  }
}
    
/** @private */
net.user1.orbiter.HTTPConnection.prototype.stopSendDelayTimer = function () {
  if (this.sendDelayTimeoutID != -1) {
    clearTimeout(this.sendDelayTimeoutID);
  }
  this.sendDelayTimeoutID = -1;
}
    
/** @private */
net.user1.orbiter.HTTPConnection.prototype.startSendDelayTimer = function () {
  this.stopSendDelayTimer();
  var currentObj = this;
  var callback   = this.sendDelayTimerListener;
  this.sendDelayTimerRunning = true;
  this.sendDelayTimeoutID = setTimeout(function () {
    callback.call(currentObj);
  }, this.sendDelay);
}
    
net.user1.orbiter.HTTPConnection.prototype.setSendDelay = function (milliseconds) {
  if (milliseconds > 0) {
    if ((milliseconds != this.sendDelay)) {
      this.sendDelay = milliseconds;
      this.orbiter.getLog().debug("[CONNECTION] " + this.toString() + " Send delay set to: ["
                             + milliseconds + "]."); 
    }
    this.sendDelayTimerEnabled = true;
  } else if (milliseconds == -1) {
    this.orbiter.getLog().debug("[CONNECTION] " + toString() + " Send delay disabled.");
    this.sendDelayTimerEnabled = false;
    this.stopSendDelayTimer();
  } else {
    throw new Error("[CONNECTION]" + this.toString() + " Invalid send-delay specified: [" 
                    + milliseconds + "]."); 
  }
}
    
net.user1.orbiter.HTTPConnection.prototype.getSendDelay = function () {
  return this.sendDelay;
}

//==============================================================================    
// DATA SENDING AND QUEUING
//==============================================================================  
    
net.user1.orbiter.HTTPConnection.prototype.send = function (data) {
  // If the timer isn't running...
  if (!this.sendDelayTimerRunning) {
    // ...it is either disabled or expired. Either way, it's time to 
    // attempt to flush the queue.
    this.messageQueue.push(data);
    this.flushMessageQueue();
  } else {
    // The send-delay timer is running, so we can't send yet. Just queue the message.
    this.messageQueue.push(data);
  }
}
    
/** @private */
net.user1.orbiter.HTTPConnection.prototype.flushMessageQueue = function () {
  if (!this.outgoingResponsePending) {
    this.openNewOutgoingRequest(this.messageQueue.join(""));
    this.messageQueue = new Array();
  } else {
    // AN OUTGOING RESPONSE IS STILL PENDING, SO DON'T SEND A NEW ONE
  }
}

//==============================================================================    
// HELLO REQUEST MANAGEMENT
//==============================================================================  

/** @private */
net.user1.orbiter.HTTPConnection.prototype.transmitHelloMessage = function (helloString) {
  this.dispatchSendData(helloString);
  this.helloResponsePending = true;
  this.postToIFrame("sendhello", helloString);
}    

/** @private */
net.user1.orbiter.HTTPConnection.prototype.helloCompleteListener = function (data) {
  if (this.disposed) return;
  
  if (this.helloResponsePending) {
    this.helloResponsePending = false;
    this.processIncomingData(data);
    
    // Don't immediately open a request in the complete handler due to Win IE bug
    var self = this;
    setTimeout(function () {
      self.openNewIncomingRequest();
    }, 0);
  } else {
    if (this.connectionState == net.user1.orbiter.ConnectionState.NOT_CONNECTED) {
      this.orbiter.getLog().error("[CONNECTION]" + toString() + " u66 (SERVER_HELLO) received, but client is not connected. Ignoring.");
    } else {
      this.orbiter.getLog().error("[CONNECTION]" + toString() + " Redundant u66 (SERVER_HELLO) received. Ignoring.");
    }
  }
}

/** @private */
net.user1.orbiter.HTTPConnection.prototype.helloErrorListener = function () {
  if (this.disposed) return;
  
  this.orbiter.getLog().error("[CONNECTION]" + this.toString() + " u65 (CLIENT_HELLO) request failed."); 
  
  // Retry
  var self = this;
  this.retryHelloTimeoutID = setTimeout(function () {
    self.postToIFrame("retryhello");
  }, this.retryDelay);
}

//==============================================================================    
// OUTGOING REQUEST MANAGEMENT
//==============================================================================

/** @private */
net.user1.orbiter.HTTPConnection.prototype.openNewOutgoingRequest = function (data) {
  this.dispatchSendData(data);
  this.outgoingResponsePending = true;
  this.postToIFrame("sendoutgoing", data);
  if (this.sendDelayTimerEnabled == true) {
    this.startSendDelayTimer();
  }
}

/** @private */
net.user1.orbiter.HTTPConnection.prototype.outgoingCompleteListener = function () {
  if (this.disposed) return;
  
  this.outgoingResponsePending = false;
  
  if (!this.sendDelayTimerRunning && this.messageQueue.length > 0) {
    // Don't immediately open a request in the complete handler due to Win IE bug
    var self = this;
    setTimeout(function () {
      self.flushMessageQueue();
    }, 0);
  }
}

/** @private */
net.user1.orbiter.HTTPConnection.prototype.outgoingErrorListener = function () {
  if (this.disposed) return;
  
  this.orbiter.getLog().error("[CONNECTION]" + this.toString() + " Outgoing request failed."); 
      
  // Retry
  var self = this;
  this.retryOutgoingTimeoutID = setTimeout(function () {
    if (!(self.disposed ||
          self.connectionState == net.user1.orbiter.ConnectionState.NOT_CONNECTED ||
          self.connectionState == net.user1.orbiter.ConnectionState.DISCONNECTION_IN_PROGRESS)) {
            self.postToIFrame("retryoutgoing");
    }
  }, this.retryDelay);
}

//==============================================================================    
// INCOMING REQUEST MANAGEMENT
//==============================================================================  

/** @private */
net.user1.orbiter.HTTPConnection.prototype.openNewIncomingRequest = function () {
  this.postToIFrame("sendincoming");
}

/** @private */
net.user1.orbiter.HTTPConnection.prototype.incomingCompleteListener = function (data) {
  if (this.disposed
      || this.connectionState == net.user1.orbiter.ConnectionState.NOT_CONNECTED
      || this.connectionState == net.user1.orbiter.ConnectionState.DISCONNECTION_IN_PROGRESS) {
    // Incoming request complete, but connection is closed. Ignore content.
    return;
  }
  
  // Don't immediately open a request in the complete handler due to Win IE bug
  var self = this;
  setTimeout(function () {
    self.processIncomingData(data);
    // A message listener might have closed this connection in response to an incoming
    // message. Do not open a new incoming request unless the connection is still open.
    if (!(self.disposed ||
          self.connectionState == net.user1.orbiter.ConnectionState.NOT_CONNECTED ||
          self.connectionState == net.user1.orbiter.ConnectionState.DISCONNECTION_IN_PROGRESS)) {
            self.openNewIncomingRequest();
    }
  }, 0);
}

/** @private */
net.user1.orbiter.HTTPConnection.prototype.incomingErrorListener = function () {
  if (this.disposed) return;
  
  this.orbiter.getLog().error("[CONNECTION]" + this.toString() + " Incoming request failed."); 
      
  // Retry
  var self = this;
  this.retryIncomingTimeoutID = setTimeout(function () {
    if (!(self.disposed ||
          self.connectionState == net.user1.orbiter.ConnectionState.NOT_CONNECTED ||
          self.connectionState == net.user1.orbiter.ConnectionState.DISCONNECTION_IN_PROGRESS)) {
            self.postToIFrame("retryincoming");
    }
  }, this.retryDelay);
}
    
//==============================================================================    
// PROCESS DATA FROM THE SERVER
//==============================================================================
 
/** @private */
net.user1.orbiter.HTTPConnection.prototype.processIncomingData = function (data) {
  if (this.disposed) return;
  var listeners;
  
  this.dispatchReceiveData(data);
  
  var upcs = new Array();
  var upcEndTagIndex = data.indexOf("</U>");
  // Empty responses are valid.
  if (upcEndTagIndex == -1 && data.length > 0) {
    this.orbiter.getLog().error("Invalid message received. No UPC found: [" + data + "]");
    if (!this.isReady()) {
      // If invalid XML is received prior to achieving ready, then this
      // probably isn't a Union server, so disconnect.
      this.disconnect();
      return;
    }
  }
  
  while (upcEndTagIndex != -1) {
    upcs.push(data.substring(0, upcEndTagIndex+4));
    data = data.substring(upcEndTagIndex+4);
    upcEndTagIndex = data.indexOf("</U>");
  }
  for (var i = 0; i < upcs.length; i++) {
    this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(net.user1.orbiter.ConnectionEvent.RECEIVE_UPC, upcs[i]));
  }
}

//==============================================================================    
// TOSTRING
//==============================================================================     
net.user1.orbiter.HTTPConnection.prototype.toString = function () {
  return "[" + this.connectionType + ", host: " + this.host + ", port: " + this.port + ", send-delay: " + this.getSendDelay() + "]";
}
    
// =============================================================================
// DISPOSAL
// =============================================================================
net.user1.orbiter.HTTPConnection.prototype.dispose = function () {
  this.postToIFrame("dispose");
  this.stopSendDelayTimer();
  net.user1.orbiter.Connection.prototype.dispose(this);
}
//==============================================================================
// CLASS DECLARATION
//==============================================================================
/** @private */
net.user1.orbiter.MessageListener = function (listener,
                                              forRoomIDs,
                                              thisArg) {
  this.listener   = listener;
  this.forRoomIDs = forRoomIDs;
  this.thisArg    = thisArg;
};

//==============================================================================
// INSTANCE METHODS
//==============================================================================
/** @private */
net.user1.orbiter.MessageListener.prototype.getListenerFunction = function () {
  return this.listener;
};
    
/** @private */
net.user1.orbiter.MessageListener.prototype.getForRoomIDs = function () {
  return this.forRoomIDs;
};
    
/** @private */
net.user1.orbiter.MessageListener.prototype.getThisArg = function () {
  return this.thisArg;
};

/** @private */
net.user1.orbiter.MessageListener.prototype.toString = function () {
  return "[object MessageListener]";
};
//==============================================================================
// CLASS DECLARATION
//==============================================================================
/** @class */
net.user1.orbiter.MessageManager = function (log) {
  this.log = log;
  this.messageListeners = new Object();
  this.removeListenersOnDisconnect = true;
  this.numMessagesReceived = 0;
  this.numMessagesSent = 0;
  this.currentConnection = null;
};

//==============================================================================
// INSTANCE METHODS
//==============================================================================
/** @private */
net.user1.orbiter.MessageManager.prototype.setConnection = function (connection) {
  if (this.currentConnection != null) {
    this.currentConnection.removeEventListener(net.user1.orbiter.ConnectionEvent.RECEIVE_UPC, 
                                          this.upcReceivedListener);
    this.currentConnection.removeEventListener(net.user1.orbiter.ConnectionEvent.DISCONNECT,
                                          this.disconnectListener);
    this.currentConnection.removeEventListener(net.user1.orbiter.ConnectionEvent.CONNECT_FAILURE,
                                          this.connectFailureListener);
  }

  this.currentConnection = connection; 

  this.currentConnection.addEventListener(net.user1.orbiter.ConnectionEvent.RECEIVE_UPC, 
                                        this.upcReceivedListener, this);
  this.currentConnection.addEventListener(net.user1.orbiter.ConnectionEvent.DISCONNECT,
                                        this.disconnectListener, this);
  this.currentConnection.addEventListener(net.user1.orbiter.ConnectionEvent.CONNECT_FAILURE,
                                        this.connectFailureListener, this);
}
  
/** @private */
net.user1.orbiter.MessageManager.prototype.disconnectListener = function (e) {
  this.cleanupAfterClosedConnection(e.target);
}
    
/** @private */
net.user1.orbiter.MessageManager.prototype.connectFailureListener = function (e) {
  this.cleanupAfterClosedConnection(e.target);
}
    
/** @private */
net.user1.orbiter.MessageManager.prototype.cleanupAfterClosedConnection = function (connection) {
  var listenerList;
  if (this.removeListenersOnDisconnect) {
    this.log.info("[MESSAGE_MANAGER] Removing registered message listeners.");
    for (var message in this.messageListeners) {
      listenerList = this.messageListeners[message];
      for (var p in listenerList) {
        this.removeMessageListener(message, listenerList[p].getListenerFunction());
      } 
    }
  } else {
    this.log.warn("[MESSAGE_MANAGER] Leaving message listeners registered. \n"
      + "Be sure to remove any unwanted message listeners manually.");
  }
  
  this.numMessagesReceived = 0;
  this.numMessagesSent = 0;
}
  
net.user1.orbiter.MessageManager.prototype.sendUPC = function (message) {
  // Quit if the connection isn't ready...
  if (!this.currentConnection.isReady()) {
    this.log.warn("[MESSAGE_MANAGER] Connection not ready. UPC not sent. Message: " 
    + message);
    return;
  }

  // Build the UPC to send.
  var theUPC = "<U><M>" + message + "</M>";
  var a;
  
  if (arguments.length > 1) {
    theUPC += "<L>";
    for (var i = 1; i < arguments.length; i++) {
      a = arguments[i];
      a = a == undefined ? "" : a.toString();
      // Wrap any non-filter argument that contains a start tag ("<") in CDATA
      if (a.indexOf("<") != -1) {
        if (a.indexOf('<f t=') != 0) {
          a = "<![CDATA[" + a + "]]>";
        }
      }
      theUPC += "<A>" + a + "</A>";
    }
    theUPC += "</L>";
  }
  theUPC += "</U>";
  
  // Send the UPC to the server
  this.log.debug("[MESSAGE_MANAGER] UPC sent: " + theUPC);
  this.currentConnection.send(theUPC);
};

/** @private */
net.user1.orbiter.MessageManager.prototype.upcReceivedListener = function (e) {
  var upc = e.getUPC();
  this.log.debug("[MESSAGE_MANAGER] UPC received: " + upc );
  
  var method;
  var upcArgs = new Array();
  
  var closeMTagIndex = upc.indexOf("</M>");
  method = upc.substring(6, closeMTagIndex);
  
  var searchBeginIndex = upc.indexOf("<A>", closeMTagIndex);
  var closeATagIndex;
  var arg;
  while (searchBeginIndex != -1) {
    closeATagIndex = upc.indexOf("</A>", searchBeginIndex);
    arg = upc.substring(searchBeginIndex+3, closeATagIndex);
    if (arg.indexOf("<![CDATA[") == 0) {
      arg = arg.substr(9, arg.length-12);
    }
    upcArgs.push(arg);
    searchBeginIndex = upc.indexOf("<A>", closeATagIndex);
  }     
  
  this.notifyMessageListeners(method, upcArgs);
};

net.user1.orbiter.MessageManager.prototype.addMessageListener = function (message, 
                                                                          listener,
                                                                          thisArg,
                                                                          forRoomIDs) {
  if (forRoomIDs != null) {
    var typeString = Object.prototype.toString.call(forRoomIDs);
    if (typeString != "[object Array]") {
      throw new Error("[MESSAGE_MANAGER] Illegal argument type " + typeString
                      + " supplied for addMessageListener()'s forRoomIDs"
                      + " parameter. Value must be an Array.");
    }
  }
  
  // Each message gets a list of MessageListener objects. 
  // If this message has no such list, make one.
  if (this.messageListeners[message] === undefined) {
    this.messageListeners[message] = new Array();
  } 
  var listenerArray = this.messageListeners[message];
  
  // Quit if the listener is already registered
  if (this.hasMessageListener(message, listener)) {
    return false;
  }
  
  // Add the listener
  var newListener = new net.user1.orbiter.MessageListener(listener,
                                            forRoomIDs === undefined ? null : forRoomIDs,
                                            thisArg);
  listenerArray.push(newListener);
  return true;      
};

net.user1.orbiter.MessageManager.prototype.removeMessageListener = function (message,
                                                           listener) {
  // Quit if the message has no listeners
  var listenerArray = this.messageListeners[message];
  if (listenerArray == null) {
    return false;
  } 
  
  // Remove the listener
  var foundListener;
  for (var i = 0; i < listenerArray.length; i++) {
    if (listenerArray[i].getListenerFunction() == listener) {
      foundListener = true;
      listenerArray.splice(i, 1);
      break;
    }
  }
  
  // Delete the listeners array if it's now empty
  if (listenerArray.length == 0) {
    delete this.messageListeners[message];
  }
  
  return foundListener;      
};
    
net.user1.orbiter.MessageManager.prototype.hasMessageListener = function (message, 
                                                        listener) {
  // Quit if the message has no listeners
  var listenerArray = this.messageListeners[message];
  if (listenerArray == null) {
    return false;
  } 
      
   // Check for the listener
  for (var i = 0; i < listenerArray.length; i++) {
    if (listenerArray[i].getListenerFunction() 
      == listener) {
      return true;
    }
  }
  return false;
};
    
net.user1.orbiter.MessageManager.prototype.getMessageListeners = function (message) {
  return this.messageListeners[message] != undefined ? this.messageListeners[message] : [];
};

/** @private */
net.user1.orbiter.MessageManager.prototype.notifyMessageListeners = function (message, args) {
  // Retrieve the list of listeners for this message.
  var listeners = this.messageListeners[message];
  // If there are no listeners registered, then quit
  if (listeners === undefined) {
    // Log a warning if it's not a UPC
    if (!(message.charAt(0) == "u" && parseInt(message.substring(1)) > 1)) {
      this.log.warn("Message delivery failed. No listeners found. Message: " + 
               message + ". Arguments: " + args.join());
    }
    return;
  }
  var numListeners = listeners.length; 
  for (var i = 0; i < numListeners; i++) {
    listeners[i].getListenerFunction().apply(listeners[i].getThisArg(), args);
  }
};

net.user1.orbiter.MessageManager.prototype.toString = function () {
  return "[object MessageManager]";
};

//==============================================================================
// LOADED FLAG
//==============================================================================
/** 
 * @constant 
 * 
 * Indicates that Orbiter has finished loading.
 */
net.user1.orbiter.LOADED = true;


