/*
 * Copyright (c) 2007 Mockito contributors
 * This program is made available under the terms of the MIT License.
 */

package org.mockito.internal.invocation;

import static org.mockito.internal.invocation.ArgumentsProcessor.argumentsToMatchers;
import static org.mockito.internal.invocation.MatcherApplicationStrategy.getMatcherApplicationStrategyFor;
import static org.mockito.internal.invocation.TypeSafeMatching.matchesTypeSafe;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import org.mockito.ArgumentMatcher;
import org.mockito.internal.matchers.CapturesArguments;
import org.mockito.internal.reporting.PrintSettings;
import org.mockito.invocation.DescribedInvocation;
import org.mockito.invocation.Invocation;
import org.mockito.invocation.Location;
import org.mockito.invocation.MatchableInvocation;

/**
 * In addition to all content of the invocation, the invocation matcher contains the argument matchers. Invocation matcher is used during verification and stubbing. In those cases, the user can provide argument matchers instead of 'raw' arguments. Raw arguments are converted to 'equals' matchers anyway.
 */
@SuppressWarnings("serial")
public class InvocationMatcher implements MatchableInvocation, DescribedInvocation, Serializable {

    private final Invocation invocation;
    private final List<ArgumentMatcher<?>> matchers;

    @SuppressWarnings({ "rawtypes", "unchecked" })
    public InvocationMatcher(Invocation invocation, List<ArgumentMatcher> matchers) {
        this.invocation = invocation;
        if (matchers.isEmpty()) {
            this.matchers = (List) argumentsToMatchers(invocation.getArguments());
        } else {
            this.matchers = (List) matchers;
        }
    }

    @SuppressWarnings("rawtypes")
    public InvocationMatcher(Invocation invocation) {
        this(invocation, Collections.<ArgumentMatcher> emptyList());
    }

    public static List<InvocationMatcher> createFrom(List<Invocation> invocations) {
        LinkedList<InvocationMatcher> out = new LinkedList<InvocationMatcher>();
        for (Invocation i : invocations) {
            out.add(new InvocationMatcher(i));
        }
        return out;
    }

    public Method getMethod() {
        return invocation.getMethod();
    }

    public Invocation getInvocation() {
        return invocation;
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    public List<ArgumentMatcher> getMatchers() {
        return (List) matchers;
    }

    @Override
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public String toString() {
        return new PrintSettings().print((List) matchers, invocation);
    }

    public boolean matches(Invocation candidate) {
        return invocation.getMock().equals(candidate.getMock()) && hasSameMethod(candidate) && argumentsMatch(candidate);
    }

    /**
     * similar means the same method name, same mock, unverified and: if arguments are the same cannot be overloaded
     */
    public boolean hasSimilarMethod(Invocation candidate) {
        String wantedMethodName = getMethod().getName();
        String candidateMethodName = candidate.getMethod().getName();

        if (!wantedMethodName.equals(candidateMethodName)) {
            return false;
        }
        if (candidate.isVerified()) {
            return false;
        }
        if (getInvocation().getMock() != candidate.getMock()) {
            return false;
        }
        if (hasSameMethod(candidate)) {
            return true;
        }

        return !argumentsMatch(candidate);
    }

    public boolean hasSameMethod(Invocation candidate) {
        // not using method.equals() for 1 good reason:
        // sometimes java generates forwarding methods when generics are in play see JavaGenericsForwardingMethodsTest
        Method m1 = invocation.getMethod();
        Method m2 = candidate.getMethod();

        if (m1.getName() != null && m1.getName().equals(m2.getName())) {
            /* Avoid unnecessary cloning */
            Class<?>[] params1 = m1.getParameterTypes();
            Class<?>[] params2 = m2.getParameterTypes();
            if (params1.length == params2.length) {
                for (int i = 0; i < params1.length; i++) {
                    if (params1[i] != params2[i])
                        return false;
                }
                return true;
            }
        }
        return false;
    }

    @Override
    public Location getLocation() {
        return invocation.getLocation();
    }

    @Override
    public void captureArgumentsFrom(Invocation invocation) {
        MatcherApplicationStrategy strategy = getMatcherApplicationStrategyFor(invocation, matchers);
        strategy.forEachMatcherAndArgument(captureArgument());
    }

    private ArgumentMatcherAction captureArgument() {
        return new ArgumentMatcherAction() {
            
            @Override
            public boolean apply(ArgumentMatcher<?> matcher, Object argument) {
                if (matcher instanceof CapturesArguments) {
                    ((CapturesArguments) matcher).captureFrom(argument);
                }
                
                return true;
            }
        };
    }
    
    @SuppressWarnings({ "rawtypes", "unchecked" })
    private boolean argumentsMatch(Invocation actual) {
        List matchers = getMatchers();
        return getMatcherApplicationStrategyFor(actual, matchers).forEachMatcherAndArgument( matchesTypeSafe());
    }
}
@@ -492,16 +492,41 @@
 Method;%0A
+import java.util.Arrays;%0A
 import j
@@ -547,16 +547,16 @@
 ctions;%0A
-
 import j
@@ -2363,32 +2363,46 @@
 ethod();%0A    %7D%0A%0A
+    @Override%0A
     public Invoc
@@ -2451,32 +2451,46 @@
 ocation;%0A    %7D%0A%0A
+    @Override%0A
     @SuppressWar
@@ -2778,32 +2778,46 @@
 cation);%0A    %7D%0A%0A
+    @Override%0A
     public boole
@@ -3109,24 +3109,38 @@
 ded%0A     */%0A
+    @Override%0A
     public b
@@ -3704,32 +3704,46 @@
 didate);%0A    %7D%0A%0A
+    @Override%0A
     public boole
@@ -4293,138 +4293,36 @@
     
-if (params1.length == params2.length) %7B%0A                for (int i = 0; i %3C params1.length; i++) %7B%0A                    if 
+return Arrays.equals
 (params1
 %5Bi%5D 
@@ -4321,22 +4321,17 @@
 ams1
-%5Bi%5D !=
+,
  params2
 %5Bi%5D)
@@ -4330,111 +4330,10 @@
 ams2
-%5Bi%5D)%0A                        return false;%0A                %7D%0A                return true;%0A            %7D
+);
 %0A   

Improve internal code in InvocationMatcher (#776)