/*
 * Copyright 2012 Google Inc. All Rights Reserved.
 *
 * 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.errorprone.bugpatterns;

import static com.google.errorprone.BugPattern.Category.JDK;
import static com.google.errorprone.BugPattern.MaturityLevel.EXPERIMENTAL;
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;

import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.matchers.DescribingMatcher;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;

import com.sun.source.tree.BinaryTree;
import com.sun.source.tree.ConditionalExpressionTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.ParenthesizedTree;
import com.sun.source.tree.TreeVisitor;
import com.sun.source.tree.Tree.Kind;
import com.sun.tools.javac.code.Symbol.OperatorSymbol;
import com.sun.tools.javac.tree.JCTree.JCBinary;
import com.sun.tools.javac.tree.JCTree.JCLiteral;
import com.sun.tools.javac.tree.JCTree.JCUnary;
import com.sun.tools.javac.util.Name;

import java.util.HashMap;
import java.util.Map;

/**
 * Fix unnecessary uses of the conditional operator ?: when the true and false expressions are
 * just boolean literals.
 *
 * @author sjnickerson@google.com (Simon Nickerson)
 */
@BugPattern(name = "UnneededConditionalOperator",
    summary = "You don't need a conditional operator if the conditional values are true and false",
    explanation = "An expression of the form isFoo() ? true : false is needlessly wordy. You can "
        + "skip the conditional operator entirely",
    category = JDK, severity = WARNING, maturity = EXPERIMENTAL)
public class UnneededConditionalOperator extends DescribingMatcher<ConditionalExpressionTree> {

  private static final Matcher<ConditionalExpressionTree> matcher =
      new Matcher<ConditionalExpressionTree>() {
        @Override
        public boolean matches(ConditionalExpressionTree t, VisitorState state) {
          return t.getTrueExpression().getKind() == Kind.BOOLEAN_LITERAL &&
              t.getFalseExpression().getKind() == Kind.BOOLEAN_LITERAL;
        }
      };

  private static Map<String, String> OPERATOR_OPPOSITES = new HashMap<String, String>();
  
  static {
    OPERATOR_OPPOSITES.put("<", ">=");
    OPERATOR_OPPOSITES.put("<=", ">");
    OPERATOR_OPPOSITES.put(">", "<=");
    OPERATOR_OPPOSITES.put(">=", "<");
    OPERATOR_OPPOSITES.put("==", "!=");
    OPERATOR_OPPOSITES.put("!=", "==");
  }
      
  @Override
  public boolean matches(ConditionalExpressionTree t, VisitorState state) {
    return matcher.matches(t, state);
  }

  @Override
  public Description describe(ConditionalExpressionTree t, VisitorState state) {
    JCLiteral trueExpr = (JCLiteral) t.getTrueExpression();
    JCLiteral falseExpr = (JCLiteral) t.getFalseExpression();
    boolean trueExprValue = (Boolean) trueExpr.getValue();
    boolean falseExprValue = (Boolean) falseExpr.getValue();
    SuggestedFix fix;
    if (trueExprValue && !falseExprValue) {
      fix = new SuggestedFix().replace(t, t.getCondition().toString());
    } else if (!trueExprValue && falseExprValue) {
      fix = new SuggestedFix().replace(t, negate(t.getCondition()));
    } else {
      // trueExprValue == falseExprValue
      // Our suggested fix ignores any possible side-effects of the condition.
      fix = new SuggestedFix().replace(t, Boolean.toString(trueExprValue));
    }
    return new Description(t, diagnosticMessage, fix);
  }

  /**
   * Provides a string representation of the negation of a conditional expression, trying to
   * simplify as much as possible.
   *
   * We have special handling for parenthesised expressions (final expression will be parenthesised
   * too), binary expressions (signs are swapped for simple comparisons, de Morgan's laws are used
   * for && and ||) and unary expressions (!x becomes x rather than !(!x)).
   */
  private String negate(ExpressionTree cond) {
    if (cond instanceof ParenthesizedTree) {
      return "(" + negate(((ParenthesizedTree) cond).getExpression()) + ")";
    } else if (cond instanceof JCBinary) {
      JCBinary binary = (JCBinary) cond;
      String operator = binary.getOperator().getSimpleName().toString();
      String opposite = OPERATOR_OPPOSITES.get(operator);
      if (opposite != null) {
        return String.format("%s %s %s", binary.lhs.toString(), opposite, binary.rhs.toString());
      } else if (operator.equals("&&")) {
        return String.format("%s || %s", negate(binary.lhs), negate(binary.rhs));
      } else if (operator.equals("||")) {
        return String.format("%s && %s", negate(binary.lhs), negate(binary.rhs));
      } else {
        return "!(" + cond.toString() + ")";
      }
    } else if (cond instanceof JCUnary) {
      JCUnary unary = (JCUnary) cond;
      String operator = unary.getOperator().getSimpleName().toString();
      if (operator.equals("!")) {
        return unary.getExpression().toString();
      } else {
        return "!(" + cond.toString() + ")";
      }
    } else {
      return "!" + cond.toString();
    }
  }

  public static class Scanner extends com.google.errorprone.Scanner {
    private final DescribingMatcher<ConditionalExpressionTree> describingMatcher
        = new UnneededConditionalOperator();

    @Override
    public Void visitConditionalExpression(
        ConditionalExpressionTree node, VisitorState visitorState) {
      evaluateMatch(node, visitorState, describingMatcher);
      return super.visitConditionalExpression(node, visitorState);
    }
  }
}

@@ -1154,47 +1154,8 @@
 r;%0A%0A
-import com.sun.source.tree.BinaryTree;%0A
 impo
@@ -1328,108 +1328,13 @@
 Tree
-Visitor;%0Aimport com.sun.source.tree.Tree.Kind;%0Aimport com.sun.tools.javac.code.Symbol.OperatorSymbol
+.Kind
 ;%0Aim
@@ -1481,46 +1481,8 @@
 ary;
-%0Aimport com.sun.tools.javac.util.Name;
 %0A%0Aim

Remove unused imports.