/*
 * Copyright 2008 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;

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

/**
 * This should probably be refactored at some point.
 */
public class MiscellaneousTest extends GWTTestCase {

  interface I {
  }

  interface IBar extends I {
  }

  interface IFoo extends I {
  }

  static class PolyA implements IFoo {
    @Override
    public String toString() {
      return "A";
    }
  }

  static class PolyB implements IBar {
    @Override
    public String toString() {
      return "B";
    }
  }

  private static class HasClinit {
    public static int i = 1;

    private static HasClinit sInstance = new HasClinit();

    public static int sfoo() {
      return sInstance.foo();
    }

    private static native void clinitInNative() /*-{
    }-*/;

    @SuppressWarnings("ReturnValueIgnored")
    private int foo() {
      this.toString();
      return 3;
    }
  }

  private static volatile boolean FALSE = false;

  private static volatile boolean TRUE = true;

  private static void assertAllCanStore(Object[] dest, Object[] src) {
    for (int i = 0; i < src.length; ++i) {
      dest[0] = src[i];
    }
  }

  private static void assertNoneCanStore(Object[] dest, Object[] src) {
    for (int i = 0; i < src.length; ++i) {
      try {
        dest[0] = src[i];
        fail();
      } catch (ArrayStoreException e) {
      }
    }
  }

  private static native void clinitFromNative() /*-{
    @com.google.gwt.dev.jjs.test.MiscellaneousTest$HasClinit::i = 5;
  }-*/;

  private static native void noOp() /*-{
  }-*/;

  private static native void throwNativeException() /*-{
    var a; a.asdf();
  }-*/;

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

  public void testArrayCasts() {
    {
      // thwart optimizer
      Object f1 = FALSE ? (Object) new PolyA() : (Object) new IFoo[1];
      if (expectClassMetadata()) {
        assertEquals("[Lcom.google.gwt.dev.jjs.test.MiscellaneousTest$IFoo;",
            f1.getClass().getName());
      }
      assertFalse(f1 instanceof PolyA[][]);
      assertFalse(f1 instanceof IFoo[][]);
      assertFalse(f1 instanceof PolyA[]);
      assertTrue(f1 instanceof IFoo[]);
      assertFalse(f1 instanceof PolyA);
      assertFalse(f1 instanceof IFoo);
      assertTrue(f1 instanceof Object[]);
      assertFalse(f1 instanceof Object[][]);

      assertAllCanStore((Object[]) f1, new Object[] {new PolyA(), new IFoo() {
      }});
      assertNoneCanStore((Object[]) f1, new Object[] {
          new PolyB(), new Object(), new Object[0]});
    }

    {
      // thwart optimizer
      Object a1 = FALSE ? (Object) new PolyA() : (Object) new PolyA[1];
      if (expectClassMetadata()) {
        assertEquals("[Lcom.google.gwt.dev.jjs.test.MiscellaneousTest$PolyA;",
            a1.getClass().getName());
      }
      assertFalse(a1 instanceof PolyA[][]);
      assertFalse(a1 instanceof IFoo[][]);
      assertTrue(a1 instanceof PolyA[]);
      assertTrue(a1 instanceof IFoo[]);
      assertFalse(a1 instanceof PolyA);
      assertFalse(a1 instanceof IFoo);
      assertTrue(a1 instanceof Object[]);
      assertFalse(a1 instanceof Object[][]);

      assertAllCanStore((Object[]) a1, new Object[] {new PolyA()});
      assertNoneCanStore((Object[]) a1, new Object[] {new IFoo() {
      }, new PolyB(), new Object(), new Object[0]});
    }

    {
      // thwart optimizer
      Object f2 = FALSE ? (Object) new PolyA() : (Object) new IFoo[1][];
      if (expectClassMetadata()) {
        assertEquals("[[Lcom.google.gwt.dev.jjs.test.MiscellaneousTest$IFoo;",
            f2.getClass().getName());
      }
      assertFalse(f2 instanceof PolyA[][]);
      assertTrue(f2 instanceof IFoo[][]);
      assertFalse(f2 instanceof PolyA[]);
      assertFalse(f2 instanceof IFoo[]);
      assertFalse(f2 instanceof PolyA);
      assertFalse(f2 instanceof IFoo);
      assertTrue(f2 instanceof Object[]);
      assertTrue(f2 instanceof Object[][]);

      assertAllCanStore((Object[]) f2, new Object[] {new PolyA[0], new IFoo[0]});
      assertNoneCanStore((Object[]) f2, new Object[] {new IFoo() {
      }, new PolyB(), new Object(), new Object[0]});
    }

    {
      // thwart optimizer
      Object a2 = FALSE ? (Object) new PolyA() : (Object) new PolyA[1][];
      if (expectClassMetadata()) {
        assertEquals("[[Lcom.google.gwt.dev.jjs.test.MiscellaneousTest$PolyA;",
            a2.getClass().getName());
      }
      assertTrue(a2 instanceof PolyA[][]);
      assertTrue(a2 instanceof IFoo[][]);
      assertFalse(a2 instanceof PolyA[]);
      assertFalse(a2 instanceof IFoo[]);
      assertFalse(a2 instanceof PolyA);
      assertFalse(a2 instanceof IFoo);
      assertTrue(a2 instanceof Object[]);
      assertTrue(a2 instanceof Object[][]);

      assertAllCanStore((Object[]) a2, new Object[] {new PolyA[0]});
      assertNoneCanStore((Object[]) a2, new Object[] {new IFoo() {
      }, new PolyB(), new Object(), new Object[0], new IFoo[0]});
    }
  }

  public void testArrays() {
    int[] c = new int[] {1, 2};
    int[][] d = new int[][] { {1, 2}, {3, 4}};
    int[][][] e = new int[][][] { { {1, 2}, {3, 4}}, { {5, 6}, {7, 8}}};
    if (expectClassMetadata()) {
      assertEquals("[I", c.getClass().getName());
      assertEquals("[[I", d.getClass().getName());
      assertEquals("[I", d[1].getClass().getName());
      assertEquals("[[[I", e.getClass().getName());
      assertEquals("[[I", e[1].getClass().getName());
      assertEquals("[I", e[1][1].getClass().getName());
    }
    assertEquals(2, c[1]);
    assertEquals(3, d[1][0]);
    assertEquals(8, e[1][1][1]);

    int[][][] b = new int[3][2][1];
    b[2][1][0] = 1;
    b = new int[3][2][];
    b[2][1] = null;
    b = new int[3][][];
    b[2] = null;
  }

  public void testAssociativityCond() {
    int result = (TRUE ? TRUE : FALSE) ? 100 : 200;
    assertEquals(100, result);
  }

  @SuppressWarnings({"cast", "SelfAssignment"})
  public void testCasts() {
    Object o = FALSE ? (Object) new PolyA() : (Object) new PolyB();
    assertTrue(o instanceof I);
    assertFalse(o instanceof IFoo);
    assertTrue(o instanceof IBar);
    assertFalse(o instanceof PolyA);
    assertTrue(o instanceof PolyB);
    try {
      o = (PolyA) o;
      fail();
    } catch (ClassCastException e) {
    }
  }

  public void testClinit() {
    ++HasClinit.i;
    HasClinit x = new HasClinit();
    ++x.i;
    new HasClinit().i++;
    HasClinit.i /= HasClinit.i;
    HasClinit.sfoo();
    HasClinit.i /= HasClinit.sfoo();
    HasClinit.clinitInNative();
    clinitFromNative();
  }

  public void testExceptions() {
    int i;
    for (i = 0; i < 5; ++i) {
      boolean hitOuter = false;
      boolean hitInner = false;
      try {
        try {
          switch (i) {
            case 0:
              throw new RuntimeException();
            case 1:
              throw new IndexOutOfBoundsException();
            case 2:
              throw new Exception();
            case 3:
              throw new StringIndexOutOfBoundsException();
            case 4:
              throwNativeException();
          }
        } catch (StringIndexOutOfBoundsException e) {
          assertEquals(3, i);
        } finally {
          hitInner = true;
        }
      } catch (IndexOutOfBoundsException f) {
        assertEquals(1, i);
      } catch (JavaScriptException js) {
        assertEquals(4, i);
      } catch (RuntimeException g) {
        assertEquals(0, i);
      } catch (Throwable e) {
        assertEquals(2, i);
      } finally {
        assertTrue(hitInner);
        hitOuter = true;
      }
      assertTrue(hitOuter);
    }
    assertEquals(5, i);
  }

  public void testIssue2479() {
    if (TRUE) {
      FALSE = false;
    } else if (FALSE) {
      TRUE = true;
    } else {
      noOp();
    }
  }

  @Override
  public String toString() {
    return "com.google.gwt.dev.jjs.test.MiscellaneousTest";
  }

  private boolean expectClassMetadata() {
    String name = Object.class.getName();

    if (name.equals("java.lang.Object")) {
      return true;
    } else if (name.startsWith("Class$")) {
      return false;
    }

    throw new RuntimeException("Unexpected class name " + name);
  }
}
