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

package net.ranides.assira.reflection;

import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.util.function.Function;
import net.ranides.asm.Type;
import net.ranides.assira.trace.ExceptionUtils;
import sun.reflect.ConstantPool;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public class LambdaParams {

	public static void main(String[] args) throws Exception {
		LambdaParams that = new LambdaParams();
		System.out.printf("(Integer a) -> String %n");
		that.runme( (Integer a) -> "A" );
		System.out.printf("%n%n");
		
		System.out.printf("(int[] a) -> Float %n");
		that.runme( (int[] a) -> 11.0f );
		System.out.printf("%n%n");
		
		System.out.printf("(String a) -> Character %n");
		that.runme( OtherLambda::flow );
		System.out.printf("%n%n");
		
		System.out.printf("(Params2 a) -> Integer %n");
		that.runme( (Param2 v) -> 3*OtherLambda.flow("s") );
		System.out.printf("%n%n");
		
		System.out.printf("(String) -> Integer %n");
		that.runme( new Function<String, Integer>() {
			@Override
			public Integer apply(String t) {
				return 0;
			}
			
		});
		System.out.printf("%n%n");
		
		System.out.printf("(Float) -> Double %n");
		that.runme( new Hello(22));
		System.out.printf("%n%n");
		
		Function<Integer, ?> f31 = (Integer a) -> "A";
		Function f32 = f31;
		
		System.out.printf("(Integer) -> String %n");
		that.runme(f32);
		System.out.printf("%n%n");
	}
		
	public <T,R> void runme(Function<T,R> function) throws Exception {
		System.out.printf("type = %s%n", typeof(function));
	}
	
	public static Class<?> typeof(Function function) throws Exception {
		if(!function.getClass().isSynthetic()) {
			return typeof_generic(function);
		} else {
			return typeof_lambda(function);
		}
	}
	
	private static Class<?> typeof_lambda(Function function) {
		ConstantPool cp = Li.get(function.getClass());
		
		for(int i=0; i<cp.getSize(); i++) {
			String[] info;
			try {
				info = cp.getMemberRefInfoAt(i);
			} catch(Exception ex) {
				continue;
			}
			
			String parent = info[0];
			if("java/lang/Object".equals(parent) || "java/lang/Class".equals(parent)) {
				continue;
			}
			try {
				if(1 != Type.getArgumentTypes(info[2]).length) {
					continue;
				}
			} catch(Exception ex) {
				continue;
			}
			
			return vm2class(function.getClass().getClassLoader(), Type.getReturnType(info[2]).getDescriptor());
		}
		throw new AssertionError("Deduction of return type failed. Unsupported lambda: " + function);
	}
	
	@SuppressWarnings("PMD")
    private static Class<?> vm2class(ClassLoader loader, String vmtype) {
		try {
			if(vmtype.startsWith("[")) {
				return Array.newInstance(vm2class(loader, vmtype.substring(1)), 0).getClass();
			}
			if(vmtype.startsWith("L") && vmtype.endsWith(";")) {
				return Class.forName(vmtype.substring(1,vmtype.length()-1).replace('/', '.'), false, loader);
			}
			if("Z".equals(vmtype)) {
				return boolean.class;
			}
			if("B".equals(vmtype)) {
				return byte.class;
			}
			if("C".equals(vmtype)) {
				return char.class;
			}
			if("S".equals(vmtype)) {
				return short.class;
			}
			if("I".equals(vmtype)) {
				return int.class;
			}
			if("J".equals(vmtype)) {
				return long.class;
			}
			if("F".equals(vmtype)) {
				return float.class;
			}
			if("D".equals(vmtype)) {
				return double.class;
			}
			if("V".equals(vmtype)) {
				return void.class;
			}
		} catch(ClassNotFoundException cause) {
			throw ExceptionUtils.rethrow(cause);
		}
		throw new AssertionError("Unknown type: " + vmtype);
    }
	
	private static Class<?> typeof_generic(Object function) {
		ParameterizedType it = getGenericInterface(function.getClass(), Function.class);
		java.lang.reflect.Type pt = it.getActualTypeArguments()[1];
        if(pt instanceof Class<?>) {
            return (Class<?>)pt;
        }
        if(pt instanceof ParameterizedType) {
            return (Class<?>)(((ParameterizedType)pt).getRawType());
        }
        throw new AssertionError("Deduction of return type failed. Unsupported function: " + function);
	}
	
	private static ParameterizedType getGenericInterface(Class<?> type, Class<?> intface) {
		for(java.lang.reflect.Type t : type.getGenericInterfaces()) {
			ParameterizedType pt = (ParameterizedType)t;
			if( intface.equals(pt.getRawType()) ) {
				return pt;
			}
		}
		throw new AssertionError(type.getName() + " does not implement " + intface.getName());
	}
	
	
	private static final class Li { // NOPMD - lazy init idiom
		
		public static final Method GET_CONSTANT_POOL;
		
		static {
			try {
				GET_CONSTANT_POOL = Class.class.getDeclaredMethod("getConstantPool");
				GET_CONSTANT_POOL.setAccessible(true);
			} catch(ReflectiveOperationException cause) {
				throw ExceptionUtils.rethrow(cause);
			}
		}
		
		public static ConstantPool get(Class<?> type) {
			try {
				return (ConstantPool) GET_CONSTANT_POOL.invoke(type);
			} catch (ReflectiveOperationException cause) {
				throw ExceptionUtils.rethrow(cause);
			}
		}
		
	}
	
}
