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

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import net.ranides.assira.collection.maps.HashMap;
import net.ranides.assira.collection.maps.MapUtils;
import net.ranides.assira.generic.TypeToken;
import net.ranides.assira.generic.Wrapper;
import net.ranides.assira.reflection.BeanModel;
import net.ranides.assira.reflection.BeanProperty;
import net.ranides.assira.reflection.mockup.ForBeanModel;
import static net.ranides.assira.junit.NewAssert.*;
import net.ranides.assira.reflection.IClass;
import net.ranides.assira.reflection.mockup.ForIClass;
import org.junit.Test;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public class ABeanPropertyTest {
    
    private static final Map<String, String> PROPERTIES = new HashMap<>(
        new String[]{
            "width", 
            "optional", 
            "height",
            "area",
            "show",
            "state",
            
            "array",
            "arrayFlags",
            "arrayOW",
            "arrayOR",
            "arrayIR",
            "arrayIA",
            
            "list",
            "listFlags",
            "listOW", 
            "listOR", 
            "listIR", 
            "listIW", 
            "listIWs", 
            "listIA",
        }, new String[]{
            "RPField", 
            "RPField", 
            "RPField", 
            "RPField", 
            "RPField", 
            "RPField", 
            
            "RPArray", 
            "RPArray", 
            "RPArray", 
            "RPArray", 
            "RPArray", 
            "RPArray", 
            
            "RPField", 
            "RPIndexed", 
            "RPField", 
            "RPField", 
            "RPIndexed", 
            "RPIndexed", 
            "RPIndexed", 
            "RPIndexed"
    });
    
    @Test
    public void testProperties() {
        ForBeanModel bean = new ForBeanModel();
        BeanModel model = BeanModel.typefor(bean);
        
        Map<String, String> properties = MapUtils.map(model.properties(), p -> p.getClass().getSimpleName());
        assertEquals(PROPERTIES, properties);
    }
    
    @Test
    public void testValues() {
        ForBeanModel bean = new ForBeanModel();
        BeanModel model = BeanModel.typefor(bean);
        
        Map<String, Object> map = model.values(bean);
        assertEquals(PROPERTIES.keySet(), map.keySet());
        
        bean.$height = 5;
        bean.$width = 8;
        assertEquals(8, map.get("width"));
        assertEquals(5*8, map.get("area"));
        assertThrows(UnsupportedOperationException.class, ()->{
            map.get("height");
        });
        
        map.put("height",9);
        assertEquals(8*9, map.get("area"));
                
    }
    
    
    @Test
    public void testRPField() {
        ForBeanModel bean = new ForBeanModel();
        BeanModel model = BeanModel.typefor(bean);
        
        BeanProperty p1 = model.property("width").get();
        p1.set(bean, 17);
        assertEquals(17, p1.get(bean));
        assertEquals(17, bean.$width);
        assertTrue(p1.isReadable());
        assertTrue(p1.isWritable());
        
        BeanProperty p2 = model.property("optional").get();
        assertNull(p2.get(bean));
        p2.set(bean, 19);
        assertEquals(19, p2.get(bean));
        assertEquals(19, bean.$optional.intValue());
        p2.set(bean, null);
        assertNull(p2.get(bean));
        assertNull(bean.$optional);
        assertTrue(p2.isReadable());
        assertTrue(p2.isWritable());
        
        BeanProperty p3 = model.property("height").get();
        Wrapper<Object> w3 = p3.bind(bean); 
        w3.set(4);
        assertEquals(4, bean.$height);
        assertThrows(UnsupportedOperationException.class, ()->{
            w3.get();
        });
        assertFalse(p3.isReadable());
        assertTrue(p3.isWritable());
        
        BeanProperty p4 = model.property("area").get();
        assertEquals(4*17, p4.get(bean));
        assertThrows(UnsupportedOperationException.class, ()->{
            p4.set(bean, 6);
        });
        assertTrue(p4.isReadable());
        assertFalse(p4.isWritable());
        
        BeanProperty p5 = model.property("show").get();
        p5.set(bean, Boolean.TRUE);
        assertEquals(true, p5.get(bean));
        p5.set(bean, false);
        assertEquals(false, p5.get(bean));
        assertThrows(IllegalArgumentException.class, ()->{
            p5.set(bean, null);
        });
        
        
        BeanProperty p6 = model.property("state").get();
        p6.set(bean, true);
        assertEquals(true, p6.get(bean));
        p6.set(bean, false);
        assertEquals(false, p6.get(bean));
        p6.set(bean, null);
        assertNull(p6.get(bean));
        
        assertThrows(NoSuchElementException.class, ()->{
            model.property("something").get();
        });
        
    }
    
    @Test
    public void testArray() {
        ForBeanModel bean = new ForBeanModel();
        BeanModel model = BeanModel.typefor(bean);
        
        BeanProperty p2 = model.property("array").get();
        Wrapper<List<String>> w2 = p2.$bind(bean);
        assertNull(w2.get());
        w2.set(Arrays.asList("1","2","3"));
        assertArrayEquals(new String[]{"1","2","3"}, bean.$array);
        assertEquals(Arrays.asList("1","2","3"), w2.get());
        
        List<String> v2 = w2.get();
        assertEquals("2", v2.get(1));
        v2.set(2, "q");
        bean.$array[0] = "w";
        assertEquals(Arrays.asList("1","2","q"), v2);
        assertEquals(Arrays.asList("w","2","3"), w2.get());
        w2.set(v2);
        assertEquals(Arrays.asList("1","2","q"), w2.get());
        assertArrayEquals(new String[]{"1","2","q"}, bean.$array);
        
        w2.set(null);
        assertNull(bean.$array);
        assertNull(w2.get());
    }
    
    @Test
    public void testArrayFlags() {
        ForBeanModel bean = new ForBeanModel();
        BeanModel model = BeanModel.typefor(bean);
        
        BeanProperty p1 = model.property("arrayFlags").get();
        assertEquals(new TypeToken<List<Boolean>>(){}, p1.type());
        assertNull(p1.get(bean));
        p1.set(bean, Arrays.asList(true,true,false,true));
        assertArrayEquals(new boolean[]{true, true,false,true}, bean.$arrayFlags);
        
        List<Object> p1list = p1.$get(bean);
        assertEquals(Arrays.asList(true,true,false,true), p1list);
        p1list.set(1, false);
        bean.$arrayFlags[0] = false;
        assertEquals(4, p1list.size());
        assertEquals(false, p1list.get(0));
        assertEquals(false, bean.$arrayFlags[1]);
        
        bean.$arrayFlags = new boolean[]{true,true};
        assertEquals(4, p1list.size());
        assertEquals(true, p1list.get(0));
        assertEquals(true, p1list.get(1));
        
        assertThrows(IndexOutOfBoundsException.class, ()->{
            p1list.get(2);
        });
    }
    
    @Test
    public void testArrayRW() {
        ForBeanModel bean = new ForBeanModel();
        BeanModel model = BeanModel.typefor(bean);    
        
        BeanProperty p3 = model.property("arrayOW").get();
        assertTrue(p3.isWritable());
        assertFalse(p3.isReadable());
        assertThrows(UnsupportedOperationException.class, ()->{
            p3.get(bean);
        });
        p3.set(bean, Arrays.asList("a","b","c"));
        assertArrayEquals(new String[]{"a","b","c"}, bean.$arrayOW);
        assertThrows(UnsupportedOperationException.class, ()->{
            p3.get(bean);
        });
        
        
        BeanProperty p4 = model.property("arrayOR").get();
        assertFalse(p4.isWritable());
        assertTrue(p4.isReadable());
        assertThrows(UnsupportedOperationException.class, ()->{
            p4.set(bean, Arrays.asList("41","42"));
        });
        assertThrows(UnsupportedOperationException.class, ()->{
            p4.set(bean, null);
        });
        assertNull(p4.get(bean));
        bean.$arrayOR = new String[]{"441","442"};
        assertEquals(Arrays.asList("441","442"), p4.get(bean));
    }
    
    @Test
    public void testArrayIndex() {
        ForBeanModel bean = new ForBeanModel();
        BeanModel model = BeanModel.typefor(bean);    
        
        BeanProperty p5 = model.property("arrayIR").get();
        assertEquals(new TypeToken<List<String>>(){}, p5.type());
        Wrapper<List<String>> w5 = p5.$bind(bean);
        assertNull(w5.get());
        assertThrows(UnsupportedOperationException.class, ()->{
            w5.set(Arrays.asList("q51","q52","q53"));
        });
        bean.$arrayIR = new String[]{"51","52","53"};
        List<String> list = w5.get();
        assertEquals(Arrays.asList("51","52","53"), list);
        bean.$arrayIR[1] = "52q";
        assertEquals(Arrays.asList("51","52q","53"), list);
        bean.$arrayIR = new String[]{"51!","52!", "53!", "54"};
        assertEquals(Arrays.asList("51!","52!","53!"), list);
        assertEquals(Arrays.asList("51!","52!","53!", "54"), w5.get());
        
        
        BeanProperty p6 = model.property("arrayIA").get();
        assertEquals(new TypeToken<List<Long>>(){}, p6.type());
        Wrapper<List<Long>> w6 = p6.$bind(bean);
        w6.set(Arrays.asList(61L, 62L, 63L));
        assertEquals(Arrays.asList(61L, 62L, 63L), w6.get());
        List<Long> v6 = w6.get();
        bean.$arrayIA[1] = 162;
        assertEquals(Arrays.asList(61L, 162L, 63L), v6);
        v6.set(2, 163L);
        assertEquals(Arrays.asList(61L, 162L, 163L), v6);
    }
    
    @Test
    public void testList() {
        ForBeanModel bean = new ForBeanModel();
        BeanModel model = BeanModel.typefor(bean);
        
        BeanProperty p2 = model.property("list").get();
        Wrapper<List<String>> w2 = p2.$bind(bean);
        assertNull(w2.get());
        w2.set(Arrays.asList("1","2","3"));
        assertEquals(Arrays.asList("1","2","3"), bean.$list);
        assertEquals(Arrays.asList("1","2","3"), w2.get());
        
        List<String> v2 = w2.get();
        assertEquals("2", v2.get(1));
        v2.set(2, "q");
        bean.$list.set(0,"w");
        assertEquals(Arrays.asList("1","2","q"), v2);
        assertEquals(Arrays.asList("w","2","3"), w2.get());
        w2.set(v2);
        assertEquals(Arrays.asList("1","2","q"), w2.get());
        assertEquals(Arrays.asList("1","2","q"), bean.$list);
        
        w2.set(null);
        assertNull(bean.$list);
        assertNull(w2.get());
    }
    
    @Test
    public void testListFlags() {
        ForBeanModel bean = new ForBeanModel();
        BeanModel model = BeanModel.typefor(bean);
        
        BeanProperty p1 = model.property("listFlags").get();
        assertEquals(new TypeToken<List<Boolean>>(){}, p1.type());
        assertNull(p1.get(bean));
        p1.set(bean, Arrays.asList(true,true,false,true));
        assertEquals(Arrays.asList(true, true,false,true), bean.$listFlags);
        
        List<Object> p1list = p1.$get(bean);
        assertEquals(Arrays.asList(true,true,false,true), p1list);
        p1list.set(1, false);
        bean.$listFlags.set(0,false);
        assertEquals(4, p1list.size());
        assertEquals(false, p1list.get(0));
        assertEquals(false, bean.$listFlags.get(1));
        
        bean.$listFlags = Arrays.asList(true,true);
        assertEquals(4, p1list.size());
        assertEquals(true, p1list.get(0));
        assertEquals(true, p1list.get(1));
        
        assertThrows(IndexOutOfBoundsException.class, ()->{
            p1list.get(2);
        });
    }
    
    @Test
    public void testListRW() {
        ForBeanModel bean = new ForBeanModel();
        BeanModel model = BeanModel.typefor(bean);    
        
        BeanProperty p3 = model.property("listOW").get();
        assertTrue(p3.isWritable());
        assertFalse(p3.isReadable());
        assertThrows(UnsupportedOperationException.class, ()->{
            p3.get(bean);
        });
        p3.set(bean, Arrays.asList("a","b","c"));
        assertEquals(Arrays.asList("a","b","c"), bean.$listOW);
        assertThrows(UnsupportedOperationException.class, ()->{
            p3.get(bean);
        });
        
        
        BeanProperty p4 = model.property("listOR").get();
        assertFalse(p4.isWritable());
        assertTrue(p4.isReadable());
        assertThrows(UnsupportedOperationException.class, ()->{
            p4.set(bean, Arrays.asList("41","42"));
        });
        assertThrows(UnsupportedOperationException.class, ()->{
            p4.set(bean, null);
        });
        assertNull(p4.get(bean));
        bean.$listOR = Arrays.asList("441","442");
        assertEquals(Arrays.asList("441","442"), p4.get(bean));
    }
    
    @Test
    public void testListIndex() {
        ForBeanModel bean = new ForBeanModel();
        BeanModel model = BeanModel.typefor(bean);    
        
        BeanProperty p5 = model.property("listIR").get();
        assertEquals(new TypeToken<List<String>>(){}, p5.type());
        Wrapper<List<String>> w5 = p5.$bind(bean);
        assertNull(w5.get());
        assertThrows(UnsupportedOperationException.class, ()->{
            w5.set(Arrays.asList("q51","q52","q53"));
        });
        bean.$listIR = Arrays.asList("51","52","53");
        List<String> list = w5.get();
        assertEquals(Arrays.asList("51","52","53"), list);
        bean.$listIR.set(1,"52q");
        assertEquals(Arrays.asList("51","52q","53"), list);
        bean.$listIR = Arrays.asList("51!","52!", "53!", "54");
        assertEquals(Arrays.asList("51!","52!","53!"), list);
        assertEquals(Arrays.asList("51!","52!","53!", "54"), w5.get());
        
        
        BeanProperty p6 = model.property("listIA").get();
        assertEquals(new TypeToken<List<Long>>(){}, p6.type());
        Wrapper<List<Long>> w6 = p6.$bind(bean);
        w6.set(Arrays.asList(61L, 62L, 63L));
        assertEquals(Arrays.asList(61L, 62L, 63L), w6.get());
        List<Long> v6 = w6.get();
        bean.$listIA.set(1,162L);
        assertEquals(Arrays.asList(61L, 162L, 63L), v6);
        v6.set(2, 163L);
        assertEquals(Arrays.asList(61L, 162L, 163L), v6);
    }
    
    @Test
    public void testListIW() {
        ForBeanModel bean = new ForBeanModel();
        BeanModel model = BeanModel.typefor(bean);    
        
        BeanProperty p3 = model.property("listIW").get();
        assertTrue(p3.isWritable());
        assertFalse(p3.isReadable());
        assertThrows(UnsupportedOperationException.class, ()->{
            p3.get(bean);
        });
        p3.set(bean, Arrays.asList("a","b","c"));
        assertEquals(Arrays.asList("a","b","c"), bean.$listIW);
        assertThrows(UnsupportedOperationException.class, ()->{
            p3.get(bean);
        });

        BeanProperty p4 = model.property("listIWs").get();
        assertTrue(p4.isWritable());
        assertTrue(p4.isReadable());
        
        p4.set(bean, Arrays.asList("1","2","3"));
        assertEquals(Arrays.asList("1","2","3"), bean.$listIWs);
        assertEquals(Arrays.asList("1","2","3"), p4.get(bean));
        
        List<String> v4 = p4.$get(bean);
        v4.set(1, "a");
        assertEquals(Arrays.asList("1","a","3"), p4.get(bean));
        
    }
    
    @Test
    public void testReplace() {
        ForBeanModel bean = new ForBeanModel();
        BeanModel model = BeanModel.typefor(bean);    
        BeanProperty p1 =  model.property("optional").get();
        assertNull(p1.replace(bean, 17));
        assertEquals(17, p1.replace(bean, 19));
        assertEquals(19, p1.replace(bean, 21));
        
        BeanProperty p2 =  model.property("height").get();
        assertNull(p2.replace(bean, 17));
        assertNull(p2.replace(bean, 18));
        assertEquals(18, bean.$height);
    }
    
    @Test
    public void testBindReplace() {
        ForBeanModel bean = new ForBeanModel();
        BeanModel model = BeanModel.typefor(bean);    
        Wrapper<Object> w1 =  model.property("optional").get().bind(bean);
        assertNull(w1.replace(17));
        assertEquals(17, w1.replace(19));
        assertEquals(19, w1.replace(21));
        
        Wrapper<Object> p2 =  model.property("height").get().bind(bean);
        assertNull(p2.replace(17));
        assertNull(p2.replace(18));
        assertEquals(18, bean.$height);
    }
    
    @Test
    public void testType() {
        ForBeanModel bean = new ForBeanModel();
        BeanModel model = BeanModel.typefor(bean);    
        IClass<?> ic1 = ForIClass.INT;
        IClass<?> ic2 = ForIClass.I_INTEGER;
        IClass<?> ic3 = new TypeToken<List<Boolean>>(){};
        IClass<?> ic4 = IClass.typeinfo(boolean.class);
        IClass<?> ic5 = IClass.typeinfo(Boolean.class);
        
        
        assertEquals(ic1, model.property("height").get().type());
        assertEquals(ic2, model.property("optional").get().type());
        assertEquals(ic3, model.property("arrayFlags").get().type());
        assertEquals(ic4, model.property("show").get().type());
        assertEquals(ic5, model.property("state").get().type());
        
        assertEquals(ic1, model.property("height").get().bind(bean).type());
        assertEquals(ic2, model.property("optional").get().bind(bean).type());
        assertEquals(ic3, model.property("arrayFlags").get().bind(bean).type());
        assertEquals(ic4, model.property("show").get().bind(bean).type());
        assertEquals(ic5, model.property("state").get().bind(bean).type());
    }
        
}
