/*
 * Copyright 2009 Google Inc.
 * 
 * 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.
 */
package com.google.gwt.xhr.client;

import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.typedarrays.shared.ArrayBuffer;

/**
 * The native XMLHttpRequest object. Most applications should use the higher-
 * level {@link com.google.gwt.http.client.RequestBuilder} class unless they
 * need specific functionality provided by the XMLHttpRequest object.
 * 
 * See <a href="http://www.w3.org/TR/XMLHttpRequest/"
 * >http://www.w3.org/TR/XMLHttpRequest/</a>/
 */
public class XMLHttpRequest extends JavaScriptObject {

  /**
   * The type of response expected from the XHR.
   */
  public enum ResponseType {
    /**
     * The default response type -- use {@link XMLHttpRequest#getResponseText()}
     * for the return value.
     */
    Default(""),

    /**
     * The default response type -- use
     * {@link XMLHttpRequest#getResponseArrayBuffer()} for the return value.
     * This value may only be used if
     * {@link com.google.gwt.typedarrays.shared.TypedArrays#isSupported()}
     * returns true.
     */
    ArrayBuffer("arraybuffer");

    // not implemented yet
    /*
    Blob("blob"),
    
    Document("document"),
    
    Text("text");
    */

    private final String responseTypeString;

    private ResponseType(String responseTypeString) {
      this.responseTypeString = responseTypeString;
    }

    public String getResponseTypeString() {
      return responseTypeString;
    }
  }

  /*
   * NOTE: Testing discovered that for some bizarre reason, on Mozilla, the
   * JavaScript <code>XmlHttpRequest.onreadystatechange</code> handler
   * function maybe still be called after it is deleted. The theory is that the
   * callback is cached somewhere. Setting it to null or an empty function does
   * seem to work properly, though.
   * 
   * On IE, setting onreadystatechange to null (as opposed to an empty function)
   * sometimes throws an exception.
   * 
   * End result: *always* set onreadystatechange to an empty function (never to
   * null).
   */

  /**
   * When constructed, the XMLHttpRequest object must be in the UNSENT state.
   */
  public static final int UNSENT = 0;

  /**
   * The OPENED state is the state of the object when the open() method has been
   * successfully invoked. During this state request headers can be set using
   * setRequestHeader() and the request can be made using send().
   */
  public static final int OPENED = 1;

  /**
   * The HEADERS_RECEIVED state is the state of the object when all response
   * headers have been received.
   */
  public static final int HEADERS_RECEIVED = 2;

  /**
   * The LOADING state is the state of the object when the response entity body
   * is being received.
   */
  public static final int LOADING = 3;

  /**
   * The DONE state is the state of the object when either the data transfer has
   * been completed or something went wrong during the transfer (infinite
   * redirects for instance).
   */
  public static final int DONE = 4;

  /**
   * Creates an XMLHttpRequest object.
   * 
   * @return the created object
   */
  public static native XMLHttpRequest create() /*-{
    return new $wnd.XMLHttpRequest();
  }-*/;

  protected XMLHttpRequest() {
  }

  /**
   * Aborts the current request.
   * <p>
   * See <a href="http://www.w3.org/TR/XMLHttpRequest/#the-abort-method"
   * >http://www.w3.org/TR/XMLHttpRequest/#the-abort-method</a>.
   */
  public final native void abort() /*-{
    this.abort();
  }-*/;

  /**
   * Clears the {@link ReadyStateChangeHandler}.
   * <p>
   * See <a href="http://www.w3.org/TR/XMLHttpRequest/#handler-xhr-onreadystatechange"
   * >http://www.w3.org/TR/XMLHttpRequest/#handler-xhr-onreadystatechange</a>.
   * 
   * @see #clearOnReadyStateChange()
   */
  public final native void clearOnReadyStateChange() /*-{
    this.onreadystatechange = function() {};
  }-*/;

  /**
   * Gets all the HTTP response headers, as a single string.
   * <p>
   * See <a href="http://www.w3.org/TR/XMLHttpRequest/#the-getallresponseheaders-method"
   * >http://www.w3.org/TR/XMLHttpRequest/#the-getallresponseheaders-method</a>.
   * 
   * @return the response headers.
   */
  public final native String getAllResponseHeaders() /*-{
    return this.getAllResponseHeaders();
  }-*/;

  /**
   * Get's the current ready-state.
   * <p>
   * See <a href="http://www.w3.org/TR/XMLHttpRequest/#dom-xmlhttprequest-readystate"
   * >http://www.w3.org/TR/XMLHttpRequest/#dom-xmlhttprequest-state</a>.
   * 
   * @return the ready-state constant
   */
  public final native int getReadyState() /*-{
    return this.readyState;
  }-*/;

  /**
   * Get the response as an {@link ArrayBuffer}.
   * 
   * @return an {@link ArrayBuffer} containing the response, or null if the
   *     request is in progress or failed
   */
  public final native ArrayBuffer getResponseArrayBuffer() /*-{
    return this.response;
  }-*/;

  /**
   * Gets an HTTP response header.
   * <p>
   * See <a href="http://www.w3.org/TR/XMLHttpRequest/#the-getresponseheader-method"
   * >http://www.w3.org/TR/XMLHttpRequest/#the-getresponseheader-method</a>.
   * 
   * @param header the response header to be retrieved
   * @return the header value
   */
  public final native String getResponseHeader(String header) /*-{
    return this.getResponseHeader(header);
  }-*/;

  /**
   * Gets the response text.
   * <p>
   * See <a href="http://www.w3.org/TR/XMLHttpRequest/#the-responsetext-attribute"
   * >http://www.w3.org/TR/XMLHttpRequest/#the-responsetext-attribute</a>.
   * 
   * @return the response text
   */
  public final native String getResponseText() /*-{
    return this.responseText;
  }-*/;

  /**
   * Gets the response type.
   * <p>
   * See <a href="http://www.w3.org/TR/XMLHttpRequest/#the-responsetype-attribute"
   * >http://www.w3.org/TR/XMLHttpRequest/#the-responsetype-attribute</a>
   *
   * @return the response type
   */
  public final native String getResponseType() /*-{
    return this.responseType || "";
  }-*/;

  /**
   * Gets the status code.
   * <p>
   * See <a href="http://www.w3.org/TR/XMLHttpRequest/#the-status-attribute"
   * >http://www.w3.org/TR/XMLHttpRequest/#the-status-attribute</a>.
   * 
   * @return the status code
   */
  public final native int getStatus() /*-{
    return this.status;
  }-*/;

  /**
   * Gets the status text.
   * <p>
   * See <a href="http://www.w3.org/TR/XMLHttpRequest/#the-statustext-attribute"
   * >http://www.w3.org/TR/XMLHttpRequest/#the-statustext-attribute</a>.
   * 
   * @return the status text
   */
  public final native String getStatusText() /*-{
    return this.statusText;
  }-*/;

  /**
   * Opens an asynchronous connection.
   * <p>
   * See <a href="http://www.w3.org/TR/XMLHttpRequest/#the-open-method"
   * >http://www.w3.org/TR/XMLHttpRequest/#the-open-method</a>.
   * 
   * @param httpMethod the HTTP method to use
   * @param url the URL to be opened
   */
  public final native void open(String httpMethod, String url) /*-{
    this.open(httpMethod, url, true);
  }-*/;

  /**
   * Opens an asynchronous connection.
   * <p>
   * See <a href="http://www.w3.org/TR/XMLHttpRequest/#the-open-method"
   * >http://www.w3.org/TR/XMLHttpRequest/#the-open-method</a>.
   * 
   * @param httpMethod the HTTP method to use
   * @param url the URL to be opened
   * @param user user to use in the URL
   */
  public final native void open(String httpMethod, String url, String user) /*-{
    this.open(httpMethod, url, true, user);
  }-*/;

  /**
   * Opens an asynchronous connection.
   * <p>
   * See <a href="http://www.w3.org/TR/XMLHttpRequest/#the-open-method"
   * >http://www.w3.org/TR/XMLHttpRequest/#the-open-method</a>.
   * 
   * @param httpMethod the HTTP method to use
   * @param url the URL to be opened
   * @param user user to use in the URL
   * @param password password to use in the URL
   */
  public final native void open(String httpMethod, String url, String user,
      String password) /*-{
    this.open(httpMethod, url, true, user, password);
  }-*/;

  /**
   * Initiates a request with no request data. This simply calls
   * {@link #send(String)} with <code>null</code> as an argument, because the
   * no-argument <code>send()</code> method is unavailable on Firefox.
   */
  public final void send() {
    send(null);
  }

  /**
   * Initiates a request with data.  If there is no data, specify null.
   * <p>
   * See <a href="http://www.w3.org/TR/XMLHttpRequest/#the-send-method"
   * >http://www.w3.org/TR/XMLHttpRequest/#the-send-method</a>.
   * 
   * @param requestData the data to be sent with the request
   */
  public final native void send(String requestData) /*-{
    this.send(requestData);
  }-*/;

  /**
   * Sets the {@link ReadyStateChangeHandler} to be notified when the object's
   * ready-state changes.
   * <p>
   * See <a href="http://www.w3.org/TR/XMLHttpRequest/#handler-xhr-onreadystatechange"
   * >http://www.w3.org/TR/XMLHttpRequest/#handler-xhr-onreadystatechange</a>.
   * 
   * <p>
   * Note: Applications <em>must</em> call {@link #clearOnReadyStateChange()}
   * when they no longer need this object, to ensure that it is cleaned up
   * properly. Failure to do so will result in memory leaks on some browsers.
   * </p>
   * 
   * @param handler the handler to be called when the ready state changes
   * @see #clearOnReadyStateChange()
   */
  public final native void setOnReadyStateChange(ReadyStateChangeHandler handler) /*-{
    // The 'this' context is always supposed to point to the xhr object in the
    // onreadystatechange handler, but we reference it via closure to be extra sure.
    var _this = this;
    this.onreadystatechange = $entry(function() {
      handler.@com.google.gwt.xhr.client.ReadyStateChangeHandler::onReadyStateChange(Lcom/google/gwt/xhr/client/XMLHttpRequest;)(_this);
    });
  }-*/;

  /**
   * Sets a request header.
   * <p>
   * See <a href="http://www.w3.org/TR/XMLHttpRequest/#the-setrequestheader-method"
   * >http://www.w3.org/TR/XMLHttpRequest/#the-setrequestheader-method</a>.
   * 
   * @param header the header to be set
   * @param value the header's value
   */
  public final native void setRequestHeader(String header, String value) /*-{
    this.setRequestHeader(header, value);
  }-*/;

  /**
   * Sets withCredentials attribute.
   * <p>
   * See <a href="http://www.w3.org/TR/XMLHttpRequest/#the-withcredentials-attribute"
   * >http://www.w3.org/TR/XMLHttpRequest/#the-withcredentials-attribute</a>.
   *
   * @param withCredentials whether to include credentials in XHR
   */
  public final native void setWithCredentials(boolean withCredentials) /*-{
    this.withCredentials = withCredentials;
  }-*/;

  /**
   * Sets the response type.
   * <p>
   * See <a href="http://www.w3.org/TR/XMLHttpRequest/#the-responsetype-attribute"
   * >http://www.w3.org/TR/XMLHttpRequest/#the-responsetype-attribute</a>
   *
   * @param responseType the type of response desired.  See {@link ResponseType}
   *     for limitations on using the different values
   */
  public final void setResponseType(ResponseType responseType) {
    this.setResponseType(responseType.getResponseTypeString());
  }

  /**
   * Sets the response type.
   * <p>
   * See <a href="http://www.w3.org/TR/XMLHttpRequest/#the-responsetype-attribute"
   * >http://www.w3.org/TR/XMLHttpRequest/#the-responsetype-attribute</a>
   *
   * @param responseType the type of response desired.  See {@link ResponseType}
   *     for limitations on using the different values
   */
  public final native void setResponseType(String responseType) /*-{
    this.responseType = responseType;
  }-*/;
}
