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

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.ranides.assira.asm.MethodInspectorVM.VMMethod;
import net.ranides.assira.collection.map.CrossMap;
import net.ranides.assira.events.Event;
import net.ranides.assira.generic.Function;
import net.ranides.assira.reflection.GenericParam;
import net.ranides.assira.reflection.inspect.MethodInspector;
import net.ranides.assira.text.LexicalCast;
import net.ranides.assira.trace.LoggerUtils;
import org.junit.Test;
import static org.junit.Assert.*;

/**
 *
 * @author ranides
 */
public class MethodInspectorTest {

    private static final boolean DEBUG = true;

    public MethodInspectorTest() {
    }
    
    private static Method fm(Class<?> clazz, String name) {
        return MethodInspector.inclass(clazz).declared().name(name).first().reflective();
    }

    private void assertArgs(Method method, String[] expected) {
        String message = method.getDeclaringClass().getName() + "." + method.getName();
        if(DEBUG) {
            LoggerUtils.dump("%s\n", message);
        }
        int index=0;
        for(GenericParam var : MethodInspectorVM.params(method)) {
            if(DEBUG) {
                LoggerUtils.dump("\t\"%s\"\n", var);
            }
            assertEquals(message, expected[index++], var.toString());
        }
    }

    private void assertVars(Method method, String[] expected) {
        String message = method.getDeclaringClass().getName() + "." + method.getName();
        if(DEBUG) {
            LoggerUtils.dump("%s\n", message);
        }
        int index=0;
        for(GenericParam var : MethodInspectorVM.variables(method)) {
            if(DEBUG) {
                LoggerUtils.dump("\t\"%s\"\n", var);
            }
            assertEquals(message, expected[index++], var.toString());
        }
    }

    @Test
    public void testgetVMReturn() throws IOException {
        assertEquals("CrossMap<Set<Character[]>, Float, List<Function<Integer, Long>>>",
            MethodInspectorVM.typeof(fm(A.class, "map1")).toString()
        );
        assertEquals("CrossMap<?, ?, ?>",
            MethodInspectorVM.typeof(fm(A.class, "map2")).toString()
        );
        assertEquals("CrossMap<?, Byte, ?>",
            MethodInspectorVM.typeof(fm(A.class, "map3")).toString()
        );
        assertEquals("CrossMap<Byte, Byte, Byte>",
            MethodInspectorVM.typeof(fm(A.class, "map4")).toString()
        );
        assertEquals("Map<Arg, Ret>",
            MethodInspectorVM.typeof(fm(A.class, "map5")).toString()
        );
    }

    @Test
    public void testGetArguments() throws IOException {
        assertArgs(fm(A.class, "params1"), new String[]{
            "String name",
            "int size",
            "long maxtime",
            "A<?, ?> joiner"
        });
        assertArgs(fm(A.class, "params2"), new String[]{
            "Class<T> event",
            "Ort<T> listener"
        });
        assertArgs(fm(A.class, "params3"), new String[]{
            "Norm value"
        });
        assertArgs(fm(A.class, "params4"), new String[]{
        });
        assertArgs(fm(A.class, "params5"), new String[]{
            "Collection<S> content",
            "Function<T, Ret> function",
            "Ort<Arg> extra"
        });
        assertArgs(fm(A.class, "dec1"), new String[]{
            "int a",
            "float b"
        });
    }

    @Test
    public void testGetVariables() throws IOException {
        assertVars(fm(A.class, "dec1"), new String[]{
            "CrossMap<Set<Character[]>, Float, List<Function<Integer, Long>>> map1",
            "CrossMap<?, ?, ?> map2",
            "CrossMap<?, Byte, ?> map3",
            "CrossMap<Byte, Byte, Byte> map4"
        });
        assertVars(fm(A.class, "dec2"), new String[]{
            "int x",
            "int y",
            "int z"
        });
    }

    @Test
    public void testInspector() {
        for(VMMethod item : MethodInspectorVM.methods(B.class) ) {
            LoggerUtils.dump("%s\n", item.owner.getSimpleName()+":"+item.name);
            LoggerUtils.dump("%s\n", LexicalCast.asText(item.params) );
        }

    }


    @SuppressWarnings("PMD")
    private static abstract class Norm { }

    @SuppressWarnings("PMD")
    private static abstract class Ort<T> { }

    @SuppressWarnings("PMD")
    private static abstract class A<Arg, Ret> {

        public abstract CrossMap<Set<Character[]>, Float, List<Function<Integer, Long>>> map1();

        public abstract CrossMap<?,?,?> map2();

        public abstract CrossMap<?,Byte,?> map3();

        public abstract CrossMap<? super Byte,Byte,? extends Byte> map4();

        public abstract Map<Arg, Ret> map5();

        public static void dec1(int a, float b) {
            CrossMap<Set<Character[]>, Float, List<Function<Integer, Long>>> map1 = null;
            CrossMap<?,?,?> map2 = null;
            CrossMap<?,Byte,?> map3 = null;
            CrossMap<? super Byte,Byte,? extends Byte> map4 = null;
        }

        public void dec2(int a, float b) {
            int removed;
            int x = 0;
            int y = 0;
            int z = 0;
            int reduced;
        }

        public static void params1(String name, int size, long maxtime, A joiner) { }

        public <T extends Event> void params2(Class<T> event, Ort<? super T> listener) { }

        public void params3(Norm value) { }

        public void params4() { }

        public <S,T> void params5(Collection<S> content, Function<T,Ret> function, Ort<Arg> extra) { }

    }

    private static abstract class B {

        public static void mstat(int first, int second) { /* non abstract */ }

        public abstract void unknown(int first, int second);

        protected abstract void unknownGeneric(List<Map<String,Integer>> source, Set<Float> vars);

        private void nop(float left, float right) { /* non abstract */ }

        private void nop(int... array) { /* non abstract */ }

        public void ret(int start, int stop) { /* non abstract */ }

        public void complex(String[] words, int v, char z, List<Map<String,Integer>> r) { /* non abstract */ }

    }
}
