/*
 * Copyright 2013 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.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.javascript.jscomp.MinimizedCondition.MinimizationStyle;
import com.google.javascript.rhino.Node;

import junit.framework.TestCase;

import java.util.ArrayList;
import java.util.List;

/**
 * Tests for {@link MinimizedCondition} in isolation.
 * Tests for the containing PeepholeMinimizeConditions pass are in
 * {@link PeepholeMinimizeConditionsTest}.
 *
 * @author blickly@google.com (Ben Lickly)
 */
public final class MinimizedConditionTest extends TestCase {

  private static Node parseExpr(String code) {
    Compiler compiler = new Compiler();
    List<SourceFile> input =
        ImmutableList.of(SourceFile.fromCode("code", code));
    List<SourceFile> externs = new ArrayList<>();
    compiler.init(externs, input, new CompilerOptions());
    Node root = compiler.parseInputs();
    assertNotNull("Unexpected parse error(s): " + Joiner.on("\n").join(compiler.getErrors()), root);
    Node externsRoot = root.getFirstChild();
    Node mainRoot = externsRoot.getNext();
    Node script = mainRoot.getFirstChild();
    Node exprResult = script.getFirstChild();
    return exprResult.getFirstChild();
  }

  private static void minCond(String input, String positive, String negative) {
    Node inputNode = parseExpr(input);
    MinimizedCondition result = MinimizedCondition.fromConditionNode(inputNode);
    Node positiveNode = parseExpr(positive);
    Node negativeNode = parseExpr(negative);
    // With counting the leading NOT node:
    Node positiveResult =
        result.getMinimized(MinimizationStyle.PREFER_UNNEGATED).getNode();
    // Without counting the leading NOT node:
    Node negativeResult =
        result.getMinimized(MinimizationStyle.ALLOW_LEADING_NOT).getNode();
    if (!positiveResult.isEquivalentTo(positiveNode)) {
      fail("Not equal:" +
          "\nExpected: " + positive +
          "\nBut was : " + (new Compiler()).toSource(positiveResult) +
          "\nExpected tree:\n" + positiveNode.toStringTree() +
          "\nActual tree:\n" + positiveResult.toStringTree());
    }
    if (!negativeResult.isEquivalentTo(negativeNode)) {
      fail("Not equal:" +
          "\nExpected: " + negative +
          "\nBut was : " + (new Compiler()).toSource(negativeResult) +
          "\nExpected tree:\n" + negativeNode.toStringTree() +
          "\nActual tree:\n" + negativeResult.toStringTree());
    }
  }

  public void testTryMinimizeCondSimple() {
    minCond("x", "x", "x");
    minCond("!x", "!x", "!x");
    minCond("!!x", "x", "x");
    minCond("!(x && y)", "!x || !y", "!(x && y)");
  }

  public void testMinimizeDemorganSimple() {
    minCond("!(x&&y)", "!x||!y", "!(x&&y)");
    minCond("!(x||y)", "!x&&!y", "!(x||y)");
    minCond("!x||!y", "!x||!y", "!(x&&y)");
    minCond("!x&&!y", "!x&&!y", "!(x||y)");
    minCond("!(x && y && z)", "!(x && y && z)", "!(x && y && z)");
    minCond("(!a||!b)&&c", "(!a||!b)&&c", "!(a&&b||!c)");
    minCond("(!a||!b)&&(c||d)", "!(a&&b||!c&&!d)", "!(a&&b||!c&&!d)");
  }

  public void testMinimizeBug8494751() {
    minCond(
        "x && (y===2 || !f()) && (y===3 || !h())",
        // TODO(tbreisacher): The 'positive' option could be better:
        // "x && !((y!==2 && f()) || (y!==3 && h()))",
        "!(!x || (y!==2 && f()) || (y!==3 && h()))",
        "!(!x || (y!==2 && f()) || (y!==3 && h()))");
  }

  public void testMinimizeComplementableOperator() {
    minCond(
        "0===c && (2===a || 1===a)",
        "0===c && (2===a || 1===a)",
        "!(0!==c || 2!==a && 1!==a)");
  }

  public void testMinimizeHook() {
    minCond("!(x ? y : z)", "(x ? !y : !z)",  "!(x ? y : z)");
  }

  public void testMinimizeComma() {
    minCond("!(inc(), test())", "inc(), !test()", "!(inc(), test())");
    minCond("!((x,y)&&z)", "(x,!y)||!z", "!((x,y)&&z)");
  }

}
