if (typeof(JS_LIB_LOADED)=='boolean') 
{
  const JS_SOCKET_FILE     = "socket.js";
  const JS_SOCKET_LOADED   = true;

  function 
  Socket ()
  {
    if (arguments.length)
      this.init(arguments[0]);
    else
      this.init();
  }
  
  /**
   *  function: available
   *  purpose:  number of bytes waiting to be read
   *
   *  interface: available()
   * 
   *   returns: integer : number of bytes waiting to be read
   */
  Socket.prototype.available = function ()
  {
    if (!this.isAlive())
        return 0;
  
    var bytesAvailable = 0;
    try {
        bytesAvailable = this._inputInterface.available();
    } catch(exception) {
        this.isConnected = false;
        this._exception = exception;
    }
 
    return bytesAvailable;
  }
  
  /**
   *  function:  close
   *  purpose:  closes the socket connection
   *  interface: close()
   * 
   *  returns: nothing.
   */
  Socket.prototype.close = function ()
  {
    if (!this.isOpen()) return;
  
    this.isOpenFlag = false;
    this.isConnected = false;

    // calls to _inputStream.close() and _outputStream.close() didn't 
    // function.
    this._transport.close(0);
  }
  
  /**
   *  function:  init
   *  purpose:   provides initialization for the Socket class.
   *  interface: socket([classID])
   *  arguments: classID : classesByID class-identifier (see Socket for info)
   *  returns:   nothing.
  */
  Socket.prototype.init = function ()
  {
    this.isOpenFlag = false;
    this.isConnected = false;
 
    this.openInputFlags = 0;
    this.openInputSegmentSize = 0;
    this.openInputSegmentCount = 0;
    
    this.openOutputFlags = 0;
    this.openOutputSegmentSize = 0;
    this.openOutputSegmentCount = 0;
    
    var defaultContractID = "@mozilla.org/network/socket-transport-service;1";
    // var defaultClassID = "{c07e81e0-ef12-11d2-92b6-00105a1b0d64}"
      
    var socketServiceClass;
    switch (arguments.length)
    {
      case 0:
        socketServiceClass = C.classes[defaultContractID];
        break;
    
      case 1:
        socketServiceClass = C.classesByID[arguments[0]];
        break;
    
      default:
        throw( "Socket.init: unexpected arguments" );
        break;
    }

    if (!socketServiceClass)
      throw ("Socket constructor: Couldn't get socket service class.");
    
    var socketService = socketServiceClass.getService();

    if (!socketService)
        throw ("Socket constructor: Couldn't get socket service.");

    this._socketService = jslibQI(socketService, "nsISocketTransportService");
  }
  
  /**
   *  function: isAlive
   *  purpose:  tests the connection to see if it still works.
   *  interface: isAlive()
   *  arguments: none.
   *  returns: boolean : true if the connection still works.
   * 
   *  Note:
   *    This function is not accurate if invoked in the same javascript
   *    stack crawl as open().
   */
  Socket.prototype.isAlive = function ()
  {
      this.isConnected = ( this.isOpen() ? this._transport.isAlive() : false );

      return this.isConnected;
  }
  
  /**
   *  function: isOpen
   *  purpose:  returns true if the socket has been opened (which is
   *            different from connect).  Multiple invocations of open will
   *            fail if each old connection is not closed first.
   *  interface: isOpen()
   *  arguments: none.
   *  returns: boolean : true if open() has been invoked without close.
   */
  Socket.prototype.isOpen = function () { return this.isOpenFlag; }
  
  /**
   *  function: open
   *  purpose:  opens the socket
   *  interface:  open( host, port [ binary ] )
   * 
   *  arguments: host : String, host to connect to
   *             port : int, port number to use
   *             binary : optional, use binary input support, defaults false
   *   
   *  returns: nothing.
   */
  Socket.prototype.open = function (host, port)
  {
    if (this.isOpen()) return;
 
    this.host = host.toLowerCase();
    this.port = port;
    this.binary = (arguments.length > 2) ? arguments[ 2 ] : false;
    this.isOpenFlag = true;

    // in theory, we'd look up proxy information here.  but we're being
    // a bare socket so.... 

    // create the transport:
    // socketTypes = null
    // typeCount = 0
    // host
    // port
    // proxy-info = null
    this._transport = this._socketService.createTransport(null, 0, 
                                                          host, port, null);
    if (!this._transport)
      throw ("Socket.open: Error opening transport.");

    var openFlags = (this.blocking ) && ( typeof document == "object")
        ? 0
        : C.interfaces.nsITransport.OPEN_BLOCKING;

    this._inputStream = this._transport.openInputStream( 
                          this.openInputFlags, 
                          this.openInputSegmentSize, 
                          this.openInputSegmentCount);

    if (!this._inputStream)
      throw ("Socket.open: Error getting input stream.");

    if (this.binary)
      this._inputInterface = this.toBinaryInputStream( this._inputStream );
    else
      this._inputInterface = this.toScriptableInputStream( this._inputStream );
  
    this._outputStream = this._transport.openOutputStream( 
            this.openOutputFlags, 
            this.openOutputSegmentSize, 
            this.openOutputSegmentCount );
    if (!this._outputStream)
      throw ("Socket.open: Error getting output stream.");
  
    // We really should call _transport.isAlive (?) but that is never reliable
    // (either always false or always true).
    // Experimentation shows that calls to available() or isAlive() will not 
    // catch any problems with the connection until the javascript call 
    // stack has completely unwound.
    this.isConnected = true;
  }
  
  /**
   *  function: read
   *  purpose: reads data from a socket.
   *
   *  interface: read(bytes)
   *
   *  arguments: bytes : integer, number of bytes to read in
   * 
   *  returns: string
   *
   *  Note:
   *    will only return the smaller of specified vs available bytes.
   * 
   */
  Socket.prototype.read = function (bytes)
  {
    if (!this.isAlive())
      throw "Socket.read: Not Connected.";
  
    var rv = new String;

    if (bytes == 0)
      return rv;
  
    var availableBytes = this.available();

    if (availableBytes == 0)
      return rv;
  
    bytes = Math.min(availableBytes, bytes);

    if (bytes) {
      if (this.binary)
        // despite the documentation, this call works
        rv = this._inputInterface.readBytes( bytes );
      else
        rv = this._inputInterface.read( bytes );
    }
      
      return rv;
  }
  
  /**
   *  function: write
   *  purpose: writes the given string to the socket.
   * 
   *  interface: write(str)
   * 
   *  arguments: str : string to be written.
   * 
   *  returns: integer : number of bytes written.
   */
  Socket.prototype.write = function (str)
  {
    if (!this.isAlive())
      throw "Socket.write: Not Connected.";
  
    var rv = 0;
    try {
      rv = this._outputStream.write(str, str.length);
    } catch (e) { this.isConnected = false; }
      
    return rv;
  }
  
  /*
   *  function:  toBinaryInputStream
   *  purpose: creates an nsIBinaryInputStream wrapper around the given
   *  inputStream.
   * 
   *  interface: toBinaryInputStream(inputStream)
   * 
   *  arguments: inputStream : result of openInputStream
   * 
   *  returns: nsIBinaryInputStream
   */
  Socket.prototype.toBinaryInputStream = function (inputStream)
  {
    var rv = jslibCreateInstance("@mozilla.org/binaryinputstream;1", 
                                 "nsIBinaryInputStream");
    rv.setInputStream(inputStream);
  
    return rv;
  }
  
  /**
   *  function: toScriptableInputStream
   *  purpose: creates an nsIScriptableInputStream wrapper around the given
   *           inputStream.
   * 
   *  interface: toScriptableInputStream( inputStream )
   * 
   *  arguments: inputStream : result of openInputStream
   * 
   *  returns: nsIScriptableInputStream
   */
  Socket.prototype.toScriptableInputStream = function (inputStream)
  {
    var rv = jslibCreateInstance("@mozilla.org/scriptableinputstream;1", 
                                 "nsIScriptableInputStream");
    rv.init( inputStream );
  
    return rv;
  }
  
  /**
   *  function: async
   *  purpose:
   *  interface:
   * 
   *  returns:
   *  usage:
   * 
   *  var aSocket = new Socket;
   *  var observer = {
   *    streamStarted: function (socketContext){ },           //onstart action
   *    streamStopped: function (socketContext, status){ },   //onstop action
   *    receiveData:   function (data){alert(data)}
   *  }
   *  aSocket.open("ftp.mozilla.org", 21);
   *  aSocket.async(observer );
   * 
   */
  Socket.prototype.async = function (observer)
  {
    // to preserve ourselves within necko/async
    this.wrappedJSObject = this;
  
    this._pump = jslibCreateInstance("@mozilla.org/network/input-stream-pump;1",
                                     "nsIInputStreamPump");
  
    this._pump.init(this._inputStream, -1, -1, 0, 0, false);
    this._pump.asyncRead(new SocketListener(observer), this);
  }
  
  // async callbacks
  function SocketListener(observer) { this._observer = observer; }
  
  SocketListener.prototype.onStartRequest = 
    function (channel, socketContext)
    {
      theSocket = socketContext.wrappedJSObject;
      this._observer.streamStarted( theSocket );
    }
  
  SocketListener.prototype.onStopRequest = 
    function (channel, socketContext, status, errorMsg)
    {
      theSocket = socketContext.wrappedJSObject;
      this._observer.streamStopped( theSocket, status );
    }
  
  SocketListener.prototype.onDataAvailable = 
    function (channel, socketContext, inputStream, sourceOffset, count)
    {
      theSocket = socketContext.wrappedJSObject;
      // try and maintain the connection
      // but read here because Socket.read will fail with HTTP requests due
      // to the socket being closed.
      theSocket._inputStream = inputStream;
      if (theSocket.binary) {
        theSocket._inputInterface =
        theSocket.toBinaryInputStream( theSocket._inputStream );
        // despite the documentation, readBytes works
        this._observer.receiveData( theSocket._inputInterface.readBytes(count) );
      } else {
        theSocket._inputInterface =
        theSocket.toScriptableInputStream( theSocket._inputStream );
        this._observer.receiveData( theSocket._inputInterface.read(count) );
      }
   }

  jslibLoadMsg(JS_SOCKET_FILE);

} else { dump("Load Failure: socket.js\n"); }

