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

/**
 * Tests the JsStaticEval optimizer.
 */
public class JsStaticEvalTest extends OptimizerTestBase {

  public void testAddLiterals() throws Exception {
    assertEquals("alert(42);", optimize("alert(21+21);"));
    assertEquals("alert('Hello World');", optimize("alert('Hello '+'World');"));
    assertEquals("alert('Hello 42');", optimize("alert('Hello ' + 42);"));
    assertEquals("alert('42 Hello');", optimize("alert(42 + ' Hello');"));
    assertEquals("alert('42 Hello');", optimize("alert(42.0 + ' Hello');"));
    assertEquals("alert('42.2 Hello');", optimize("alert(42.2 + ' Hello');"));
    assertEquals("alert('Hello 42.2');", optimize("alert('Hello ' + 42.2);"));
    assertEquals("alert('2004318071');", optimize("alert(2004318071 + '');"));
  }

  public void testAssociativity() throws Exception {
    // Simple test
    assertEquals("alert(a||b||c||d);", optimize("alert((a||b)||(c||d));"));
    assertEquals("alert(a||b||c||d||e||f);", optimize("alert((a||b)||(c||(d||(e||f))));"));
    assertEquals("alert(a&&b&&c&&d);", optimize("alert((a&&b)&&(c&&d));"));

    // Preserve precedence
    assertEquals("alert((a||b)&&(c||d));",
        optimize("alert((a || b) && (c || d));"));
    assertEquals("alert(a&&b||c&&d);",
        optimize("alert((a && b) || ( c && d));"));
    assertEquals("a(),b&&c();", optimize("a(), b && c()"));
    assertEquals("a()&&b,c();", optimize("a() && b, c()"));

    // Don't damage math expressions
    assertEquals("alert(seconds/3600);",
        optimize("alert(seconds / (60 * 60))"));
    assertEquals("alert(seconds/60*60);",
        optimize("alert(seconds / 60 * 60)"));
    assertEquals("alert(1-(1-foo));", optimize("alert(1 - (1 - foo))"));

    // Don't damage assignments
    assertEquals("alert((a=0,b=foo));",
        optimize("alert((a = 0, b = (bar, foo)))"));
    assertEquals("alert(1+(a='2')+3+4);",
        optimize("alert(1 + (a = '2') + 3 + 4);"));
    assertEquals("alert(1+(a='2')+7);",
        optimize("alert(1 + (a = '2') + (3 + 4));"));

    // Break comma expressions up
    assertEquals("alert((a(),b(),c(),d));",
        optimize("alert(((a(),b()),(c(),d)));"));
    assertEquals("alert((a(),b(),c(),d));",
        optimize("alert(((a(),b()),(c(),d)));"));
    // and remove expressions without side effects
    assertEquals("alert(d);", optimize("alert(((a,b),(c,d)));"));

    // Pattern of coercing a numeric add operation to a string
    assertEquals("alert(''+(a+b));", optimize("alert('' + (a + b))"));

    // Tests involving numeric and string literals and identifiers
    assertEquals("alert(21+(1+$foo));",
        optimize("alert((20 + 1) + (1 + $foo));"));

    // These are also tricky, because $foo could be non-numeric
    assertEquals("alert($foo+1+21);", optimize("alert(($foo + 1) + (20 + 1));"));
    assertEquals("alert($bar+13+7+(2+$foo));",
        optimize("alert((($bar + (10 + 3)) + (2 + 5)) + (2 + $foo));"));

    // Without type info, there's nothing that can be done for this expr
    assertEquals("alert($foo+($bar+($baz+$quux)));",
        optimize("alert($foo + ($bar + ($baz + $quux)));"));
  }

  /**
   * Test for issue 7088. JsStatic eval infinite loop in
   * {@link JsStaticEval.StaticEvalVisitor#endVisit(JsBlock, JsContext)}
   */
  public void testDeclareAfterReturn() throws Exception {
    // TODO(rluble):  Note that the source output has the wrong precedence for function definition
    // and application.
    assertEquals("(function(){return 0;var a;var b}());",
        optimize("(function(){return 0;{var a;var b}})();"));
  }

  public void testIfWithEmptyThen() throws Exception {
    assertEquals("a();", optimize("if (a()) { }"));
  }

  public void testIfWithEmptyThenAndElseExpression() throws Exception {
    assertEquals("a()||b();", optimize("if (a()) { } else { b(); }"));
  }

  public void testIfWithEmptyThenAndElse() throws Exception {
    assertEquals("if(!a()){throw 1}",
        optimize("if (a()) { } else { throw 1; }"));
  }

  public void testIfWithEmptyThenAndEmptyElse() throws Exception {
    assertEquals("a();", optimize("if (a()) { } else { }"));
  }

  public void testIfWithThenAndEmptyElse() throws Exception {
    assertEquals("if(a()){throw 1}", optimize("if (a()) { throw 1; } else { }"));
  }

  public void testIfWithThenExpressionAndEmptyElse() throws Exception {
    assertEquals("a()&&b();", optimize("if (a()) { b() } else { }"));
  }

  public void testIfWithThenExpressionAndElseExpression() throws Exception {
    assertEquals("a()?b():c();", optimize("if (a()) { b() } else { c(); }"));
  }

  public void testIfWithThenExpressionAndElseStatement() throws Exception {
    // This can't be optimized further
    assertEquals("if(a()){b()}else{throw 1}",
        optimize("if (a()) { b() } else { throw 1; }"));
  }

  public void testLiteralCompares() throws Exception {
    assertEquals("alert(false);", optimize("alert(2 != 2)"));
    assertEquals("alert(false);", optimize("alert(2 == 3)"));
    assertEquals("alert(true);", optimize("alert(2 == 2)"));
    assertEquals("alert(true);", optimize("alert(2 != 3)"));
    assertEquals("alert(true);", optimize("alert(2 < 3)"));
    assertEquals("alert(true);", optimize("alert(3 <= 3)"));
    assertEquals("alert(true);", optimize("alert(3 > 2)"));
    assertEquals("alert(true);", optimize("alert(3 >= 3)"));
    assertEquals("alert(false);", optimize("alert(2 > 3)"));
    assertEquals("alert(false);", optimize("alert(2 >= 3)"));
    assertEquals("alert(false);", optimize("alert(3 < 2)"));
    assertEquals("alert(false);", optimize("alert(3 <= 2)"));
    assertEquals("alert(false);", optimize("alert(1.8E+10308 < 1.9E+10308)"));
    assertEquals("alert(false);", optimize("alert(1.8E+10308 > 1.9E+10308)"));
    assertEquals("alert(true);", optimize("alert(\"a\" == \"a\")"));
    assertEquals("alert(true);", optimize("alert(\"a\" === \"a\")"));
    assertEquals("alert(true);", optimize("alert(\"a\" != \"b\")"));
    assertEquals("alert(true);", optimize("alert(\"a\" !== \"b\")"));
    assertEquals("alert(true);", optimize("alert(\"a\" != null)"));
    assertEquals("alert(true);", optimize("alert(\"a\" !== null)"));
  }

  public void testLiteralEqNull() throws Exception {
    assertEquals("alert(false);", optimize("alert('test' == null)"));
  }

  public void testLiteralNeNull() throws Exception {
    assertEquals("alert(true);", optimize("alert('test' != null)"));
  }

  public void testNullEqNull() throws Exception {
    assertEquals("alert(true);", optimize("alert(null == null)"));
  }

  public void testNullNeNull() throws Exception {
    assertEquals("alert(false);", optimize("alert(null != null)"));
  }

  private String optimize(String js) throws Exception {
    return optimizeToSource(js, JsStaticEval.class);
  }
}
