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

import java.lang.reflect.Method;
import org.junit.Test;
import static net.ranides.assira.junit.NewAssert.*;
import net.ranides.assira.reflection.mockup.ForMethodUtils.*;
import static net.ranides.assira.reflection.mockup.ForMemberTraits.$method;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public class MethodUtilsTest {
    
    @Test
    public void testInvoke() {
        String error1 = "java.lang.IllegalArgumentException: object is not an instance of declaring class";
        String error2 = "java.lang.IllegalArgumentException: argument type mismatch";
        String error3 = "java.lang.IllegalArgumentException: wrong number of arguments";
        String error4 = "java.lang.IllegalArgumentException: non-static method require this!=null";
        String error5 = "java.lang.NumberFormatException: NFE";

        
        Method r2 = $method(Root.class, "fun2");
        Method b2 = $method(B.class, "fun2");
        Method b3 = $method(B.class, "fun3");

        Object r = new Root();
        Object b = new B();
        Object c1 = new C("c1");
        Object c2 = new C("c2");
        
        assertEquals(null, MethodUtils.invoke(r2, r, 33));
        assertEquals("hello", MethodUtils.invoke(r2, b, 33));
        assertEquals("c1#8", MethodUtils.invoke(r2, c1, 8));
        assertEquals("c2#9", MethodUtils.invoke(r2, c2, 9));
        
        
        
        assertThrows(e -> error1.equals(e.toString()), () -> { 
            MethodUtils.invoke(b2, r, 33); 
        });
        assertThrows(e -> error1.equals(e.toString()), () -> { 
            MethodUtils.invoke(b2, c1, 8);
        });
        assertEquals("hello", MethodUtils.invoke(b2, b, 33));
        
        assertThrows(e -> error5.equals(e.toString()), () -> { 
            MethodUtils.invoke(b3, b, 'a', 'b');
        });
        assertThrows(e -> error2.equals(e.toString()), () -> { 
            MethodUtils.invoke(b3, b, 'a', 5);
        });
        assertThrows(e -> error3.equals(e.toString()), () -> { 
            MethodUtils.invoke(b3, b, 'a', 'b', 'c');
        });
        assertThrows(e -> error4.equals(e.toString()), () -> { 
            MethodUtils.invoke(b3, null, 'a', 'b', 'c');
        });
    }

    @Test
    public void testOverride() {
        Method r1 = $method(Root.class, "fun1");
        Method r2 = $method(Root.class, "fun2");
        Method r3 = $method(Root.class, "fun3");
        Method r4 = $method(Root.class, "fun4");
        Method r5 = $method(Root.class, "fun5");
        
        // no inheritance
        assertFalse(MethodUtils.overrides(r1, $method(NoRoot.class, "fun1")));
        assertFalse(MethodUtils.overrides(r2, $method(NoRoot.class, "fun2")));
        
        // public sygnature
        assertTrue(MethodUtils.overrides(r1, $method(A.class, "fun1")));
        assertTrue(MethodUtils.overrides(r2, $method(A.class, "fun2")));
        assertFalse(MethodUtils.overrides(r3, $method(A.class, "fun3")));
        
        // package scope: inside package
        assertTrue(MethodUtils.overrides(r4, $method(A.class, "fun4")));
        
        // private member
        assertFalse(MethodUtils.overrides(r5, $method(A.class, "fun5")));
        
        // different names
        assertFalse(MethodUtils.overrides(r1, $method(A.class, "fun1c")));
        
        // inverted inheritance
        assertFalse(MethodUtils.overrides($method(A.class, "fun1"), r1));
        assertFalse(MethodUtils.overrides($method(A.class, "fun2"), r2));
        
       
        // outside package: public sygnature
        assertTrue(MethodUtils.overrides(r1, $method(B.class, "fun1")));
        assertTrue(MethodUtils.overrides(r2, $method(B.class, "fun2")));
        assertFalse(MethodUtils.overrides(r3, $method(B.class, "fun3")));
        
        // package scope: outside package
        assertFalse(MethodUtils.overrides(r4, $method(B.class, "fun4")));
        
        // inverted inheritance
        assertFalse(MethodUtils.overrides($method(B.class, "fun1"), r1));
        assertFalse(MethodUtils.overrides($method(B.class, "fun2"), r2));        
        
    }

    public static class B extends Root {
        
        @Override
        public void fun1(int a) { }
        
        @Override
        public String fun2(int a) { 
            return "hello"; 
        }
        
        public void fun3(int x, char y) {
            throw new NumberFormatException("NFE");
        }
        
        void fun4(String z) { }
        
    }
    
    public static class C extends Root {
        
        private final String text;

        public C(String text) {
            this.text = text;
        }
        
        @Override
        public String fun2(int a) { 
            return text + "#" + a; 
        }
        
    }
        
}
