/*
 * @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.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import net.ranides.assira.collection.lists.ListUtils;
import net.ranides.assira.collection.sets.HashSet;
import org.junit.Test;
import static net.ranides.assira.junit.NewAssert.*;
import net.ranides.assira.reflection.mockup.ForCPInspector;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public class CPInspectorTest {
	
    /**
     * Metoda "main" służy do sprawdzenia, jak sytuacja wygląda przy włączonym JRebel.
     * NetBeans z zainstalowanym pluginem JRebel uruchamia #main z włączonym JRebelem.
     * Sprawdzamy to, ponieważ JRebel dorzuca własne metody, które są widoczne w ConstantPool
     * i mogą nam one zepsuć inspektora.
     * 
     * @param args 
     */
	public static void main(String[] args) {
        System.out.printf("methods:%n");
        CPInspector.getMethods(ForCPInspector.class)
            .map(v -> "   - " + v)
            .forEach(System.out::println);
        
        System.out.printf("fields:%n");
        CPInspector.getFields(ForCPInspector.class)
            .map(v -> "   - " + v)
            .forEach(System.out::println);
        
        System.out.printf("classes:%n");
        CPInspector.getClasses(ForCPInspector.class)
            .map(v -> "   - " + v.getName())
            .forEach(System.out::println);
	}
    
    @Test
    public void testFields() {
        int value = 1;
		int[] array = new int[]{1,2,3,4,5};
		Function<String, Character> function1 = s -> s.charAt(array[value]);
        assertFields(Arrays.asList("arg$1", "arg$2"), CPInspector.getFields(type0(function1)));

//        CPInspector.getMethods(ForCPInspector.class).forEach(System.out::println);
//        CPInspector.getFields(ForCPInspector.class).forEach(System.out::println);
//        CPInspector.getClasses(ForCPInspector.class).map(Class::getSimpleName).forEach(System.out::println);
        
        assertFields(Arrays.asList("S2", "index", "listener"), CPInspector.getFields(ForCPInspector.class));
        assertMethods(Arrays.asList("toUpperCase","length","handleEvent"), CPInspector.getMethods(ForCPInspector.class));
        assertClasses(Arrays.asList("ForCPInspector","EventListener","String"), CPInspector.getClasses(ForCPInspector.class));
    }
	
	@Test
	public void testMethods() {

		assertReturns(Float.class, CPInspector.getMethods(type1((int[] array) -> 5.7f)));
		assertReturns(float[].class, CPInspector.getMethods(type1((int[] array) -> new float[2])));
		assertReturns(Float[].class, CPInspector.getMethods(type1((int[] array) -> new Float[2])));
		
		assertReturns(Object.class, CPInspector.getMethods(type2((int[] array) -> 5.7f)));
		assertReturns(Object.class, CPInspector.getMethods(type2((int[] array) -> new float[2])));
		assertReturns(Object.class, CPInspector.getMethods(type2((int[] array) -> new Float[2])));
		
		int value = 1;
		int[] array = new int[]{1,2,3,4,5};
		Function<String, Character> function1 = s -> ' ';
		Function<String, Character> function2 = s -> s.charAt(array[value]);
		
		assertReturns(Character.class, CPInspector.getMethods(type2(function1)));
		assertReturns(Character.class, CPInspector.getMethods(type2(function2)));
		assertReturns(Character.class, CPInspector.getMethods(type1((String s) -> s.charAt(array[value]))));

	}
    
    @Test
	public void testConstructor() {
        Class<?>[] NONE = new Class<?>[0];

		assertConstructors(NONE, CPInspector.getConstructors(type1((int[] array) -> 5.7f)));
		assertConstructors(NONE, CPInspector.getConstructors(type1((int[] array) -> new float[2])));
		assertConstructors(NONE, CPInspector.getConstructors(type1((int[] array) -> new Float[2])));
		
		assertConstructors(NONE, CPInspector.getConstructors(type2((int[] array) -> 5.7f)));
		assertConstructors(NONE, CPInspector.getConstructors(type2((int[] array) -> new float[2])));
		assertConstructors(NONE, CPInspector.getConstructors(type2((int[] array) -> new Float[2])));
		
		int value = 1;
		int[] array = new int[]{1,2,3,4,5};
		long[] array2 = new long[]{1,2,3,4,5};
        char value2 = 'z';
        
		Function<String, Character> function1 = s -> ' ';
		Function<String, Character> function2 = s -> s.charAt(array[value]);
		Function<String, Character> function3 = s -> s.charAt((int)array2[value2]);
        
        Class<?>[] exp1 = new Class<?>[]{int[].class, int.class};
        Class<?>[] exp2 = new Class<?>[]{long[].class, char.class};
		
		assertConstructors(NONE, CPInspector.getConstructors(type2(function1)));
		assertConstructors(exp1, CPInspector.getConstructors(type2(function2)));
		assertConstructors(exp1, CPInspector.getConstructors(type1((String s) -> s.charAt(array[value]))));
		
        assertConstructors(exp2, CPInspector.getConstructors(type2(function3)));
		assertConstructors(exp2, CPInspector.getConstructors(type1((String s) -> s.charAt((int)array2[value2]))));

	}
    
    private static void assertClasses(List<String> names, Stream<Class<?>> stream) {
        assertTrue("some types not found",stream.map(Class::getSimpleName).collect(Collectors.toSet()).containsAll(names));
    }
    
    private static void assertMethods(List<String> names, Stream<Method> stream) {
        assertTrue("some methods not found",stream.map(Method::getName).collect(Collectors.toSet()).containsAll(names));
    }
    
    private static void assertFields(List<String> names, Stream<Field> stream) {
        assertTrue("some fields not found", stream.map(Field::getName).collect(Collectors.toSet()).containsAll(names) );
    }
    
    private static void assertReturns(Class<?> type, Stream<Method> stream) {
        Optional<Method> method = stream.findFirst();
        assertTrue("method not found", method.isPresent());
        assertEquals(type, method.get().getReturnType());
    }
	
    private static <T> void assertConstructors(Class<?>[] types, Stream<Constructor<T>> stream) {
        List<Constructor<T>> list = stream.collect(Collectors.toList());

        assertTrue(list.stream().anyMatch(c -> {
            return "public java.lang.Object()".equals(c.toString());
        }));        
        assertTrue(list.stream().anyMatch(c -> {
            return Arrays.equals(types, c.getParameterTypes());
        }));
    }
    
	private static Class<?> type0(Object object) {
		return object.getClass();
	}
	
	private static <T,R> Class<?> type1(Function<T,R> object) {
		return object.getClass();
	}
	
	private static Class<?> type2(Function<?,?> object) {
		return object.getClass();
	}
	
}
