/*
 * @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.MethodHandleProxies;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Set;
import net.ranides.assira.collection.query.CQuery;
import net.ranides.assira.reflection.*;
import net.ranides.assira.trace.ExceptionUtils;
import static net.ranides.assira.reflection.IAttribute.*;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public final class LMethod extends AMethod implements IMethod {
    
    private static final Set<IAttribute> ATTR = IAttribute.collect(LAMBDA, STATIC, DECLARED, SYNTHETIC);

	private final IMethod typeinfo;
	private final IMethod method;
	private final MethodHandle handle;
	private final CQuery<IArgument> arguments;

	LMethod(IMethod method, Object lambda) {
        super(IContext.DEFAULT);
        Method cpinfo = CPInspector.getMethods(lambda.getClass()).findFirst().get();
        this.typeinfo = IMethod.typeinfo(cpinfo);
		this.method = method;
        this.handle = method.handle().bindTo(lambda);
        
        int count = method.arguments().count();
        int offset = cpinfo.getParameterCount() - count;
        this.arguments = this.typeinfo.arguments().stream().skip(offset);
		
	}
    
    @Override
	public List<IClass<?>> params() {
        return typeinfo.params();
	}
    
	@Override
	public IArguments arguments() {
        return FElements.newArguments(AHints.EMPTY, arguments.size(), arguments);
	}
    
	@Override
	public IClass<?> returns() {
        return typeinfo.returns();
	}
    
	@Override
	public String name() {
        return method.name();
	}

	@Override
	public Set<IAttribute> attributes() {
        Set<IAttribute> attr = method.attributes();
        attr.addAll(ATTR);
        return attr;
	}
    
	@Override
	public IClass<?> parent() {
        return method.parent();
	}

	@Override
	public Object apply() {
        return call();
	}

	@Override
	public Object apply(Object that) {
        check(that);
        return call();
	}

	@Override
	public Object apply(Object that, Object... arguments) {
        check(that);
        return call(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.reflective();
	}

	@Override
	public MethodHandle handle() {
        return handle;
	}
    
    @Override
    public <F> F handle(IClass<F> functor) {
        return (F)MethodHandleProxies.asInterfaceInstance(functor.raw(), handle());
    }
    
	@Override
	public IAnnotations annotations() {
        return method.annotations();
	}

    private static void check(Object that) throws IllegalArgumentException {
        if(null != that) {
            throw new IllegalArgumentException("Lambda does not support [this] argument");
        }
    }
}
