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

/**
 * Tests that static evaluation, mainly in
 * {@link com.google.gwt.dev.jjs.impl.DeadCodeElimination DeadCodeElimination},
 * does not go wrong.
 * 
 * This test does not verify that static evaluation is happening, merely that it
 * does not make incorrect changes. All calls to assert should become trivial things like
 * <code>assertEquals("", 5, 5)</code>.
 * 
 * To verify the test itself, which includes a lot of random constants, run in
 * Development Mode.
 */
public class JStaticEvalTest extends GWTTestCase {
  private static void assertEquals(double expected, double actual) {
    assertEquals(expected, actual, 0.0001);
  }

  private volatile double fieldDoubleFive = 5.0;
  private volatile boolean fieldFalse = false;
  private volatile float fieldFloatFive = 5.0F;
  private volatile int[] fieldIntArray = new int[10];
  private volatile int fieldIntFive = 5;
  private volatile long fieldLongFive = 5L;
  private volatile Object fieldObject = new Object();
  private volatile boolean fieldTrue = true;

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

  /**
   * Tests simplifications on ternary conditional expressions.
   */
  public void testConditionalExpressions() {
    assertEquals(1, returnTrue() ? 1 : 2);
    assertEquals(2, returnFalse() ? 1 : 2);
    assertEquals(true, fieldTrue ? true : fieldFalse);
    assertEquals(false, fieldTrue ? false : fieldTrue);
    assertEquals(true, fieldTrue ? fieldTrue : false);
    assertEquals(false, fieldTrue ? fieldFalse : true);
    assertEquals(2, !fieldTrue ? 1 : 2);
    assertEquals(2, !fieldTrue ? 1 : 2);

    /*
     * This example causes Simplifier to recurse into itself, which in previous
     * versions could cause a NullPointerException. The sequence of
     * simplifications is:
     */
    // (fieldFalse = false, !fieldFalse) ? 1 : 2 // inlining
    // (fieldFalse = false, !fieldfalse ? 1 : 2) // move multi outward
    // (fieldFalse = false, fieldFalse ? 2 : 1) // flip negative condition
    int res = doSomethingAndReturnTrue() ? 1 : 2;
    assertEquals(1, res);
  }

  /**
   * Test "true == booleanField" and permutations, as well as "true == false"
   * and permutations.
   */
  @SuppressWarnings("IdentityBinaryExpression")
  public void testEqualsBool() {
    assertTrue(fieldTrue == returnTrue());
    assertTrue(returnTrue() == fieldTrue);
    assertFalse(fieldTrue == returnFalse());
    assertFalse(returnFalse() == fieldTrue);
    assertTrue(fieldTrue != returnFalse());
    assertTrue(returnFalse() != fieldTrue);
    assertFalse(fieldTrue != returnTrue());
    assertFalse(returnTrue() != fieldTrue);
    assertTrue(returnTrue() & returnTrue());
    assertTrue(returnTrue() | returnTrue());
  }

  /**
   * Tests equality on literals.
   */
  public void testEqualsLit() {
    assertTrue(returnTrue() == returnTrue());
    assertFalse(returnTrue() == returnFalse());
    assertFalse(returnFalse() == returnTrue());
    assertTrue(returnFalse() == returnFalse());
    assertFalse(returnTrue() != returnTrue());
    assertTrue(returnTrue() != returnFalse());
    assertTrue(returnFalse() != returnTrue());
    assertFalse(returnFalse() != returnFalse());

    assertTrue(returnIntFive() == returnIntFive());
    assertFalse(returnIntFive() != returnIntFive());
    assertTrue(returnDoubleOneHalf() == returnDoubleOneHalf());
    assertFalse(returnDoubleOneHalf() != returnDoubleOneHalf());
  }

  /**
   * Test rewriting if (!x) a else b to if(x) b else a. Likewise for conditional
   * expressions.
   */
  public void testFlippedIf() {
    String branch;
    if (!fieldTrue) {
      branch = "A";
    } else {
      branch = "B";
    }
    assertEquals("B", branch);
    assertEquals("B", !fieldTrue ? "A" : "B");
  }

  /**
   * Tests constant folding.
   */
  @SuppressWarnings("IdentityBinaryExpression")
  public void testOpsOnLiterals() {
    assertEquals(10, returnIntFive() + returnIntFive());
    assertEquals(0, returnIntFive() - returnIntFive());
    assertEquals(25, returnIntFive() * returnIntFive());
    assertEquals(1, returnIntFive() / returnIntFive());
    assertEquals(3, returnIntThree() % returnIntFive());
    assertEquals(96, returnIntThree() << returnIntFive());
    assertEquals(0, returnIntThree() >> returnIntFive());
    assertEquals(134217727, (-returnIntThree()) >>> returnIntFive());
    assertEquals(7, returnIntFive() | returnIntThree());
    assertEquals(1, returnIntFive() & returnIntThree());
    assertEquals(0, returnIntFive() ^ returnIntFive());

    assertEquals(1.0, returnDoubleOneHalf() + returnDoubleOneHalf());
    assertEquals(0.0, returnDoubleOneHalf() - returnDoubleOneHalf());
    assertEquals(0.25, returnDoubleOneHalf() * returnDoubleOneHalf());
    assertEquals(1.0, returnDoubleOneHalf() / returnDoubleOneHalf());
    assertEquals(0.5, returnDoubleOneHalf() % returnDoubleFive());

    assertTrue(returnIntFive() == returnIntFive());
    assertTrue(returnLongFive() == returnLongFive());
    assertTrue(returnFloatFive() == returnFloatFive());
    assertTrue(returnDoubleFive() == returnDoubleFive());
    assertTrue(returnCharFive() == returnCharFive());
    assertTrue(returnIntFive() == returnFloatFive());

    assertFalse(returnIntFive() != returnIntFive());
    assertFalse(returnLongFive() != returnLongFive());
    assertFalse(returnFloatFive() != returnFloatFive());
    assertFalse(returnDoubleFive() != returnDoubleFive());
    assertFalse(returnCharFive() != returnCharFive());
    assertFalse(returnIntFive() != returnFloatFive());

    assertTrue(returnTrue() || returnFalse());
    assertFalse(returnTrue() && returnFalse());
    assertFalse(returnTrue() ^ returnTrue());

    assertFalse(returnIntFive() < returnIntThree());
    assertFalse(returnIntFive() <= returnIntThree());
    assertTrue(returnIntFive() > returnIntThree());
    assertTrue(returnIntFive() >= returnIntThree());

    assertTrue(returnDoubleOneHalf() < returnDoubleFive());
    assertTrue(returnDoubleOneHalf() <= returnDoubleFive());
    assertFalse(returnDoubleOneHalf() > returnDoubleFive());
    assertFalse(returnDoubleOneHalf() >= returnDoubleFive());

    assertEquals(10, returnIntFive() + returnCharFive());
    assertTrue(returnIntThree() < returnCharFive());

    assertEquals(-5, -returnIntFive());
    assertEquals(-5L, -returnLongFive());
    assertEquals(-5.0, -returnFloatFive());
    assertEquals(-5.0, -returnDoubleFive());
    assertEquals(-10000000000000000000.0, -returnBigDouble());

    assertFalse(!returnTrue());
    assertTrue(!returnFalse());

    assertEquals(-6, ~returnIntFive());
    assertEquals(-6L, ~returnLongFive());

    assertEquals(65536, ((char) returnIntNegOne()) + 1);
    assertEquals(10.0, ((double) returnIntFive()) + ((double) returnIntFive()));
    assertEquals(1.5, returnFloatOneHalf() + 1);
  }

  /**
   * Test various useless operations like x+0 and x*1.
   */
  public void testUselessOps() {
    assertEquals(5, fieldIntFive + returnIntZero());
    assertEquals(5L, fieldLongFive + returnIntZero());
    assertEquals(5.0, fieldFloatFive + returnIntZero());
    assertEquals(5.0, fieldDoubleFive + returnIntZero());
    assertEquals(5.0, fieldDoubleFive + returnCharZero());

    assertEquals(5, returnIntZero() + fieldIntFive);
    assertEquals(5L, returnIntZero() + fieldLongFive);
    assertEquals(5.0, returnIntZero() + fieldFloatFive);
    assertEquals(5.0, returnIntZero() + fieldDoubleFive);
    assertEquals(5.0, returnCharZero() + fieldDoubleFive);

    assertEquals(5, fieldIntFive - returnIntZero());
    assertEquals(5L, fieldLongFive - returnIntZero());
    assertEquals(5.0, fieldFloatFive - returnIntZero());
    assertEquals(5.0, fieldDoubleFive - returnIntZero());

    assertEquals(-5, returnIntZero() - fieldIntFive);
    assertEquals(-5L, returnIntZero() - fieldLongFive);
    assertEquals(-5.0, returnDoubleZero() - fieldLongFive);
    assertEquals(-5.0, returnIntZero() - fieldFloatFive);
    assertEquals(-5.0, returnIntZero() - fieldDoubleFive);

    assertEquals(5, fieldIntFive * returnIntOne());
    assertEquals(5L, fieldLongFive * returnLongOne());
    assertEquals(5.0, fieldFloatFive * returnFloatOne());
    assertEquals(5.0, fieldDoubleFive * returnDoubleOne());

    assertEquals(5, fieldIntFive / returnIntOne());
    assertEquals(5L, fieldLongFive / returnLongOne());
    assertEquals(5.0, fieldFloatFive / returnFloatOne());
    assertEquals(5.0, fieldDoubleFive / returnDoubleOne());

    assertEquals(-5, fieldIntFive * -returnIntOne());
    assertEquals(-5L, fieldLongFive * -returnLongOne());
    assertEquals(-5.0, fieldFloatFive * -returnFloatOne());
    assertEquals(-5.0, fieldDoubleFive * -returnDoubleOne());
    assertEquals(-327675, fieldIntFive * -returnCharNegOne());

    assertEquals(-5, fieldIntFive / -returnIntOne());
    assertEquals(-5L, fieldLongFive / -returnLongOne());
    assertEquals(-5.0, fieldFloatFive / -returnFloatOne());
    assertEquals(-5.0, fieldDoubleFive / -returnDoubleOne());

    assertEquals(5, -returnMinusFieldIntFive());
    assertEquals(5L, -returnMinusFieldLongFive());
    assertEquals(5.0, -returnMinusFieldFloatFive());
    assertEquals(5.0, -returnMinusFieldDoubleFive());

    assertEquals(5, fieldIntFive << returnIntZero());
    assertEquals(5L, fieldLongFive << returnIntZero());

    assertEquals(5, fieldIntFive >> returnIntZero());
    assertEquals(5L, fieldLongFive >> returnIntZero());

    assertEquals(5, fieldIntFive >>> returnIntZero());
    assertEquals(5L, fieldLongFive >>> returnIntZero());

    assertTrue(!returnNotFieldTrue());

    assertTrue(fieldTrue ^ returnFalse());
    assertFalse(fieldTrue ^ returnTrue());
    assertFalse(returnTrue() ^ fieldTrue);
    assertTrue(returnFalse() ^ fieldTrue);

    assertEquals(0.0, fieldIntFive * returnDoubleZero());

    // do not simplify x*0 if x has a side effect
    try {
      assertEquals(0.0, throwError() * returnDoubleZero());
      fail("Expected an exception");
    } catch (IllegalArgumentException e) {
    }

    assertEquals(0.0, returnDoubleZero() * fieldIntFive);

    // do not simplify 0*x if x has a side effect
    try {
      assertEquals(0.0, returnDoubleZero() * throwError());
      fail("Expected an exception");
    } catch (IllegalArgumentException e) {
    }

    assertTrue(fieldIntArray != null);
    assertFalse(fieldIntArray == null);
    if (fieldIntArray == null) {
      fail();
    }
    if (fieldIntArray != null) {
    } else {
      fail();
    }

    // do not simplify foo==null if foo can be a string
    assertTrue(returnEmptyString() != null);
    assertFalse(returnEmptyString() == null);
    if (returnEmptyString() == null) {
      fail();
    }
    assertFalse(fieldObject == null);
    if (fieldObject == null) {
      fail();
    }
  }

  /**
   * This method will inline as a multi.
   */
  private boolean doSomethingAndReturnTrue() {
    fieldTrue = true;
    return !fieldFalse;
  }

  // All of these returnFoo() methods exist so that the
  // JDT will not be able to statically evaluate with
  // the returned values. These simple methods will be
  // inlined by GWT, though, thus giving its static
  // evaluation a chance to run.

  /**
   * Return a double too large to fit in a long.
   */
  private double returnBigDouble() {
    return 10000000000000000000.0;
  }

  private char returnCharFive() {
    return (char) 5;
  }

  private char returnCharNegOne() {
    return (char) -1;
  }

  private char returnCharZero() {
    return (char) 0;
  }

  private double returnDoubleFive() {
    return 5.0;
  }

  private double returnDoubleOne() {
    return 1.0;
  }

  private double returnDoubleOneHalf() {
    return 0.5;
  }

  private double returnDoubleZero() {
    return 0.0;
  }

  private String returnEmptyString() {
    return "";
  }

  private boolean returnFalse() {
    return false;
  }

  private float returnFloatFive() {
    return 5.0F;
  }

  private float returnFloatOne() {
    return 1.0F;
  }

  private float returnFloatOneHalf() {
    return 0.5F;
  }

  private int returnIntFive() {
    return 5;
  }

  private int returnIntNegOne() {
    return -1;
  }

  private int returnIntOne() {
    return 1;
  }

  private int returnIntThree() {
    return 3;
  }

  private int returnIntZero() {
    return 0;
  }

  private long returnLongFive() {
    return 5L;
  }

  private long returnLongOne() {
    return 1L;
  }

  private double returnMinusFieldDoubleFive() {
    return -fieldDoubleFive;
  }

  private float returnMinusFieldFloatFive() {
    return -fieldFloatFive;
  }

  private int returnMinusFieldIntFive() {
    return -fieldIntFive;
  }

  private long returnMinusFieldLongFive() {
    return -fieldLongFive;
  }

  private boolean returnNotFieldTrue() {
    return !fieldTrue;
  }

  private boolean returnTrue() {
    return true;
  }

  private int throwError() {
    throw new IllegalArgumentException();
  }
}
