/*
 * 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.GUAVA;
import static com.google.errorprone.BugPattern.MaturityLevel.EXPERIMENTAL;
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
import static com.google.errorprone.matchers.Matchers.allOf;
import static com.google.errorprone.matchers.Matchers.argument;
import static com.google.errorprone.matchers.Matchers.methodSelect;
import static com.google.errorprone.matchers.Matchers.staticMethod;

import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
import com.google.errorprone.fixes.Fix;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.matchers.NewInstanceAnonymousInnerClass;

import com.sun.source.tree.MethodInvocationTree;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.JCClassDecl;
import com.sun.tools.javac.tree.JCTree.JCFieldAccess;
import com.sun.tools.javac.tree.JCTree.JCIdent;
import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
import com.sun.tools.javac.tree.JCTree.JCMethodInvocation;
import com.sun.tools.javac.tree.JCTree.JCNewClass;
import com.sun.tools.javac.tree.JCTree.JCTypeApply;
import com.sun.tools.javac.tree.Pretty;
import com.sun.tools.javac.util.List;

import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;

/**
 * Checker for a call of the form:
 * <pre>
 * Ordering.from(new Comparator<T>() { ... })
 * </pre>
 *
 * <p>This can be unwrapped to a new anonymous subclass of Ordering:
 * <pre>
 * new Ordering<T>() { ... }
 * </pre>
 * which is shorter and cleaner (and potentially more efficient).
 *
 * @author sjnickerson@google.com (Simon Nickerson)
 *
 */
@BugPattern(name = "OrderingFrom",
    summary = "Ordering.from(new Comparator<T>() { }) can be refactored to cleaner form",
    explanation =
        "Calls of the form\n" +
        "{{{Ordering.from(new Comparator<T>() { ... })}}}\n" +
        "can be unwrapped to a new anonymous subclass of Ordering\n" +
        "{{{new Ordering<T>() { ... }}}}\n" +
        "which is shorter and cleaner (and potentially more efficient).",
    category = GUAVA, severity = WARNING, maturity = EXPERIMENTAL)
public class OrderingFrom extends BugChecker implements MethodInvocationTreeMatcher {

  @SuppressWarnings({"unchecked", "varargs"})
  private static final Matcher<MethodInvocationTree> matcher = allOf(
      methodSelect(staticMethod("com.google.common.collect.Ordering", "from")),
      argument(0, new NewInstanceAnonymousInnerClass("java.util.Comparator")));

  @Override
  public Description matchMethodInvocation(MethodInvocationTree methodInvocation, VisitorState state) {
    if(!matcher.matches(methodInvocation, state)) {
      return Description.NO_MATCH;
    }

    // e.g. new Comparator<String>() { ... }
    JCNewClass newComparatorInvocation = (JCNewClass) methodInvocation.getArguments().get(0);

    // e.g. Ordering
    JCIdent orderingIdent = (JCIdent)
        ((JCFieldAccess) ((JCMethodInvocation) methodInvocation).meth).selected;
    // e.g. Ordering<String>
    JCTypeApply newOrderingType = state.getTreeMaker().TypeApply(orderingIdent,
        ((JCTypeApply) newComparatorInvocation.clazz).arguments);

    // Find the class definition and remove the default constructor (it confuses the pretty printer)
    JCClassDecl def = newComparatorInvocation.def;

    // Note that List is not java.util.List, and it's not very nice to deal with.
    ArrayList<JCTree> allDefsExceptConstructor = new ArrayList<>();
    for (JCTree individualDef : def.defs) {
      if (individualDef instanceof JCMethodDecl) {
        JCMethodDecl methodDecl = (JCMethodDecl) individualDef;
        if (!methodDecl.name.toString().equals("<init>")) {
          allDefsExceptConstructor.add(individualDef);
        }
      } else {
        allDefsExceptConstructor.add(individualDef);
      }
    }

    // e.g. new Ordering<String>() { ... }
    JCNewClass newClass = state.getTreeMaker().NewClass(
        newComparatorInvocation.encl, newComparatorInvocation.typeargs, newOrderingType,
        newComparatorInvocation.args,
        state.getTreeMaker().ClassDef(
            def.mods, def.name, def.typarams, def.extending, def.implementing,
            List.from(allDefsExceptConstructor.toArray(
                new JCTree[allDefsExceptConstructor.size()]))));

    StringWriter sw = new StringWriter();
    try {
      Pretty pretty = new Pretty(sw, true);
      pretty.printExpr(newClass);
    } catch (IOException impossible) {
      throw new AssertionError("Impossible IOException");
    }

    String replacement = sw.toString().replace("@Override()", "@Override");

    Fix fix = SuggestedFix.replace(methodInvocation, replacement);

    return describeMatch(methodInvocation, fix);
  }
}

@@ -2706,19 +2706,17 @@
        %22
-%7B%7B%7B
+%60
 Ordering
@@ -2749,19 +2749,17 @@
 %7B ... %7D)
-%7D%7D%7D
+%60
 %5Cn%22 +%0A  
@@ -2840,11 +2840,9 @@
    %22
-%7B%7B%7B
+%60
 new 
@@ -2862,19 +2862,17 @@
  %7B ... %7D
-%7D%7D%7D
+%60
 %5Cn%22 +%0A  

Remove triple curly braces.