/*
 * Copyright 2018, TeamDev. All rights reserved.
 *
 * Redistribution and use in source and/or binary forms, with or without
 * modification, must retain the above copyright notice and the following
 * disclaimer.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package io.spine.tools.check.vbuilder.matcher;

import com.google.errorprone.VisitorState;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.matchers.method.MethodMatchers.MethodNameMatcher;
import com.google.errorprone.suppliers.Supplier;
import com.google.protobuf.Message;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.tree.JCTree;
import io.spine.annotation.Internal;
import io.spine.tools.check.BugPatternMatcher;
import io.spine.tools.check.Fixer;
import io.spine.tools.check.vbuilder.fixer.NewBuilderFixer;

import static com.google.errorprone.matchers.method.MethodMatchers.staticMethod;
import static com.google.errorprone.suppliers.Suppliers.typeFromClass;
import static com.google.errorprone.util.ASTHelpers.enclosingClass;
import static com.google.errorprone.util.ASTHelpers.isSubtype;

@Internal
public class NewBuilderMatcher implements BugPatternMatcher<MethodInvocationTree> {

    @SuppressWarnings("DuplicateStringLiteralInspection") // Commonly used method name.
    private static final String METHOD_NAME = "newBuilder";

    private final Fixer<MethodInvocationTree> fixer = new NewBuilderFixer();

    @Override
    public boolean matches(MethodInvocationTree tree, VisitorState state) {
        boolean methodNameMatches = methodNameMatcher().matches(tree, state);
        ExpressionTree methodSelect = tree.getMethodSelect();
        boolean calledOnClass = methodSelect instanceof JCTree.JCFieldAccess;
        boolean staticImported = methodSelect instanceof JCTree.JCIdent;
        if (!methodNameMatches || (!calledOnClass && !staticImported)) {
            return false;
        }
        Type type = getTypeOnWhichInvoked(tree);
        Supplier<Type> typeSupplier = typeFromClass(Message.class);
        Type messageClassAsType = typeSupplier.get(state);
        boolean invokedOnMessage = isSubtype(type, messageClassAsType, state);
        return invokedOnMessage;
    }

    @Override
    public Fixer<MethodInvocationTree> getFixer() {
        return fixer;
    }

    Type getTypeOnWhichInvoked(MethodInvocationTree tree) {
        ExpressionTree expression = tree.getMethodSelect();
        if (expression instanceof JCTree.JCFieldAccess) {
            JCTree.JCFieldAccess fieldAccess = (JCTree.JCFieldAccess) expression;
            JCTree.JCExpression invokedOn = fieldAccess.selected;
            return invokedOn.type;
        }

        // Static imported method.
        JCTree.JCIdent ident = (JCTree.JCIdent) expression;
        Symbol.MethodSymbol method = (Symbol.MethodSymbol) ident.sym;
        Symbol.ClassSymbol classSymbol = enclosingClass(method);
        return classSymbol.type;
    }

    private static Matcher<ExpressionTree> methodNameMatcher() {
        MethodNameMatcher matcher = staticMethod().anyClass()
                                                  .named(METHOD_NAME);
        return matcher;
    }
}

@@ -1215,32 +1215,87 @@
 hodNameMatcher;%0A
+import com.google.errorprone.predicates.TypePredicate;%0A
 import com.googl
@@ -1491,91 +1491,11 @@
 ode.
-Symbol;%0Aimport com.sun.tools.javac.code.Type;%0Aimport com.sun.tools.javac.tree.JCTre
+Typ
 e;%0Ai
@@ -1829,76 +1829,8 @@
 ss;%0A
-import static com.google.errorprone.util.ASTHelpers.enclosingClass;%0A
 impo
@@ -2322,38 +2322,18 @@
 an m
-ethodNameMatches = methodNameM
+atches = m
 atch
@@ -2371,379 +2371,249 @@
     
-ExpressionTree methodSelect = tree.getMethodSelect()
+return matches
 ;%0A    
+%7D%0A%0A
     
-boolean calledOnClass = methodSelect instanceof JCTree.JCFieldAccess;%0A        boolean staticImported = methodSelect instanceof JCTree.JCIdent
+@Override%0A    public Fixer%3CMethodInvocationTree%3E getFixer() %7B%0A        return fixer
 ;%0A    
+%7D%0A%0A
     
-if (!methodNameMatches %7C%7C (!calledOnClass && !staticImported)) %7B%0A            return false;%0A        %7D%0A        Type type = getTypeOnWhichInvoked(tree);%0A
+private static Matcher%3CExpressionTree%3E matcher() %7B%0A        TypePredicate messageSubtype = (type, state) -%3E %7B%0A  
         
 Supp
@@ -2608,16 +2608,18 @@
         
+  
 Supplier
@@ -2674,24 +2674,28 @@
 s);%0A        
+    
 Type message
@@ -2725,32 +2725,36 @@
 ier.get(state);%0A
+    
         boolean 
@@ -2754,31 +2754,32 @@
 oolean i
-nvokedOnMessage
+sMessageSubclass
  = isSub
@@ -2829,841 +2829,48 @@
     
-return invokedOnMessage;%0A    %7D%0A%0A    @Override%0A    public Fixer%3CMethodInvocationTree%3E getFixer() %7B%0A        return fixer;%0A    %7D%0A%0A    Type getTypeOnWhichInvoked(MethodInvocationTree tree) %7B%0A        ExpressionTree expression = tree.getMethodSelect();%0A        if (expression instanceof JCTree.JCFieldAccess) %7B%0A            JCTree.JCFieldAccess fieldAccess = (JCTree.JCFieldAccess) expression;%0A            JCTree.JCExpression invokedOn = fieldAccess.selected;%0A            return invokedOn.type;%0A        %7D%0A%0A        // Static imported method.%0A        JCTree.JCIdent ident = (JCTree.JCIdent) expression;%0A        Symbol.MethodSymbol method = (Symbol.MethodSymbol) ident.sym;%0A        Symbol.ClassSymbol classSymbol = enclosingClass(method);%0A        return classSymbol.type;%0A    %7D%0A%0A    private static Matcher%3CExpressionTree%3E methodNameMatcher() %7B
+    return isMessageSubclass;%0A        %7D;
 %0A   
@@ -2921,17 +2921,30 @@
 d().
-any
+on
 Class(
+messageSubtype
 )%0A  

Simplify newBuilder matcher