package net.bytebuddy.asm;

import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.implementation.bytecode.assign.Assigner;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;

import java.util.Arrays;
import java.util.Collection;

import static net.bytebuddy.matcher.ElementMatchers.named;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;

@RunWith(Parameterized.class)
public class AdviceBoxedReturnAssignmentTest {

    private static final String FOO = "foo";

    private static final byte NUMERIC_VALUE = 42;

    @Parameterized.Parameters
    public static Collection<Object[]> data() {
        return Arrays.asList(new Object[][]{
                {VoidAssignment.class, null},
                {BooleanAssignment.class, true},
                {ByteAssignment.class, NUMERIC_VALUE},
                {ShortAssignment.class, (short) NUMERIC_VALUE},
                {CharacterAssignment.class, (char) NUMERIC_VALUE},
                {IntegerAssignment.class, (int) NUMERIC_VALUE},
                {LongAssignment.class, (long) NUMERIC_VALUE},
                {FloatAssignment.class, (float) NUMERIC_VALUE},
                {DoubleAssignment.class, (double) NUMERIC_VALUE},
                {ReferenceAssignment.class, FOO},
        });
    }

    private final Class<?> type;

    private final Object expected;

    public AdviceBoxedReturnAssignmentTest(Class<?> type, Object expected) {
        this.type = type;
        this.expected = expected;
    }

    @Test
    public void testAssignment() throws Exception {
        Class<?> dynamicType = new ByteBuddy()
                .redefine(type)
                .visit(Advice.to(type).on(named(FOO)))
                .make()
                .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
                .getLoaded();
        assertThat(dynamicType.getDeclaredMethod(FOO).invoke(dynamicType.getDeclaredConstructor().newInstance()), is(expected));
    }

    @SuppressWarnings("all")
    public static class VoidAssignment {

        public void foo() {
            /* empty */
        }

        @Advice.OnMethodExit
        private static void exit(@Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object value) {
            value = FOO;
        }
    }

    @SuppressWarnings("all")
    public static class BooleanAssignment {

        public boolean foo() {
            return false;
        }

        @Advice.OnMethodExit
        private static void exit(@Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object value) {
            value = true;
        }
    }

    @SuppressWarnings("all")
    public static class ByteAssignment {

        public byte foo() {
            return 0;
        }

        @Advice.OnMethodExit
        private static void exit(@Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object value) {
            value = NUMERIC_VALUE;
        }
    }

    @SuppressWarnings("all")
    public static class ShortAssignment {

        public short foo() {
            return 0;
        }

        @Advice.OnMethodExit
        private static void exit(@Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object value) {
            value = (short) NUMERIC_VALUE;
        }
    }

    @SuppressWarnings("all")
    public static class CharacterAssignment {

        public char foo() {
            return 0;
        }

        @Advice.OnMethodExit
        private static void exit(@Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object value) {
            value = (char) NUMERIC_VALUE;
        }
    }

    @SuppressWarnings("all")
    public static class IntegerAssignment {

        public int foo() {
            return 0;
        }

        @Advice.OnMethodExit
        private static void exit(@Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object value) {
            value = (int) NUMERIC_VALUE;
        }
    }

    @SuppressWarnings("all")
    public static class LongAssignment {

        public long foo() {
            return 0L;
        }

        @Advice.OnMethodExit
        private static void exit(@Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object value) {
            value = (long) NUMERIC_VALUE;
        }
    }

    @SuppressWarnings("all")
    public static class FloatAssignment {

        public float foo() {
            return 0f;
        }

        @Advice.OnMethodExit
        private static void exit(@Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object value) {
            value = (float) NUMERIC_VALUE;
        }
    }

    @SuppressWarnings("all")
    public static class DoubleAssignment {

        public double foo() {
            return 0d;
        }

        @Advice.OnMethodExit
        private static void exit(@Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object value) {
            value = (double) NUMERIC_VALUE;
        }
    }

    @SuppressWarnings("all")
    public static class ReferenceAssignment {

        public String foo() {
            return null;
        }

        @Advice.OnMethodExit
        private static void exit(@Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object value) {
            value = FOO;
        }
    }
}
