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

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
import net.ranides.assira.ContractTesters;
import net.ranides.assira.collection.mockup.TPoint;
import org.junit.Test;
import static net.ranides.assira.junit.NewAssert.*;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public class ClassUtilsTest {
    
    @Test
    public void testBox() throws Exception {
        assertEquals(Byte.class, ClassUtils.box(byte.class));
        assertEquals(Short.class, ClassUtils.box(short.class));
        assertEquals(Integer.class, ClassUtils.box(int.class));
        assertEquals(Long.class, ClassUtils.box(long.class));
        assertEquals(Float.class, ClassUtils.box(float.class));
        assertEquals(Double.class, ClassUtils.box(double.class));
        assertEquals(Character.class, ClassUtils.box(char.class));
        assertEquals(Boolean.class, ClassUtils.box(boolean.class));
        assertEquals(Void.class, ClassUtils.box(void.class));
        assertEquals(Object.class, ClassUtils.box(Object.class));
        assertEquals(TPoint.class, ClassUtils.box(TPoint.class));
    }
    
    @Test
    public void testUnbox() throws Exception {
        assertEquals(byte.class, ClassUtils.unbox(Byte.class));
        assertEquals(short.class, ClassUtils.unbox(Short.class));
        assertEquals(int.class, ClassUtils.unbox(Integer.class));
        assertEquals(long.class, ClassUtils.unbox(Long.class));
        assertEquals(float.class, ClassUtils.unbox(Float.class));
        assertEquals(double.class, ClassUtils.unbox(Double.class));
        assertEquals(char.class, ClassUtils.unbox(Character.class));
        assertEquals(boolean.class, ClassUtils.unbox(Boolean.class));
        assertEquals(void.class, ClassUtils.unbox(Void.class));
        assertEquals(Object.class, ClassUtils.unbox(Object.class));
        assertEquals(TPoint.class, ClassUtils.unbox(TPoint.class));
    }
    
	@SuppressWarnings("PMD")
    @SuppressFBWarnings("DLS_DEAD_LOCAL_STORE")
    @Test
    public void testIsLoaded() throws IOException {
        TPoint point = new TPoint(0, 0, 0);
        assertTrue(ClassUtils.isLoaded("net.ranides.assira.collection.mockup.TPoint"));
        
        assertFalse(ClassUtils.isLoaded("net.ranides.assira.reflection.util.ClassUtilsTest$IType1"));
        IType1 a = new IType1();
        assertTrue(ClassUtils.isLoaded("net.ranides.assira.reflection.util.ClassUtilsTest$IType1"));
        
        IType1.IType2.IType3 b = null;
        assertFalse(ClassUtils.isLoaded("net.ranides.assira.reflection.util.ClassUtilsTest$IType1$IType2$IType3"));
        b = new IType1.IType2.IType3();
        assertTrue(ClassUtils.isLoaded("net.ranides.assira.reflection.util.ClassUtilsTest$IType1$IType2$IType3"));
		
		ClassLoader prev = replace(ClassUtils.loader(ClassUtils.loader(), ContractTesters.JAR_TEST));

		assertTrue(ClassUtils.isLoaded("net.ranides.assira.collection.mockup.TPoint"));
		assertTrue(ClassUtils.isLoaded("net.ranides.assira.reflection.util.ClassUtilsTest$IType1"));
		assertTrue(ClassUtils.isLoaded("net.ranides.assira.reflection.util.ClassUtilsTest$IType1$IType2$IType3"));
		
		replace(prev);
    }
	
	@Test
	public void testIsChild() throws IOException {
		ClassLoader loader0 = getClass().getClassLoader();
		ClassLoader loader1 = ClassUtils.loader(loader0, ContractTesters.JAR_RULES);
		ClassLoader loader2 = ClassUtils.loader(loader1, ContractTesters.JAR_TEST);
		
		assertTrue(ClassUtils.isChild(loader0, loader2));
		assertTrue(ClassUtils.isChild(loader0, loader1));
		assertTrue(ClassUtils.isChild(loader1, loader1));
		assertTrue(ClassUtils.isChild(loader1, loader2));
		assertTrue(ClassUtils.isChild(loader2, loader2));
		
		assertFalse(ClassUtils.isChild(loader2, loader0));
		assertFalse(ClassUtils.isChild(loader1, loader0));
		assertFalse(ClassUtils.isChild(loader2, loader1));
	}
	
	
	@Test
	public void testLoader() throws Exception {
		ClassLoader loader = ClassUtils.loader(ClassUtils.loader(), ContractTesters.JAR_TEST);
		
		Function<String,Supplier<Integer>> f1 = newInstance(loader, "net.ranides.assira.test.TypeLoader1");
		BiFunction<String, ClassLoader, Supplier<Integer>> f2 = newInstance(loader, "net.ranides.assira.test.TypeLoader2");
		BiFunction<String, Supplier<ClassLoader>, Supplier<Integer>> f3 = newInstance(loader, "net.ranides.assira.test.TypeLoader3");
		
		Supplier<Integer> s1 = f1.apply("net.ranides.assira.test.Type1");
		assertEquals(11, s1.get().intValue());
		
		Supplier<Integer> s2 = f2.apply("net.ranides.assira.test.Type2", loader);
		assertEquals(22, s2.get().intValue());
		
		assertThrows(AssertionError.class, ()->{
			f2.apply("net.ranides.assira.test.Type3", ClassUtils.loader());
		});
		assertThrows(AssertionError.class, ()->{
			f3.apply("net.ranides.assira.test.Type3", ClassUtils::loader);
		});
		
		
		ClassLoader prev = replace(loader);
		
		Supplier<Integer> s3 = f3.apply("net.ranides.assira.test.Type3", ClassUtils::loader);
		
		assertEquals(33, s3.get().intValue());
		
		replace(prev);
	}
    
    @Test
    public void testLoaderJAR() throws IOException, ReflectiveOperationException {
        String cname = "net.ranides.assira.test.TypeLoader1";
        
		ClassLoader loader1 = ClassUtils.loader(ClassUtils.loader(), ContractTesters.JAR_TEST);
		ClassLoader loader2 = ClassUtils.loader(ClassUtils.loader(), ContractTesters.JAR_TEST.toURI());
        
        
        Class<?> c1 = loader1.loadClass(cname);
        Class<?> c2 = loader2.loadClass(cname);
        
        assertEquals(cname, c1.getName());
        assertEquals(cname, c2.getName());
        assertNotEquals(c1, c2);
        
        assertThrows(FileNotFoundException.class, () -> {
            ClassUtils.loader(ClassUtils.loader(), new File("HTTP404"));
        });
    }
	
	@Test
	public void testIsInstance() {
		assertTrue(ClassUtils.isInstance(Integer.class, null));
		assertTrue(ClassUtils.isInstance(int.class, null));
		
		assertTrue(ClassUtils.isInstance(long.class, 55L));
		assertTrue(ClassUtils.isInstance(Long.class, 55L));
		assertTrue(ClassUtils.isInstance(Number.class, 55L));
		assertTrue(ClassUtils.isInstance(Object.class, 55L));
		
		assertFalse(ClassUtils.isInstance(long.class, 'z'));
	}
	
	@Test
	public void testIsInstance_Array() {
		assertTrue(ClassUtils.isInstance(
			new Class<?>[]{ Integer.class }, 
			new Object[]{ null }
		));
		assertTrue(ClassUtils.isInstance(
			new Class<?>[]{ Long.class, int.class, Number.class }, 
			new Object[]{ 11L, 11, 11f }
		));
		
		assertFalse(ClassUtils.isInstance(
			new Class<?>[]{ int.class, int.class, int.class }, 
			new Object[]{ 11, 11 }
		));
		assertFalse(ClassUtils.isInstance(
			new Class<?>[]{ int.class, int.class, int.class }, 
			new Object[]{ 11, 11, 11L }
		));
	}
	
	@SuppressWarnings("unchecked")
	private static <T> T newInstance(ClassLoader loader, String name) throws ReflectiveOperationException {
		return (T)loader.loadClass(name).newInstance();
	}
	
	private static ClassLoader replace(ClassLoader loader) {
		ClassLoader prev = Thread.currentThread().getContextClassLoader();
		Thread.currentThread().setContextClassLoader(loader);
		return prev;
	}
    
	@SuppressWarnings("PMD")
    private static final class IType1 {
        private static final class IType2 {
            private static final class IType3 {
    
            }
        }
    }
    
}
