package net.bytebuddy.implementation;

import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.implementation.bind.annotation.AllArguments;
import org.junit.Test;

import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
import static net.bytebuddy.matcher.ElementMatchers.isToString;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.MatcherAssert.assertThat;

public class MethodDelegationAllArgumentsTest {

    private static final int FOO = 42, BAR = 21;

    private static final String QUX = "qux", BAZ = "baz", FOOBAR = "foobar";

    @Test
    public void testStrictBindable() throws Exception {
        DynamicType.Loaded<Foo> loaded = new ByteBuddy()
                .subclass(Foo.class)
                .method(isDeclaredBy(Foo.class))
                .intercept(MethodDelegation.to(Bar.class))
                .make()
                .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
        Foo instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
        assertThat(instance.foo(FOO, BAR), is((Object) (QUX + FOO + BAR)));
    }

    @Test
    public void testStrictBindableObjectType() throws Exception {
        DynamicType.Loaded<Foo> loaded = new ByteBuddy()
                .subclass(Foo.class)
                .method(isDeclaredBy(Foo.class))
                .intercept(MethodDelegation.to(FooBar.class))
                .make()
                .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
        Foo instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
        assertThat(instance.foo(FOO, BAR), is((Object) (QUX + FOO + BAR)));
    }

    @Test(expected = IllegalArgumentException.class)
    public void testStrictNonBindableThrowsException() throws Exception {
        new ByteBuddy()
                .subclass(Qux.class).method(isDeclaredBy(Qux.class))
                .intercept(MethodDelegation.to(BazStrict.class))
                .make();
    }

    @Test
    public void testSlackNonBindable() throws Exception {
        DynamicType.Loaded<Qux> loaded = new ByteBuddy()
                .subclass(Qux.class)
                .method(isDeclaredBy(Qux.class))
                .intercept(MethodDelegation.to(BazSlack.class))
                .make()
                .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
        Qux instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
        assertThat(instance.foo(FOOBAR, BAZ), is((Object) (QUX + BAZ)));
    }

    @Test
    public void testIncludeSelf() throws Exception {
        DynamicType.Loaded<Qux> loaded = new ByteBuddy()
                .subclass(Qux.class)
                .method(isDeclaredBy(Qux.class))
                .intercept(MethodDelegation.to(IncludeSelf.class))
                .make()
                .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
        Qux instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
        assertThat(instance.foo(QUX, BAZ), is((Object) instance));
    }

    @Test
    public void testNoArguments() throws Exception {
        DynamicType.Loaded<Object> loaded = new ByteBuddy()
                .subclass(Object.class)
                .method(isToString())
                .intercept(MethodDelegation.to(NoArguments.class))
                .make()
                .load(NoArguments.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
        Object instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
        assertThat(instance.toString(), nullValue(String.class));
    }

    public static class Foo {

        public Object foo(int i1, Integer i2) {
            return null;
        }
    }

    public static class Bar {

        public static String qux(@AllArguments int[] args) {
            return QUX + args[0] + args[1];
        }
    }

    public static class FooBar {

        public static String qux(@AllArguments Object args) {
            return QUX + ((Object[]) args)[0] + ((Object[]) args)[1];
        }
    }

    public static class Qux {

        public Object foo(Object o, String s) {
            return null;
        }
    }

    public static class BazStrict {

        public static String qux(@AllArguments String[] args) {
            assertThat(args.length, is(2));
            return QUX + args[0] + args[1];
        }
    }

    public static class BazSlack {

        public static String qux(@AllArguments(AllArguments.Assignment.SLACK) String[] args) {
            assertThat(args.length, is(1));
            return QUX + args[0];
        }
    }

    public static class IncludeSelf {

        public static Object intercept(@AllArguments(includeSelf = true) Object[] args) {
            assertThat(args.length, is(3));
            assertThat(args[1], is((Object) QUX));
            assertThat(args[2], is((Object) BAZ));
            return args[0];
        }
    }

    public static class NoArguments {

        public static String intercept(@AllArguments(nullIfEmpty = true) Object[] args) {
            assertThat(args, nullValue(Object[].class));
            return null;
        }
    }
}
