package net.bytebuddy.asm;

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

import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.Collection;

import static junit.framework.TestCase.fail;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.MatcherAssert.assertThat;

@RunWith(Parameterized.class)
public class AdviceNoRegularReturnTest {

    private static final String FOO = "foo";

    private final Class<?> type;

    public AdviceNoRegularReturnTest(Class<?> type) {
        this.type = type;
    }

    @Parameterized.Parameters
    public static Collection<Object[]> data() {
        return Arrays.asList(new Object[][]{
                {VoidSample.class},
                {BooleanSample.class},
                {ByteSample.class},
                {ShortSample.class},
                {CharacterSample.class},
                {IntegerSample.class},
                {LongSample.class},
                {FloatSample.class},
                {DoubleSample.class},
                {ReferenceSample.class}
        });
    }

    @Test
    public void testNoRegularReturnWithSkip() throws Exception {
        Class<?> type = new ByteBuddy()
                .redefine(this.type)
                .visit(Advice.to(EnterAdviceSkip.class).on(named(FOO)))
                .make()
                .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
                .getLoaded();
        try {
            type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance());
            fail();
        } catch (InvocationTargetException exception) {
            assertThat(exception.getCause(), instanceOf(RuntimeException.class));
        }
    }

    @Test
    public void testNoRegularReturnWithoutHandler() throws Exception {
        Class<?> type = new ByteBuddy()
                .redefine(this.type)
                .visit(Advice.to(ExitAdviceWithoutHandler.class).on(named(FOO)))
                .make()
                .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
                .getLoaded();
        try {
            type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance());
            fail();
        } catch (InvocationTargetException exception) {
            assertThat(exception.getCause(), instanceOf(RuntimeException.class));
        }
    }

    @Test
    public void testNoRegularReturnWithHandler() throws Exception {
        Class<?> type = new ByteBuddy()
                .redefine(this.type)
                .visit(Advice.to(ExitAdviceWithHandler.class).on(named(FOO)))
                .make()
                .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
                .getLoaded();
        try {
            type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance());
            fail();
        } catch (InvocationTargetException exception) {
            assertThat(exception.getCause(), instanceOf(RuntimeException.class));
        }
    }

    private static class EnterAdviceSkip {

        @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
        private static boolean enter() {
            return false;
        }
    }

    private static class ExitAdviceWithoutHandler {

        @Advice.OnMethodExit
        private static void exit() {
            /* empty */
        }
    }

    private static class ExitAdviceWithHandler {

        @Advice.OnMethodExit(onThrowable = RuntimeException.class)
        private static void exit() {
            /* empty */
        }
    }

    public static class VoidSample {

        public void foo() {
            throw new RuntimeException();
        }
    }

    public static class BooleanSample {

        public boolean foo() {
            throw new RuntimeException();
        }
    }

    public static class ByteSample {

        public byte foo() {
            throw new RuntimeException();
        }
    }

    public static class ShortSample {

        public short foo() {
            throw new RuntimeException();
        }
    }

    public static class CharacterSample {

        public char foo() {
            throw new RuntimeException();
        }
    }

    public static class IntegerSample {

        public int foo() {
            throw new RuntimeException();
        }
    }

    public static class LongSample {

        public long foo() {
            throw new RuntimeException();
        }
    }

    public static class FloatSample {

        public float foo() {
            throw new RuntimeException();
        }
    }

    public static class DoubleSample {

        public double foo() {
            throw new RuntimeException();
        }
    }

    public static class ReferenceSample {

        public Object foo() {
            throw new RuntimeException();
        }
    }
}
