// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// 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.

/**
 * @fileoverview Implementation of DataNode for wrapping JSON data.
 *
 */


goog.provide('goog.ds.JsonDataSource');

goog.require('goog.Uri');
goog.require('goog.dom');
goog.require('goog.dom.TagName');
goog.require('goog.ds.DataManager');
goog.require('goog.ds.JsDataSource');
goog.require('goog.ds.LoadState');
goog.require('goog.ds.logger');
goog.require('goog.log');



/**
 * Data source whose backing is a JSON-like service, in which
 * retreiving the resource specified by URL with the additional parameter
 * callback. The resource retreived is executable JavaScript that
 * makes a call to the named function with a JavaScript object literal
 * as the only parameter.
 *
 * Example URI could be:
 * http://www.google.com/data/search?q=monkey&callback=mycb
 * which might return the JS:
 * mycb({searchresults:
 *   [{uri: 'http://www.monkey.com', title: 'Site About Monkeys'}]});
 *
 * TODO(user): Evaluate using goog.net.Jsonp here.
 *
 * A URI of an empty string will mean that no request is made
 * and the data source will be a data source with no child nodes
 *
 * @param {string|goog.Uri} uri URI for the request.
 * @param {string} name Name of the datasource.
 * @param {string=} opt_callbackParamName The parameter name that is used to
 *     specify the callback. Defaults to 'callback'.
 *
 * @extends {goog.ds.JsDataSource}
 * @constructor
 * @final
 */
goog.ds.JsonDataSource = function(uri, name, opt_callbackParamName) {
  goog.ds.JsDataSource.call(this, null, name, null);
  if (uri) {
    this.uri_ = new goog.Uri(uri);
  } else {
    this.uri_ = null;
  }

  /**
   * This is the callback parameter name that is added to the uri.
   * @type {string}
   * @private
   */
  this.callbackParamName_ = opt_callbackParamName || 'callback';

};
goog.inherits(goog.ds.JsonDataSource, goog.ds.JsDataSource);


/**
 * Default load state is NOT_LOADED
 * @private
 */
goog.ds.JsonDataSource.prototype.loadState_ = goog.ds.LoadState.NOT_LOADED;


/**
 * Map of all data sources, needed for callbacks
 * Doesn't work unless dataSources is exported (not renamed)
 */
goog.ds.JsonDataSource['dataSources'] = {};


/**
 * Load or reload the backing data for this node.
 * Fires the JsonDataSource
 * @override
 */
goog.ds.JsonDataSource.prototype.load = function() {
  if (this.uri_) {
    // NOTE: "dataSources" is expose above by name so that it will not be
    // renamed.  It should therefore be accessed via array notation here so
    // that it also doesn't get renamed and stops the compiler from complaining
    goog.ds.JsonDataSource['dataSources'][this.dataName_] = this;
    goog.log.info(
        goog.ds.logger, 'Sending JS request for DataSource ' +
            this.getDataName() + ' to ' + this.uri_);

    this.loadState_ = goog.ds.LoadState.LOADING;

    var uriToCall = new goog.Uri(this.uri_);
    uriToCall.setParameterValue(
        this.callbackParamName_, 'JsonReceive.' + this.dataName_);

    goog.global['JsonReceive'][this.dataName_] =
        goog.bind(this.receiveData, this);

    var scriptEl =
        goog.dom.createDom(goog.dom.TagName.SCRIPT, {'src': uriToCall});
    goog.dom.getElementsByTagNameAndClass(goog.dom.TagName.HEAD)[0].appendChild(
        scriptEl);
  } else {
    this.root_ = {};
    this.loadState_ = goog.ds.LoadState.NOT_LOADED;
  }
};


/**
 * Gets the state of the backing data for this node
 * @return {goog.ds.LoadState} The state.
 * @override
 */
goog.ds.JsonDataSource.prototype.getLoadState = function() {
  return this.loadState_;
};


/**
 * Receives data from a Json request
 * @param {Object} obj The JSON data.
 */
goog.ds.JsonDataSource.prototype.receiveData = function(obj) {
  this.setRoot(obj);
  this.loadState_ = goog.ds.LoadState.LOADED;
  goog.ds.DataManager.getInstance().fireDataChange(this.getDataName());
};


/**
* Temp variable to hold callbacks
* until BUILD supports multiple externs.js files
*/
goog.global['JsonReceive'] = {};
