/**
 * Copyright 2010 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 org.waveprotocol.wave.client.common.util;

import org.waveprotocol.wave.model.util.ReadableStringMap.ProcV;

import com.google.gwt.core.client.JavaScriptObject;

/**
 * Presents a raw, direct view on a javascript object. Useful for exposing
 * properties that are not possible to modify with existing APIs. Also used as a
 * backend for browser-optimised data structures.
 *
 * @author danilatos@google.com (Daniel Danilatos)
 */
public final class JsoView extends JavaScriptObject {

  // As required by GWT
  protected JsoView() {}

  /** Construct an empty Jso */
  public static native JsoView create() /*-{
    return {};
  }-*/;

  /**
   * Unsafely cast any jso to a JsoView, exposing its internals.
   *
   * @param jso
   * @return a JsoView of the input javascript object
   */
  public static JsoView as(JavaScriptObject jso) {
    return jso.cast();
  }

  /**
   * @param key
   * @return true if a value indexed by the given key is in the map
   */
  public native boolean containsKey(String key) /*-{
    return this[key] !== undefined;
  }-*/;

  /**
   * @param key
   * @return true if the given key exists in the map and is null
   */
  public native boolean isNull(String key) /*-{
    return this[key] === null;
  }-*/;

  /**
   * @param key
   * @return The value with the given key, or null if not present. The value is
   *         assumed to be a boolean. The method may fail at runtime if the type
   *         is not correct.
   */
  public native boolean getBoolean(String key) /*-{
     return this[key];
   }-*/;

  /**
   * @param key
   * @return The value with the given key, or null if not present. The value is
   *         assumed to be a string. The method may fail at runtime if the type
   *         is not correct.
   */
  public native String getString(String key) /*-{
    return this[key];
  }-*/;

  /**
   * @param key
   * @return The value with the given key, or null if not present. The value is
   *         assumed to be a string. The method may fail at runtime if the type
   *         is not correct.
   */
  public native String getString(int key) /*-{
    return this[key];
  }-*/;

  /**
   * @param key
   * @return The value with the given key, or null if not present. The value is
   *         assumed to be a primitive number. The method may fail at runtime if
   *         the type is not correct.
   */
  public native double getNumber(String key) /*-{
    return this[key];
  }-*/;

  /**
   * @param key
   * @return The value with the given key, or null if not present. The value is
   *         assumed to be a JSO. The method may fail at runtime if the type
   *         is not correct.
   */
  public native JavaScriptObject getJso(String key) /*-{
    return this[key];
  }-*/;

  /**
   * @param key
   * @return The value with the given key, or null if not present. The value is
   *         assumed to be a JSO. The method may fail at runtime if the type
   *         is not correct.
   */
  public native JavaScriptObject getJso(int key) /*-{
    return this[key];
  }-*/;

  /**
   * @param key
   * @return The value with the given key, or null if not present. The value is
   *         assumed to be a JSO. The method may fail at runtime if the type
   *         is not correct.
   */
  public JsoView getJsoView(String key) {
    JavaScriptObject jso = getJso(key);
    return jso == null ? null : JsoView.as(jso);
  }

  /**
   * @param key
   * @return The value with the given key, or null if not present. The value is
   *         assumed to be a JSO. The method may fail at runtime if the type
   *         is not correct.
   */
  public JsoView getJsoView(int key) {
    JavaScriptObject jso = getJso(key);
    return jso == null ? null : JsoView.as(jso);
  }

  /**
   * @param key
   * @return The value with the given key, or null if not present. The value is
   *         assumed to be a "java" object. The method may fail at runtime if
   *         the type is not correct.
   */
  public native Object getObject(String key) /*-{
    return this[key];
  }-*/;

  /**
   * Same as {@link #getObject(String)} but does not require casting of the
   * return value
   */
  public native <V> V getObjectUnsafe(String key) /*-{
    return this[key];
  }-*/;

  /**
   * Removes the given key from the map
   * @param key
   */
  public native void remove(String key) /*-{
    delete this[key];
  }-*/;

  /**
   * Sets the value for the given key to null (does not remove it from the map)
   * @param key
   */
  public native void setNull(String key) /*-{
    this[key] = null;
  }-*/;

  /**
   * Sets the value for the key
   * @param key
   * @param value
   */
  public native void setBoolean(String key, boolean value) /*-{
    this[key] = value;
  }-*/;

  /**
   * Sets the value for the key
   * @param key
   * @param value
   */
  public native void setString(String key, String value) /*-{
    this[key] = value;
  }-*/;

  /**
   * Sets the value for the key
   * @param key
   * @param value
   */
  public native void setNumber(String key, double value) /*-{
    this[key] = value;
  }-*/;

  /**
   * Sets the value for the key
   * @param key
   * @param value
   */
  public native void setJso(String key, JavaScriptObject value) /*-{
    this[key] = value;
  }-*/;

  /**
   * Sets the value for the key
   * @param key
   * @param value
   */
  public native void setObject(String key, Object value) /*-{
    this[key] = value;
  }-*/;

  /**
   * Removes all entries from this jso.
   */
  public final native void clear() /*-{
    for (var key in this) {
      delete this[key];
    }
  }-*/;

  /**
   * Tests whether this jso is empty.
   *
   * @return true if this map has no entries.
   */
  public final native boolean isEmpty() /*-{
    for (var k in this) {
      return false;
    }
    return true;
  }-*/;

  /**
   * Counts the number of entries in this jso. This is a linear time operation.
   */
  public final native int countEntries() /*-{
    var n = 0;
    for (var k in this) {
      n++;
    }
    return n;
  }-*/;

  /**
   * @return the first key in the iteration order, or null if the object is empty
   */
  public final native String firstKey() /*-{
    for (var k in this) {
      return k;
    }
    return null;
  }-*/;

  /**
   * Ruby/prototype.js style iterating idiom, using a callback. Equivalent to a
   * for-each loop.
   *
   * @param <T> The value type. It is up to the caller to make sure only values
   *        of type T have been put into the Jso in the first place
   * @param proc
   */
  public final native <T> void each(ProcV<T> proc) /*-{
     for (var k in this) {
       proc.
           @org.waveprotocol.wave.model.util.ReadableStringMap.ProcV::apply(Ljava/lang/String;Ljava/lang/Object;)
               (k, this[k]);
     }
   }-*/;

  /**
   * Add all key value pairs from the source to the current map
   *
   * @param source
   */
  public final native void putAll(JsoView source) /*-{
    for (var k in source) {
      this[k] = source[k];
    }
  }-*/;
}
