/*
 * @author Ranides Atterwim {@literal <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.invoke.WrongMethodTypeException;
import java.lang.reflect.Method;
import java.util.List;

import net.ranides.assira.collection.query.CQuery;
import net.ranides.assira.reflection.*;
import net.ranides.assira.reflection.util.ReflectUtils;

import static net.ranides.assira.reflection.IAttribute.*;

/**
 * IMethod representing lambda expression.
 *
 * Please note, that implementation uses {@link CPInspector} which is not portable.
 *
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 */
public final class LMethod extends AMethod implements IMethod {
    
    private static final IAttributes ATTR = IAttributes.of(LAMBDA, STATIC, DECLARED, SYNTHETIC).immutable();

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

	LMethod(IMethod method, Object lambda) {
        super(IContext.getDefault());
        Method cpinfo = CPInspector.getMethods(lambda.getClass()).findFirst().orElseThrow(()->new IllegalArgumentException("Lambda object is not functional interface: " + lambda));
        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(IHints.none(), arguments);
	}
    
	@Override
	public IClass<?> returns() {
        return typeinfo.returns();
	}

    @Override
    public List<IClass<?>> exceptions() {
	    return typeinfo.exceptions();
    }
    
	@Override
	public String name() {
        return method.name();
	}

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

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

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

	@Override
	public Object invoke(Object that, Object... arguments) {
        check(that);
        return call(arguments);
	}

    @Override
    public Object call(Object... arguments) {
        try {
            return handle().invokeWithArguments(arguments);
        } catch (ResolveException | WrongMethodTypeException cause) {
            throw new IReflectiveException(cause);
        } catch (Throwable cause) {
            throw new IReflectiveException.CallException(cause);
        }
    }

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

    @Override
    public IMethod accessible() {
        ReflectUtils.access(method.reflective());
        return this;
    }

    @Override
    public boolean isAccessible() {
        return ReflectUtils.isAccessible(method.reflective());
    }

	@Override
	public MethodHandle handle() {
        return handle;
	}
    
    @SuppressWarnings("unchecked")
    @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 IReflectiveException("Lambda does not support [this] argument");
        }
    }
}
