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

import static java.util.Arrays.asList;

import static org.mockito.internal.invocation.InterceptedInvocation.NO_OP;

import java.lang.reflect.Method;
import java.util.LinkedList;
import java.util.List;

import org.mockito.Mockito;
import org.mockito.internal.debugging.LocationFactory;
import org.mockito.internal.invocation.mockref.MockReference;
import org.mockito.internal.invocation.mockref.MockStrongReference;
import org.mockito.invocation.Invocation;
import org.mockito.invocation.Location;
import org.mockitousage.IMethods;

/**
 * Build an invocation.
 */
@SuppressWarnings("unchecked")
public class InvocationBuilder {

    private String methodName = "simpleMethod";
    private int sequenceNumber = 0;
    private Object[] args = new Object[] {};
    private Object mock = Mockito.mock(IMethods.class);
    private Class<?> mockClass = IMethods.class;
    private Method method;
    private boolean verified;
    private List<Class<?>> argTypes;
    private Location location;

    /**
     * Build the invocation
     * <p>
     * If the method was not specified, use IMethods methods.
     *
     * @return invocation
     */
    public Invocation toInvocation() {
        if (method == null) {
            if (argTypes == null) {
                argTypes = new LinkedList<Class<?>>();
                for (Object arg : args) {
                    if (arg == null) {
                        argTypes.add(Object.class);
                    } else {
                        argTypes.add(arg.getClass());
                    }
                }
            }

            try {
                method =
                        mockClass.getMethod(
                                methodName, argTypes.toArray(new Class[argTypes.size()]));
            } catch (Exception e) {
                throw new RuntimeException(
                        "builder only creates invocations of IMethods interface", e);
            }
        }

        Invocation i =
                createInvocation(
                        new MockStrongReference<Object>(mock, false),
                        new SerializableMethod(method),
                        args,
                        NO_OP,
                        location == null ? LocationFactory.create() : location,
                        1);
        if (verified) {
            i.markVerified();
        }
        return i;
    }

    protected Invocation createInvocation(
            MockReference<Object> mockRef,
            MockitoMethod mockitoMethod,
            Object[] arguments,
            RealMethod realMethod,
            Location location,
            int sequenceNumber) {
        return new InterceptedInvocation(
                mockRef, mockitoMethod, arguments, realMethod, location, sequenceNumber);
    }

    public InvocationBuilder method(String methodName) {
        this.methodName = methodName;
        return this;
    }

    public InvocationBuilder seq(int sequenceNumber) {
        this.sequenceNumber = sequenceNumber;
        return this;
    }

    public InvocationBuilder args(Object... args) {
        this.args = args;
        return this;
    }

    public InvocationBuilder arg(Object o) {
        this.args = new Object[] {o};
        return this;
    }

    public InvocationBuilder mock(Object mock) {
        this.mock = mock;
        return this;
    }

    public InvocationBuilder mockClass(Class<?> mockClass) {
        this.mockClass = mockClass;
        this.mock = mock(mockClass);
        return this;
    }

    public InvocationBuilder method(Method method) {
        this.method = method;
        return this;
    }

    public InvocationBuilder verified() {
        this.verified = true;
        return this;
    }

    public InvocationMatcher toInvocationMatcher() {
        return new InvocationMatcher(toInvocation());
    }

    public InvocationBuilder simpleMethod() {
        return this.method("simpleMethod");
    }

    public InvocationBuilder differentMethod() {
        return this.method("differentMethod");
    }

    public InvocationBuilder argTypes(Class<?>... argTypes) {
        this.argTypes = asList(argTypes);
        return this;
    }

    public InvocationBuilder location(final String location) {
        this.location =
                new Location() {
                    public String toString() {
                        return location;
                    }

                    public String getSourceFile() {
                        return "SomeClass";
                    }
                };
        return this;
    }
}
