/*
 * Copyright (c) 2007 Mockito contributors
 * This program is made available under the terms of the MIT License.
 */
package org.mockito.internal.invocation;

import org.hamcrest.Matcher;
import org.mockito.internal.matchers.MatcherDecorator;
import org.mockito.internal.matchers.VarargMatcher;
import org.mockito.invocation.Invocation;

import java.util.List;

@SuppressWarnings("unchecked")
public class ArgumentsComparator {
    public boolean argumentsMatch(InvocationMatcher invocationMatcher, Invocation actual) {
        Object[] actualArgs = actual.getArguments();
        return argumentsMatch(invocationMatcher, actualArgs) || varArgsMatch(invocationMatcher, actual);
    }

    public boolean argumentsMatch(InvocationMatcher invocationMatcher, Object[] actualArgs) {
        if (actualArgs.length != invocationMatcher.getMatchers().size()) {
            return false;
        }
        for (int i = 0; i < actualArgs.length; i++) {
            if (!invocationMatcher.getMatchers().get(i).matches(actualArgs[i])) {
                return false;
            }
        }
        return true;
    }

    //ok, this method is a little bit messy but the vararg business unfortunately is messy...      
    private boolean varArgsMatch(InvocationMatcher invocationMatcher, Invocation actual) {
        if (!actual.getMethod().isVarArgs()) {
            //if the method is not vararg forget about it
            return false;
        }

        //we must use raw arguments, not arguments...
        Object[] rawArgs = actual.getRawArguments();
        List<Matcher> matchers = invocationMatcher.getMatchers();

        if (rawArgs.length != matchers.size()) {
            return false;
        }

        for (int i = 0; i < rawArgs.length; i++) {
            Matcher m = matchers.get(i);
            //it's a vararg because it's the last array in the arg list
            if (rawArgs[i] != null && rawArgs[i].getClass().isArray() && i == rawArgs.length-1) {
                Matcher actualMatcher;
                //this is necessary as the framework often decorates matchers
                if (m instanceof MatcherDecorator) {
                    actualMatcher = ((MatcherDecorator)m).getActualMatcher();
                } else {
                    actualMatcher = m;
                }
                //this is very important to only allow VarargMatchers here. If you're not sure why remove it and run all tests.
                if (!(actualMatcher instanceof VarargMatcher) || !actualMatcher.matches(rawArgs[i])) {
                    return false;
                }
            //it's not a vararg (i.e. some ordinary argument before varargs), just do the ordinary check
            } else if (!m.matches(rawArgs[i])){
                return false;
            }
        }

        return true;
    }
}