/*
 * @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.util.Arrays;
import java.util.List;
import java.util.function.Function;
import net.ranides.assira.annotations.Meta;
import net.ranides.assira.generic.CompareUtils;
import net.ranides.assira.generic.TypeToken;
import net.ranides.assira.reflection.IAnnotations;
import net.ranides.assira.reflection.IClass;
import net.ranides.assira.reflection.IMethods;
import net.ranides.assira.reflection.mockup.ForMethodUtils;
import org.junit.Test;
import static net.ranides.assira.junit.NewAssert.*;
import net.ranides.assira.reflection.IArguments;
import static net.ranides.assira.reflection.IAttribute.*;
import net.ranides.assira.reflection.IClasses;
import net.ranides.assira.reflection.mockup.ForIClass;
import net.ranides.assira.reflection.mockup.TestReflective;
import net.ranides.assira.text.StringUtils;
/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public class AMethodsTest {

    @Test
    public void testRequireAnnotations() {
        IMethods m1 = methods1().require(Override.class);
        assertEquivalent(Arrays.asList(), m1.names());
        
        IMethods m2 = methods1().require(Meta.Unsafe.class);
        assertEquivalent(Arrays.asList("some2", "some4"), m2.names());
        
        IMethods m3 = methods1().require(Deprecated.class);
        assertEquivalent(Arrays.asList("some3", "some4"), m3.names());
        
        IMethods m4 = methods1().require(IAnnotations.of(Meta.Unsafe.class, Deprecated.class));
        assertEquivalent(Arrays.asList("some4"), m4.names());
        
        IMethods m5 = methods1().require(IAnnotations.of(Deprecated.class));
        assertEquivalent(Arrays.asList("some3", "some4"), m5.names());
    }
    
    @Test
    public void testDiscardAnnotations() {
        IMethods m1 = methods1().require(DECLARED).discard(Override.class).discard(SYNTHETIC);
        TestReflective.assertEquals(Arrays.asList("compare", "fun6", "fun7", "some1", "some2", "some3", "some4"), m1.names());
        
        IMethods m2 = methods1().require(DECLARED).discard(Meta.Unsafe.class).discard(SYNTHETIC);
        TestReflective.assertEquals(Arrays.asList("compare", "fun6", "fun7", "some1", "some3"), m2.names());
        
        IMethods m3 = methods1().require(DECLARED).discard(Deprecated.class).discard(SYNTHETIC);
        TestReflective.assertEquals(Arrays.asList("compare", "fun6", "fun7", "some1", "some2"), m3.names());
        
        IMethods m4 = methods1().require(DECLARED).discard(IAnnotations.of(Meta.Unsafe.class, Deprecated.class)).discard(SYNTHETIC);
        TestReflective.assertEquals(Arrays.asList("compare", "fun6", "fun7", "some1"), m4.names());
        
        IMethods m5 = methods1().require(DECLARED).discard(IAnnotations.of(Deprecated.class)).discard(SYNTHETIC);
        TestReflective.assertEquals(Arrays.asList("compare", "fun6", "fun7", "some1", "some2"), m5.names());
    }
    
    @Test
    public void testRequireAttr() {
        List<String> m1 = Arrays.asList("fun3", "fun1c", "fun2", "fun1", "fun3", "wait", "wait", "wait", "equals", "toString", "hashCode", "getClass", "notify", "notifyAll");
        List<String> m2 = Arrays.asList("fun3", "fun1c", "fun2", "fun1", "fun4", "fun5");
        List<String> m3 = Arrays.asList();
        List<String> m4 = Arrays.asList("fun5");
        List<String> m5 = Arrays.asList("fun5", "fun5", "registerNatives");
        
        assertEquivalent(m1, methods2().require(collect(PUBLIC)).names());
        assertEquivalent(m2, methods2().require(collect(DECLARED)).discard(SYNTHETIC).names());
        assertEquivalent(m3, methods2().require(collect(STRICT)).names());
        assertEquivalent(m4, methods2().require(collect(PRIVATE,STRICT)).names());
        assertEquivalent(m5, methods2().require(collect(PRIVATE)).discard(SYNTHETIC).names());
    }
    
    @Test
    public void testRequireName() {
        List<String> m1 = Arrays.asList("fun3", "fun1c", "fun2", "fun1", "fun3", "equals", "toString", "hashCode", "getClass", "notify", "notifyAll");
        List<String> m3 = Arrays.asList("fun5");
        List<String> m4 = Arrays.asList();
        List<String> m5 = Arrays.asList("fun5", "fun5", "registerNatives");
        List<String> m6 = Arrays.asList("registerNatives");
        
        assertEquivalent(m1, methods2().require(collect(PUBLIC)).discard("wait").names());
        assertEquivalent(m3, methods2().require(collect(PRIVATE,STRICT)).discard("fun2").names());
        assertEquivalent(m4, methods2().require(collect(PRIVATE,STRICT)).discard("fun5").names());
        assertEquivalent(m5, methods2().require(collect(PRIVATE)).discard("fun2").discard(SYNTHETIC).names());
        assertEquivalent(m6, methods2().require(collect(PRIVATE)).discard("fun5").discard(SYNTHETIC).names());
    }
    
    @Test
    public void testDiscardAttr() {
        List<String> m1 = Arrays.asList("equals", "fun1", "fun1c", "fun2", "fun3", "getClass", "hashCode", "notify", "notifyAll", "toString", "wait", "wait", "wait");
        List<String> m2 = Arrays.asList("equals", "fun1",          "fun2", "fun3", "getClass", "hashCode", "notify", "notifyAll", "toString", "wait", "wait", "wait");
        List<String> m3 = Arrays.asList("clone", "finalize", "fun4", "fun5", "registerNatives");
        List<String> m4 = Arrays.asList("fun1", "fun1c", "fun2", "fun3", "fun4", "fun5");
        List<String> m5 = Arrays.asList("fun1", "fun1c", "fun2", "fun3", "fun4");
        

        assertEquivalent(m1, methods3().names());
        assertEquivalent(m2, methods3().discard(collect(DECLARED)).discard(SYNTHETIC).names());
        assertEquivalent(m3, methods3().discard(collect(PUBLIC)).discard(SYNTHETIC).names());
        assertEquivalent(m4, methods2().require(DECLARED).discard(SYNTHETIC).names());
        assertEquivalent(m5, methods2().require(DECLARED).discard(STRICT).discard(SYNTHETIC).names());
    }
    
    @Test
    public void testRequireReturns() {
        List<String> m1 = Arrays.asList("compare", "some1", "fun7", "fun6", "hashCode");
        List<String> m2 = Arrays.asList("some2");
        List<String> m3 = Arrays.asList("some3");
        List<String> m4 = Arrays.asList("some4", "wait", "wait", "wait", "notify", "notifyAll");
        List<String> m5 = Arrays.asList();
        
        assertEquivalent(m1, methods1().require(ForIClass.INT).names());
        assertEquivalent(m2, methods1().require(ForIClass.I_INTEGER).names());
        assertEquivalent(m3, methods1().require(ForIClass.FLOAT).names());
        assertEquivalent(m4, methods1().require(ForIClass.P_VOID).names());
        assertEquivalent(m5, methods1().require(IClass.typeinfo(Void.class)).names());
    }
    
    @Test
    public void testDiscardReturns() {
        List<String> m1 = Arrays.asList("some2", "some3", "some4");
        List<String> m2 = Arrays.asList("compare", "fun6", "fun7", "some1", "some3", "some4");
        List<String> m3 = Arrays.asList("compare", "fun6", "fun7", "some1", "some2", "some4");
        List<String> m4 = Arrays.asList("compare", "fun6", "fun7", "some1", "some2", "some3");
        List<String> m5 = Arrays.asList("compare", "fun6", "fun7", "some1", "some2", "some3", "some4");

        assertEquivalent(m1, methods1().require(DECLARED).discard(SYNTHETIC).discard(ForIClass.INT).names());
        assertEquivalent(m2, methods1().require(DECLARED).discard(SYNTHETIC).discard(ForIClass.I_INTEGER).names());
        assertEquivalent(m3, methods1().require(DECLARED).discard(SYNTHETIC).discard(ForIClass.FLOAT).names());
        assertEquivalent(m4, methods1().require(DECLARED).discard(SYNTHETIC).discard(ForIClass.P_VOID).names());
        assertEquivalent(m5, methods1().require(DECLARED).discard(SYNTHETIC).discard(ForIClass.O_VOID).names());
    }
    
    @Test
    public void testDiscardArguments() {
        List<String> m1 = Arrays.asList("func2", "func3", "func4", "func5", "func6", "func7");
        List<String> m2 = Arrays.asList("func1", "func3", "func4", "func4", "func5", "func6", "func7");
        List<String> m3 = Arrays.asList("func1", "func2", "func3", "func4", "func5", "func7");
        List<String> m4 = Arrays.asList("func1", "func2", "func3", "func4", "func4", "func6", "func7");
        List<String> m5 = Arrays.asList("func1", "func2", "func3", "func4", "func4", "func5", "func6");
        
        assertEquivalent(m1, methods4().require(DECLARED).discard(IArguments.typeinfo(ForIClass.INT)).discard(SYNTHETIC).names());
        assertEquivalent(m2, methods4().require(DECLARED).discard(IArguments.typeinfo(ForIClass.I_INTEGER)).discard(SYNTHETIC).names());
        assertEquivalent(m2, methods4().require(DECLARED).discard(IArguments.typeinfo(IClass.typefor(88))).discard(SYNTHETIC).names());
        
        assertEquivalent(m3, methods4().require(DECLARED).discard(IArguments.typeinfo(ForIClass.STRING)).discard(SYNTHETIC).names());
        assertEquivalent(m4, methods4().require(DECLARED).discard(IArguments.typeinfo(ForIClass.CHARSEQUENCE)).discard(SYNTHETIC).names());
        assertEquivalent(m5, methods4().require(DECLARED).discard(IArguments.typeinfo(ForIClass.OBJECT)).discard(SYNTHETIC).names());
    }
    
    @Test
    public void testMatches() {
        List<String> m1 = Arrays.asList("func1", "func4");
        List<String> m2 = Arrays.asList("func2", "func7");
        List<String> m3 = Arrays.asList("func4", "func5", "func6", "func7");
        List<String> m4 = Arrays.asList("func7");
         
        assertEquivalent(m1, methods4().require(DECLARED).matches(IClasses.typeinfo(int.class)).names());
        assertEquivalent(m2, methods4().require(DECLARED).matches(IClasses.typeinfo(Integer.class)).names());
        assertEquivalent(m2, methods4().require(DECLARED).matches(IClasses.typefor(77)).names());
        assertEquivalent(m3, methods4().require(DECLARED).matches(IClasses.typeinfo(String.class)).names());
        assertEquivalent(m4, methods4().require(DECLARED).matches(IClasses.typeinfo(Object.class)).names()); 
    }
    
    @Test
    public void testReturns() {
        List<String> m1 = Arrays.asList("int", "int", "int", "int", "java.lang.Float", "java.lang.Integer", "void");
        List<String> m2 = Arrays.asList("java.util.Comparator<T>", "java.util.Comparator<T>", "java.util.Comparator<T>");
        List<String> m3 = Arrays.asList("java.util.Comparator<java.lang.Float>", "java.util.Comparator<java.lang.Float>", "java.util.Comparator<java.lang.Float>");
        
        assertEquivalent(m1, methods1().require(DECLARED).discard(SYNTHETIC).returns().list(Object::toString));
        assertEquivalent(m2, methods1().require("thenComparing").discard(SYNTHETIC).returns().list(Object::toString));
        assertEquivalent(m3, new TypeToken<ForMethodUtils.MCP<Float>>(){}.methods().require("thenComparing").discard(SYNTHETIC).returns().list(Object::toString));
    }
    
    @Test
    public void testMethod() {
        String mcp = "net.ranides.assira.reflection.mockup.ForMethodUtils$MCP<T>";
        List<String> m1 = Arrays.asList(mcp, mcp, mcp, mcp, mcp, mcp, mcp);
        List<String> m2 = Arrays.asList("java.util.Comparator<T>", "java.util.Comparator<T>", "java.util.Comparator<T>");
        
        assertEquivalent(m1, methods1().require(DECLARED).discard(SYNTHETIC).parent().list(Object::toString));
        assertEquivalent(m2, methods1().require("thenComparing").discard(SYNTHETIC).parent().list(Object::toString));
    }
    
    @Test
    public void testAccepts() {
        List<String> m1 = Arrays.asList("func1", "func2", "func4", "func7");
        List<String> m2 = Arrays.asList("func4", "func5", "func6", "func7");
        
        assertEquivalent(m1, methods4().require(DECLARED).accepts(77).names());
        
        assertEquivalent(m2, methods4().require(DECLARED).accepts("x").names());
        
    }
    
    @Test
    public void testHandles() throws Exception, Throwable {
        ForMethodUtils.MCP<?> mcp = new ForMethodUtils.MCP<>();
        IMethods m1 = methods1().require("some1");
        
        assertEquals(777, m1.reflective().first().get().invoke(mcp));
        assertEquals(777, m1.handle().first().get().invoke(mcp));
    }

    private static IMethods methods1() {
        return IClass.typeinfo(ForMethodUtils.MCP.class).methods();
    }
    
    private static IMethods methods2() {
        return IClass.typeinfo(ForMethodUtils.A.class).methods();
    }
    
    private static IMethods methods3() {
        return IClass.typeinfo(ForMethodUtils.B.class).methods();
    }
    
    private static IMethods methods4() {
        return IClass.typeinfo(ForMethodUtils.C.class).methods();
    }
    
}
