/*
 * Copyright 2011 The Closure Compiler Authors.
 *
 * 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.javascript.jscomp;

/**
 * Unit tests for {@link RescopeGlobalSymbols}
 *
 */
public class RescopeGlobalSymbolsTest extends CompilerTestCase {

  private final String namespace = "_";

  public RescopeGlobalSymbolsTest() {
  }

  @Override protected CompilerPass getProcessor(Compiler compiler) {
    return new RescopeGlobalSymbols(compiler, namespace, false);
  }

  @Override
  protected int getNumRepetitions() {
    return 1;
  }

  public void testVarDeclarations() {
    test("var a = 1;", "_.a = 1;");
    test("var a = 1, b = 2, c = 3;", "_.a = 1; _.b = 2; _.c = 3;");
    test("var a = 'str', b = 1, c = { foo: 'bar' }, d = function() {};",
        "_.a = 'str'; _.b = 1; _.c = { foo: 'bar' }; _.d = function() {};");
    test("if(1){var x = 1;}", "if(1){_.x = 1;}");
    test("var x;", "");
    test("var a, b = 1;", "_.b = 1");
  }

  public void testForLoops() {
    test("for (var i = 0; i < 1000; i++);",
        "for (_.i = 0; _.i < 1000; _.i++);");
    test("for (var i = 0, c = 2; i < 1000; i++);",
        "for (_.i = 0, _.c = 2; _.i < 1000; _.i++);");
    test("for (var i = 0, c = 2, d = 3; i < 1000; i++);",
        "for (_.i = 0, _.c = 2, _.d = 3; _.i < 1000; _.i++);");
    test("for (var i = 0, c = 2, d = 3, e = 4; i < 1000; i++);",
        "for (_.i = 0, _.c = 2, _.d = 3, _.e = 4; _.i < 1000; _.i++);");
    test("for (var i = 0; i < 1000;)i++;",
        "for (_.i = 0; _.i < 1000;)_.i++;");
    test("for (var i = 0,b; i < 1000;)i++;b++",
        "for (_.i = 0,_.b; _.i < 1000;)_.i++;_.b++");
    test("var o={};for (var i in o)i++;",
        "_.o={};for (_.i in _.o)_.i++;");
  }

  public void testFunctionStatements() {
    test("function test(){}",
        "_.test=function (){}");
    test("if(1)function test(){}",
        "if(1)_.test=function (){}");
    new StringCompare().testFreeCallSemantics();
  }

  public void testDeeperScopes() {
    test("var a = function(b){return b}",
        "_.a = function(b){return b}");
    test("var a = function(b){var a; return a+b}",
        "_.a = function(b){var a; return a+b}");
    test("var a = function(a,b){return a+b}",
        "_.a = function(a,b){return a+b}");
    test("var x=1,a = function(b){var a; return a+b+x}",
        "_.x=1;_.a = function(b){var a; return a+b+_.x}");
    test("var x=1,a = function(b){return function(){var a;return a+b+x}}",
        "_.x=1;_.a = function(b){return function(){var a; return a+b+_.x}}");
  }

  public void testTryCatch() {
    test("try{var a = 1}catch(e){throw e}",
        "try{_.a = 1}catch(e){throw e}");
  }

  public void testShadow() {
    test("var _ = 1; (function () { _ = 2 })()",
        "_._ = 1; (function () { _._ = 2 })()");
    test("function foo() { var _ = {}; _.foo = foo; _.bar = 1; }",
        "_.foo = function () { var _$ = {}; _$.foo = _.foo; _$.bar = 1}");
    test("function foo() { var _ = {}; _.foo = foo; _.bar = 1; " +
        "(function() { var _ = 0;})() }",
        "_.foo = function () { var _$ = {}; _$.foo = _.foo; _$.bar = 1; " +
        "(function() { var _$ = 0;})() }");
    test("function foo() { var _ = {}; _.foo = foo; _.bar = 1; " +
        "var _$ = 1; }",
        "_.foo = function () { var _$ = {}; _$.foo = _.foo; _$.bar = 1; " +
        "var _$$ = 1; }");
    test("function foo() { var _ = {}; _.foo = foo; _.bar = 1; " +
        "var _$ = 1; (function() { _ = _$ })() }",
        "_.foo = function () { var _$ = {}; _$.foo = _.foo; _$.bar = 1; " +
        "var _$$ = 1; (function() { _$ = _$$ })() }");
    test("function foo() { var _ = {}; _.foo = foo; _.bar = 1; " +
        "var _$ = 1, _$$ = 2 (function() { _ = _$ = _$$; " +
        "var _$, _$$$ })() }",
        "_.foo = function () { var _$ = {}; _$.foo = _.foo; _$.bar = 1; " +
        "var _$$ = 1, _$$$ = 2 (function() { _$ = _$$ = _$$$; " +
        "var _$$, _$$$$ })() }");
    test("function foo() { var _a = 1;}",
        "_.foo = function () { var _a = 1;}");
    // We accept this unnecessary renaming as acceptable to simplify pattern
    // matching in the traversal.
    test("function foo() { var _$a = 1;}",
        "_.foo = function () { var _$a$ = 1;}");
  }

  public void testExterns() {
    test("var document;",
        "document",
        "window.document", null, null);
    test("var document;",
        "document.getElementsByTagName('test')",
        "window.document.getElementsByTagName('test')", null, null);
    test("var document;",
        "window.document.getElementsByTagName('test')",
        "window.document.getElementsByTagName('test')", null, null);
    test("var document;document.getElementsByTagName",
        "document.getElementsByTagName('test')",
        "window.document.getElementsByTagName('test')", null, null);
    test("var document,navigator",
        "document.navigator;navigator",
        "window.document.navigator;window.navigator", null, null);
    test("var iframes",
        "function test() { iframes.resize(); }",
        "_.test = function() { window.iframes.resize(); }", null, null);
    test("var iframes",
        "var foo = iframes;",
        "_.foo = window.iframes;", null, null);
    // Special names.
    test("var arguments, window, eval;",
        "arguments;window;eval;",
        "arguments;window;eval;", null, null);
    // Actually not an extern.
    test("",
        "document",
        "window.document", null, null);
  }

  private class StringCompare extends CompilerTestCase {

    StringCompare() {
      super("", false);
    }

    @Override protected CompilerPass getProcessor(Compiler compiler) {
      return new RescopeGlobalSymbols(compiler, namespace, false);
    }

    public void testFreeCallSemantics() {
      test("function x(){};var y=function(){var val=x()||{}}",
          "_.x=function(){};_.y=function(){var val=(0,_.x)()||{}}");
      test("function x(){x()}",
          "_.x=function(){(0,_.x)()}");
    }
  }
}
