/* ************************************************************************

   qooxdoo - the new era of web development

   http://qooxdoo.org

   Copyright:
     2004-2008 1&1 Internet AG, Germany, http://www.1und1.de
     2006 Derrell Lipman
     2006 STZ-IDA, Germany, http://www.stz-ida.de

   License:
     LGPL: http://www.gnu.org/licenses/lgpl.html
     EPL: http://www.eclipse.org/org/documents/epl-v10.php
     See the LICENSE file in the project's top-level directory for details.

   Authors:
     * Sebastian Werner (wpbasti)
     * Andreas Ecker (ecker)
     * Derrell Lipman (derrell)
     * Andreas Junghans (lucidcake)

************************************************************************ */

/**
 * Transports requests to a server using dynamic script tags.
 *
 * This class should not be used directly by client programmers.
 */
qx.Class.define("qx.io.remote.transport.Script",
{
  extend : qx.io.remote.transport.Abstract,




  /*
  *****************************************************************************
     CONSTRUCTOR
  *****************************************************************************
  */

  construct : function()
  {
    this.base(arguments);

    var vUniqueId = ++qx.io.remote.transport.Script.__uniqueId;

    if (vUniqueId >= 2000000000) {
      qx.io.remote.transport.Script.__uniqueId = vUniqueId = 1;
    }

    this.__element = null;
    this.__uniqueId = vUniqueId;
  },




  /*
  *****************************************************************************
     STATICS
  *****************************************************************************
  */

  statics :
  {
    /**
     * Unique identifier for each instance.
     *
     * @internal
     */
    __uniqueId : 0,

    /**
     * Registry for all script transport instances.
     *
     * @internal
     */
    _instanceRegistry : {},

    /**
     * Internal URL parameter prefix.
     *
     * @internal
     */
    ScriptTransport_PREFIX : "_ScriptTransport_",

    /**
     * Internal URL parameter ID.
     *
     * @internal
     */
    ScriptTransport_ID_PARAM : "_ScriptTransport_id",

    /**
     * Internal URL parameter data prefix.
     *
     * @internal
     */
    ScriptTransport_DATA_PARAM : "_ScriptTransport_data",

    /**
     * Capabilities of this transport type.
     *
     * @internal
     */
    handles :
    {
      synchronous           : false,
      asynchronous          : true,
      crossDomain           : true,
      fileUpload            : false,
      programaticFormFields : false,
      responseTypes         : [ "text/plain", "text/javascript", "application/json" ]
    },


    /**
     * Returns always true, because script transport is supported by all browsers.
     *
     */
    isSupported : function() {
      return true;
    },




    /*
    ---------------------------------------------------------------------------
      EVENT LISTENER
    ---------------------------------------------------------------------------
    */

    /**
     * For reference:
     * http://msdn.microsoft.com/workshop/author/dhtml/reference/properties/readyState_1.asp
     *
     * @internal
     */
    _numericMap :
    {
      "uninitialized" : 1,
      "loading"       : 2,
      "loaded"        : 2,
      "interactive"   : 3,
      "complete"      : 4
    },


    /**
     * This method can be called by the script loaded by the ScriptTransport
     * class.
     *
     * @signature function(id, content)
     * @param id {String} Id of the corresponding transport object,
     *     which is passed as an URL parameter to the server an
     * @param content {String} This string is passed to the content property
     *     of the {@link qx.io.remote.Response} object.
     */
    _requestFinished : qx.event.GlobalError.observeMethod(function(id, content)
    {
      var vInstance = qx.io.remote.transport.Script._instanceRegistry[id];

      if (vInstance == null)
      {
        if (qx.core.Variant.isSet("qx.debug", "on"))
        {
          if (qx.core.Setting.get("qx.ioRemoteDebug")) {
            this.warn("Request finished for an unknown instance (probably aborted or timed out before)");
          }
        }
      }
      else
      {
        vInstance._responseContent = content;
        vInstance._switchReadyState(qx.io.remote.transport.Script._numericMap.complete);
      }
    })
  },




  /*
  *****************************************************************************
     MEMBERS
  *****************************************************************************
  */

  members :
  {

    __lastReadyState : 0,
    __element : null,
    __uniqueId : null,

    /*
    ---------------------------------------------------------------------------
      USER METHODS
    ---------------------------------------------------------------------------
    */

    /**
     * Sends the request using "script" elements
     *
     * @return {void}
     */
    send : function()
    {
      var vUrl = this.getUrl();

      // --------------------------------------
      //   Adding parameters
      // --------------------------------------
      vUrl += (vUrl.indexOf("?") >= 0 ? "&" : "?") + qx.io.remote.transport.Script.ScriptTransport_ID_PARAM + "=" + this.__uniqueId;

      var vParameters = this.getParameters();
      var vParametersList = [];

      for (var vId in vParameters)
      {
        if (vId.indexOf(qx.io.remote.transport.Script.ScriptTransport_PREFIX) == 0) {
          this.error("Illegal parameter name. The following prefix is used internally by qooxdoo): " + qx.io.remote.transport.Script.ScriptTransport_PREFIX);
        }

        var value = vParameters[vId];

        if (value instanceof Array)
        {
          for (var i=0; i<value.length; i++) {
            vParametersList.push(encodeURIComponent(vId) + "=" + encodeURIComponent(value[i]));
          }
        }
        else
        {
          vParametersList.push(encodeURIComponent(vId) + "=" + encodeURIComponent(value));
        }
      }

      if (vParametersList.length > 0) {
        vUrl += "&" + vParametersList.join("&");
      }

      // --------------------------------------
      //   Sending data
      // --------------------------------------
      var vData = this.getData();

      if (vData != null) {
        vUrl += "&" + qx.io.remote.transport.Script.ScriptTransport_DATA_PARAM + "=" + encodeURIComponent(vData);
      }

      qx.io.remote.transport.Script._instanceRegistry[this.__uniqueId] = this;
      this.__element = document.createElement("script");

      // IE needs this (it ignores the
      // encoding from the header sent by the
      // server for dynamic script tags)
      this.__element.charset = "utf-8";
      this.__element.src = vUrl;

      if (qx.core.Variant.isSet("qx.debug", "on"))
      {
        if (qx.core.Setting.get("qx.ioRemoteDebugData"))
        {
          this.debug("Request: " + vUrl);
        }
      }

      document.body.appendChild(this.__element);
    },


    /**
     * Switches the readystate by setting the internal state.
     *
     * @param vReadyState {String} readystate value
     * @return {void}
     */
    _switchReadyState : function(vReadyState)
    {
      // Ignoring already stopped requests
      switch(this.getState())
      {
        case "completed":
        case "aborted":
        case "failed":
        case "timeout":
          this.warn("Ignore Ready State Change");
          return;
      }

      // Updating internal state
      while (this.__lastReadyState < vReadyState) {
        this.setState(qx.io.remote.Exchange._nativeMap[++this.__lastReadyState]);
      }
    },




    /*
    ---------------------------------------------------------------------------
      REQUEST HEADER SUPPORT
    ---------------------------------------------------------------------------
    */

    /**
     * Sets a request header with the given value.
     *
     * This method is not implemented at the moment.
     *
     * @param vLabel {String} Request header name
     * @param vValue {var} Request header value
     * @return {void}
     */
    setRequestHeader : function(vLabel, vValue) {},

    /*
    ---------------------------------------------------------------------------
      RESPONSE HEADER SUPPORT
    ---------------------------------------------------------------------------
    */

    /**
     * Returns the value of the given response header.
     *
     * This method is not implemented at the moment and returns always "null".
     *
     * @param vLabel {String} Response header name
     * @return {null} Returns null
     */
    getResponseHeader : function(vLabel) {
      return null;
    },

    /**
     * Provides an hash of all response headers.
     *
     * This method is not implemented at the moment and returns an empty map.
     *
     * @return {Map} empty map
     */
    getResponseHeaders : function() {
      return {};
    },

    /*
    ---------------------------------------------------------------------------
      STATUS SUPPORT
    ---------------------------------------------------------------------------
    */

    /**
     * Returns the current status code of the request if available or -1 if not.
     * This method needs implementation (returns always 200).
     *
     * @return {Integer} status code
     */
    getStatusCode : function() {
      return 200;
    },

    /**
     * Provides the status text for the current request if available and null otherwise.
     * This method needs implementation (returns always an empty string)
     *
     * @return {String} always an empty string.
     */
    getStatusText : function() {
      return "";
    },

    /*
    ---------------------------------------------------------------------------
      RESPONSE DATA SUPPORT
    ---------------------------------------------------------------------------
    */

    /**
     * Returns the length of the content as fetched thus far.
     * This method needs implementation (returns always 0).
     *
     * @return {Integer} Returns 0
     */
    getFetchedLength : function() {
      return 0;
    },

    /**
     * Returns the content of the response.
     *
     * @return {null | String} If successful content of response as string.
     */
    getResponseContent : function()
    {
      if (this.getState() !== "completed")
      {
        if (qx.core.Variant.isSet("qx.debug", "on"))
        {
          if (qx.core.Setting.get("qx.ioRemoteDebug")) {
            this.warn("Transfer not complete, ignoring content!");
          }
        }

        return null;
      }

      if (qx.core.Variant.isSet("qx.debug", "on"))
      {
        if (qx.core.Setting.get("qx.ioRemoteDebug")) {
          this.debug("Returning content for responseType: " + this.getResponseType());
        }
      }

      switch(this.getResponseType())
      {
        case "text/plain":
          // server is responsible for using a string as the response
        case "application/json":
        case "text/javascript":
          if (qx.core.Variant.isSet("qx.debug", "on"))
          {
            if (qx.core.Setting.get("qx.ioRemoteDebugData"))
            {
              this.debug("Response: " + this._responseContent);
            }
          }
          var ret = this._responseContent;
          return (ret === 0 ? 0 : (ret || null));

        default:
          this.warn("No valid responseType specified (" + this.getResponseType() + ")!");
          return null;
      }
    }
  },



  /*
  *****************************************************************************
     DEFER
  *****************************************************************************
  */

  defer : function()
  {
    // basic registration to qx.io.remote.Exchange
    // the real availability check (activeX stuff and so on) follows at the first real request
    qx.io.remote.Exchange.registerType(qx.io.remote.transport.Script, "qx.io.remote.transport.Script");
  },




  /*
  *****************************************************************************
     DESTRUCTOR
  *****************************************************************************
  */

  destruct : function()
  {
    if (this.__element)
    {
      delete qx.io.remote.transport.Script._instanceRegistry[this.__uniqueId];
      document.body.removeChild(this.__element);
    }

    this.__element = this._responseContent = null;
  }
});
