/*
 * @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.Map;
import java.util.NoSuchElementException;
import net.ranides.assira.collection.maps.HashMap;
import net.ranides.assira.collection.maps.MapUtils;
import net.ranides.assira.functional.VarFunction;
import net.ranides.assira.reflection.BeanMethod;
import net.ranides.assira.reflection.BeanModel;
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 RBeanModelTest {
    
    private static final Map<String, String> METHODS = new HashMap<>(
        new String[]{
            "toString", 
            "equals", 
            "hashCode", 
            
            "random", 
            "getMail", 
            "sendMail", 
            "setConfig", 
            "configure", 
            
            "open",
            "connect",
        }, new String[]{
            "RBeanMethod:toString", 
            "RBeanMethod:equals", 
            "RBeanMethod:hashCode", 
            
            "RBeanMethod:random",
            "RBeanMethod:getMail",
            "RBeanMethod:sendMail",
            "RBeanMethod:setConfig",
            "RBeanMethod:configure",
            
            "RBeanMethods:open", 
            "RBeanMethods:connect", 
    });

    @Test
    public void testMethods() {
        ForBeanModel bean = new ForBeanModel();
        BeanModel model = BeanModel.typefor(bean);

        Map<String, String> properties = MapUtils.map(model.methods(), p -> 
            p.getClass().getSimpleName() + ":" + p.name()
        );
        assertEquals(METHODS, properties);
    }
    
    @Test
    public void testMethod() {
        BeanModel model = BeanModel.typeinfo(ForBeanModel.class);
        assertNotNull(model.method("open"));
        assertThrows(NoSuchElementException.class, ()->{
            model.method("unknown").get();
        });
    }
    
    @Test
    public void testMethodCall() {
        ForBeanModel bean = new ForBeanModel();
        BeanModel model = BeanModel.typefor(bean);
        
        assertEquals(bean.hashCode(), model.method("hashCode").get().apply(bean));
        assertEquals(false, model.method("equals").get().apply(bean, 999));
        
        assertEquals(bean.random(), model.method("random").get().apply(bean));
        assertEquals(bean.getMail("ranides", 777), model.method("getMail").get().apply(bean, "ranides", 777));
        assertNull(model.method("setConfig").get().apply(bean, "path", "/root"));
        assertEquals("path=/root", bean.$result);
        
        assertThrows(IllegalArgumentException.class, ()->{
            model.method("setConfig").get().apply(null,"home","/root");
        });
        assertThrows(Exception.class, ()->{
            model.method("setConfig").get().apply(bean,"home",17);
        });
        assertThrows(Exception.class, ()->{
            model.method("setConfig").get().apply(bean,"home");
        });
    }
    
    @Test
    public void testMethodsCall() {
        ForBeanModel bean = new ForBeanModel();
        BeanModel model = BeanModel.typeinfo(ForBeanModel.class);
        
        BeanMethod m1 = model.method("connect").get();
        BeanMethod m2 = model.method("open").get();
        
        assertEquals(null, m1.apply(bean));
        assertEquals("connect:void", bean.$result);
        
        assertEquals(true, m1.apply(bean,17));
        assertEquals("connect:int:17", bean.$result);
        
        assertEquals("c17", m1.apply(bean,17.1f));
        assertEquals("connect:float:17.1", bean.$result);
        
        assertEquals(2, m1.apply(bean,17.1));
        assertEquals("connect:double:17.1", bean.$result);
        
        assertEquals(null, m1.apply(bean,5,6));
        assertEquals("connect:5/6", bean.$result);
        
        m2.apply(bean, "home");
        assertEquals("open:home", bean.$result);
        m2.apply(bean, "path", 777);
        assertEquals("open:path/777", bean.$result);
        
        assertThrows(IllegalArgumentException.class, ()->{
            model.method("open").get().apply(null,"home",777);
        });
        assertThrows(Exception.class, ()->{
            model.method("open").get().apply(bean);
        });
        assertThrows(Exception.class, ()->{
            model.method("open").get().apply(bean,"home","777");
        });
    }
    
    @Test
    public void testMethodBind() {
        ForBeanModel bean = new ForBeanModel();
        BeanModel model = BeanModel.typefor(bean);
        
        assertEquals(bean.hashCode(), model.method("hashCode").get().bind(bean).call());
        assertEquals(false, model.method("equals").get().bind(bean).call(999));
        
        assertEquals(bean.random(), model.method("random").get().bind(bean).call());
        assertEquals(bean.getMail("ranides", 777), model.method("getMail").get().bind(bean).call("ranides", 777));
        assertNull(model.method("setConfig").get().bind(bean).call("path", "/root"));
        assertEquals("path=/root", bean.$result);
        
        assertThrows(IllegalArgumentException.class, ()->{
            model.method("setConfig").get().bind(null);
        });
        assertThrows(Exception.class, ()->{
            model.method("setConfig").get().bind(bean).call("home",17);
        });
        assertThrows(Exception.class, ()->{
            model.method("setConfig").get().bind(bean).call("home");
        });
    }
    
    @Test
    public void testMethodsBind() {
        ForBeanModel bean = new ForBeanModel();
        BeanModel model = BeanModel.typeinfo(ForBeanModel.class);
        
        VarFunction m1 = model.method("connect").get().bind(bean);
        VarFunction m2 = model.method("open").get().bind(bean);
        
        assertEquals(null, m1.call());
        assertEquals("connect:void", bean.$result);
        
        assertEquals(true, m1.call(17));
        assertEquals("connect:int:17", bean.$result);
        
        assertEquals("c17", m1.call(17.1f));
        assertEquals("connect:float:17.1", bean.$result);
        
        assertEquals(2, m1.call(17.1));
        assertEquals("connect:double:17.1", bean.$result);
        
        assertEquals(null, m1.call(5,6));
        assertEquals("connect:5/6", bean.$result);
        
        m2.call("home");
        assertEquals("open:home", bean.$result);
        m2.call("path", 777);
        assertEquals("open:path/777", bean.$result);
        
        assertThrows(IllegalArgumentException.class, ()->{
            model.method("open").get().bind(null);
        });
        assertThrows(Exception.class, ()->{
            m2.call(bean);
        });
        assertThrows(Exception.class, ()->{
            m2.call("home","777");
        });
    }
    
    @Test
    public void testMethodMatches() {
        ForBeanModel bean = new ForBeanModel();
        BeanModel model = BeanModel.typefor(bean);
        
        assertTrue(model.method("hashCode").get().matches());
        assertFalse(model.method("hashCode").get().matches(Object.class));
        
        assertTrue(model.method("random").get().matches());
        assertFalse(model.method("random").get().matches(Integer.class));
        
        assertTrue(model.method("getMail").get().matches(String.class, int.class));
        assertFalse(model.method("getMail").get().matches(String.class, Integer.class));
    }
    
    @Test
    public void testMethodsMatches() {
        ForBeanModel bean = new ForBeanModel();
        BeanModel model = BeanModel.typefor(bean);
        
        assertTrue(model.method("open").get().matches(String.class));
        assertTrue(model.method("open").get().matches(String.class, int.class));
        
        assertTrue(model.method("connect").get().matches());
        assertTrue(model.method("connect").get().matches(ForIClass.INT));
        assertTrue(model.method("connect").get().matches(float.class));
        assertFalse(model.method("connect").get().matches(Float.class));
        assertTrue(model.method("connect").get().matches(double.class));
        assertTrue(model.method("connect").get().matches(ForIClass.INT, ForIClass.I_INTEGER));
        assertFalse(model.method("connect").get().matches(ForIClass.INT, ForIClass.INT));
    }
    
    @Test
    public void testMethodAccepts() {
        ForBeanModel bean = new ForBeanModel();
        BeanModel model = BeanModel.typefor(bean);
        
        assertTrue(model.method("hashCode").get().accepts());
        assertFalse(model.method("hashCode").get().accepts("333"));
        
        assertTrue(model.method("random").get().accepts());
        assertFalse(model.method("random").get().accepts(444));
        
        assertTrue(model.method("getMail").get().accepts("a",2));
        assertTrue(model.method("getMail").get().accepts("a", Integer.valueOf(4)));
        assertFalse(model.method("getMail").get().accepts("a", null));
    }
    
    @Test
    public void testMethodsAccepts() {
        ForBeanModel bean = new ForBeanModel();
        BeanModel model = BeanModel.typefor(bean);
        
        assertTrue(model.method("open").get().accepts("s"));
        assertTrue(model.method("open").get().accepts("s",1));
        assertTrue(model.method("open").get().accepts(new Object[]{null}));
        assertTrue(model.method("open").get().accepts(null,1));
        
        assertTrue(model.method("connect").get().accepts());
        assertTrue(model.method("connect").get().accepts(4));
        assertTrue(model.method("connect").get().accepts(14.3f));
        assertTrue(model.method("connect").get().accepts(14.3));
        assertFalse(model.method("connect").get().accepts(new Object[]{null}));
        assertTrue(model.method("connect").get().accepts(1, 2));
        assertTrue(model.method("connect").get().accepts(1, null));
        assertFalse(model.method("connect").get().accepts(null,null));
    }
    
    @Test
    public void testConstruct() {
        BeanModel model = BeanModel.typeinfo(ForBeanModel.class);
        
        
        ForBeanModel v1 = (ForBeanModel)model.construct();
        assertNotNull(v1);
        
        Map<String,Object> params = new HashMap<>(new String[]{"height","width","array"}, new Object[]{
            16,
            20,
            Arrays.asList("1","2")
        });
        ForBeanModel v2 = (ForBeanModel)model.construct(params);
        assertEquals(16, v2.$height);
        assertEquals(20, v2.$width);
        assertArrayEquals(new String[]{"1","2"}, v2.$array);
        
        assertEquals(IClass.typefor(v1), model.type());
        
        ForBeanModel v3 = (ForBeanModel)model.clone(v2);
        assertEquals(0, v3.$height);
        assertEquals(20, v3.$width);
        assertArrayEquals(new String[]{"1","2"}, v3.$array);
    }
    
}
