/*
 * @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.util.Arrays;
import java.util.List;
import net.ranides.assira.generic.TypeToken;
import net.ranides.assira.generic.Wrapper;
import net.ranides.assira.reflection.IAttribute;
import net.ranides.assira.reflection.IClass;
import net.ranides.assira.reflection.mockup.ForFields;
import org.junit.Test;
import static net.ranides.assira.junit.NewAssert.*;
import net.ranides.assira.reflection.IField;
import net.ranides.assira.reflection.IFields;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public class RFieldTest {

    private final IClass<?> cr = IClass.typeinfo(ForFields.Record.class);
    private final IClass<?> ca = IClass.typeinfo(ForFields.ARecord.class);
    private final IClass<?> ci = IClass.typeinfo(ForFields.IRecord.class);

    @Test
    public void testCollect() {
        IField field = ca.field("name").get();
        IFields fields = field.collect();
        
        assertSymEquals(field, fields.first().get());
    }
    
    @Test
    public void testEquals() {
        IField f1 = ca.field("name").get();
        IField f2 = ca.field("name").get();
        IField f3 = cr.field("rid").get();
        
//        assertNotSame(f1, f2);
        assertEquality(f1, f2, f3);
        assertEquality(f1, f2, new Object());
        
        assertEquals("java.lang.String net.ranides.assira.reflection.mockup.ForFields$ARecord.name", f1.toString());
        assertEquals("java.lang.String net.ranides.assira.reflection.mockup.ForFields$ARecord.name", f2.toString());
        assertEquals("int net.ranides.assira.reflection.mockup.ForFields$Record.rid", f3.toString());
    }
    
    @Test
    public void testFields() {
        List<String> exp1 = Arrays.asList("Record rid", "Record name", "ARecord AMAX", "ARecord aid", "ARecord name");
        List<String> exp2 = Arrays.asList("Record rid", "Record pid", "Record name");
        List<String> exp3 = Arrays.asList("Record rid", "Record pid", "Record name", "ARecord AMAX", "ARecord APRIV", "ARecord aid", "ARecord pid", "ARecord name");
        assertEquiList(exp1, cr.fields().list(this::field2str));
        assertEquiList(exp2, cr.fields().require(IAttribute.DECLARED).list(this::field2str));
        assertEquiList(exp3, cr.fields().require(IAttribute.ANY).list(this::field2str));
    }
    
    private String field2str(IField f) {
        return f.parent().shortname()+" "+f.name();
    }
    
    private int $int(Object v) {
        return ((Number)v).intValue();
    }
 
    @Test
    public void testGetSet() {
        ForFields.Record r = new ForFields.Record();
        
        ca.field("name").get().set(r, "hello");
        ca.field("pid").get().set(r, 81);
        
        cr.field("aid").get().set(r, $int(cr.field("aid").get().get(r)) + 91);
        cr.field("rid").get().set(r, $int(cr.field("rid").get().get(r)) + 92);
        cr.field("name").get().set(r, cr.field("name").get().get(r) + "world");
        cr.field("pid").get().set(r, $int(cr.field("pid").get().get(r)) + 82);
        
        assertEquals("{191:81:hello}#{392:482:R.world}", r.toString());
        
        assertThrows(IllegalArgumentException.class, ()->{
            ca.field("name").get().set(r, 556);
        });
    }
    
    @Test
    public void testGetterSetter() throws Throwable {
        ForFields.Record r = new ForFields.Record();
        
        ca.field("name").get().setter().invoke(r, "hello");
        ca.field("pid").get().setter().invoke(r, 81);
        
        cr.field("aid").get().setter().invoke(r, $int(cr.field("aid").get().getter().invoke(r)) + 91);
        cr.field("rid").get().setter().invoke(r, $int(cr.field("rid").get().getter().invoke(r)) + 92);
        cr.field("name").get().setter().invoke(r, cr.field("name").get().getter().invoke(r) + "world");
        cr.field("pid").get().setter().invoke(r, $int(cr.field("pid").get().getter().invoke(r)) + 82);
        
        assertEquals("{191:81:hello}#{392:482:R.world}", r.toString());
        
        assertThrows(WrongMethodTypeException.class, ()->{
            ca.field("name").get().setter().invoke(r, 556);
        });
    }
    
    @Test
    public void testBind() throws Throwable {
        ForFields.Record r = new ForFields.Record();
        
        Wrapper<String> a1 = ca.field("name").get().bind(r).cast(String.class);
        Wrapper<Integer> a2 = ca.field("pid").get().bind(r).cast(int.class);
        
        Wrapper<Integer> r1 = cr.field("aid").get().bind(r).cast(Integer.class);
        Wrapper<Integer> r2 = cr.field("rid").get().bind(r).cast(new TypeToken<Integer>(){});
        Wrapper<String> r3 = cr.field("name").get().bind(r).cast(new TypeToken<String>(){});
        Wrapper<Integer> r4 = cr.field("pid").get().bind(r).cast(int.class);
        
        a1.set("hello");
        a2.set(81);

        r1.set(r1.get() + 91);
        r2.set(r2.get() + 92);
        r3.set(r3.get()+"world");
        r4.set(r4.get()+82);
        
        assertEquals("{191:81:hello}#{392:482:R.world}", r.toString());
        
        assertThrows(ClassCastException.class, ()->{
            ca.field("name").get().bind(r).set(556);
        });
         
    }
    
    private static void assertEquiList(List<String> expected, List<String> values) {
        while(values.remove("$jacocoData")) { }
        while(values.remove("Record $jacocoData")) { }
        while(values.remove("ARecord $jacocoData")) { }
        assertEquivalent(expected, values);
    }

    
}
