/*
 * 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.junit.client.GWTTestCase;

import java.util.Arrays;
import java.util.Iterator;

/**
 * This test is intended to exercise as many code paths and node types as
 * possible in the Java to JavaScript compiler. This test is not at all intended
 * to execute correctly.
 */
@SuppressWarnings("hiding")
public class CoverageTest extends CoverageBase {

  /**
   * TODO: document me.
   */
  public class Inner extends Super {

    public int x = 3;
    public final int y = 4;

    public Inner() {
      // ExplicitConstructorCall
      this(4);
    }

    public Inner(int i) {
      // ExplicitConstructorCall
      super(i);
    }

    @Override
    public void foo() {
      final int z = this.y;

      new Inner() {
        {
          x = z;
          this.x = z;
          Inner.this.x = z;
          next = CoverageTest.this.next;
          next.foo();
          CoverageTest.this.next.foo();
          CoverageTest.this.x = z;
          CoverageTest.super.x = z;
        }

        @Override
        public void foo() {
          x = z;
          this.x = z;
          Inner.this.x = z;
          next = CoverageTest.this.next;
          next.foo();
          CoverageTest.this.next.foo();
          CoverageTest.this.x = z;
          CoverageTest.super.x = z;
        }
      };

      class NamedLocal extends Inner {
        @Override
        @SuppressWarnings("unused")
        public void foo() {
          CoverageTest.this.getNext();
          Inner.this.bar();
          super.bar();
          int x = z;
        }

        // JDT bug? This works in 5.0 but not in 1.4
        // TODO: will javac compile it?
        class NamedLocalSub extends NamedLocal {
          @Override
          @SuppressWarnings("unused")
          public void foo() {
            Inner.this.bar();
            NamedLocal.this.foo();
            super.foo();
            int x = z;
          }
        }
      }
      testEmptyStatement();

      new InnerSub().new InnerSubSub().fda();
      new SecondMain().new FunkyInner();
      /*
       * The statement below causes a javac bug in openJdk and sun's java 6. It
       * produces incorrect bytecode that fails with a java.lang.VerifyError --
       * see Google's internal issue 1628473. This is likely to be an hindrance
       * if and when GWT attempts to read bytecode directly.
       */
      // new NamedLocal().new NamedLocalSub().foo();
    }

    public void bar() {
    }

    private void testAllocationExpression() {
      // AllocationExpression
      o = new Super();
      assertEquals("3", o.toString());
      o = new Super(42);
      assertEquals("42", o.toString());
    }

    private void testAndAndExpression() {
      // AND_AND_Expression
      i = 2;
      z = i == 1 && betterNotEval();
      assertFalse(z);
    }

    private void testArrayAllocationExpression() {
      // ArrayAllocationExpression
      ia = new int[4];
      assertEquals(4, ia.length);
      iaa = new int[4][3];
      assertEquals(4, iaa.length);
      assertEquals(3, iaa[2].length);
      iaaa = new int[4][3][];
      assertEquals(4, iaaa.length);
      assertEquals(3, iaaa[2].length);
      assertNull(iaaa[2][2]);
    }

    private void testArrayInitializer() {
      // ArrayInitializer
      ia = new int[] {i, j};
      assertEquals(2, ia.length);
      assertEquals(j, ia[1]);
      iaa = new int[][] {{i, j}};
      assertEquals(1, iaa.length);
      assertEquals(2, iaa[0].length);
      assertEquals(j, iaa[0][1]);
      iaa = new int[][] { {i, j}, ia};
      assertEquals(2, iaa.length);
      assertEquals(2, iaa[0].length);
      assertEquals(j, iaa[0][1]);
      assertEquals(ia, iaa[1]);
    }

    private void testArrayReference() {
      ia = new int[] {i, j};
      // ArrayReference
      i = ia[0];
      assertEquals(ia[0], i);
      ia[0] = i;
    }

    private void testAssertStatement() {
      // AssertStatement

      if (!CoverageTest.class.desiredAssertionStatus()) {
        return;
      }

      i = 1;
      try {
        assert i == 2;
        throw new Error();
      } catch (AssertionError e) {
      }

      try {
        assert i == 3 : true;
        fail();
      } catch (AssertionError e) {
        assertEquals("true", e.getMessage());
      }

      try {
        assert i == 3 : 'c';
        fail();
      } catch (AssertionError e) {
        assertEquals("c", e.getMessage());
      }

      try {
        assert i == 3 : 1.1;
        fail();
      } catch (AssertionError e) {
        assertEquals("1.1", e.getMessage());
      }

      try {
        assert i == 3 : 1.5f;
        fail();
      } catch (AssertionError e) {
        assertEquals("1.5", e.getMessage());
      }

      try {
        assert i == 3 : 5;
        fail();
      } catch (AssertionError e) {
        assertEquals("5", e.getMessage());
      }

      try {
        assert i == 3 : 6L;
        fail();
      } catch (AssertionError e) {
        assertEquals("6", e.getMessage());
      }

      try {
        assert i == 3 : "foo";
        fail();
      } catch (AssertionError e) {
        assertEquals("foo", e.getMessage());
      }

      try {
        assert i == 3 : new Object() {
          @Override
          public String toString() {
            return "bar";
          }
        };
        fail();
      } catch (AssertionError e) {
        assertEquals("bar", e.getMessage());
      }
    }

    private void testAssignment() {
      // Assignment
      i = j;
      assertEquals(j, i);
    }

    private void testBinaryExpression() {
      // BinaryExpression
      i = 4;
      i = i + j;
      assertEquals(6, i);
      i = i - j;
      assertEquals(4, i);
      i = i * j;
      assertEquals(8, i);
      i = i / j;
      assertEquals(4, i);
      i = i % j;
      assertEquals(0, i);
      i = 7;
      i = i & j;
      assertEquals(2, i);
      i = 0;
      i = i | j;
      assertEquals(2, i);
      i = 7;
      i = i ^ j;
      assertEquals(5, i);
      i = i << j;
      assertEquals(20, i);
      i = i >> j;
      assertEquals(5, i);
      i = i >>> j;
      assertEquals(1, i);
    }

    private void testBreakContinueLabelStatement() {
      // BreakStatement, ContinueStatement
      z = true;
      i = 0;
      x = 0;
      outer : while (z) {
        ++x;
        inner : while (z) {
          ++i;
          if (i == 1) {
            continue;
          }
          if (i == 2) {
            continue inner;
          }
          if (i == 3) {
            continue outer;
          }
          if (i == 4) {
            break;
          }
          if (i == 5) {
            break inner;
          }
          if (i == 6) {
            break outer;
          }
        }
      }
      assertEquals(6, i);
      assertEquals(4, x);
    }

    private void testCaseSwitchStatement() {
      // CaseStatement, SwitchStatement
      i = 6;
      switch (j) {
        case 1:
          ++i;
          // fallthrough
        case 2:
          i += 2;
          // fallthrough
        case 3:
          i += 3;
          // fallthrough
        case 4:
          i += 4;
          // fallthrough
        default:
          i += 0;
      }
      assertEquals(15, i);
    }

    @SuppressWarnings({"cast", "SelfAssignment"})
    private void testCastExpression() {
      // CastExpression
      o = (Super) o;
    }

    private void testCharLiteral() {
      // CharLiteral
      i = 'c';
      assertEquals("c", String.valueOf((char) i));
    }

    private void testClassLiteralAccess() {
      // ClassLiteralAccess
      o = Super.class;
      String str = o.toString();

      // Class metadata could be disabled
      if (!str.startsWith("class Class$")) {
        assertEquals("class com.google.gwt.dev.jjs.test.CoverageTest$Super",
            str);
      }
    }

    private void testCompoundAssignment() {
      // CompoundAssignment
      i = 4;
      i += j;
      assertEquals(6, i);
      i -= j;
      assertEquals(4, i);
      i *= j;
      assertEquals(8, i);
      i /= j;
      assertEquals(4, i);
      i %= j;
      assertEquals(0, i);
      i = 7;
      i &= j;
      assertEquals(2, i);
      i = 0;
      i |= j;
      assertEquals(2, i);
      i = 7;
      i ^= j;
      assertEquals(5, i);
      i <<= j;
      assertEquals(20, i);
      i >>= j;
      assertEquals(5, i);
      i >>>= j;
      assertEquals(1, i);
    }

    private void testConditionalExpression() {
      // ConditionalExpression
      z = false;
      i = z ? 7 : j;
      assertEquals(j, i);
    }

    private void testDoStatement() {
      // DoStatement
      i = 3;
      z = false;
      do {
        i += j;
      } while (z);
      assertEquals(5, i);
    }

    private void testEmptyStatement() {
      // EmptyStatement
      ;
    }

    private void testEqualExpression() {
      // EqualExpression
      i = 3;
      assertFalse(i == j);
      assertTrue(i != j);
      assertFalse(i < j);
      assertFalse(i <= j);
      assertTrue(i > j);
      assertTrue(i >= j);
    }

    private void testForeachStatement() {
      ia = new int[] {i, j};
      // Array of primitive.
      for (int q : ia) {
        i = q;
      }
      // Array of primitive with unboxing.
      for (Integer q : ia) {
        i = q;
      }
      // Array of object.
      for (String str : sa) {
        s = str;
      }
      // Iterable.
      for (Object obj : Arrays.asList(new Object(), new Object())) {
        o = obj;
      }
      // Iterable with unboxing.
      for (int q : Arrays.asList(1, 2, 3)) {
        i = q;
      }
      // Iterable with generic cast.
      for (String str : Arrays.asList(sa)) {
        s = str;
      }
      // Iterable with array element.
      for (String[] stra : Arrays.asList(sa, sa, sa)) {
        s = sa[0];
      }
      // Iterable Iterator subclass.
      class SubIterator<T> implements Iterator<T> {
        private final Iterator<T> it;

        public SubIterator(Iterator<T> it) {
          this.it = it;
        }

        @Override
        public boolean hasNext() {
          return it.hasNext();
        }

        @Override
        public T next() {
          return it.next();
        }

        @Override
        public void remove() {
          it.remove();
        }
      }
      class SubIterableString implements Iterable<String> {
        @Override
        public SubIterator<String> iterator() {
          return new SubIterator<String>(Arrays.asList(sa).iterator());
        }
      }
      for (String str : new SubIterableString()) {
        s = str;
      }
    }

    private void testForStatement() {
      // ForStatement
      i = 0;
      for (int q = 0, v = 4; q < v; ++q) {
        i += q;
      }
      assertEquals(6, i);
      for (i = 0; i < 4; ++i) {
      }
      assertEquals(4, i);
    }

    private void testIfStatement() {
      // IfStatement
      z = false;
      if (z) {
        fail();
      }
      if (z) {
        fail();
      } else {
        assertFalse(z);
      }
      if (!z) {
        assertFalse(z);
      } else {
        fail();
      }
    }

    private void testInstanceOfExpression() {
      // InstanceOfExpression
      Object o = CoverageTest.this;
      assertTrue(o instanceof CoverageBase);
    }

    private void testLiterals() {
      // DoubleLiteral
      d = 3.141592653589793;
      assertEquals(3, (int) d);

      // FalseLiteral
      assertFalse(false);

      // FloatLiteral
      f = 3.1415927f;
      assertEquals(3, (int) f);

      // IntLiteral
      i = 4;

      // IntLiteralMinValue
      i = -2147483648;

      // LongLiteral
      l = 4L;

      // LongLiteralMinValue
      l = -9223372036854775808L;

      // NullLiteral
      o = null;

      // StringLiteral
      s = "f'oo\b\t\n\f\r\"\\";
      assertEquals("f" + '\'' + 'o' + 'o' + '\b' + '\t' + '\n' + '\f' + '\r' + '"' + '\\', s);

      // TrueLiteral
      assertTrue(true);
    }

    private void testOrOrExpression() {
      // OR_OR_Expression
      i = 1;
      assertTrue(i == 1 || betterNotEval());
    }

    private void testPostfixExpression() {
      // PostfixExpression
      i = 1;
      assertEquals(1, i++);
      assertEquals(2, i--);
    }

    private void testPrefixExpression() {
      // PrefixExpression
      i = 1;
      assertEquals(2, ++i);
      assertEquals(1, --i);
    }

    private void testQualifiedAllocationExpression() {
      // QualifiedAllocationExpression
      o = new Inner();
      o = CoverageTest.this.new Inner();
      o = new CoverageTest().new Inner();
    }

    private void testQualifiedNameReference() {
      // QualifiedNameReference
      CoverageTest m = new CoverageTest();
      ia = new int[2];
      assertEquals("1", 2, ia.length);
      assertEquals("2", 2, m.j);
      assertEquals("3", 4, m.y);
      assertEquals("4", 2, new CoverageTest().j);
      assertEquals("5", 4, new CoverageTest().y);
      assertEquals("6", 2, m.next.j);
      assertEquals("7", 4, m.next.y);
      assertEquals("8", 2, new CoverageTest().next.j);
      assertEquals("9", 4, new CoverageTest().next.y);
      assertEquals("A", 2, m.getNext().j);
      assertEquals("B", 4, m.getNext().y);
      assertEquals("C", 2, new CoverageTest().getNext().j);
      assertEquals("D", 4, new CoverageTest().getNext().y);
    }

    private void testReferenceCalls() {
      // MessageSend, QualifiedSuperReference, QualifiedThisReference,
      // SuperReference, ThisReference
      Inner other = new Inner();
      foo();
      this.foo();
      other.foo();
      CoverageTest.this.foo();
      super.foo();
      Inner.super.foo();
      CoverageTest.super.foo();

      sfoo();
      this.sfoo();
      CoverageTest.sfoo();
      Inner.sfoo();
      Super.sfoo();
      other.sfoo();
      CoverageTest.this.sfoo();
      super.sfoo();
      Inner.super.sfoo();
      CoverageTest.super.sfoo();
    }

    private Inner testReferences() {
      // FieldReference, QualifiedSuperReference, QualifiedThisReference,
      // SuperReference, ThisReference
      Inner other = new Inner();
      i = 3;
      i = i + j + x + y;
      assertEquals(12, i);
      i = this.i + this.j + this.x + this.y;
      assertEquals(21, i);
      i = CoverageTest.i + CoverageTest.j;
      assertEquals(8, i);
      i = Inner.i + Inner.j;
      assertEquals(10, i);
      i = Super.i + Super.j;
      assertEquals(12, i);
      i = other.i + other.j + other.x + other.y;
      assertEquals(21, i);
      i = Inner.this.i + Inner.this.j + Inner.this.x + Inner.this.y;
      assertEquals(30, i);
      i = CoverageTest.this.i + CoverageTest.this.j + CoverageTest.this.x
          + CoverageTest.this.y;
      assertEquals(15, i);
      i = super.i + super.j + super.x + super.y;
      assertEquals(25, i);
      i = Inner.super.i + Inner.super.j + Inner.super.x + Inner.super.y;
      assertEquals(35, i);
      i = CoverageTest.super.i + CoverageTest.super.j + CoverageTest.super.x
          + CoverageTest.super.y;
      assertEquals(10, i);
      return other;
    }

    private void testReturnStatement() {
      // ReturnStatement
      assertEquals("foo", doReturnFoo());
      if (true) {
        return;
      }
      fail();
    }

    private void testSynchronizedStatement() {
      // SynchronizedStatement
      synchronized (inner) {
        inner.i = i;
      }
    }

    private void testTryCatchFinallyThrowStatement() {
      // ThrowStatement, TryStatement
      try {
        i = 3;
        if (true) {
          throw new Exception();
        }
        fail();
      } catch (Exception e) {
      } finally {
        i = 7;
      }
      assertEquals(7, i);

      try {
        try {
          i = 3;
        } catch (Throwable t) {
          fail();
        }
      } catch (Throwable t) {
        fail();
      } finally {
        i = 7;
      }
      assertEquals(7, i);
    }

    private void testUnaryExpression() {
      // UnaryExpression
      i = 4;
      assertEquals(-4, -i);
      assertEquals(-5, ~i);
      z = true;
      assertFalse(!z);
    }

    private void testWhileStatement() {
      // WhileStatement
      z = false;
      while (z) {
        fail();
      }
    }
  }

  /**
   * TODO: document me.
   */
  public static class Super {
    public static int i = 2;
    public static final int j = 2;

    // Initializer
    static {
      Super.i = 1;
    }

    // Initializer
    static {
      Super.i = 3;
    }

    protected static void sfoo() {
    }

    public int x = 2;
    public final int y = 4;

    // Initializer
    {
      x = 1;
    }

    // Initializer
    {
      x = 3;
    }

    public Super() {
    }

    public Super(int i) {
      x = i;
    }

    public void foo() {
    }

    @Override
    public String toString() {
      return String.valueOf(x);
    }
  }

  private static class InnerSub extends Inner {
    private class InnerSubSub extends InnerSub {
      {
        asdfasdfasdf = InnerSub.this.asdfasdfasdf;
        InnerSub.this.asdfasdfasdf = asdfasdfasdf;
        asdfasdfasdf = super.asdfasdfasdf;
        super.asdfasdfasdf = asdfasdfasdf;
      }

      void fda() {
        asdfasdfasdf = InnerSub.this.asdfasdfasdf;
        InnerSub.this.asdfasdfasdf = asdfasdfasdf;
        asdfasdfasdf = super.asdfasdfasdf;
        super.asdfasdfasdf = asdfasdfasdf;
      }
    }

    private int asdfasdfasdf = 3;

    InnerSub() {
      new CoverageTest().super();
    }
  }

  private static class SecondMain {
    private class FunkyInner extends Inner {
      FunkyInner() {
        new CoverageTest().super();
      }
    }
  }

  public static double d;

  public static float f;

  public static int i = 1 + 2 + 3;

  public static int[] ia;

  public static int[][] iaa;

  public static int[][][] iaaa;

  public static final int j = 2;

  public static long l;

  public static Object o;

  public static String s = "foo";

  public static String[] sa = new String[]{"foo", "bar", "bar"};

  public static CoverageTest singleton;

  public static boolean z;

  public static boolean betterNotEval() {
    fail();
    return false;
  }

  @Override
  public void gwtSetUp() throws Exception {
    super.gwtSetUp();
    x = 3;
    d = 0;
    f = 0;
    i = 1 + 2 + 3;
    ia = null;
    iaa = null;
    iaaa = null;
    l = 0;
    o = null;
    s = "foo";
    sa = new String[]{"foo", "bar", "bar"};
    z = false;
  }
  
  protected static void sfoo() {
  }

  private static String doReturnFoo() {
    if (true) {
      return "foo";
    }
    fail();
    return "bar";
  }

  public final Inner inner = new Inner();

  public CoverageTest next;

  public int x = 3;

  public final int y = 4;

  public CoverageTest() {
    if (singleton == null) {
      singleton = this;
    }
    next = this;
  }

  @Override
  public void foo() {
  }

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

  public CoverageTest getNext() {
    return next;
  }

  public void testAllocationExpression() {
    inner.testAllocationExpression();
  }

  public void testAndAndExpression() {
    inner.testAndAndExpression();
  }

  public void testArrayAllocationExpression() {
    inner.testArrayAllocationExpression();
  }

  public void testArrayInitializer() {
    inner.testArrayInitializer();
  }

  public void testArrayReference() {
    inner.testArrayReference();
  }

  public void testAssertStatement() {
    inner.testAssertStatement();
  }

  public void testAssignment() {
    inner.testAssignment();
  }

  public void testBinaryExpression() {
    inner.testBinaryExpression();
  }

  public void testBreakContinueLabelStatement() {
    inner.testBreakContinueLabelStatement();
  }

  public void testCaseSwitchStatement() {
    inner.testCaseSwitchStatement();
  }

  public void testCastExpression() {
    inner.testCastExpression();
  }

  public void testCharLiteral() {
    inner.testCharLiteral();
  }

  public void testClassLiteralAccess() {
    inner.testClassLiteralAccess();
  }

  public void testCompoundAssignment() {
    inner.testCompoundAssignment();
  }

  public void testConditionalExpression() {
    inner.testConditionalExpression();
  }

  public void testDoStatement() {
    inner.testDoStatement();
  }

  public void testEmptyStatement() {
    inner.testEmptyStatement();
  }

  public void testEqualExpression() {
    inner.testEqualExpression();
  }

  public void testForeachStatement() {
    inner.testForeachStatement();
  }

  public void testForStatement() {
    inner.testForStatement();
  }

  public void testIfStatement() {
    inner.testIfStatement();
  }

  public void testInstanceOfExpression() {
    inner.testInstanceOfExpression();
  }

  public void testLiterals() {
    inner.testLiterals();
  }

  public void testOrOrExpression() {
    inner.testOrOrExpression();
  }

  public void testPostfixExpression() {
    inner.testPostfixExpression();
  }

  public void testPrefixExpression() {
    inner.testPrefixExpression();
  }

  public void testQualifiedAllocationExpression() {
    inner.testQualifiedAllocationExpression();
  }

  public void testQualifiedNameReference() {
    inner.testQualifiedNameReference();
  }

  public void testReferenceCalls() {
    inner.testReferenceCalls();
  }

  public void testReferences() {
    inner.testReferences();
  }

  public void testReturnStatement() {
    inner.testReturnStatement();
  }

  public void testSynchronizedStatement() {
    inner.testSynchronizedStatement();
  }

  public void testTryCatchFinallyThrowStatement() {
    inner.testTryCatchFinallyThrowStatement();
  }

  public void testUnaryExpression() {
    inner.testUnaryExpression();
  }

  public void testWhileStatement() {
    inner.testWhileStatement();
  }

}

abstract class CoverageBase extends GWTTestCase {

  public static int i = 1;
  public static final int j = 2;

  protected static void sfoo() {
  }

  public int x = 3;
  public final int y = 4;

  public void foo() {
  }

}
