package com.linkedin.android.litr.render;

import android.media.MediaCodec;

import com.linkedin.android.litr.codec.Encoder;
import com.linkedin.android.litr.codec.Frame;

import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.nio.ByteBuffer;

import static com.linkedin.android.litr.render.PassthroughSoftwareRenderer.FRAME_WAIT_TIMEOUT;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

public class PassthroughSoftwareRendererShould {

    private static final long FRAME_PRESENTATION_TIME = 42L;
    private static final int FRAME_TAG = 1;
    private static final int FRAME_SIZE = 128;
    private static final int FRAME_OFFSET = 0;

    @Mock private Encoder encoder;

    private PassthroughSoftwareRenderer renderer;

    private Frame frame;

    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);

        MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
        bufferInfo.set(FRAME_OFFSET, FRAME_SIZE, FRAME_PRESENTATION_TIME, 0);

        ByteBuffer inputBuffer = ByteBuffer.allocate(FRAME_SIZE);
        for (int index = 0; index < FRAME_SIZE; index++) {
            inputBuffer.put((byte) index);
        }

        frame = new Frame(
                0,
                inputBuffer,
                bufferInfo);

        renderer = new PassthroughSoftwareRenderer(encoder);
    }

    @Test
    public void notUseInputSurface() {
        assertNull(renderer.getInputSurface());
    }

    @Test
    public void notRenderWhenNoFrameProvided() {
        renderer.renderFrame(null, FRAME_PRESENTATION_TIME);

        verify(encoder, never()).dequeueInputFrame(anyLong());
    }

    @Test
    public void notRenderWhenFrameHasNullBuffer() {
        Frame frame = new Frame(0, null, null);

        renderer.renderFrame(frame, FRAME_PRESENTATION_TIME);

        verify(encoder, never()).dequeueInputFrame(anyLong());
    }

    @Test
    public void dropFrameWhenCannotQueueToEncoder() {
        when(encoder.dequeueInputFrame(FRAME_WAIT_TIMEOUT)).thenReturn(MediaCodec.INFO_TRY_AGAIN_LATER);

        renderer.renderFrame(frame, FRAME_PRESENTATION_TIME);

        verify(encoder, never()).getInputFrame(anyInt());
    }

    @Test
    public void notRenderWhenEncoderReturnsNullInputFrame() {
        when(encoder.dequeueInputFrame(FRAME_WAIT_TIMEOUT)).thenReturn(FRAME_TAG);
        when(encoder.getInputFrame(FRAME_TAG)).thenReturn(null);

        renderer.renderFrame(frame, FRAME_PRESENTATION_TIME);

        verify(encoder, never()).queueInputFrame(frame);
    }

    @Test
    public void renderWhenEncoderAcceptsFrames() {
        int encoderFrameTag = 2;
        ByteBuffer encoderInputBuffer = ByteBuffer.allocate(FRAME_SIZE);
        MediaCodec.BufferInfo encoderBufferInfo = new MediaCodec.BufferInfo();
        Frame encoderInputFrame = new Frame(encoderFrameTag, encoderInputBuffer, encoderBufferInfo);

        when(encoder.dequeueInputFrame(FRAME_WAIT_TIMEOUT)).thenReturn(FRAME_TAG);
        when(encoder.getInputFrame(FRAME_TAG)).thenReturn(encoderInputFrame);

        renderer.renderFrame(frame, FRAME_PRESENTATION_TIME);

        verify(encoder).queueInputFrame(encoderInputFrame);
        assertThat(encoderInputFrame.bufferInfo.flags, is(frame.bufferInfo.flags));
        assertThat(encoderInputFrame.bufferInfo.presentationTimeUs, is(frame.bufferInfo.presentationTimeUs));
        assertThat(encoderInputFrame.bufferInfo.offset, is(0));
        assertThat(encoderInputFrame.bufferInfo.size, is(frame.bufferInfo.size));

        for (int index = 0; index < FRAME_SIZE; index++) {
            assertThat(frame.buffer.get(index), is(encoderInputBuffer.get(index)));
        }
    }
}