/*
 * @author Ranides Atterwim <ranides@gmail.com>
 * @copyright Ranides Atterwim
 * @license WTFPL
 * @url http://ranides.net/projects/assira
 */

package net.ranides.assira.reflection.impl;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.function.Supplier;
import net.ranides.assira.collection.lists.ListUtils;
import net.ranides.assira.generic.LazyReference;
import net.ranides.assira.reflection.*;
import net.ranides.assira.reflection.util.MethodUtils;
import net.ranides.assira.reflection.util.ReflectUtils;
import net.ranides.assira.trace.ExceptionUtils;
import net.ranides.assira.collection.query.CQueryBuilder;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public class RMethod extends AMethod implements IMethod {

	private final boolean declared;
	private final Method method;
	private final Supplier<MethodHandle> handle;

    RMethod(boolean declared, IContext context, Method method, Object that) {
        super(context);
        ReflectUtils.access(method);
		this.method = method;
        this.handle = LazyReference.concurrent(() -> {
            try {
                MethodHandle m = MethodHandles.lookup().unreflect(method);
                return null!=that ? m.bindTo(that) : m;
            } catch (IllegalAccessException cause) {
                throw ExceptionUtils.rethrow(cause);
            }
        });
        this.declared = declared;
	}
    
	@Override
	public List<IClass<?>> params() {
        IContext c = context();
        return ListUtils.map(Arrays.asList(method.getTypeParameters()), c::typeinfo);
	}

	@Override
	public IClass<?> returns() {
        IContext c = context();
        return FClass.newClass(c, method.getGenericReturnType(), method.getAnnotatedReturnType());
	}

	@Override
	public IArguments arguments() {
        IContext c = context();
        return FElements.newArguments(
            AHints.EMPTY, 
            method.getParameterCount(), 
            CQueryBuilder.fromArray(method.getParameters()).map((i,v)-> FArgument.newArgument(c, this, v, i))
        );
	}
    
	@Override
	public IClass<?> parent() {
		return context().typeinfo(method.getDeclaringClass());
	}

	@Override
	public Object apply() {
		return MethodUtils.invoke(method, null);
	}

	@Override
	public Object apply(Object that) {
		return MethodUtils.invoke(method, that);
	}

	@Override
	public Object apply(Object that, Object... arguments) {
		return MethodUtils.invoke(method, that, arguments);
	}

    @Override
    public Object call(Object... arguments) {
        try {
            return handle().invokeWithArguments(arguments);
        } catch (Throwable cause) { // NOPMD rethrow
            throw ExceptionUtils.rethrow(cause);
        }
    }

	@Override
	public Method reflective() {
        return method;
	}

	@Override
	public MethodHandle handle() {
        return handle.get();
	}

	@Override
	public String name() {
        return method.getName();
	}

	@Override
	public Set<IAttribute> attributes() {
        Set<IAttribute> attr = IAttribute.resolve(method);
        if(declared) {
            attr.add(IAttribute.DECLARED);
        }
        return attr;
	}

	@Override
	public IAnnotations annotations() {
		return FElements.newAnnotations(AHints.EMPTY, CQueryBuilder.fromArray(method.getAnnotations()));
	}

}
