/*

        Copyright 2006-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.widget.WidgetModel = function( id, properties, views )
{
    if ( ! id ) {
        return;
    }
    
    // when connecting to the hub, use "__<ID>" in order to avoid conflict with
    // the widget instance, which has subscribed with "<ID>"
    this.hubId = '__' + id;

    this.init( id, properties, views );
    this._specURL = null;
    this._site = null;
}

OpenAjax.widget.WidgetModel.prototype = new OpenAjax.widget.Widget();

OpenAjax.widget.WidgetModel.prototype.getSite = function() 
{
    return this._site;
}

OpenAjax.widget.WidgetModel.prototype.setProperties = function(propertiesCollection) 
{
    if ( ! propertiesCollection ) {
        return;
    }
    
    if ( typeof propertiesCollection.length === 'number' ) {  // is array
        this.properties = new OpenAjax.widget.WidgetModelProperties( propertiesCollection, this );
    } else if ( typeof propertiesCollection.setProperties === 'function' ) {  // is of type OpenAjax.widget.WidgetModelProperties
        this.properties = propertiesCollection;
    }
}

OpenAjax.widget.WidgetModel.prototype.getTopicInfo = function() 
{
    var properties = this.getProperties();
    if ( ! properties ) {
        return null;
    }
    
    var topicInfo = {};
    topicInfo['publishes'] = new Array();
    topicInfo['subscribes'] = new Array();
    for (var i = 0; i < properties.length; i++) {
        var property = properties[i];
        // XXX it makes sense for a property to listen w/o a topic since
        // one can be wired in later.  But makes little sense for a
        // property to publish but have no topic.  Need to resolve how
        // to best handle both of those situations w.r.t. topicInfo
        // lists.
        if (property.listen()) {
            topicInfo['subscribes'].push(property.topic());
        }
        if (property.publish()) {
            topicInfo['publishes'].push(property.topic());
        }
    }
    return topicInfo;
}

OpenAjax.widget.WidgetModel.prototype.getSpecUrl = function()
{
    return this._specURL;
}

OpenAjax.widget.WidgetModel.prototype.setSpecUrl = function(specurl)
{
    this._specURL = specurl;
}

OpenAjax.widget.WidgetModel.prototype.listen = function( prop, topic, subCallback )
{
    if ( typeof topic == 'undefined' ) {
        topic = prop.topic();
    }
    if ( typeof subCallback == 'undefined' ) {
        subCallback = prop.listenCallback;
    }
    
    var callback = function( success, subHandle ) {
        if ( !success ) {
            // XXX handle error
            alert( "subscribe failed" );
            return;
        }
        prop._sub = subHandle;
    };
    this.connHandle.subscribe( topic, callback, OpenAjax.widget.Widget.bind(prop, subCallback) );
}

OpenAjax.widget.WidgetModel.prototype.publish = function( prop, topic, value )
{
    if ( typeof topic == 'undefined' ) {
        topic = prop.defaultTopic();
    }
    if ( typeof value == 'undefined' ) {
        value = JSON.stringify( prop.value().toString() );
    }

    console.log( "PUBLISHED: Prop <"+this.gID+'.'+prop.name()+"> topic <"+topic + '.' + this.gID+"> for data<"+value+">." );
    
    this.connHandle.publish( topic + '.' + this.gID, value );
}

OpenAjax.widget.WidgetModel.prototype.listenForEvents = function()
{
    // listen for property value changes from the Widget
    this.registerCallback( '_propValueChange', function( data ) {
        for ( var i = 0; i < data.length; i++ ) {
            this.getProperty( data[i].name ).value( data[i].value, false );
        }
    });

    // this.registerCallback( '_availDimensions', function() {
    // });
    // 
    // this.registerCallback( '_getDimensions', function() {
    // });
    // 
    this.registerCallback( '_adjustDimensions', function( data ) {
        this.adjustDimensions( data );
    });
}

OpenAjax.widget.WidgetModel.prototype.removeFromHub = function()
{
    if ( this.properties ) {
        this.properties.removeFromHub();
    }
    this.connHandle.disconnect();
}

OpenAjax.widget.WidgetModel.prototype.adjustDimensions = function( dimensions )
{
    this._site.resize( dimensions.width, dimensions.height );
    this.fireEvent('resize', dimensions);
}

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

OpenAjax.widget.WidgetModelProperties = function( datums, widget )
{
    this.init( datums, widget );
}

OpenAjax.widget.WidgetModelProperties.prototype = new OpenAjax.widget.WidgetProperties();

OpenAjax.widget.WidgetModelProperties.prototype.setProperties = function( datums )
{
    for ( var i = 0 ; i < datums.length; i++ ) {
        var preference = new OpenAjax.widget.WidgetModelProperty( datums[i], this.widget );
        this.properties[ preference.name() ] = preference;
    }
}

// XXX change to 'getWidget'.
// XXX why is this method necessary?
OpenAjax.widget.WidgetModelProperties.prototype.getGadget = function()
{
    return this.widget;
}

OpenAjax.widget.WidgetModelProperties.prototype.removeFromHub = function()
{
    for ( var propName in this.properties ) {
        this.properties[ propName ].removeFromHub();
    }
}

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

OpenAjax.widget.WidgetModelProperty = function( datum, widget )
{
    this.widget = widget;
    this.datum = datum;

    this._setValue( this.decodedValue(), false );
    
    this._defaultTopic = datum.topic;
    this._singleBoundWidget = datum['singlebound'] ? datum['singlebound'] : null;

    //check to see if we need to subscribe events
    if( this.listen() && this.topic() != "" ){
        var stem = this._singleBoundWidget != null ? this._singleBoundWidget : "*";
        var listenTopic = this.topic() + '.' + stem;
        this.widget.listen( this, listenTopic );
    }
}

OpenAjax.widget.WidgetModelProperty.prototype = new OpenAjax.widget.WidgetProperty();

// 'options' isn't exactly an attribute on a property, but it is a key into
// the datum array
OpenAjax.widget.WidgetModelProperty.ATTRIBUTES =  {
    HIDDEN: "hidden",
    NAME: "name",
    PUBLISH: "publish",
    READONLY: "readonly",
    TARGET:"target",
    TOPIC: "topic",
    VALUE: "default",
    DATATYPE: "datatype",
    SCOPE: "scope",
    REQUIRED: "required",
    EVANESCENT: "transient",
    GETTERPATTERN: "getterpattern",
    SETTERPATTERN: "setterpattern",
    SUBSCRIBE: "subscribe",
    FORMAT: "format",
    ISINTEGER: "isInteger",
    MINIMUM: "minimum",
    MAXIMUM: "maximum",
    MIN_LENGTH: "min_length",
    MAX_LENGTH: "max_length",
    DESCRIPTION: "description",
    TITLE: "title",
    PATTERN: "pattern",
    OPTIONS: "options"
}

OpenAjax.widget.WidgetModelProperty.prototype.name =  function()
{
    return this.datum[OpenAjax.widget.WidgetModelProperty.ATTRIBUTES.NAME];
}       

OpenAjax.widget.WidgetModelProperty.prototype.value =  function( newVal, doPropagate )
{
    if ( typeof newVal === "undefined" ) {
        return this._getValue();
    }
    
    if ( this._setValue( newVal, doPropagate ) ) {
        // fire hub events if necessary
        if( this.publish() ){
            this.widget.publish( this );
        }
    }
    
    return this._getValue();
}

OpenAjax.widget.WidgetModelProperty.prototype.publish = function( newVal )
{       
    if( typeof newVal === "undefined" ){
        return this.datum[OpenAjax.widget.WidgetModelProperty.ATTRIBUTES.PUBLISH] === "true";
    }
    else{
        newVal = !!newVal; //convert to boolean
                        
        if( this.publish() === newVal ){
            return newVal; // not changed
        }
                        
        this.datum[OpenAjax.widget.WidgetModelProperty.ATTRIBUTES.PUBLISH] =  newVal;
        return newVal;
    }
}

OpenAjax.widget.WidgetModelProperty.prototype.listen = function(newVal)
{
        
    if( typeof newVal === "undefined" ){
        return this.datum[OpenAjax.widget.WidgetModelProperty.ATTRIBUTES.SUBSCRIBE] === "true";
    }
    else{
        newVal = !!newVal; //convert to boolean
                        
        if( this.listen() === newVal ){
            return newVal;// not changed
        }
                        
        //the value changed so unsub needs to happen no matter what
        if( this._sub ){
            //unsub if there is a subscription handle
            this._sub.unsubscribe();
            this._sub = null;
            this._singleBoundWidget = null;
        }
                        
        //check if subscription is needed
        if( newVal && this.topic() != "" ){
            this.widget.listen( this, this.topic() + ".*" );
        }
                
        this.datum[OpenAjax.widget.WidgetModelProperty.ATTRIBUTES.SUBSCRIBE] = newVal;
        return newVal;
    }
}

OpenAjax.widget.WidgetModelProperty.prototype.topic = function(newVal, widget) 
{
    if( typeof newVal === "undefined" ){
        var topic = this.datum[OpenAjax.widget.WidgetModelProperty.ATTRIBUTES.TOPIC];
        return !topic ? "" : topic;    
    } else {

        if(this.topic() === newVal ){
            if( typeof widget === "undefined" || widget == this._singleBoundWidget ) {
                return newVal;// not changed
            }
        }
                        
        //the value changed so unsub needs to happen no matter what
        if( this._sub ){
            //unsub if there is a subscription handle
            this._sub.unsubscribe();
            this._sub = null;
            this._singleBoundWidget = null;
        }
                        
        if( typeof widget === "undefined" ) {
            widget = null;
        }
                        
        //check if subscription is needed
        if( this.listen() && newVal != "" ){
            var newTopic = null;
            if( widget ){
                newTopic = newVal + "." + widget.getId();
                this._singleBoundWidget = widget.getId();
            } else {
                newTopic = newVal + ".*";
            }
            this.widget.listen( this, newTopic );
        }

        this.datum[OpenAjax.widget.WidgetModelProperty.ATTRIBUTES.TOPIC] = newVal ;
        return newVal;
    }
                        
}

OpenAjax.widget.WidgetModelProperty.prototype.targets = function(targets)
{
    if (typeof targets == 'undefined') {
        if (! this._targets) {
            if (this.datum[OpenAjax.widget.WidgetModelProperty.ATTRIBUTES.TARGET]) {
                this._targets = this.datum[OpenAjax.widget.WidgetModelProperty.ATTRIBUTES.TARGET].split(',');
            }
        }
        return(this._targets);
    }
    this._targets = targets ? targets.split(',') : null;
}

OpenAjax.widget.WidgetModelProperty.prototype.hidden =  function()
{
    return this.datum[OpenAjax.widget.WidgetModelProperty.ATTRIBUTES.HIDDEN] === "true";
}       

OpenAjax.widget.WidgetModelProperty.prototype.scope =  function()
{
    var scope = this.datum[OpenAjax.widget.WidgetModelProperty.ATTRIBUTES.SCOPE];
    return !scope ? "instance" : scope;
}       

OpenAjax.widget.WidgetModelProperty.prototype.readonly =  function()
{
    return this.datum[OpenAjax.widget.WidgetModelProperty.ATTRIBUTES.READONLY] === "true";
}       

OpenAjax.widget.WidgetModelProperty.prototype.required =  function()
{
    return this.datum[OpenAjax.widget.WidgetModelProperty.ATTRIBUTES.REQUIRED] === "true";
}       

OpenAjax.widget.WidgetModelProperty.prototype.evanescent =  function()
{
    return this.datum[OpenAjax.widget.WidgetModelProperty.ATTRIBUTES.EVANESCENT] === "true";
}       

OpenAjax.widget.WidgetModelProperty.prototype.getterpattern =  function()
{
    return this.datum[OpenAjax.widget.WidgetModelProperty.ATTRIBUTES.GETTERPATTERN];
}       

OpenAjax.widget.WidgetModelProperty.prototype.setterpattern =  function()
{
    return this.datum[OpenAjax.widget.WidgetModelProperty.ATTRIBUTES.SETTERPATTERN];
}       

OpenAjax.widget.WidgetModelProperty.prototype.subscribe =  function(newVal)
{
    return this.listen(newVal);
}       

OpenAjax.widget.WidgetModelProperty.prototype.format =  function()
{
    return this.datum[OpenAjax.widget.WidgetModelProperty.ATTRIBUTES.FORMAT];
}       

OpenAjax.widget.WidgetModelProperty.prototype.isInteger =  function()
{
    return this.datum[OpenAjax.widget.WidgetModelProperty.ATTRIBUTES.ISINTEGER] === "true";
}       

OpenAjax.widget.WidgetModelProperty.prototype.minimum =  function()
{
    return this.datum[OpenAjax.widget.WidgetModelProperty.ATTRIBUTES.MINIMUM];
}       

OpenAjax.widget.WidgetModelProperty.prototype.maximum =  function()
{
    return this.datum[OpenAjax.widget.WidgetModelProperty.ATTRIBUTES.MAXIMUM];
}       

OpenAjax.widget.WidgetModelProperty.prototype.min_length =  function()
{
    return this.datum[OpenAjax.widget.WidgetModelProperty.ATTRIBUTES.MIN_LENGTH];
}       

OpenAjax.widget.WidgetModelProperty.prototype.max_length =  function()
{
    return this.datum[OpenAjax.widget.WidgetModelProperty.ATTRIBUTES.MAX_LENGTH];
}       

OpenAjax.widget.WidgetModelProperty.prototype.pattern =  function()
{
    return this.datum[OpenAjax.widget.WidgetModelProperty.ATTRIBUTES.PATTERN];
}       

OpenAjax.widget.WidgetModelProperty.prototype.description =  function()
{
    return this.datum[OpenAjax.widget.WidgetModelProperty.ATTRIBUTES.DESCRIPTION];
}       

OpenAjax.widget.WidgetModelProperty.prototype.title =  function()
{
    return this.datum[OpenAjax.widget.WidgetModelProperty.ATTRIBUTES.TITLE];
}       

OpenAjax.widget.WidgetModelProperty.prototype.defaultTopic =  function()
{
    return this._defaultTopic;
}       

OpenAjax.widget.WidgetModelProperty.prototype.listenCallback = function(subHandle, topic, data) 
{
    data = JSON.parse( data );
    
    console.log( "Got Event: Prop <"+this.widget.getId()+'.'+this.name()+"> topic <"+topic+"> for data<"+data+">." );
                
    //change the value
    var targets = this.targets();
    if (targets) {
        var pieces = topic.split('.');
        var sourceID = pieces[pieces.length - 1];
        if (targets && targets.indexOf(sourceID) == -1) {
            return;
        }
    }
    this.value( data ); //@GINO: should validate the data based on type/constraints
}

OpenAjax.widget.WidgetModelProperty.prototype.removeFromHub = function() 
{
    if (this._sub) {
        this._sub.unsubscribe();
        this._sub = null;
    }
}

// XXX rename to 'getSingleBoundWidget'
OpenAjax.widget.WidgetModelProperty.prototype.getSingleBoundGadget =  function()
{
    return this._singleBoundWidget;
}

// XXX rename to 'getWidget'
// XXX why is this method necessary?
OpenAjax.widget.WidgetModelProperty.prototype.getGadget =  function()
{
    return this.widget;
}

OpenAjax.widget.WidgetModelProperty.prototype.getOptionsValue =  function(attrName)
{
  // Returns the value for the given oaa:options attribute (currently only
  // 'multiple' and 'unconstrained' are supported).  If the attribute isn't
  // provided, then return the default value
  //
  // XXX This might need to be updated when the spec finally says what the
  // default values are.  Just taking a stab at it for now.
  var options = this.datum[OpenAjax.widget.WidgetModelProperty.ATTRIBUTES.OPTIONS];
  var value = '';
  switch (attrName) {
    case 'multiple':
    case 'unconstrained':
      if (options) {
          value = options[attrName];
      }
      if (value == '') {
        value = 'false';
      }
      break;
  }
  return value;
}
OpenAjax.widget.WidgetModelProperty.prototype.getOptionArray =  function()
{
  var options = this.datum[OpenAjax.widget.WidgetModelProperty.ATTRIBUTES.OPTIONS];
  if (!options || !options.items) {
    return null;
  }
  return options.items;
}

