/*
 * @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.Optional;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;
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 final class RMethod extends AMethod implements IMethod {
    
	private final Method method;
	private final Supplier<MethodHandle> handle;

	RMethod(IContext context, Method method) {
        super(context);
        ReflectUtils.access(method);
		this.method = method;
        this.handle = LazyReference.concurrent(() -> {
            try {
                return MethodHandles.lookup().unreflect(method);
            } catch (IllegalAccessException cause) {
                throw ExceptionUtils.rethrow(cause);
            }
        });
        
	}
    
    public static IMethod typeinfo(Method method) {
        IContext c = IClass.typeinfo(method.getDeclaringClass()).context();
        return new RMethod(c, method);
    }
    
    public static IMethod function(Object object) {
        Class<?> type = object.getClass();
        if(type.isSynthetic()) {
            Optional<Method> method = CPInspector.getMethods(type).findFirst();
            if( method.isPresent() ) {
                return new RMethod(IContext.DEFAULT, method.get());
            }
        }
        
        Set<IMethod> methods = IClass.typefor(object)
            .interfaces()
            .methods()
            .require(IAttribute.ABSTRACT)
            .collect(Collectors.toSet());
        if(1 == methods.size() ) {
            return methods.iterator().next();
        }
        
        throw new IllegalArgumentException(type + " is not lambda");
    }

	@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 new RFClass(c, c.typeinfo(method.getGenericReturnType()), method.getAnnotatedReturnType());
	}

	@Override
	public IArguments arguments() {
        IContext c = context();
        return new AArguments(AHints.EMPTY, CQueryBuilder.fromArray(method.getParameters()).map(a -> new RArgument(c,this,a)));
	}

	@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.get().invoke(arguments);
        } catch (Throwable cause) {
            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() {
        return IAttribute.resolve(method);
	}

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

}
