/*******************************************************************************
 * OpenAjax-mashup.js
 *
 * Reference implementation of the OpenAjax Hub, as specified by OpenAjax Alliance.
 * Specification is under development at: 
 *
 *   http://www.openajax.org/member/wiki/OpenAjax_Hub_Specification
 *
 * Copyright 2008 OpenAjax Alliance
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 
 * use this file except in compliance with the License. You may obtain a copy 
 * of the License at http://www.apache.org/licenses/LICENSE-2.0 . Unless 
 * required by applicable law or agreed to in writing, software distributed 
 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 
 * CONDITIONS OF ANY KIND, either express or implied. See the License for the 
 * specific language governing permissions and limitations under the License.
 *
 ******************************************************************************/

OpenAjax.hub.implVersion = "1.1";
OpenAjax.hub.specVersion = "1.1";

(function() {
  
  var providers = {};  // registered communications channel providers
  var bindings = {};   // client to provider mapping
  

  // createManagedHub({ pubCallback:function, subCallback:function })
  OpenAjax.hub.createManagedHub = function( params )
  {
    return new ManagerConnectionHandle( new ManagedHub( params ) );
  }

  // OpenAjax.hub.connect({[host:string], clientName:string, callback:function, providerName:string, [connectionConfig:object]})
  OpenAjax.hub.connect = function( params )
  {
    var p = providers[ params.providerName ];
    if ( typeof( p ) != 'undefined' ) {
      p.connect( params );
      return;
    }
    // failure
    params.callback( false, null );
  }
  
  OpenAjax.hub.configureProvider = function( name, config )
  {
    var p = providers[ name ];
    if ( p != 'undefined' ) {
      return p.configure( config );
    }
    return false;
  }

  //////////////////////////////////////////////////////////////////////////////

  OpenAjax.hubspi = {};
  
  OpenAjax.hubspi.register = function( providerObject )
  {
    var name = providerObject.getName();
    if ( typeof( providers[ name ] ) != 'undefined' ) {
      // this provider has already been bound
      // XXX do we want to support multiple versions of a provider?
      return false;
    }
    providers[ name ] = providerObject;
    return true;
  }

  OpenAjax.hubspi.registerClient = function( providerObject, clientName )
  {
    var b = bindings[ clientName ];
    if ( typeof( b ) != 'undefined' ) {
      // if the provider object has already been set and it is different than
      // the given provider object, return error condition
      if ( b.p && b.p != providerObject ) {
        return false;
      }
      b.p = providerObject;
      return true;
    }
    return false;
  }

  OpenAjax.hubspi.subscribeReq = function( subClientName, topic, subId )
  {
    var h = bindings[ subClientName ].h;
    if ( typeof( h ) != 'undefined' ) {
      return h.subscribe( subClientName, topic, subId );
    }
    return false;
  }

  OpenAjax.hubspi.unsubscribeReq = function( subClientName, subId )
  {
    var h = bindings[ subClientName ].h;
    if ( typeof( h ) != 'undefined' ) {
      return h.unsubscribe( subClientName, subId );
    }
    return false;
  }

  OpenAjax.hubspi.publishEvent = function( pubClientName, topic, data )
  {
    var h = bindings[ pubClientName ].h;
    if ( typeof( h ) != 'undefined' ) {
      h.publish( pubClientName, topic, data );
    }
  }
  
  //////////////////////////////////////////////////////////////////////////////

  var bind = function( hub, clientName )
  {
    if ( typeof( bindings[ clientName ] ) != 'undefined' ) {
      // this client has already been bound
      return false;
    }
    bindings[ clientName ] = { h: hub, p: null };
  }
  
  var deleteBindings = function( hub )
  {
    for ( var i in bindings ) {
      if ( bindings[i].h == hub ) {  // XXX use .equals()?
        delete bindings[i];
      }
    }
  }

  //////////////////////////////////////////////////////////////////////////////

  /*
   * managed hub instance
   */
  function ManagedHub( params )
  {
    var pubMgr = null;
    var subMgr = null;
    if ( params ) {
      pubMgr = params.pubCallback;
      subMgr = params.subCallback;
    }
    
    this._subscriptions = { c:{}, s:[] };
    this._cleanup = [];
    this._pubDepth = 0;

    var topics = [];  // TODO - need to cleanup

    this.subscribe = function( subClientName, topic, subId )
    {
      if ( subMgr && !subMgr( topic, subClientName ) ) {
        return false;
      }
      var sid = subClientName + "." + subId;
      var handle = topic + "." + sid;
      var sub = { cb: this._subCallback, sid: sid, hdl: handle, cid: subClientName };
      var path = topic.split(".");
      this._subscribe( this._subscriptions, path, 0, sub );
      topics[ sid ] = topic;
      return true;
    }
    
    this._subCallback = function( topic, message, d, sid )
    {
      // 'd' is unused in Hub 1.1
      
      var a = sid.split(".");
      var clientName = a.shift();
      if ( clientName == "manager" ) {
        clientName += "." + a.shift();
      }
      var subId = a.shift();
      
      var providerObject = bindings[ clientName ].p;
      providerObject.sendEvent( clientName, topic, message, [ subId ] );
    }
    
    this.unsubscribe = function( subClientName, subId )
    {
      // var path = sub.split(".");
      // var subId = path.pop();
      // var subClientName = path.pop();
      var sid = subClientName + "." + subId;
      var path = topics[ sid ].split( "." );
      this._unsubscribe( this._subscriptions, path, 0, sid );
      // XXX Using 'topics' array to find topic name associated with clientName and Id,
      //     but this is not ideal, since this is a mostly unnecessary object.
      //     (1) We can have subscribeReq return 'handle', which is a combination
      //     of the topic, clientName and subId.  Then the subscription handle
      //     object could just store this.
      //     (2) Or, the subscription handle object already has all 3 necessary
      //     params.  Just have unsubscribeReq also take the topic as a param.
    }
    
    this.publish = function( pubClientName, topic, message )
    {
      var path = topic.split(".");
      this._pubDepth++;
      this._publish( this._subscriptions, path, 0, topic, message, pubMgr, pubClientName );
      this._pubDepth--;
      if ( this._cleanup.length > 0 && this._pubDepth == 0 ) {
        for ( var i = 0; i < this._cleanup.length; i++ ) {
          var p = topics[ this._cleanup[i].sid ].split( "." );
          this._unsubscribe( this._subscriptions, p, 0, this._cleanup[i].sid );
        }
        delete( this._cleanup );
        this._cleanup = [];
      }
    }
  }

  ManagedHub.prototype._subscribe = OpenAjax.hub._subscribe;
  ManagedHub.prototype._unsubscribe = OpenAjax.hub._unsubscribe;
  ManagedHub.prototype._publish = OpenAjax.hub._publish;
  
  //////////////////////////////////////////////////////////////////////////////
  
  var managerId = 0;
  
  /*
   * connection handle for 'manager'
   */
  function ManagerConnectionHandle( hub )
  {
    this._hub = hub;
    this._subIndex = 0;
    this._subs = [];
    this._id = "manager." + managerId++;
    
    this.bind = function( clientName )
    {
      return bind( this._hub, clientName );
    }
    
    this.isConnected = function()
    {
      return true;
    }
    
    this.equals = function( object )
    {
      // XXX should we be doing more here?
      return this === object;
    }
    
    this.getClientName = function()
    {
      return "manager";
    }

    this._getInternalName = function()
    {
      return this._id;
    }
    
    this.getManagerDomain = function() 
    { // return own domain since this is the manager
      return document.domain;
    }

    this.subscribe = function( topic, callback, eventCallback )
    {
      var subId = this._subIndex++;
      this._subs[ subId ] = { cb: eventCallback };
      var h = this._hub.subscribe( this._getInternalName(), topic, subId );
      this._subs[ subId ].h = new SubscriptionHandle( this, topic, subId );
      callback( true, this._subs[ subId ].h );
    }
    
    this.publish = function( topic, data )
    {
      if ( !this.isConnected() ) {
        return false;
      }
      this._hub.publish( this._getInternalName(), topic, data );
      return true;
    }
    
    this.disconnect = function( callback )
    {
      deleteBindings( this._hub );
      delete this;  // XXX
      callback( true, this );
    }
    
    this._unsubscribe = function( subClientName, subId )
    {
      delete this._subs[ subId ];
      this._hub.unsubscribe( subClientName, subId );
    }
    
    this.sendEvent = function( clientName, topic, message, matchingSubs )
    {
      for ( var i = 0; i < matchingSubs.length; i++ ) {
        var s = this._subs[ matchingSubs[i] ];
        s.cb( s.h, topic, message );
      }
    }
    
    if ( !this.bind( this._getInternalName() ) && !OpenAjax.hubspi.registerClient( this, this._getInternalName() ) ) {
      // XXX handle error
    }
  }

  //////////////////////////////////////////////////////////////////////////////
  
  /*
   * handle which represents the subscription of a given client and topic
   */
  function SubscriptionHandle( connHandle, topic, handle )
  {
    var _connHandle = connHandle;
    var _topic = topic;
    var _subId = handle;
    var _subscribed = true;
    
    this.unsubscribe = function( callback )
    {
//      OpenAjax.hubspi.unsubscribeReq( _connHandle.getClientName(), _subId );
      _subscribed = false;
      _connHandle._unsubscribe( _connHandle._getInternalName(), _subId );
      if ( callback ) {
          callback( true, this );
      }
    }
    
    this.isSubscribed = function()
    {
      return _subscribed;
    }
    
    this.getConnHandle = function()
    {
      return _connHandle;
    }
    
    this.getTopic = function()
    {
      return _topic;
    }
    
    this.equals = function( object )
    {
      // XXX should we be doing more here?
      return this === object;
    }
  }

})();

