/*
 * @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.invoke.WrongMethodTypeException;
import java.lang.reflect.Type;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import net.ranides.assira.reflection.IClass;
import net.ranides.assira.reflection.mockup.ForRMethod;
import net.ranides.assira.reflection.IMethod;
import org.junit.Test;
import static net.ranides.assira.junit.NewAssert.*;
import net.ranides.assira.reflection.IContext;
import net.ranides.assira.reflection.mockup.ForRMethod.TAction;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
@SuppressWarnings({"PMD.AvoidDuplicateLiterals"})
public class RMethodTest {
    
    private static final String RECORD_TYPE = "net.ranides.assira.reflection.mockup.ForRMethod<java.lang.Float>.Record<java.lang.Double>";
    
    @Test(expected = java.lang.reflect.GenericSignatureFormatError.class)
    public void testJVM1() { 
        // @todo (assira #9) JVM BUG :)
        
        // to zadziała
        ForRMethod<Float> parent1 = new ForRMethod<Float>(){};
        Object record1 = parent1.new Record<Double>(){};
        Type type1 = record1.getClass().getGenericSuperclass();
        
        assertEquals(RECORD_TYPE, type1.toString());
        
        
        // Wyjątek jaki leci jest zaskakujący:
        // java.lang.reflect.GenericSignatureFormatError: Signature Parse error: 
        //      ; expected in signature of type variable named
        //      Remaining input: <captured wildcard>;>.Record<Ljava/lang/Double;>;
        
        // Nie nasza wina. Kompilator coś sobie źle generuje i JVM samo tego sparsować sobie nie potrafi.
        
        ForRMethod<? super Float> parent2 = new ForRMethod<Float>(){};
        Object record2 = parent2.new Record<Double>(){};
        Type type2 = record2.getClass().getGenericSuperclass();
        
        assertEquals(RECORD_TYPE, type2.toString());
    }
    
    @Test
    public void testJVM2() {
        ForRMethod<Float> parent = new ForRMethod<Float>(){};
        Object record = parent.new Record<Double>(){};
        String type = record.getClass().getGenericSuperclass().getTypeName();
        
        assertEquals(RECORD_TYPE, type);
    }
    
    @Test
    public void testMethodReturs() {
        // Ja pierdolę: uzupełniamy TypeParams poprawnie. Szok.
        // Jakkolwiek JVM trochę abstrakcyjnie te sygnatury generuje ;)

        ForRMethod p0 = new ForRMethod(){};
        ForRMethod<Float> p1 = p0;
        ForRMethod<String> p2 = p0;
        
        Object o1 = p1.new Record<Double>(){};
        Object o2 = p2.new Record<Character>(){};
        
        IClass<?> ic1 = IClass.typefor(o1);
        IClass<?> ic2 = IClass.typefor(o2);
        
        String o1map1 = ic1.methods().require("map1").first().get().toString();
        String o1map2 = ic1.methods().require("map2").first().get().toString();
        String o2map1 = ic2.methods().require("map1").first().get().toString();
        String o2map2 = ic2.methods().require("map2").first().get().toString();
        
        assertEquals(Arrays.asList("Q=>class java.lang.Double", "T=>class java.lang.Float"), asText(ic1.context()));
        assertEquals(Arrays.asList("Q=>class java.lang.Character", "T=>class java.lang.String"), asText(ic2.context()));
        
//        System.out.printf("1.context = %s%n", LexicalCast.toJSON(ic1.context().map()));
//        System.out.printf("2.context = %s%n", LexicalCast.toJSON(ic2.context().map()));
                
        String e1map1 = MessageFormat.format(
            "{1}<{2}, {1}<{3}, {1}<R, {4}>>> {0}<{3}>.map1(R a, {2} b, {3} c, {4} d)",
            "net.ranides.assira.reflection.mockup.ForRMethod$Record",
            "java.util.Map",
            "java.lang.Float",
            "java.lang.Double",
            "java.lang.Integer"
        );
        String e1map2 = MessageFormat.format(
            "{1}<{2}, {1}<{3}, {1}<R, {4}>>> {0}<{3}>.map2({5}<R> a, {5}<{2}> b, {5}<{3}> c, {5}<{4}> d)", 
            "net.ranides.assira.reflection.mockup.ForRMethod$Record",
            "java.util.Map",
            "java.lang.Float",
            "java.lang.Double",
            "java.lang.Integer",
            "java.util.List"
        );
        String e2map1 = MessageFormat.format(
            "{1}<{2}, {1}<{3}, {1}<R, {4}>>> {0}<{3}>.map1(R a, {2} b, {3} c, {4} d)",
            "net.ranides.assira.reflection.mockup.ForRMethod$Record",
            "java.util.Map",
            "java.lang.String",
            "java.lang.Character",
            "java.lang.Integer"
        );
        String e2map2 = MessageFormat.format(
            "{1}<{2}, {1}<{3}, {1}<R, {4}>>> {0}<{3}>.map2({5}<R> a, {5}<{2}> b, {5}<{3}> c, {5}<{4}> d)", 
            "net.ranides.assira.reflection.mockup.ForRMethod$Record",
            "java.util.Map",
            "java.lang.String",
            "java.lang.Character",
            "java.lang.Integer",
            "java.util.List"
        );
        
        assertEquals(e1map1, o1map1);
        assertEquals(e1map2, o1map2);
        assertEquals(e2map1, o2map1);
        assertEquals(e2map2, o2map2);
    }
    
    @Test
    public void testTypeInfo() {
        IClass<?> type = IClass.typeinfo(TAction.class);
        
        IMethod m1 = type.methods().require("hello").first().get();
        IMethod m2 = type.methods().require("stat").first().get();
        
        IMethod r1 = IMethod.typeinfo(m1.reflective());
        IMethod r2 = IMethod.typeinfo(m2.reflective());
        
        assertEquals(m1, r1);
        assertEquals(m2, r2);
    }
    
    @Test
    public void testApply() {
        TAction a1 = new TAction(3);
        TAction a2 = new TAction(10);
        IMethod m1 = IClass.typefor(a1).methods().require("hello").first().get();
        IMethod m2 = IClass.typefor(a1).methods().require("stat").first().get();
        
        assertEquals("[6:9]", m1.apply(a1, 2, 3));
        assertEquals("[20:30]", m1.apply(a2, 2, 3));
        
        assertEquals("[2/3]", m2.apply(a1, 2, 3));
        assertEquals("[2/3]", m2.apply(null, 2, 3));
        
        assertThrows(IllegalArgumentException.class, ()->{
            m1.apply(a1, "q", 3);
        });
        assertThrows(IllegalArgumentException.class, ()->{
            m1.apply(a1, 1, 2, 3);
        });
        assertThrows(IllegalArgumentException.class, ()->{
            m1.apply(null, 2, 3);
        });
    }
    
    @Test
    public void testCall() {
        TAction a1 = new TAction(3);
        TAction a2 = new TAction(10);
        IMethod m1 = IClass.typefor(a1).methods().require("hello").first().get();
        IMethod m2 = IClass.typefor(a1).methods().require("stat").first().get();
        
        assertEquals("[6:9]", m1.call(a1, 2, 3));
        assertEquals("[20:30]", m1.call(a2, 2, 3));
        
        assertEquals("[2/3]", m2.call(2, 3));
        assertThrows(WrongMethodTypeException.class, ()->{
            m2.call(null, 2, 3);
        });
        assertThrows(ClassCastException.class, ()->{
            m2.call("z", 3);
        });
        
        assertThrows(ClassCastException.class, ()->{
            m1.call(a1, "q", 3);
        });
        assertThrows(IllegalArgumentException.class, ()->{
            m1.apply(a1, 1, 2, 3);
        });
        assertThrows(IllegalArgumentException.class, ()->{
            m1.apply(null, 2, 3);
        });
    }
    
    @Test
    public void testCallVoid() {
        TAction a1 = new TAction(3);
        IMethod m0 = IClass.typefor(a1).methods().require("nop").first().get();
        
        assertEquals("nop!", m0.call());
        assertEquals("nop!", m0.apply());
    }
    
    private static List<String> asText(IContext context) {
        return context.map().entrySet().stream()
            .map(Object::toString)
            .sorted()
            .collect(Collectors.toList());
    }
    
}
