/*
 * 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.dev.jjs.test.singlejso;

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

/**
 * Tests SingleJso semantics in non-trivial type hierarchies.
 */
public class TypeHierarchyTest extends GWTTestCase {

  /**
   * Used with PlainJso and PlainJsoWithInterface to mix interfaces into
   * existing base classes.
   */
  interface Arrayish {
    int getLength();

    JavaScriptObject getObject(int i);

    /**
     * Used to test virtual override where the implementation has a narrower
     * return type.
     */
    Wide wide();
  }

  /**
   * The bottom type for a non-trivial diamond-shaped inheritance pattern.
   */
  static class DiamondImpl extends JavaScriptObject implements IDiamond2A,
      IDiamond2B {
    public static native DiamondImpl create() /*-{
      return {size : 42};
    }-*/;

    protected DiamondImpl() {
    }

    @Override
    public final native int size() /*-{
      return this.size;
    }-*/;
  }

  /**
   * The root type for a non-trivial diamond-shaped inheritance pattern.
   */
  interface IDiamond1 {
    int size();
  }

  /**
   * The left type for a non-trivial diamond-shaped inheritance pattern.
   */
  interface IDiamond2A extends IDiamond1 {
  }

  /**
   * The right type for a non-trivial diamond-shaped inheritance pattern.
   */
  interface IDiamond2B extends IDiamond1 {
  }

  /**
   * Used for testing virtual overrides.
   */
  static class Narrow extends Wide {
    @Override
    public String toString() {
      return "Narrow";
    }
  }

  /**
   * This is a base class that is used to test adding interfaces to a JSO via a
   * subclass.
   */
  static class PlainJso extends JavaScriptObject {
    protected PlainJso() {
    }

    public final native int getLength()/*-{
      return this.length;
    }-*/;

    public final native JavaScriptObject getObject(int i) /*-{
      return this[i];
    }-*/;

    public final Narrow wide() {
      return new Narrow();
    }
  }

  /**
   * We'll mix in an interface into PlainJso.
   */
  static class PlainJsoWithInterface extends PlainJso implements Arrayish {
    public static PlainJsoWithInterface create() {
      return JavaScriptObject.createArray().cast();
    }

    protected PlainJsoWithInterface() {
    }
  }

  /**
   * Used for testing virtual overrides.
   */
  static class Wide {
  }

  private interface Element extends Node {
  }

  private static class JvmNode implements Node {
    @Override
    public JvmNode appendChild(Node node) {
      return (JvmNode) node;
    }
  }
  
  private final static class JvmElement extends JvmNode implements Element {
    static Element create() {
      return new JvmElement();
    }
  }
  
  private final static class JsElement extends JsNode implements Element {
    static Element create() {
      return (Element) JavaScriptObject.createObject();
    }

    protected JsElement() {
    }
  }

  private static class JsNode extends JavaScriptObject implements Node {
    @Override
    public final native JsNode appendChild(Node node) /*-{
      return node;
    }-*/;

    protected JsNode() {
    }
  }

  private interface Node {
    Node appendChild(Node node);
  }

  @Override
  public String getModuleName() {
    return "com.google.gwt.dev.jjs.CompilerSuite";
  }

  public void testCase1() {
    A a = A.create();
    assertEquals("A", a.whoAmI());

    B1 b1 = new B1();
    assertEquals("B1", b1.whoAmI());

    B2 b2 = new B2();
    assertEquals("B2", b2.whoAmI());
  }

  public void testCase2() {
    IA a = A.create();
    assertEquals("A", a.whoAmI());

    IA b1 = new B1();
    assertEquals("B1", b1.whoAmI());

    IA b2 = new B2();
    assertEquals("B2", b2.whoAmI());
  }
  
  public void testCase3() {
    IA a = A.create();
    assertEquals("A", a.whoAmI());

    IB b1 = new B1();
    assertEquals("B1", b1.whoAmI());

    IB b2 = new B2();
    assertEquals("B2", b2.whoAmI());
  }

  public void testDiamond() {
    IDiamond1 d1 = DiamondImpl.create();
    assertEquals(42, d1.size());

    IDiamond2A d2a = DiamondImpl.create();
    assertEquals(42, d2a.size());

    IDiamond2B d2b = DiamondImpl.create();
    assertEquals(42, d2b.size());
  }
  
  /**
   * Tests that dispatches through a hierarchy of interfaces works properly.
   */
  public void testInterfaceHierarchyDispatch() {
    Element jsElement = JsElement.create();
    assertEquals(jsElement, jsElement.appendChild(jsElement));
    
    Element jvmElement = JvmElement.create();
    assertEquals(jvmElement, jvmElement.appendChild(jvmElement));
  }

  public void testVirtualOverrides() {
    Arrayish array = PlainJsoWithInterface.create();
    assertEquals(0, array.getLength());
    assertNull(array.getObject(0));
    assertEquals("Narrow", array.wide().toString());
  }
}
