/*
 * DynamicMethod.java
 *
 * Copyright (c) 2012 Mike Strobel
 *
 * This source code is subject to terms and conditions of the Apache License, Version 2.0.
 * A copy of the license can be found in the License.html file at the root of this distribution.
 * By using this source code in any fashion, you are agreeing to be bound by the terms of the
 * Apache License, Version 2.0.
 *
 * You must not remove this notice, or any other, from this software.
 */

package com.strobel.reflection;

import com.strobel.core.VerifyArgument;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;

/**
 * @author Mike Strobel
 */
public final class DynamicMethod extends MethodInfo {
    private static final Method INVOKE_EXACT;
    private static final Method INVOKE;

    static {
        try {
            INVOKE = MethodHandle.class.getMethod("invoke", Object[].class);
            INVOKE_EXACT = MethodHandle.class.getMethod("invokeExact", Object[].class);
        }
        catch (NoSuchMethodException e) {
            throw Error.targetInvocationException(e);
        }
    }

    public static DynamicMethod invoke(final MethodHandle methodHandle) {
        return new DynamicMethod(methodHandle, INVOKE);
    }

    public static DynamicMethod invokeExact(final MethodHandle methodHandle) {
        return new DynamicMethod(methodHandle, INVOKE_EXACT);
    }

    public static DynamicMethod invoke(final MethodType methodType) {
        return new DynamicMethod(methodType, INVOKE);
    }

    public static DynamicMethod invokeExact(final MethodType methodType) {
        return new DynamicMethod(methodType, INVOKE_EXACT);
    }

    private final ParameterList _parameters;
    private final Method _invokeMethod;
    private final MethodHandle _methodHandle;
    private final SignatureType _signatureType;

    private DynamicMethod(final MethodHandle methodHandle, final Method invokeMethod) {
        _methodHandle = VerifyArgument.notNull(methodHandle, "methodHandle");
        _invokeMethod = VerifyArgument.notNull(invokeMethod, "invokeMethod");
        
        final MethodType methodType = methodHandle.type();

        final ParameterInfo[] parameters = new ParameterInfo[methodType.parameterCount()];

        for (int i = 0, n = parameters.length; i < n; i++) {
            parameters[i] = new ParameterInfo(
                "p" + i,
                i,
                Type.of(methodType.parameterType(i))
            );
        }

        _parameters = new ParameterList(parameters);

        _signatureType = new SignatureType(
            Type.of(methodType.returnType()),
            _parameters.getParameterTypes()
        );
    }

    private DynamicMethod(final MethodType methodType, final Method invokeMethod) {
        VerifyArgument.notNull(methodType, "methodType");

        _invokeMethod = VerifyArgument.notNull(invokeMethod, "invokeMethod");
        _methodHandle = null;

        final ParameterInfo[] parameters = new ParameterInfo[methodType.parameterCount()];

        for (int i = 0, n = parameters.length; i < n; i++) {
            parameters[i] = new ParameterInfo(
                "p" + i,
                i,
                Type.of(methodType.parameterType(i))
            );
        }

        _parameters = new ParameterList(parameters);

        _signatureType = new SignatureType(
            Type.of(methodType.returnType()),
            _parameters.getParameterTypes()
        );
    }

    public MethodHandle getHandle() {
        return _methodHandle;
    }

    @Override
    public Type<?> getReturnType() {
        return _signatureType.getReturnType();
    }

    @Override
    public SignatureType getSignatureType() {
        return _signatureType;
    }

    @Override
    public Method getRawMethod() {
        return _invokeMethod;
    }

    @Override
    public Type getDeclaringType() {
        return Types.MethodHandle;
    }

    @Override
    public int getModifiers() {
        return _invokeMethod.getModifiers();
    }

    @Override
    public ParameterList getParameters() {
        return _parameters;
    }
}
