
package org.jgroups.tests;


import org.jgroups.Address;
import org.jgroups.Global;
import org.jgroups.blocks.MethodCall;
import org.jgroups.blocks.MethodLookup;
import org.jgroups.util.Util;
import org.testng.Assert;
import org.testng.annotations.Test;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;


/**
 * @author Bela Ban belaban@yahoo.com
 * @author <a href="mailto:ovidiu@jboss.org">Ovidiu Feodorov</a>
 **/
@Test(groups=Global.FUNCTIONAL)
public class MethodCallTest {

    protected static final Method CALL;
    protected static final Map<Short,Method> methods=new HashMap<>();

    static {
        try {
            CALL=MethodCallTest.class.getMethod("call", boolean.class, Boolean.class, int.class, double.class,
                                                float.class, byte[].class, String[].class);
        }
        catch(NoSuchMethodException e) {
            throw new RuntimeException(e);
        }

        methods.put((short)1, CALL);

        try {
            methods.put((short)2, TargetClass.class.getMethod("foobar"));
        }
        catch(NoSuchMethodException e) {
            throw new IllegalStateException(e);
        }
    }

    public static final MethodLookup lookup=new MethodLookup() {
        public Method findMethod(short id) {
            return methods.get(id);
        }
    };

    public static class TargetClass {
        public static boolean foo(int a, String b) {
            System.out.println("test(" + a + ", " + b + ')');
            return true;
        }

        public static void bar(String[] a, String b) {
            if(a != null) {
                for(int i=0; i < a.length; i++) {
                    String s=a[i];
                    System.out.print(s + ' ');
                }
            }
            else
                System.out.println("a=null");
            if(b != null)
                System.out.println("b=" + b);
            else
                System.out.println("b=null");
        }

        public static void foobar() {
            System.out.println("foobar()");
        }
    }

    @Test(enabled=false)
    public static void call(boolean b, Boolean bool, int i, double d, float f, byte[] buf, String[] strings) {
        System.out.println("b = " + b + ", bool=" + bool + ", i=" + i + ", d=" + d + ", f=" + f +
                             ", buf=" + buf.length + " bytes, strings=" + Arrays.toString(strings));
    }

    final TargetClass target=new TargetClass();



    public void testMethod() throws Exception {
        Method m=TargetClass.class.getMethod("foo", new Class[]{int.class, String.class});
        MethodCall mc=new MethodCall(m,22, "Bela");
        Assert.assertEquals(mc.invoke(target),Boolean.TRUE);
    }


    public void testMethod2() throws Exception {
        MethodCall call=new MethodCall(CALL, true, Boolean.FALSE, 322649, 3.24, (float)54.345,
                                       new byte[]{'b', 'e', 'l', 'a'}, new String[]{"Bela", "Michelle"});
        call.invoke(this);
    }


    public void testTypes() throws Exception {
        MethodCall mc=new MethodCall("foo", new Object[]{35,"Bela"}, new Class[]{int.class,String.class});
        Assert.assertEquals(mc.invoke(target),Boolean.TRUE);
    }

    public void testTypes2() throws Exception {
        new MethodCall("bar", new Object[]{new String[]{"one", "two", "three"}, "Bela"},
                       new Class[]{String[].class, String.class}).invoke(target);
    }


    public void testTypesWithArray() throws Exception {
        new MethodCall("bar", new Object[]{new String[]{"one", "two", "three"}, "Bela"},
                       new Class[]{String[].class, String.class}).invoke(target);
    }


    public void testTypesWithNullArgument() throws Exception {
        new MethodCall("bar", new Object[]{new String[]{"one", "two", "three"}, null},
                       new Class[]{String[].class, String.class}).invoke(target);
    }

    public void testTypesWithNullArgument2() throws Exception {
        MethodCall mc=new MethodCall("bar", new Object[]{new String[]{"one", "two", "three"}, new Object[]{}},
                                     new Class[]{String[].class, String.class});
        try {
            mc.invoke(target);
            assert false: "we should not get here as there should be an argument mismatch exception";
        }
        catch(IllegalArgumentException ex) {
            System.out.println("caught IllegalArgumentException - as expected");
        }
    }


    public void testTypesWithNullArgument3() throws Exception {
        new MethodCall("foobar", new Object[]{}, new Class[]{}).invoke(target);
    }


    public void testTypesWithNullArgument4() throws Exception {
        new MethodCall("foobar", null, null).invoke(target);
    }


    public void testTypesWithNullArgument5() throws Exception {
        new MethodCall("foobar", new Object[0], new Class[0]).invoke(target);
    }


    public void testSignature() throws Exception {
        MethodCall mc=new MethodCall("foo", new Object[]{35, "Bela"},
                                     new Class[]{int.class, String.class});
        Assert.assertEquals(mc.invoke(target),Boolean.TRUE);
    }

    public void testID() throws Exception {
        MethodCall call=new MethodCall((short)1, true, Boolean.FALSE, 322649, 3.24, (float)54.345,
                                       new byte[]{'b', 'e', 'l', 'a'}, new String[]{"Bela", "Michelle"})
          .lookup(lookup);
        call.invoke(this);

        call=new MethodCall((short)2).lookup(lookup);
        call.invoke(target);
    }


    public static void testBufferSize() throws Exception {
        MethodCall m=new MethodCall("foo", new Object[]{10,"Bela"}, new Class[]{int.class, String.class});
        byte[] data=Util.objectToByteBuffer(m);
        
        MethodCall m2=(MethodCall)Util.objectFromByteBuffer(data);
        System.out.println(m2);
        Object[] args=m2.getArgs();
        assert args.length == 2;
        assert args[0].equals(10);
        assert args[1].equals("Bela");
    }


    public static void testOLD() throws Exception {
        MethodCall methodCall = new MethodCall("someMethod", new Object[] {"abc"}, new Class[]{String.class});
        Target target = new Target();
        Object result = methodCall.invoke(target);
        Assert.assertEquals("ABC",result);
    }


    public static void testInheritanceOLD() throws Exception {
        MethodCall methodCall = new MethodCall("someMethod", new Object[] {"abc"}, new Class[]{String.class});
        TargetSubclass target = new TargetSubclass();
        Object result = methodCall.invoke(target);
        Assert.assertEquals("ABC",result);
    }


    public static void testMETHOD() throws Exception {
        Method method = Target.class.getMethod("someMethod", new Class[] { String.class });
        MethodCall methodCall = new MethodCall(method, "abc");
        Target target = new Target();
        Object result = methodCall.invoke(target);
        Assert.assertEquals("ABC",result);
    }


    public static void testInheritanceMETHOD() throws Exception {
        Method method = Target.class.getMethod("someMethod", new Class[] { String.class });
        MethodCall methodCall = new MethodCall(method, "abc");
        TargetSubclass target = new TargetSubclass();
        Object result = methodCall.invoke(target);
        Assert.assertEquals("ABC",result);
    }


    public static void testTYPES() throws Exception {
        MethodCall methodCall = new MethodCall("someMethod", new Object[] { "abc" }, new Class[] { String.class });
        Target target = new Target();
        Object result = methodCall.invoke(target);
        Assert.assertEquals("ABC",result);
    }


    public static void testInheritanceTYPES() throws Exception {
        MethodCall methodCall = new MethodCall("someMethod", new Object[] { "abc" }, new Class[] { String.class });
        TargetSubclass target = new TargetSubclass();
        Object result = methodCall.invoke(target);
        Assert.assertEquals("ABC",result);
    }

    /**
     * This tests whether overriden methods are correctly identified and invoked.
     */
    public static void testOverriddenForTYPES() throws Exception  {
        MethodCall methodCall = new MethodCall("overriddenMethod", new Object[] { "abc" }, new Class[] { String.class });
        TargetSubclass target = new TargetSubclass();
        Object result = methodCall.invoke(target);
        Assert.assertEquals("TargetSubclassABC", result);
    }


    public static void testNoArgumentMethodForTYPES() throws Exception  {
        MethodCall methodCall = new MethodCall("noArgumentMethod", new Object[0], new Class[0]);
        TargetSubclass target = new TargetSubclass();
        Object result = methodCall.invoke(target);
        Assert.assertEquals("noArgumentMethodResult", result);
    }



    public static void testSIGNATURE() throws Exception {
        MethodCall methodCall = new MethodCall("someMethod", new Object[] { "abc" }, new Class[] {String.class});
        Target target = new Target();
        Object result = methodCall.invoke(target);
        Assert.assertEquals("ABC",result);
    }


    public static void testInheritanceSIGNATURE() throws Exception {
        MethodCall methodCall = new MethodCall("someMethod", new Object[] {"abc"}, new Class[] {String.class});
        TargetSubclass target = new TargetSubclass();
        Object result = methodCall.invoke(target);
        Assert.assertEquals("ABC",result);
    }



    public static void testMarshalling() throws Exception {
        MethodCall methodCall = new MethodCall("someMethod", new Object[] { "abc" }, new Class[] {String.class});
        System.out.println("methodCall: " + methodCall);
        MethodCall m=marshalAndUnmarshal(methodCall);
        System.out.println("m: " + m);
    }

    public static void testMarshalling2() throws Exception {
        Address addr=Util.createRandomAddress("A");
        MethodCall call=new MethodCall("foo",
                                       new Object[]{"hello", 23, addr},
                                       new Class[]{String.class, int.class, Address.class});
        MethodCall m=marshalAndUnmarshal(call);
        System.out.println("m = " + m);
    }

    public static void testMarshallingMETHOD() throws Exception {
        Method m=TargetClass.class.getMethod("foo", new Class[]{int.class, String.class});
        MethodCall mc=new MethodCall(m,22, "Bela");
        MethodCall call2=marshalAndUnmarshal(mc);
        System.out.println("call2 = " + call2);

        mc=new MethodCall(CALL, true, Boolean.FALSE, 322649, 3.24, (float)54.345,
                          new byte[]{'b', 'e', 'l', 'a'}, new String[]{"Bela", "Michelle"});
        call2=marshalAndUnmarshal(mc);
        System.out.println("call2 = " + call2);
    }

    public static void testMarshallingTYPES() throws Exception {
        MethodCall mc=new MethodCall("call", new Object[]
                                     {true,Boolean.FALSE,322649,3.24,(float)54.345,
                                       new byte[]{'b','e','l','a'},new String[]{"Bela","Michelle"}},
                                     new Class<?>[]{boolean.class, Boolean.class, int.class, double.class, float.class,
                                       byte[].class, String[].class});
        MethodCall call2=marshalAndUnmarshal(mc);
        System.out.println("call2 = " + call2);
    }

    public static void testMarshallingID() throws Exception {
        MethodCall mc=new MethodCall((short)1, true, Boolean.FALSE, 322649, 3.24, (float)54.345,
                                     new byte[]{'b', 'e', 'l', 'a'}, new String[]{"Bela", "Michelle"});
        MethodCall call2=marshalAndUnmarshal(mc);
        System.out.println("call2 = " + call2);

        mc=new MethodCall((short)1, true, Boolean.FALSE, 322649, 3.24, (float)54.345,
                          new byte[]{'b', 'e', 'l', 'a'}, new String[]{"Bela", "Michelle"});
        call2=marshalAndUnmarshal(mc);
        System.out.println("call2 = " + call2);


        mc=new MethodCall((short)2).lookup(lookup);
        call2=marshalAndUnmarshal(mc);
        System.out.println("call2 = " + call2);
    }


    private static MethodCall marshalAndUnmarshal(MethodCall m) throws Exception {
        byte[] buf=Util.objectToByteBuffer(m);
        System.out.println("marshalled buffer size: " + buf.length + " bytes");
        return (MethodCall)Util.objectFromByteBuffer(buf);
    }


    public static class Target {

        public static String someMethod(String arg) {
            return arg.toUpperCase();
        }

        public String overriddenMethod(String arg) {
            return "Target" + arg.toUpperCase();
        }

        public static String noArgumentMethod() {
            return "noArgumentMethodResult";
        }
    }

    public static class TargetSubclass extends Target {

        public String overriddenMethod(String arg) {
            return "TargetSubclass" + arg.toUpperCase();
        }
    }
}
