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

import com.google.javascript.jscomp.type.ClosureReverseAbstractInterpreter;
import com.google.javascript.jscomp.type.FlowScope;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.jstype.JSType;
import com.google.javascript.rhino.testing.Asserts;

public class ClosureReverseAbstractInterpreterTest extends
    CompilerTypeTestCase {

  public void testGoogIsDef1() throws Exception {
    testClosureFunction("goog.isDef",
        createOptionalType(NUMBER_TYPE),
        NUMBER_TYPE,
        VOID_TYPE);
  }

  public void testGoogIsDef2() throws Exception {
    testClosureFunction("goog.isDef",
        createNullableType(NUMBER_TYPE),
        createNullableType(NUMBER_TYPE),
        NO_TYPE);
  }

  public void testGoogIsDef3() throws Exception {
    testClosureFunction("goog.isDef",
        ALL_TYPE,
        createUnionType(OBJECT_NUMBER_STRING_BOOLEAN,NULL_TYPE),
        VOID_TYPE);
  }

  public void testGoogIsDef4() throws Exception {
    testClosureFunction("goog.isDef",
        UNKNOWN_TYPE,
        UNKNOWN_TYPE,  // TODO(johnlenz): should be CHECKED_UNKNOWN_TYPE
        UNKNOWN_TYPE);
  }

  public void testGoogIsNull1() throws Exception {
    testClosureFunction("goog.isNull",
        createOptionalType(NUMBER_TYPE),
        NO_TYPE,
        createOptionalType(NUMBER_TYPE));
  }

  public void testGoogIsNull2() throws Exception {
    testClosureFunction("goog.isNull",
        createNullableType(NUMBER_TYPE),
        NULL_TYPE,
        NUMBER_TYPE);
  }

  public void testGoogIsNull3() throws Exception {
    testClosureFunction("goog.isNull",
        ALL_TYPE,
        NULL_TYPE,
        createUnionType(OBJECT_NUMBER_STRING_BOOLEAN, VOID_TYPE));
  }

  public void testGoogIsNull4() throws Exception {
    testClosureFunction("goog.isNull",
        UNKNOWN_TYPE,
        UNKNOWN_TYPE,
        UNKNOWN_TYPE); // TODO(johnlenz): this should be CHECK_UNKNOWN
  }

  public void testGoogIsDefAndNotNull1() throws Exception {
    testClosureFunction("goog.isDefAndNotNull",
        createOptionalType(NUMBER_TYPE),
        NUMBER_TYPE,
        VOID_TYPE);
  }

  public void testGoogIsDefAndNotNull2() throws Exception {
    testClosureFunction("goog.isDefAndNotNull",
        createNullableType(NUMBER_TYPE),
        NUMBER_TYPE,
        NULL_TYPE);
  }

  public void testGoogIsDefAndNotNull3() throws Exception {
    testClosureFunction("goog.isDefAndNotNull",
        createOptionalType(createNullableType(NUMBER_TYPE)),
        NUMBER_TYPE,
        NULL_VOID);
  }

  public void testGoogIsDefAndNotNull4() throws Exception {
    testClosureFunction("goog.isDefAndNotNull",
        ALL_TYPE,
        OBJECT_NUMBER_STRING_BOOLEAN,
        NULL_VOID);
  }

  public void testGoogIsDefAndNotNull5() throws Exception {
    testClosureFunction("goog.isDefAndNotNull",
        UNKNOWN_TYPE,
        UNKNOWN_TYPE,  // TODO(johnlenz): this should be "CHECKED_UNKNOWN"
        UNKNOWN_TYPE);
  }

  public void testGoogIsString1() throws Exception {
    testClosureFunction("goog.isString",
        createNullableType(STRING_TYPE),
        STRING_TYPE,
        NULL_TYPE);
  }

  public void testGoogIsString2() throws Exception {
    testClosureFunction("goog.isString",
        createNullableType(NUMBER_TYPE),
        createNullableType(NUMBER_TYPE),
        createNullableType(NUMBER_TYPE));
  }

  public void testGoogIsBoolean1() throws Exception {
    testClosureFunction("goog.isBoolean",
        createNullableType(BOOLEAN_TYPE),
        BOOLEAN_TYPE,
        NULL_TYPE);
  }

  public void testGoogIsBoolean2() throws Exception {
    testClosureFunction("goog.isBoolean",
        createUnionType(BOOLEAN_TYPE, STRING_TYPE, NO_OBJECT_TYPE),
        BOOLEAN_TYPE,
        createUnionType(STRING_TYPE, NO_OBJECT_TYPE));
  }

  public void testGoogIsBoolean3() throws Exception {
    testClosureFunction("goog.isBoolean",
        ALL_TYPE,
        BOOLEAN_TYPE,
        ALL_TYPE); // TODO(johnlenz): this should be:
                   //   {Object|number|string|null|void}
  }

  public void testGoogIsBoolean4() throws Exception {
    testClosureFunction("goog.isBoolean",
        UNKNOWN_TYPE,
        BOOLEAN_TYPE,
        CHECKED_UNKNOWN_TYPE);
  }

  public void testGoogIsNumber() throws Exception {
    testClosureFunction("goog.isNumber",
        createNullableType(NUMBER_TYPE),
        NUMBER_TYPE,
        NULL_TYPE);
  }

  public void testGoogIsFunction() throws Exception {
    testClosureFunction("goog.isFunction",
        createNullableType(OBJECT_FUNCTION_TYPE),
        OBJECT_FUNCTION_TYPE,
        NULL_TYPE);
  }

  public void testGoogIsFunction2() throws Exception {
    testClosureFunction("goog.isFunction",
        OBJECT_NUMBER_STRING_BOOLEAN,
        U2U_CONSTRUCTOR_TYPE,
        OBJECT_NUMBER_STRING_BOOLEAN);
  }

  public void testGoogIsFunction3() throws Exception {
    testClosureFunction("goog.isFunction",
        createUnionType(U2U_CONSTRUCTOR_TYPE, NUMBER_STRING_BOOLEAN),
        U2U_CONSTRUCTOR_TYPE,
        NUMBER_STRING_BOOLEAN);
  }

  public void testGoogIsFunctionOnNull() throws Exception {
    testClosureFunction("goog.isFunction",
        null,
        U2U_CONSTRUCTOR_TYPE,
        null);
  }

  public void testGoogIsArray1() throws Exception {
    testClosureFunction("goog.isArray",
        OBJECT_TYPE,
        ARRAY_TYPE,
        OBJECT_TYPE);
  }

  public void testGoogIsArray2() throws Exception {
    testClosureFunction("goog.isArray",
        ALL_TYPE,
        ALL_TYPE, // TODO(johnlenz): should be ARRAY_TYPE?
        ALL_TYPE);
  }

  public void testGoogIsArray3() throws Exception {
    testClosureFunction("goog.isArray",
        UNKNOWN_TYPE,
        CHECKED_UNKNOWN_TYPE,
        CHECKED_UNKNOWN_TYPE);
  }

  public void testGoogIsArray4() throws Exception {
    testClosureFunction("goog.isArray",
        createUnionType(ARRAY_TYPE, NULL_TYPE),
        ARRAY_TYPE,
        NULL_TYPE);
  }

  public void testGoogIsArrayOnNull() throws Exception {
    testClosureFunction("goog.isArray",
        null,
        ARRAY_TYPE,
        null);
  }

  public void testGoogIsObjectOnNull() throws Exception {
    testClosureFunction("goog.isObject",
        null,
        OBJECT_TYPE,
        null);
  }

  public void testGoogIsObject1() throws Exception {
    testClosureFunction("goog.isObject",
        ALL_TYPE,
        NO_OBJECT_TYPE,
        createUnionType(NUMBER_STRING_BOOLEAN, NULL_TYPE, VOID_TYPE));
  }

  public void testGoogIsObject2() throws Exception {
    testClosureFunction("goog.isObject",
          createUnionType(OBJECT_TYPE, NUMBER_STRING_BOOLEAN),
          OBJECT_TYPE,
          NUMBER_STRING_BOOLEAN);
  }

  public void testGoogIsObject3() throws Exception {
    testClosureFunction("goog.isObject",
          createUnionType(
              OBJECT_TYPE, NUMBER_STRING_BOOLEAN, NULL_TYPE, VOID_TYPE),
          OBJECT_TYPE,
          createUnionType(NUMBER_STRING_BOOLEAN, NULL_TYPE, VOID_TYPE));
  }

  public void testGoogIsObject4() throws Exception {
    testClosureFunction("goog.isObject",
        UNKNOWN_TYPE,
        NO_OBJECT_TYPE,  // ? Should this be CHECKED_UNKNOWN?
        CHECKED_UNKNOWN_TYPE);
  }

  private void testClosureFunction(String function, JSType type,
      JSType trueType, JSType falseType) {
    // function(a) where a : type
    Node n = compiler.parseTestCode("var a; " + function + "(a)");
    Node call = n.getLastChild().getLastChild();
    Node name = call.getLastChild();

    Scope scope = new SyntacticScopeCreator(compiler).createScope(n, null);
    FlowScope flowScope = LinkedFlowScope.createEntryLattice(scope);

    assertEquals(Token.CALL, call.getType());
    assertEquals(Token.NAME, name.getType());

    GoogleCodingConvention convention = new GoogleCodingConvention();
    flowScope.inferSlotType("a", type);
    ClosureReverseAbstractInterpreter rai =
        new ClosureReverseAbstractInterpreter(convention, registry);

    // trueScope
    Asserts.assertTypeEquals(
        trueType,
        rai.getPreciserScopeKnowingConditionOutcome(call, flowScope, true)
        .getSlot("a").getType());

    // falseScope
    Asserts.assertTypeEquals(
        falseType,
        rai.getPreciserScopeKnowingConditionOutcome(call, flowScope, false)
        .getSlot("a").getType());
  }
}
