// Copyright 2012 Google Inc. 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.

package com.google.collide.json.client;

import com.google.collide.json.shared.JsonStringMap;
import com.google.gwt.core.client.JavaScriptObject;

/**
 * This is used to satisfy DTO casting requirements.
 *
 *  On the client, if you have a reference to JsonStringMap, or JsoStringMap
 * (the JavaScriptObject backed impl), feel free to cross cast this to the more
 * robust {@link com.google.collide.json.client.Jso}.
 *
 */
public final class JsoStringMap<T> extends JavaScriptObject implements JsonStringMap<T> {

  /*
   * GWT dev mode adds a __gwt_ObjectId property to all objects. We have to call
   * hasOwnProperty (which is overridden by GWT) when using for(in) loops to verify
   * that the key is actually a property of the object, and not the __gwt_ObjectId
   * set by Chrome dev mode.
   */

  /**
   * Convenience factory method.
   */
  public static <T> JsoStringMap<T> create() {
    return Jso.create().cast();
  }

  protected JsoStringMap() {
  }

  @Override
  public native T get(String key) /*-{
    return Object.prototype.hasOwnProperty.call(this, key) ? this[key] : undefined;
  }-*/;

  @Override
  public native final JsoArray<String> getKeys() /*-{
    keys = [];
    for (key in this) {
      if (Object.prototype.hasOwnProperty.call(this, key)) {
        keys.push(key);
      }
    }
    return keys;
  }-*/;

  @Override
  public final native boolean isEmpty()/*-{
    for (key in this) {
      if (Object.prototype.hasOwnProperty.call(this, key)) {
        return false;
      }
    }
    return true;
  }-*/;

  /**
   * Method for iterating through the contents of a Map.
   *
   * <p>{@code T} is the expected type of the values returned from the map.
   * <b>Caveat:</b> if you have a map of heterogeneous types, you need to think
   * what value of T you specify here.
   *
   * @param callback The callback object that gets called on each iteration.
   */
  @Override
  public native void iterate(IterationCallback<T> callback) /*-{
    for (key in this) {
      if (Object.prototype.hasOwnProperty.call(this, key)) {
        callback.
        @com.google.collide.json.shared.JsonStringMap.IterationCallback::onIteration(Ljava/lang/String;Ljava/lang/Object;)
        (key, this[key]);
      }
    }
  }-*/;

  // TODO: We still have problem with "__proto__"
  @Override
  public native void put(String key, T value) /*-{
    this[key] = value;
  }-*/;

  @Override
  public void putAll(JsonStringMap<T> map) {
    map.iterate(new IterationCallback<T>() {
      @Override
      public void onIteration(String key, T value) {
        put(key, value);
      }
    });
  }

  @Override
  public native T remove(String key) /*-{
    if (!Object.prototype.hasOwnProperty.call(this, key)) {
      return undefined;
    }
    var retVal = this[key];
    delete this[key];
    return retVal;
  }-*/;

  @Override
  public native boolean containsKey(String key) /*-{
    return Object.prototype.hasOwnProperty.call(this, key);
  }-*/;

  /**
   * Returns the size of the map (the number of keys).
   *
   * <p>NB: This method is currently O(N) because it iterates over all keys.
   *
   * @return the size of the map.
   */
  @Override
  public final native int size() /*-{
    size = 0;
    for (key in this) {
      if (Object.prototype.hasOwnProperty.call(this, key)) {
        size++;
      }
    }
    return size;
  }-*/;
}
