/*
 * @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.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import net.ranides.assira.TestFiles;
import net.ranides.assira.annotations.Meta;
import net.ranides.assira.generic.TypeToken;
import net.ranides.assira.reflection.IClass;
import net.ranides.assira.reflection.IClasses;
import net.ranides.assira.reflection.mockup.ForClassTraits;
import org.junit.Test;
import static net.ranides.assira.junit.NewAssert.*;
import static net.ranides.assira.reflection.IAttribute.*;
import net.ranides.assira.reflection.IMethod;
import net.ranides.assira.reflection.IMethods;
import net.ranides.assira.reflection.mockup.ForMemberTraits;
import net.ranides.assira.reflection.mockup.ForMethodUtils;
import static net.ranides.assira.reflection.mockup.ForIClass.*;
import net.ranides.assira.reflection.mockup.TestReflective;
import net.ranides.assira.text.Charsets;
import net.ranides.assira.text.IOStrings;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public class AClassesTest {
    
    @Test
    public void testNames() {
        IClasses ic = FElements.newClasses(new IClass<?>[]{STRING, I_INTEGER, I_NUMBER, T_INTERFACE});
        
        List<String> names = Arrays.asList(
            "java.lang.String", 
            "java.lang.Integer", 
            "java.lang.Number", 
            "net.ranides.assira.reflection.mockup.ForClassTraits$CInterface"
        );
        
        TestReflective.assertEquals(names, ic.names());
    }
    
    @Test
    public void testRequire() {
        
        List<String> exp1 = Arrays.asList(
            "net.ranides.assira.reflection.mockup.ForClassTraits$CAnnotated1", 
            "net.ranides.assira.reflection.mockup.ForClassTraits$CAnnotated2"
        );
        List<String> exp2 = Arrays.asList(
            "net.ranides.assira.reflection.mockup.ForClassTraits$CAnnotated2"
        );
        List<String> exp3 = Arrays.asList(
            "net.ranides.assira.reflection.mockup.ForClassTraits$CInterface"
        );
        List<String> exp4 = Arrays.asList(
            "java.lang.String", "java.lang.Integer"
        );
        List<String> exp5 = Arrays.asList(
            "java.lang.String"
        );
        List<String> exp6 = Arrays.asList(
            "net.ranides.assira.reflection.mockup.ForMemberTraits$Bean",
            "net.ranides.assira.reflection.mockup.ForMemberTraits$Bean2"
        );
        
        TestReflective.assertEquals(exp1, iclist().require(Meta.Immutable.class).names());
        
        TestReflective.assertEquals(exp1, iclist().require(ANNOTATED_1.annotations()).names());
        TestReflective.assertEquals(exp2, iclist().require(ANNOTATED_2.annotations()).names());
        
        TestReflective.assertEquals(exp3, iclist().require(INTERFACE).names());
        
        TestReflective.assertEquals(exp4, iclist().require(collect(PUBLIC, FINAL)).names());
        
        IMethod m = STRING.methods().require("valueOf").first().get();
        TestReflective.assertEquals(exp5, iclist().require(m).names());

        IClasses ic2 = FElements.newClasses(new IClass<?>[]{I_INTEGER, BEAN_1, BEAN_2});
        IMethod m2 = BEAN_1.methods().require("getName1").first().get();
        TestReflective.assertEquals(exp6, ic2.require(m2).names());

    }
    
    @Test
    public void testDiscard() {
        
        List<String> exp1 = Arrays.asList(
            "java.lang.String", 
            "java.lang.Integer", 
            "java.lang.Number", 
            "net.ranides.assira.reflection.mockup.ForClassTraits$CInterface"
        );
        List<String> exp2 = Arrays.asList(
            "java.lang.String", 
            "java.lang.Integer", 
            "java.lang.Number", 
            "net.ranides.assira.reflection.mockup.ForClassTraits$CInterface",
            "net.ranides.assira.reflection.mockup.ForClassTraits$CAnnotated1"
        );
        List<String> exp3 = Arrays.asList(
            "java.lang.Number", 
            "net.ranides.assira.reflection.mockup.ForClassTraits$CInterface", 
            "net.ranides.assira.reflection.mockup.ForClassTraits$CAnnotated1", 
            "net.ranides.assira.reflection.mockup.ForClassTraits$CAnnotated2"
        );
        List<String> exp4 = Arrays.asList(
            "java.lang.Number", 
            "net.ranides.assira.reflection.mockup.ForClassTraits$CInterface", 
            "net.ranides.assira.reflection.mockup.ForClassTraits$CAnnotated1", 
            "net.ranides.assira.reflection.mockup.ForClassTraits$CAnnotated2"
        );
        List<String> exp5 = Arrays.asList(
            "java.lang.Integer", 
            "java.lang.Number", 
            "net.ranides.assira.reflection.mockup.ForClassTraits$CInterface", 
            "net.ranides.assira.reflection.mockup.ForClassTraits$CAnnotated1", 
            "net.ranides.assira.reflection.mockup.ForClassTraits$CAnnotated2"
        );
        List<String> exp6 = Arrays.asList(
            "java.lang.Integer"
        );
        List<String> exp7 = Arrays.asList(
            "java.lang.String", "java.lang.Integer", "java.lang.Number"
        );
        
        assertEquals(exp1, iclist().discard(Meta.Immutable.class).names());
        assertEquals(exp2, iclist().discard(Meta.Unsafe.class).names());
        
        assertEquals(exp1, iclist().discard(ANNOTATED_1.annotations()).names());
        assertEquals(exp1, iclist().discard(ANNOTATED_2.annotations()).names());
        
        assertEquals(exp3, iclist().discard(FINAL).names());
        
        assertEquals(exp4, iclist().discard(collect(PUBLIC,FINAL)).names());
        
        IMethod m = STRING.methods().require("valueOf").first().get();
        assertEquals(exp5, iclist().discard(m).names());

        IClasses ic2 = FElements.newClasses(new IClass<?>[]{I_INTEGER, BEAN_1, BEAN_2});
        IMethod m2 = BEAN_1.methods().require("getName1").first().get();
        assertEquals(exp6, ic2.discard(m2).names());
        
        assertEquals(exp7, iclist().discard(n -> n.name().startsWith("net.")).names());
        

    }
    
    @Test
    public void testFields() {
        List<String> exp1 = Arrays.asList(
            "CASE_INSENSITIVE_ORDER", 
            "MIN_VALUE", 
            "MAX_VALUE", 
            "TYPE", 
            "SIZE", 
            "BYTES"
        );
        List<String> exp2 = Arrays.asList(
            "serialVersionUID",
            "serialVersionUID",
            "serialVersionUID",
            "serialVersionUID",
            "value",
            "value",
            "serialPersistentFields",
            "hash"
        );
        List<String> exp3 = Arrays.asList(
            "serialVersionUID", 
            "serialVersionUID", 
            "serialVersionUID", 
            "this#", 
            "this#",
            "value", 
            "value", 
            "sizeTable", 
            "serialPersistentFields", 
            "hash", 
            "digits", 
            "DigitTens", 
            "DigitOnes", 
            "TYPE", 
            "SIZE", 
            "MIN_VALUE", 
            "MAX_VALUE", 
            "CASE_INSENSITIVE_ORDER", 
            "BYTES"
        );
        
        TestReflective.assertEquals(exp1, iclist().fields().names());
        TestReflective.assertEquals(exp2, iclist().fields().require(PRIVATE).names());
        TestReflective.assertEquals(exp3, iclist().fields().require(DECLARED).names());
    }
    
    @Test
    public void testConstructors() throws IOException {
        List<String> exp1 = IOStrings.readLines(TestFiles.CONSTRUCT_1, Charsets.UTF8);
        List<String> exp2 = IOStrings.readLines(TestFiles.CONSTRUCT_2, Charsets.UTF8);
        List<String> exp3 = IOStrings.readLines(TestFiles.CONSTRUCT_3, Charsets.UTF8);
        List<String> exp4 = IOStrings.readLines(TestFiles.CONSTRUCT_4, Charsets.UTF8);
        List<String> exp5 = IOStrings.readLines(TestFiles.CONSTRUCT_5, Charsets.UTF8);
        
        TestReflective.assertEquals(exp1, iclist().constructors());
        TestReflective.assertEquals(exp1, iclist().constructors().require(PUBLIC));
        TestReflective.assertEquals(exp2, iclist().constructors().require(DECLARED));
        TestReflective.assertEquals(exp3, iclist().constructors().require(PRIVATE));
        TestReflective.assertEquals(exp4, iclist().constructors().require(PROTECTED));
        TestReflective.assertEquals(exp5, iclist().constructors().require(PACKAGE));
    }
    
    @Test
    public void testMethods() throws IOException {
        List<String> exp1 = IOStrings.readLines(TestFiles.METHODS_1, Charsets.UTF8);
        List<String> exp2 = IOStrings.readLines(TestFiles.METHODS_2, Charsets.UTF8);
        List<String> exp3 = IOStrings.readLines(TestFiles.METHODS_3, Charsets.UTF8);
        List<String> exp4 = IOStrings.readLines(TestFiles.METHODS_4, Charsets.UTF8);
        List<String> exp5 = IOStrings.readLines(TestFiles.METHODS_5, Charsets.UTF8);
        
        TestReflective.assertEquals(exp1, iclist().methods());
        TestReflective.assertEquals(exp1, iclist().methods().require(PUBLIC));
        TestReflective.assertEquals(exp2, iclist().methods().require(DECLARED));
        TestReflective.assertEquals(exp3, iclist().methods().require(PRIVATE));
        TestReflective.assertEquals(exp4, iclist().methods().require(PROTECTED));
        TestReflective.assertEquals(exp5, iclist().methods().require(PACKAGE));
    }
    
    @Test
    public void testParent() {
        List<String> exp = Arrays.asList(
            "java.lang.Object",
            "java.lang.Number",
            "java.lang.Object",
            "net.ranides.assira.reflection.mockup.ForMethodUtils$Root",
            "net.ranides.assira.reflection.mockup.ForMemberTraits$Bean",
            "$NULL"
        );
        TestReflective.assertEquals(exp, iclist2().parent());
    }
    
    @Test
    public void testParents() {
        IClass<?>[] array = new IClass<?>[]{
            STRING,
            I_INTEGER,
            I_NUMBER, 
            IClass.typeinfo(ForMethodUtils.A.class),
            BEAN_2,
            INT,
            IClass.typefor(new ArrayList<Float>() {}),
            IClass.typefor(new ArrayList<Float>())
            
        };
        List<String> exp1 = Arrays.asList(
            "java.lang.Object",
            "java.lang.Number",
            "java.lang.Object",
            "java.lang.Object",
            "net.ranides.assira.reflection.mockup.ForMethodUtils$Root",
            "java.lang.Object",
            "net.ranides.assira.reflection.mockup.ForMemberTraits$Bean",
            "java.lang.Object",
            "java.util.ArrayList<java.lang.Float>",
            "java.util.AbstractList<java.lang.Float>",
            "java.util.AbstractCollection<java.lang.Float>",
            "java.lang.Object",
            "java.util.AbstractList<E>",
            "java.util.AbstractCollection<E>",
            "java.lang.Object"
        );
        List<String> exp2 = Arrays.asList(
            "java.lang.Object",
            "java.lang.Number",
            "java.lang.Object",
            "net.ranides.assira.reflection.mockup.ForMethodUtils$Root",
            "net.ranides.assira.reflection.mockup.ForMemberTraits$Bean",
            "java.util.ArrayList<java.lang.Float>",
            "java.util.AbstractList<E>"
        );
        TestReflective.assertEquals(exp1, FElements.newClasses(array).parents());
        TestReflective.assertEquals(exp2, FElements.newClasses(array).parents().require(DECLARED));
    }
    
    @Test
    public void testInterfaces() {
        Class<?>[] array = new Class<?>[]{
            String.class,
            Integer.class,
            Number.class, 
            ForMethodUtils.A.class,
            ForMemberTraits.Bean2.class,
            int.class
        };
        List<String> exp1 = Arrays.asList(
            "java.io.Serializable",
            "java.lang.Comparable<T>",
            "java.lang.CharSequence",
            "java.lang.Comparable<T>",
            "java.io.Serializable",
            "java.io.Serializable"
        );
        List<String> exp2 = Arrays.asList(
            "java.io.Serializable",
            "java.lang.Comparable<T>",
            "java.lang.CharSequence",
            "java.lang.Comparable<T>",
            "java.io.Serializable"
        );
        TestReflective.assertEquals(exp1, IClasses.typeinfo(array).interfaces());
        TestReflective.assertEquals(exp2, IClasses.typeinfo(array).interfaces().require(DECLARED));
    }
    
    @Test
    public void testOuter() {
        List<String> exp = Arrays.asList(
            "$NULL",
            "$NULL",
            "$NULL",
            "net.ranides.assira.reflection.mockup.ForMethodUtils",
            "net.ranides.assira.reflection.mockup.ForMemberTraits<Q>",
            "$NULL"
        );
        TestReflective.assertEquals(exp, iclist2().outer());
    }
    
    @Test
    public void testInner() {
        List<String> exp = Arrays.asList(
            "java.lang.String$CaseInsensitiveComparator",
            "java.lang.Integer$IntegerCache"
        );
        TestReflective.assertEquals(exp, iclist2().inner().discard(c -> c.name().equals("java.lang.String$1")));
    }
    
    @Test
    public void testRaw() {
        IClass<?>[] array = new IClass<?>[]{STRING, I_INTEGER, I_NUMBER, T_INTERFACE, ANNOTATED_1, new TypeToken<List<String>>(){}};
        List<Class<?>> exp = Arrays.asList(
            String.class, 
            Integer.class, 
            Number.class, 
            ForClassTraits.INTERFACE, 
            ForClassTraits.CAnnotated1.class,
            List.class
        );
        assertEquals(exp, FElements.newClasses(array).raw().list());
        assertEquals(exp, FElements.newClasses(array).reflective().list());
    }

    @Test
    public void testSubClass() {
        IClasses a = IClasses.typeinfo(CharSequence.class, Number.class, Integer.class);
        IClasses b = IClasses.typeinfo(String.class, Integer.class, Integer.class);
        IClasses c = IClasses.typeinfo(String.class, Integer.class, int.class);
        
        assertTrue( b.isSubclass(a) );
        assertFalse( c.isSubclass(a) );
        assertTrue( a.isSuper(b) );
        
        assertTrue( a.isInstance("", 41.5, 7) );
        assertTrue( b.isInstance("", 41, 7) );
        assertTrue( c.isInstance("", 41, 7) );
        
        assertFalse( c.isInstance("", 41, 7.0) );
        assertFalse( c.isInstance("", 41.0, 7) );
        
    }
    
    private static AClasses iclist() {
        return FElements.newClasses(new IClass<?>[]{STRING, I_INTEGER, I_NUMBER, T_INTERFACE, ANNOTATED_1, ANNOTATED_2});
    }
    
    private static IClasses iclist2() {
        return IClasses.typeinfo(new Class<?>[]{
            String.class, 
            Integer.class, 
            Number.class, 
            ForMethodUtils.A.class,
            ForMemberTraits.Bean2.class,
            int.class
        });
    }
    
}
