package net.bytebuddy.implementation.bytecode.collection;

import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.implementation.Implementation;
import net.bytebuddy.implementation.bytecode.StackManipulation;
import net.bytebuddy.implementation.bytecode.StackSize;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.rules.MethodRule;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;

import java.util.Collections;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;

public abstract class AbstractArrayFactoryTest {

    @Rule
    public MethodRule mockitoRule = MockitoJUnit.rule().silent();

    @Mock
    private TypeDescription.Generic componentType;

    @Mock
    private TypeDescription rawComponentType;

    @Mock
    private MethodVisitor methodVisitor;

    @Mock
    private StackManipulation stackManipulation;

    @Mock
    private Implementation.Context implementationContext;

    @Before
    public void setUp() throws Exception {
        when(stackManipulation.isValid()).thenReturn(true);
        when(componentType.asErasure()).thenReturn(rawComponentType);
    }

    @After
    public void tearDown() throws Exception {
        verifyNoMoreInteractions(implementationContext);
    }

    protected void testCreationUsing(Class<?> componentType, int storageOpcode) throws Exception {
        defineComponentType(componentType);
        CollectionFactory arrayFactory = ArrayFactory.forType(this.componentType);
        StackManipulation arrayStackManipulation = arrayFactory.withValues(Collections.singletonList(stackManipulation));
        assertThat(arrayStackManipulation.isValid(), is(true));
        verify(stackManipulation, atLeast(1)).isValid();
        StackManipulation.Size size = arrayStackManipulation.apply(methodVisitor, implementationContext);
        assertThat(size.getSizeImpact(), is(1));
        assertThat(size.getMaximalSize(), is(3 + StackSize.of(componentType).toIncreasingSize().getSizeImpact()));
        verify(methodVisitor).visitInsn(Opcodes.ICONST_1);
        verifyArrayCreation(methodVisitor);
        verify(methodVisitor).visitInsn(Opcodes.DUP);
        verify(methodVisitor).visitInsn(Opcodes.ICONST_0);
        verify(stackManipulation).apply(methodVisitor, implementationContext);
        verify(methodVisitor).visitInsn(storageOpcode);
        verifyNoMoreInteractions(methodVisitor);
        verifyNoMoreInteractions(stackManipulation);
    }

    protected abstract void verifyArrayCreation(MethodVisitor methodVisitor);

    private void defineComponentType(Class<?> type) {
        when(componentType.isPrimitive()).thenReturn(type.isPrimitive());
        when(componentType.represents(type)).thenReturn(true);
        when(rawComponentType.getInternalName()).thenReturn(Type.getInternalName(type));
        when(componentType.getStackSize()).thenReturn(StackSize.of(type));
        when(stackManipulation.apply(any(MethodVisitor.class), any(Implementation.Context.class))).thenReturn(StackSize.of(type).toIncreasingSize());
    }
}
